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

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

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

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

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

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


神经网络(Neural network)

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

https://en.wikipedia.org/wiki/Neural_network

神经网络

单一职责原则(Single responsibility principle)

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

https://en.wikipedia.org/wiki/Single_responsibility_principle

单一职责原则

里氏代换原则(Liskov substitution principle)

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

https://en.wikipedia.org/wiki/Liskov_substitution_principle

里氏代换原则

依赖倒置原则

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

https://en.wikipedia.org/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.0f, 1.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!


相关图文

推荐阅读更多精彩内容

  • 原文地址:http://www.cnblogs.com/subconscious/p/5058741.html 神...
    Albert陈凯阅读 2,698评论 0 43
  • 主要内容 自然语言输入编码 前馈网络 卷积网络 循环网络(recurrent networks ) 递归网络(re...
    JackHorse阅读 2,119评论 0 2
  • 目标,是一个人前进的动力,是一个人变得优秀的重要因素,因为目标,人才会知道什么时候改做什么,怎么让自己逼近目...
    lyang阅读 33评论 0 0
  • 男神加缪,因车祸骤逝,距今已有58个年头。他短短47载生命,一顶哲学桂冠,一个诺贝尔文学奖,关键是,还挺帅。 然而...
    子迪君阅读 113评论 0 4
  • 那天听我在深圳上班的朋友说,她的上司每天工作到凌晨1点,早上6-30起来坚持看半个小时书然后上班,把每天时间...
    C嘉晴阅读 21评论 0 2