分享

如何利用 C# 对神经网络模型进行抽象?

 老马的程序人生 2020-08-17

「旅游需求预测」在旅游业发展中具有重要的作用。正确的预测是进行科学决策的依据。制定发展战略、编制计划以及日常管理的决策,都是以科学的预测工作为前提的。

常见的旅游需求预测方法是基于统计学的数学模型:时间序列预测法和因果模型预测法。然而,旅游市场往往受到许多因素的制约,这些因素之间呈现出错综复杂的关系,而且不稳定因素也很多,市场多变,难以得到有效的预测结果。有必要研究应用一些新的解决非线性问题的方法,如神经网络、灰色模型等。

以上是论文《基于BP神经网络的云南国际旅游需求预测》中引言部分的一小段,该论文利用BP神经网络对云南旅游外汇收入及入境游客人数进行预测和分析,分析结果表明,人工神经网络方法在旅游预测中的应用是可行的,而且是十分有效的(公众号后台回复 20190316 可以下载这篇论文)。

由于神经网络根据其结构和功能的不同,可以构成不同的网络,处理不同的问题(分类、回归、预测、评价、聚类等等)。所以,我们在写神经网络的代码时,就要考虑遵循一些程序设计的原则,在这些原则的指导下,把代码写的可复用、可扩展、易于维护、灵活性好。

今天,我们就先构造神经网络的抽象层部分,后面我们再写一些图文来构造具体的实现部分(包括感知器、线性神经网络、BP神经网络、进化神经网络、SOM神经网络、基于概率模型的神经网络等),根据不同的实现可以得到不同类型的神经网络。


神经网络(Neural network)

「人工神经网络」是一种由许多简单的并行工作的处理单元组成的系统,其功能取决于网络的结构,连接强度以及各单元的处理方式。学习神经网络要搞清楚几个基本概念,比如:神经元模型、激活函数、权值阈值、学习算法等,有关于神经网络的详细介绍可以参见维基百科中的相应部分。

https://en./wiki/Neural_network

神经网络

单一职责原则(Single responsibility principle)

「单一职责原则」是指一个类只负责一个功能领域中的相应职责。即就一个类而言,应该只有一个引起它变化的原因。这是我们写程序要遵循的最基本原则,有关该原则的详细介绍可以参见维基百科中的相应部分。

https://en./wiki/Single_responsibility_principle

单一职责原则

里氏代换原则(Liskov substitution principle)

「里氏代换原则」是指所有引用基类的地方必须能透明地使用其子类的对象。有关该原则的详细介绍可以参见维基百科中的相应部分。

https://en./wiki/Liskov_substitution_principle

里氏代换原则

依赖倒置原则

「依赖倒置原则」是指抽象不应该依赖于细节,细节应该依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。有关该原则的详细介绍可以参见维基百科中的相应部分。

https://en./wiki/Dependency_inversion_principle

依赖倒转原则

通过以上介绍,我们了解了神经网络的基本概念,根据 单一职责原则,我们需要设计不同的类去承担不同的职责,根据 里氏代换原则依赖倒置原则,我们可以先定义不容易发生改变的抽象类和接口,等后期我们再根据使用场景选择不同的实体类来继承或实现它们,通过 运行时多态,在程序运行的时候动态的选择运行哪个类中的哪个方法,来达到我们的目标。基本思路就是这样,下面我们来看一下对神经网络进行抽象的代码。

1. 对神经元的抽象,封装了诸如神经元的输入、输出和权值等常见的属性。

public abstract class Neuron
{

    // 随机数生成器
    public static Random Rand { get; set; } = new Random();

    // 随机数范围
    public static Range RandRange { get; set; } = new Range(0.0f1.0f); 

    // 多输入
    public int InputsCount { get; }

    // 单输出
    public double Output { get; protected set; } = 0.0;  

    // 权值数组
    public double[] Weights { get; }

    // 构造函数
    protected Neuron(int inputs)
    
{
        InputsCount = Math.Max(1, inputs);
        Weights = new double[InputsCount];
        Randomize();
    }

    // 初始化权值
    public virtual void Randomize()
    
{
        double d = RandRange.Length;
        for (int i = 0; i < InputsCount; i++)
            Weights[i] = Rand.NextDouble() * d + RandRange.Min;
    }
    // 输出
    public abstract double Compute(double[] input);
}    

2. 对激活函数的抽象。

所有与神经元一起使用的激活函数,都应该实现这个接口,这些函数将神经元的输出作为其输入加权和的函数来计算。

public interface IActivationFunction
{
    // 激活函数
    double Function(double x);

    // 求x点的导数
    double Derivative(double x);

    // 求y点的导数
    double Derivative2(double y);
}

3. 对神经网络层的抽象,代表该层神经元的集合。

public abstract class Layer
{

    // 该层神经元的个数
    protected int NeuronsCount;

    // 该层的输入个数
    public int InputsCount { get; }

    // 该层神经元的集合
    public Neuron[] Neurons { get; }

    // 该层的输出向量
    public double[] Output { get; protected set; }

    // 构造函数
    protected Layer(int neuronsCount, int inputsCount)
    
{
        InputsCount = Math.Max(1, inputsCount);
        NeuronsCount = Math.Max(1, neuronsCount);
        Neurons = new Neuron[NeuronsCount];
    }

    // 计算该层的输出向量
    public virtual double[] Compute(double[] input)
    {
        double[] output = new double[NeuronsCount];
        for (int i = 0; i < Neurons.Length; i++)
            output[i] = Neurons[i].Compute(input);

        Output = output;
        return output;
    }

    // 初始化该层神经元的权值
    public virtual void Randomize()
    
{
        foreach (Neuron neuron in Neurons)
            neuron.Randomize();
    }
}

4. 对学习算法的抽象。

由于机器学习可以分为带标签的监督学习和不带标签的非监督学习,所以这块的抽象也分成两种:

  • 第一种是对监督学习的抽象(在学习阶段系统的期望输出是已知的)。

  • 第二种是对非监督学习的抽象(在学习阶段系统的期望输出是未知的)。

对监督学习的抽象,这个接口描述了所有监督学习算法应该实现的方法。

public interface ISupervisedLearning
{
    // 单样本训练
    double Run(double[] input, double[] output);

    // 多样本训练
    double RunEpoch(double[][] input, double[][] output);
}

对非监督学习的抽象,该接口描述了所有非监督学习算法都应该实现的方法。

public interface IUnsupervisedLearning
{
    // 单样本训练
    double Run(double[] input);

    // 多样本训练
    double RunEpoch(double[][] input);
}

5. 对神经网络结构的抽象,它表示神经元层的集合。

public abstract class Network
{

    // 网络层的个数
    protected int LayersCount;

    // 网络输入的个数
    public int InputsCount { get; }

    // 构成网络的层
    public Layer[] Layers { get; }

    // 网络的输出向量
    public double[] Output { get; protected set; }

    // 构造函数 
    protected Network(int inputsCount, int layersCount)
    
{
        InputsCount = Math.Max(1, inputsCount);
        LayersCount = Math.Max(1, layersCount);
        Layers = new Layer[LayersCount];
    }

    // 计算网络的输出
    public virtual double[] Compute(double[] input)
    {
        double[] output = input;

        for (int i = 0; i < Layers.Length; i++)
        {
            output = Layers[i].Compute(output);
        }
        Output = output;
        return output;
    }

    // 初始化整个网络的权值
    public virtual void Randomize()
    
{
        foreach (Layer layer in Layers)
        {
            layer.Randomize();
        }
    }

    // 保存网络结构
    public void Save(string fileName)
    
{
        FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
        Save(stream);
        stream.Close();
    }

    // 加载网络结构
    public static Network Load(string fileName)
    
{
        FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
        Network network = Load(stream);
        stream.Close();
        return network;
    }
}

通过上面的介绍,神经网络模型已经被我们抽象出来,由于没有写具体的实现,暂时还不能使用。不要紧,后面我们会写一系列的实体类去继承或实现这些抽象类或接口以便得到不同类型的神经网络。

今天就到这里吧!希望大家能够有所收获!See You!


相关图文


经过8年多的发展,LSGO软件技术团队在「地理信息系统」、「数据统计分析」、「计算机视觉」等领域积累了丰富的研发经验,也建立了人才培养的完备体系,欢迎对计算机技术感兴趣的同学加入,与我们共同成长进步。

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多