设计并实现一个用于手写识别、图像分析等的神经网络库——BrainNet库——包含完整代码、简化理论、完整图解和示例






4.76/5 (92投票s)
本文将非常简单地解释反向传播神经网络的实际概念和实现——请参阅项目代码和示例,例如源压缩包中的简单模式检测器、手写识别板、基于XML的神经网络处理语言等。
![]() |
本文将非常简单地解释反向传播神经网络的实际概念,即使是没有任何神经网络知识的人也能轻松理解所需的理论和概念。相关项目演示了“反向传播”神经网络库(我称之为 Brain Net 库)的设计和实现。你可以在本文中找到理论、插图和概念,以及对神经网络库项目的解释。此外,还可以在相关的zip文件中找到该库的完整源代码和相关的演示项目(一个简单的模式检测器、一个手写识别板、一个基于XML的神经网络处理语言等)。 |
目录
1. 概述
- 解决方案架构师:“嗯,你学到了一些关于神经网络的知识吗?”
- (愚蠢的?)开发人员:“不,我很聪明。我喜欢使用别人的代码。”
- 解决方案架构师:“但是,如果你不理解这些概念,你怎么能优化和重用别人的代码呢?”
- (愚蠢的?)开发人员:“呃……我觉得大多数人编码都比我好,所以我为什么要优化呢?”
在我上一篇文章中,重点是“神经网络能做什么”。在本文中,我们将看到“神经网络是什么,以及如何自己创建一个”。我将深入探讨。阅读本文后,你将能够:
- 理解神经网络的基本理论(特别是反向传播神经网络)
- 理解神经网络实际“工作”的原理
- 更详细地理解BrainNet库的设计和源代码。
- 更详细地理解如何在你的项目中使用BrainNet库。
- 思考神经网络编程的新可能性
- 提出一些优化和泛化BrainNet库的概念。
现在,让我回答一些过去收到的问题。
- 问)你为什么为这个神经网络库选择面向对象编程模型?
- 答:重点在于理解**基本**概念,而不是性能。
- 问)这个神经网络库是否完全优化了?
- 答:还没有,我们仍处于测试阶段。重点在于可读性,因此代码经过简化,即使是初学者也能理解。我们随时欢迎建议和修改。请将你的修改、技巧和建议发送至 amazedsaint@gmail.com。
- 问)这个库可以在项目中使用吗?
- 你可以使用它——只要你的使用符合相关许可证通知中的规定(请参阅源代码)。无论如何,如果你修改它或在你的任何项目中使用它,我请求你给我发送一份通知(和修改后的代码)。
2. 开始之前。
本文自成一体。它解释了什么是神经网络,以及如何自己创建一个神经网络。但是,要了解神经网络能做什么,并获得用户级别的体验,请阅读本文的第一部分。
本系列文章的第一篇题为“**BrainNet神经网络库——第一部分——逐步学习神经网络编程并开发一个简单的手写识别系统**”。你可以阅读并下载源代码,[点击此处]。
如果你真是个新手,这会对你大有帮助,并可能为你提供逐步理解神经网络的方法。
这是我关于神经网络和 BrainNet 神经网络库的第二篇文章。本文更详细、更简单地解释了神经网络及其工作原理。然后我将解释 BrainNet 库的设计概念。
|
3. 理解神经网络
人工神经网络的一个迷人之处在于,它们主要受到人脑的启发。这并不意味着人工神经网络是人脑中生物神经网络的**精确**模拟——因为人脑的实际工作原理仍然是个谜。人工神经网络的概念以其目前的形式出现,源于我们对自己大脑非常有限的理解(“我只知道我一无所知”)。
Brain Net 神经网络库是使用面向对象概念设计和实现的。
|
在理解神经元和神经网络实际工作原理之前,让我们回顾一下神经网络的结构。正如我之前提到的,神经网络由几个层组成,每个层中都有多个神经元。一层中的神经元连接到下一层中的多个或所有神经元。输入被馈送到输入层中的神经元,输出则从最后一层中的神经元获得。
图:一个全连接的4-4-2神经网络,其中输入层有4个神经元,隐藏层有4个神经元,输出层有2个神经元。
一个人工神经网络可以从一组样本中**学习**。
为了训练神经网络,首先你需要提供一组输入和输出。例如,如果你需要一个神经网络从骨骼的X光片中检测骨折,首先你需要用大量的样本训练网络。你提供一张X光片,以及该X光片是否有骨折的信息。在用这样的样本(可能成千上万个样本)多次训练网络之后,假设神经网络可以“检测”给定的X光片是否显示骨骼骨折(这只是一个例子)。网络训练的概念在我的第一篇文章中有详细说明。稍后,在本文中,我们将讨论网络学习的理论。
正如我们已经讨论过的,神经网络的基本组成部分是神经元。首先,让我们简要了解一下生物神经元及其相应的人工模型。
3.1 生物神经元
首先,让我们看看生物神经元。坦率地说,我对生物神经元的实际结构了解不多——但是,在目前阶段,以下信息足以让我们进入状态。一个生物神经元看起来大致是这样的。
生物神经元的四个基本组成部分是:
-
**树突** - 树突是神经元的毛发状延伸,每个树突都可以将一些输入(来自前一层的神经元)带到神经元。这些输入被传递给胞体。
-
**胞体** - 胞体负责处理这些输入,并通过轴突和突触将输出提供给其他神经元。
-
**轴突** - 轴突负责通过突触将胞体的输出传递给其他神经元。
-
**突触** - 一个神经元的突触连接到下一层神经元的树突。神经元之间的连接是由于突触和树突的作用。
一个神经元连接到下一层的多个(通常是所有)神经元。此外,一层中的一个神经元可以接受来自前一层中多个(通常是所有)神经元的输入。
3.2 人工神经元
现在,让我们看看人工神经元的模型。
人工神经元由各种输入组成,很像生物神经元。与胞体和轴突不同,我们有一个求和单元和一个传输函数单元。一个神经元的输出可以作为输入提供给多个神经元。
请注意,对于人工神经元,每个输入都关联一个权重值。现在,让我们看看神经元的工作原理。
求和单元
-
当输入馈送到神经元时,求和单元将首先找到**净值**。为了找到净值,计算每个输入值和相应连接权重的乘积。
-
也就是说,神经元的每个输入值 x(i) 都与关联的连接权重 w(i) 相乘。在最简单的情况下,这些乘积被求和并输入到传递函数。请参阅下面的伪代码,它更容易理解。
此外,神经元还具有影响净值的偏置值。当网络初始化时,神经元的偏置被设置为随机值。在训练阶段,我们将改变网络中所有神经元(输入层神经元除外)的连接权重和偏置。
即,如果 **x** 是输入,**w** 是关联权重,则净值计算的伪代码如下。
netValue=0
for i=0 to neuron.inputs.count-1
netValue=netValue + x(i) * w(i)
next
netValue=netValue + Bias
传输函数
传输函数是一个简单的函数,它使用净值生成输出。然后将此输出传播到下一层中的神经元。我们可以使用各种类型的传输函数,如下所示。
*硬限幅传输函数:* 例如,一个简单的硬限幅函数,如果净值大于0.5,则输出1;如果净值小于0.5,则输出0——如图所示。
if (netValue<0.5)
output = 0
else
output = 1
*S型传输函数:* 另一种类型的传输函数是S型传输函数。S型传输函数将以净值作为输入,并产生介于0和1之间的输出,如图所示。
output = 1 / (1 + Exp(-netValue))
求和单元和传输函数单元的实现可能在不同的网络中有所不同。
因此,神经网络是由这些称为神经元的基本模型构建而成的,它们按层排列并相互连接,正如前面所解释的。现在让我们看看这些神经元如何在神经网络内部协同工作。
4. 神经网络如何实际“工作”
使用神经网络包括:
- 通过提供输入和相应的输出来训练网络。
- 在此阶段,我们用样本训练神经网络以执行特定任务。
- 通过提供输入来获取输出来运行网络。
- 在此阶段,我们将向网络提供输入并获得输出。输出可能并非总是准确的。一般来说,运行阶段输出的准确性很大程度上取决于我们在训练阶段提供的样本以及我们训练网络的次数。
4.1. 训练阶段
本节解释了反向传播神经网络中训练是如何进行的。在反向传播神经网络中,有多个层,每个层中的每个神经元都连接到下一层中的所有神经元。对于每个连接,当网络初始化时都会分配一个随机权重。此外,在初始化期间,还会为每个神经元分配一个随机偏置值。
训练是调整网络中所有神经元(输入层神经元除外)的连接权重和偏置的过程,以使网络能够为所有输入集产生预期的输出。
现在,让我们看看训练是如何实际发生的。考虑一个小型2-2-1网络。现在,我们将用AND真值表训练这个网络。如你所知,AND真值表是:
|
|
图:一个2-2-1神经网络和AND真值表
在上述网络中,N1和N2是输入层神经元,N3和N4是隐藏层神经元,N5是输出层神经元。输入被馈送到N1和N2。每一层中的每个神经元都连接到下一层中的所有神经元。我们根据每一层中神经元的数量,将上述网络称为2-2-1网络。
|
上图将用于说明训练过程。
首先,让我们看看如何训练我们的2-2-1网络,即真值表中的第一个条件:当A=0,B=0时,输出=0。
步骤1 - 馈送输入
最初,我们将把输入馈送到神经网络。这通过简单地将第1层神经元的输出设置为我们需要馈送的输入值来完成。也就是说,根据上面的例子,我们的输入是0,0,输出是0。我们将神经元N1的输出设置为0,N2的输出设置为0。
看看这段伪代码,它会让你更清楚。Inputs是输入数组。输入数组中的元素数量应与输入层中的神经元数量匹配。
i = 0
For Each neuron In InputLayer
someNeuron.OutputValue = Inputs(i)
i = i + 1
Next
步骤2 - 寻找网络的输出
我们已经看到了如何计算单个神经元的输出。根据我们上面的例子,神经元 N1 和 N2 的输出将作为 N3 和 N4 的输入。
寻找神经网络的输出包括计算所有隐藏层和输出层的输出。正如我们前面讨论的,神经网络可以有多个隐藏层。
'Find output of all neurons in all hidden layers
For each layer in HiddenLayers
For Each neuron In layer.Neurons
neuron.UpdateOutput()
Next
Next
'Find output of all neurons in output layer
For Each neuron In OutputLayer.Neurons
neuron.UpdateOutput()
Next
单个神经元的 `UpdateOutput()` 函数的工作方式与我们之前讨论的完全相同。首先,求和单元计算净值,然后将其提供给传递函数以获得神经元的输出。伪代码再次显示在下面。
**求和单元**的工作原理如下:
Dim netValue As Single = bias
For Each InputNeuron connected to ThisNeuron
netValue = netValue + (Weight Associated With InputNeuron * _
Output of InputNeuron)
Next
即,根据我们上面的例子,让我们计算神经元N3的净值。我们知道N1和N2连接到N3。
- N3的净值 = N3.偏置 + (N1.输出 * N1到N3连接的权重) + (N2.输出 * N2到N3连接的权重)
同样,要计算 N4 的净值,
- N4的净值 = N4.偏置 + (N1.输出 * N1到N4连接的权重) + (N2.输出 * N2到N4连接的权重)
激活单元或传输单元
现在,让我们看看如何使用传输单元生成输出。这里,我们使用的是 sigmoid 传输函数。这与我们之前讨论的完全一致。
Output of Neuron = 1 / (1 + Exp( - NetValue )
现在,N3和N4的输出将作为输入传递给下一层中的每个神经元。这种将一层输出作为输入传播到下一层的过程在训练阶段被称为前向传播部分。
因此,在步骤2之后,我们刚刚找到了每一层中每个神经元的输出——从第一个隐藏层到输出层。网络的输出就是输出层中所有神经元的输出。
步骤3 - 计算误差或增量
在此步骤中,我们将计算网络的误差。误差或增量可以表示为预期输出与实际输出之间的差异。例如,当我们第一次找到网络的输出值时,很可能输出是错误的。对于输入 A=0 和 B=0,我们希望得到 0 作为输出。但输出可能是其他值,例如 0.55,这取决于分配给每个神经元的偏置和连接权重的随机值。
现在让我们看看,如何计算误差。让我们看看如何计算所有层中每个神经元的误差或增量。
- 首先,我们将计算输出层中每个神经元的误差或增量。
- 这样计算出的增量值将用于计算前一层(即最后一个隐藏层)中神经元的误差或增量。
- 最后一个隐藏层中所有神经元的增量值用于计算前一层(即倒数第二个隐藏层)中所有神经元的误差或增量。
- 这个过程会持续下去,直到我们到达第一个隐藏层(不计算输入层的增量)。
请注意一个有趣的观点。在步骤2中,我们为了找到输出,从第一个隐藏层到输出层向前传播值。在步骤3中,我们从输出层开始,向后传播误差值——因此,这个神经网络被称为反向传播神经网络。
是时候看看事情是如何实际运作的了。寻找神经元增量的通用公式是:
Neuron.Delta = Neuron.Output * (1 - Neuron.Output) * ErrorFactor
现在,让我们看看如何计算每个神经元的误差因子。输出层神经元的误差因子可以直接计算(因为我们知道输出层中每个神经元的预期输出)。
对于输出层中的一个神经元,
ErrorFactor Of An Output Layer Neuron = _
ExpectedOutput - Neuron's Actual Output
即,根据我们上面的例子,如果 N5 的输出是 0.5,预期输出是 0,那么误差因子 = 0 - 0.5 = -0.5
对于隐藏层中的神经元,误差因子计算有些不同。要计算隐藏层中神经元的误差因子,
- 首先,将该神经元连接到的每个神经元的增量乘以该连接的权重。
- 这些乘积相加,得到隐藏层神经元的误差因子。
简单来说,隐藏层中的神经元利用下一层中所有连接神经元的增量以及相应的连接权重来计算误差因子。这是因为我们没有直接的参数来计算隐藏层神经元的误差(就像我们在输出层神经元中所做的那样)。
|
'Calculating the error factor of a neuron in a hidden layer
For Each Neuron N to which ThisNeuron Is Connected
'Sum up all the delta * weight
errorFactor = errorFactor + (N.DeltaValue * _
Weight Of Connection From ThisNeuron To N)
Next
为了说明这一点,考虑一个神经元 x1 (ThisNeuron),它是一个隐藏层神经元。X1 连接到神经元 y1、y2、y3 和 y4 ——这些神经元位于下一层。
也就是说,简单来说:
- X1 的误差因子 = (Y1.增量 * X1到Y1连接的权重) + (Y2.增量 * X1到Y2连接的权重) + (Y3.增量 * X1到Y3连接的权重) + (Y4.增量 * X4到Y4连接的权重)
现在,正如我们之前讨论的,X1 的增量可以计算为:
- X1.Delta = X1.Output * (1 - X1.Output) * X1的误差因子
因此,完成步骤3后,我们拥有了所有神经元的增量。
步骤4 - 调整权重和偏置
在计算完所有层中所有神经元的增量后,我们应该根据误差或增量来修正权重和偏置,以便下次产生更准确的输出。连接权重和偏置共同被称为自由参数。请记住,一个神经元应该更新不止一个权重——因为,正如我们已经讨论过的,每个连接到神经元的输入都关联一个权重。
请参阅用于更新所有层中所有神经元自由参数的伪代码
'Update free parameters of all neurons in hidden layer
For each layer in HiddenLayers
For Each neuron In layer.Neurons
neuron.UpdateFreeParams()
Next
Next
'Update free parameters of all neurons in output layer
For Each neuron In OutputLayer.Neurons
neuron.UpdateFreeParams()
Next
UpdateFreeParams() 函数只做两件事。
- 根据上面计算出的增量找到神经元的新偏置
- 根据上面计算出的增量更新连接权重
找到神经元的新偏置值非常简单。请参阅伪代码。如果学习率是一个常数(例如,学习率=0.5)
New Bias Value = Old Bias Value + _
LEARNING_RATE * 1 * Delta
现在让我们看看如何更新连接权重。与输入神经元关联的新权重可以如下计算。
New Weight = Old Weight + LEARNING_RATE * 1 * Output Of InputNeuron * Delta
由于一个神经元可以有多个输入,上述步骤应该针对连接到该神经元的所有输入神经元执行。
即,
For Each InputNeuron N connected to ThisNeuron
New Weight of N = Old Weight of N + _
LEARNING_RATE * 1 * N.Output * ThisNeuron.Delta
Next
现在,在步骤4之后,我们有了一个更好的网络。这个过程会重复进行,对AND真值表中的所有其他条目进行,可能超过数千次,以“良好地”训练网络。
4.2. 运行网络
运行网络包括:
- 完全按照上面步骤1所述向网络提供输入
- 按照上面步骤2所述计算输出
然而,重要的是要注意,网络应该用足够的样本(和足够的次数)进行训练,以获得期望的结果。无论如何,几乎不可能说神经网络的输出对任何输入都将是100%准确的。
现在,让我们看看这些概念如何在BrainNet神经网络库中实现。
5. 设计BrainNet神经网络库
对于任何解决方案开发者而言,根本的挑战在于根据他对系统的抽象概念,创建、构建或组装一个可工作的程序。这种转化的质量很大程度上取决于他对系统的理解程度。在此,我想提一下,Brain Net库的实际设计并非基于对现有各种神经网络模型和神经网络领域新兴可能性的完整而透彻的理解。因此,我怀疑该框架的当前设计大多偏向于我之前解释的反向传播系统——尽管它也可以修改以创建其他神经网络模型。
我们只是将上述概念映射到库中。因此,如果你阅读了上面关于神经网络的概念,那么以下代码和解释就非常容易理解。
5.1. UML模型
现在,让我们看看BrainNet库中的一些接口和类。
|
请看下面的模型。请注意,此模型仅包含模型中的主要接口和类。
图:BrainNet框架的部分模型
正如我们之前讨论的,神经网络由多个神经元层组成,每个神经元层又包含多个神经元。神经元有一个策略,它决定了神经元应如何执行诸如求和、激活、误差计算、偏置调整、权重调整等任务。
简要介绍一下上面的UML图:
- INeuron、INeuronStrategy、INeuralNetwork 和 INetworkFactory 都是接口。
- 一个神经元应该实现INeuron接口
- 神经网络应该实现INeuralNetwork接口
- 一个神经元拥有一个策略,该策略应该实现INeuronStrategy接口。我们有INeuronStrategy的一个具体实现,称为BackPropNeuronStrategy(用于反向传播神经网络)。
- 神经网络由神经网络工厂进行初始化,并建立层之间的连接。工厂应该实现INetworkFactory接口。我们有一个INetworkFactory的具体实现,称为BackPropNetworkFactory,用于创建反向传播神经网络。
模型中的主要接口概述如下。
INetworkFactory |
定义神经网络工厂的接口 |
INeuron |
定义神经元的接口 |
INeuronStrategy |
定义神经元策略的接口 |
INeuralNetwork |
定义神经网络的接口 |
模型中的主要类概述如下。
|
5.2. BrainNet库中的神经元
**INeuron**接口提供了一个抽象接口,应该实现它来创建一个具体的神经元。我建议您回顾我们之前讨论的人工神经元的概念。
**INeuron** 接口中的元素详述如下。
'The interface for defining a neuron
Public Interface INeuron
'The current bias this neuron
Property BiasValue() As Single
'The current output this neuron
Property OutputValue() As Single
'The current delta value this neuron
Property DeltaValue() As Single
'A list of neurons to which this neuron is connected
ReadOnly Property ForwardConnections() As NeuronCollection
'Gets a list of neurons connected to this neuron
ReadOnly Property Inputs() As NeuronConnections
'Gets or sets the strategy of this neuron
Property Strategy() As INeuronStrategy
'Method to update the output of a neuron
Sub UpdateOutput()
'Method to find new delta value
Sub UpdateDelta(ByVal errorFactor As Single)
'Method to update free parameters
Sub UpdateFreeParams()
End Interface
一个具体的神经元将实现INeuron接口。**Neuron**类是**INeuron**的一个具体实现。神经元的Strategy属性保存其当前策略。**Inputs**属性保存连接到该神经元(在前一层)的神经元引用。**ForwardConnections**保存连接到该神经元(在下一层)的神经元引用。
现在,通过解压 BrainNet 库的源代码 zip 文件,查看 **Neuron** 类。让我们检查 **Neuron** 类中实现的三个主要函数——**UpdateOutput**、**UpdateDelta** 和 **UpdateFreeParams**。这些函数由 NeuralNetwork 类通过训练和运行网络来调用。我们稍后将看到 NeuralNetwork 类中的函数如何调用这些函数。
这些函数使用神经元的当前策略对象来执行操作。
- **UpdateDelta** - 使用当前策略找到该神经元的新增量。误差因子(请记住,这会根据神经元的层而变化)将从神经网络类中的函数传递给 UpdateDelta 函数。
- **UpdateOutput** - 通过找到净值,然后调用激活函数,计算神经元的新输出——如当前策略中定义的。
- **UpdateFreeParams** - 更新自由参数包括根据该神经元的当前策略调用函数,以找到新的偏置并更新权重。
'Calculate the error value
Public Sub UpdateDelta(ByVal errorFactor As Single) Implements _
NeuralFramework.INeuron.UpdateDelta
If _strategy Is Nothing Then
Throw New StrategyNotInitializedException("", Nothing)
'Error factor is found and passed to this
DeltaValue = Strategy.FindDelta(OutputValue, errorFactor)
End Sub
'Calculate the output
Public Sub UpdateOutput() _
Implements NeuralFramework.INeuron.UpdateOutput
If _strategy Is Nothing Then
Throw New StrategyNotInitializedException("..", Nothing)
Dim netValue As Single = Strategy.FindNetValue(Inputs, BiasValue)
OutputValue = Strategy.Activation(netValue)
End Sub
'Calculate the free parameters
Public Sub UpdateFreeParams() _
Implements NeuralFramework.INeuron.UpdateFreeParams
If _strategy Is Nothing Then
Throw New StrategyNotInitializedException("..", Nothing)
BiasValue = Strategy.FindNewBias(BiasValue, DeltaValue)
Strategy.UpdateWeights(Inputs, DeltaValue)
End Sub
5.3. 神经元的策略
神经元如何实际工作由神经元的策略决定。一个具体策略应该实现**INeuronStrategy**接口。该接口如下所示。**BackPropNeuronStrategy**是INeuronStrategy接口的一个具体实现。
**INeuronStrategy**接口中的元素及其描述如下。
'The interface for defining the strategy of a neuron
Public Interface INeuronStrategy
'Function to find the delta or error rate of this INeuron
Function FindDelta(ByVal output As Single, _
ByVal errorFactor As Single) As Single
'Activation Function, or ThreshHold function
Function Activation(ByVal value As Single) As Single
'Summation Function for finding the net value
Function FindNetValue(ByVal inputs As NeuronConnections, _
ByVal bias As Single) As Single
'Function for calculating new bias
Function FindNewBias(ByVal bias As Single, _
ByVal delta As Single) As Single
'Function for updating weights
Sub UpdateWeights(ByRef connections As NeuronConnections, _
ByVal delta As Single)
End Interface
查看代码中的 **BackPropNeuronStrategy** 类,看看这些函数是如何按照我们之前描述的方式实现的。它非常容易理解。
5.4. BrainNet库中的神经网络
现在,让我们看看神经网络是如何实现的。任何具体的神经网络都应该实现**INeuralNetwork**接口。INeuralNetwork接口如下所示。
Public Interface INeuralNetwork
'Method to train a network
Sub TrainNetwork(ByVal t As TrainingData)
'This function can be used for connecting two neurons together
Sub ConnectNeurons(ByVal source As INeuron, _
ByVal destination As INeuron, ByVal weight As Single)
'This function can be used for connecting
'two neurons together with random weight
Sub ConnectNeurons(ByVal source As INeuron, _
ByVal destination As INeuron)
'This function can be used for connecting neurons
'in two layers together with random weights
Sub ConnectLayers(ByVal layer1 As NeuronLayer, _
ByVal layer2 As NeuronLayer)
'This function can be used for connecting all
'neurons in all layers together
Sub ConnectLayers()
'This function may be used for running the network
Function RunNetwork(ByVal inputs As ArrayList) As ArrayList
'This function may be used to obtain the output list
Function GetOutput() As ArrayList
ReadOnly Property Layers() As NeuronLayerCollection
'Gets the first (input) layer
ReadOnly Property InputLayer() As NeuronLayer
'Gets the last (output) layer
ReadOnly Property OutputLayer() As NeuronLayer
End Interface
有两个有趣的函数,**TrainNetwork**和**RunNetwork**,用于训练和运行网络。TrainNetwork函数的输入是一个**TrainingData**类的对象。TrainingData类有两个ArrayList类型的属性——Inputs和Outputs。为了训练网络,我们将输入值放入Inputs ArrayList,并将相应的输出值填充到Outputs ArrayList。
5.5. 训练网络
首先,将输入馈送到输入层中的所有神经元。然后,算法如下:
- **步骤1**:找到隐藏层神经元和输出层神经元的输出
- **步骤2**:查找增量
- 2.1) 找到输出层的增量(误差率)
- 2.2) 倒序计算所有隐藏层的增量
- **步骤3**:更新隐藏层和输出层的自由参数
在 NeuralNetwork 类中的 TrainNetwork 函数中,看看它是如何进行的,注释非常详细。TrainNetwork 函数的一部分如下所示。
Dim i As Long
Dim someNeuron As INeuron
i = 0
'Give our inputs to the first layer.
't is an object of TrainingData class
For Each someNeuron In InputLayer
someNeuron.OutputValue = t.Inputs(i)
i = i + 1
Next
'Step1: Find the output of hidden layer
'neurons and output layer neurons
Dim nl As NeuronLayer
Dim count As Long = 1
For count = 1 To _layers.Count - 1
nl = _layers(count)
For Each someNeuron In nl
someNeuron.UpdateOutput()
Next
Next
'Step2: Finding Delta
'2.1) Find the delta (error rate) of output layer
i = 0
For Each someNeuron In OutputLayer
'Find the target-output value and pass it
someNeuron.UpdateDelta(t.Outputs(i) - _
someNeuron.OutputValue)
i = i + 1
Next
'2.2) Calculate delta of all the hidden layers, backwards
Dim layer As Long
Dim currentLayer As NeuronLayer
For i = _layers.Count - 2 To 1 Step -1
currentLayer = _layers(i)
For Each someNeuron In currentLayer
Dim errorFactor As Single = 0
Dim connectedNeuron As INeuron
For Each connectedNeuron In _
someNeuron.ForwardConnections
'Sum up all the delta * weight
errorFactor = _
errorFactor + (connectedNeuron.DeltaValue * _
connectedNeuron.Inputs.Weight(someNeuron))
Next
someNeuron.UpdateDelta(errorFactor)
Next
Next
'Step3: Update the free parameters of hidden and output layers
For i = 1 To _layers.Count - 1
For Each someNeuron In _layers(i)
someNeuron.UpdateFreeParams()
Next
Next
5.6. 运行网络
运行网络非常简单。要运行网络,我们只需将输入馈送到第一层,并计算输出,就像训练阶段前面解释的那样。以下是RunNetwork函数的一部分。
Dim someNeuron As INeuron
Dim i As Long = 0
For Each someNeuron In InputLayer
someNeuron.OutputValue = CType(inputs(i), System.Single)
i += 1
Next
'Step1: Find the output of each hidden neuron layer
Dim nl As NeuronLayer
For i = 1 To _layers.Count - 1
nl = _layers(i)
For Each someNeuron In nl
someNeuron.UpdateOutput()
Next
Next
5.7. 创建网络
现在,让我们看看如何轻松创建一个网络。这是一个简单的代码,展示了如何创建一个网络。假设方法的输入是一个数组列表,其中包含表示每层神经元数量的long值列表。
'Demo Routine to create a network. The input parameter is a list of
'long values that represent the number of neurons in each layer
Public Sub CreateNetwork(ByVal neuronsInLayers As ArrayList)
Dim bnn As New NeuralNetwork()
Dim neurons As Long
Dim strategy As New BackPropNeuronStrategy()
'NeuronsInLayers is an arraylist which holds
'the number of neurons in each layer
For Each neurons In neuronsInLayers
Dim layer As NeuronLayer
Dim i As Long
layer = New NeuronLayer()
'Let us add
For i = 0 To neurons - 1
layer.Add(New Neuron(strategy))
Next
bnn.Layers.Add(layer)
Next
'Connect all layers together
bnn.ConnectLayers()
'Now the network is ready, do other stuff here
End Function
或者更好的是,您可以使用 **BackPropNetworkFactory** 类轻松创建网络。请查看 BackPropNetworkFactory 类。它有两个重载的 **CreateNetwork** 函数,用于创建神经网络。
一些注意事项。
- 本文更像是一份 BrainNet 神经网络库的“开发者指南”。
- 如果您还没有阅读我的上一篇文章,请务必查看。它或多或少是该库的“用户指南”——其中包含更多关于如何在您自己的项目中使用 BrainNet 库以及演示项目的运行情况。
接下来是什么?
**干杯!!** 至此,我们完成了关于神经网络的第二篇文章。回头看看,确保你清楚地理解了所有要点。
自己动手尝试这个库,并尝试对其进行一些优化,或者更好的是,以此为例自己创建一个神经网络。在我的下一篇文章中,
- 我将解释如何自己创建一个基于XML的语言,用于创建、训练和处理神经网络。
- 解释框架中我未在本文中提及的一些类的概念(例如NXML解释器、NetworkSerializer等)。
BrainNet 库的源代码中包含一些我尚未提及的“彩蛋”。例如,如果你足够聪明,可以开始玩转随附压缩包中已包含的 nxml 工具。zip 文件包含全部代码。**nxml** 是一个命令行工具,可以帮助你使用 xml 创建、训练和运行神经网络。我将在下一篇文章中详细解释它。无论如何,编译项目后,在命令行中输入 **nxml** 将显示其用法 :) - 如果你等不及我的下一篇文章。另一个演示项目是一个简单的手写识别板,也包含在源代码 zip 文件中。
此外,
- 您可以访问我的网站 http://amazedsaint.blogspot.com/,获取大量技术资源、代码和项目。
当您使用该库时,如果遇到任何错误,请报告。
附录 - 我博客中一些有趣的文章
- BingyBot - 一个用.NET编写的Google Wave Bot,与Bing API接口 - 在我上周末对API的黑客攻击之后,我整合了我的第一个简单的.NET机器人,Bingy Bot……
- 在运行时“附加”成员(属性、方法等)到对象 - C# 4.0 Expando- 关于.NET 4.0系列 - 我将在这些帖子中介绍.NET 4.0及相关技术的各个方面……
- C# 4.0 中动态对象和 MEF 的乐趣 - 关于 .NET 4.0 系列 - 我将在这些帖子中介绍 .NET 4.0 及相关技术的各个方面……