反向传播神经网络入门

http://www.hankcs.com/ml/back-propagation-neural-network.html

单个神经元

神经网络是多个“神经元”(感知机)的带权级联,神经网络算法可以提供非线性的复杂模型,它有两个参数:权值矩阵{Wl}和偏置向量{bl},不同于感知机的单一向量形式,{Wl}是复数个矩阵,{bl}是复数个向量,其中的元素分别属于单个层,而每个层的组成单元,就是神经元。

神经元

神经网络是由多个“神经元”(感知机)组成的,每个神经元图示如下:

这其实就是一个单层感知机,其输入是由和+1组成的向量,其输出为,其中f是一个激活函数,模拟的是生物神经元在接受一定的刺激之后产生兴奋信号,否则刺激不够的话,神经元保持抑制状态这种现象。这种由一个阈值决定两个极端的函数有点像示性函数,然而这里采用的是Sigmoid函数,其优点是连续可导。

Sigmoid函数

常用的Sigmoid有两种——

单极性Sigmoid函数

或者写成

其图像如下

双极性Sigmoid函数

或者写成

把第一个式子分子分母同时除以ez,令x=-2z就得到第二个式子了,换汤不换药。

其图像如下

从它们两个的值域来看,两者名称里的极性应该指的是正负号。从导数来看,它们的导数都非常便于计算:

对于,对于tanh,有

视频作者Ryan还担心观众微积分学的不好,细心地给出了1/(1+e^-x)求导的过程:

一旦知道了f(z),就可以直接求f‘(z),所以说很方便。

本Python实现使用的就是1/(1+e^-x)

  1. def sigmoid(x):
  2. """
  3. sigmoid 函数,1/(1+e^-x)
  4. :param x:
  5. :return:
  6. """
  7. return 1.0/(1.0+math.exp(-x))
  8. def dsigmoid(y):
  9. """
  10. sigmoid 函数的导数
  11. :param y:
  12. :return:
  13. """
  14. return y * (1 - y)

也可以使用双曲正切函数tanh

  1. def sigmoid(x):
  2. """
  3. sigmoid 函数,tanh
  4. :param x:
  5. :return:
  6. """
  7. return math.tanh(x)

其导数对应于:

  1. def dsigmoid(y):
  2. """
  3. sigmoid 函数的导数
  4. :param y:
  5. :return:
  6. """
  7. return 1.0 - y ** 2

神经网络模型

神经网络就是多个神经元的级联,上一级神经元的输出是下一级神经元的输入,而且信号在两级的两个神经元之间传播的时候需要乘上这两个神经元对应的权值。例如,下图就是一个简单的神经网络:

其中,一共有一个输入层,一个隐藏层和一个输出层。输入层有3个输入节点,标注为+1的那个节点是偏置节点,偏置节点不接受输入,输出总是+1。

定义上标为层的标号,下标为节点的标号,则本神经网络模型的参数是:,其中是第l层的第j个节点与第l+1层第i个节点之间的连接参数(或称权值);表示第l层第i个偏置节点。这些符号在接下来的前向传播将要用到。

前向传播

虽然标题是《(误差)后向传播神经网络入门》,但这并不意味着可以跳过前向传播的学习。因为如果后向传播对应训练的话,那么前向传播就对应预测(分类),并且训练的时候计算误差也要用到预测的输出值来计算误差。

定义为第l层第i个节点的激活值(输出值)。当l=1时,。前向传播的目的就是在给定模型参数的情况下,计算l=2,3,4…层的输出值,直到最后一层就得到最终的输出值。具体怎么算呢,以上图的神经网络模型为例:

这没什么稀奇的,核心思想是这一层的输出乘上相应的权值加上偏置量代入激活函数等于下一层的输入,一句大白话,所谓中文伪码。

另外,追求好看的话可以把括号里面那个老长老长的加权和定义为一个参数:表示第l层第i个节点的输入加权和,比如。那么该节点的输出可以写作

于是就得到一个好看的形式:

在这个好看的形式下,前向传播可以简明扼要地表示为:

在Python实现中,对应如下方法:

  1. def runNN(self, inputs):
  2. """
  3. 前向传播进行分类
  4. :param inputs:输入
  5. :return:类别
  6. """
  7. if len(inputs) != self.ni - 1:
  8. print ‘incorrect number of inputs‘
  9. for i in range(self.ni - 1):
  10. self.ai[i] = inputs[i]
  11. for j in range(self.nh):
  12. sum = 0.0
  13. for i in range(self.ni):
  14. sum += ( self.ai[i] * self.wi[i][j] )
  15. self.ah[j] = sigmoid(sum)
  16. for k in range(self.no):
  17. sum = 0.0
  18. for j in range(self.nh):
  19. sum += ( self.ah[j] * self.wo[j][k] )
  20. self.ao[k] = sigmoid(sum)
  21. return self.ao

其中,ai、ah、ao分别是输入层、隐藏层、输出层,而wi、wo则分别是输入层到隐藏层、隐藏层到输出层的权值矩阵。在本Python实现中,将偏置量一并放入了矩阵,这样进行线性代数运算就会方便一些。

后向传播

后向传播指的是在训练的时候,根据最终输出的误差来调整倒数第二层、倒数第三层……第一层的参数的过程。

符号定义

在Ryan的讲义中,符号定义与斯坦福前向传播讲义相似但略有不同:

:第l层第j个节点的输入。

:从第l-1层第i个节点到第l层第j个节点的权值。

:Sigmoid函数。

:第l层第j个节点的偏置。

:第l层第j个节点的输出。

:输出层第j个节点的目标值(Target value)。

输出层权值调整

给定训练集和模型输出(这里没有上标l是因为这里在讨论输出层,l是固定的),输出层的输出误差(或称损失函数吧)定义为:

其实就是所有实例对应的误差的平方和的一半,训练的目标就是最小化该误差。怎么最小化呢?看损失函数对参数的导数呗。

将E的定义代入该导数:

无关变量拿出来:

看到这里大概明白为什么非要把误差定义为误差平方和的一半了吧,就是为了好看,数学家都是外貌协会的。

=(输出层的输出等于输入代入Sigmoid函数)这个关系代入有:

对Sigmoid求导有:

要开始耍小把戏了,由于输出层第k个节点的输入等于上一层第j个节点的输出乘上,即=,而上一层的输出是与到输出层的权值变量无关的,可以看做一个常量,是线性关系。所以对求权值变量的偏导数直接等于,也就是说:=()=

然后将上面用过的=代进去就得到最终的:

为了表述方便将上式记作:

其中:

隐藏层权值调整

依然采用类似的方法求导,只不过求的是关于隐藏层和前一层的权值参数的偏导数:

老样子:

还是老样子:

还是把Sigmoid弄进去:

=代进去,并且将导数部分拆开:

又要耍把戏了,输出层的输入等于上一层的输出乘以相应的权值,亦即=,于是得到:

把最后面的导数挪到前面去,接下来要对它动刀了:

再次利用=,这对j也成立,代进去:

再次利用=,j换成i,k换成j也成立,代进去:

利用刚才定义的,最终得到:

其中:

我们还可以仿照的定义来定义一个,得到:

其中

偏置的调整

因为没有任何节点的输出流向偏置节点,所以偏置节点不存在上层节点到它所对应的权值参数,也就是说不存在关于权值变量的偏导数。虽然没有流入,但是偏置节点依然有输出(总是+1),该输出到下一层某个节点的时候还是会有权值的,对这个权值依然需要更新。

我们可以直接对偏置求导,发现:

原视频中说?O/?θ=1,这是不对的,作者也在讲义中修正了这个错误,?O/?θ=O(1–O)。

然后再求,后面的导数等于,代进去有

其中,

后向传播算法步骤

    • 随机初始化参数,对输入利用前向传播计算输出。
    • 对每个输出节点按照下式计算delta:
    • 对每个隐藏节点按照下式计算delta:
    • 计算梯度,并更新权值参数和偏置参数:。这里的是学习率,影响训练速度。

后向传播算法实现

  1. def backPropagate(self, targets, N, M):
  2. """
  3. 后向传播算法
  4. :param targets: 实例的类别
  5. :param N: 本次学习率
  6. :param M: 上次学习率
  7. :return: 最终的误差平方和的一半
  8. """
  9. # http://www.youtube.com/watch?v=aVId8KMsdUU&feature=BFa&list=LLldMCkmXl4j9_v0HeKdNcRA
  10. # 计算输出层 deltas
  11. # dE/dw[j][k] = (t[k] - ao[k]) * s‘( SUM( w[j][k]*ah[j] ) ) * ah[j]
  12. output_deltas = [0.0] * self.no
  13. for k in range(self.no):
  14. error = targets[k] - self.ao[k]
  15. output_deltas[k] = error * dsigmoid(self.ao[k])
  16. # 更新输出层权值
  17. for j in range(self.nh):
  18. for k in range(self.no):
  19. # output_deltas[k] * self.ah[j] 才是 dError/dweight[j][k]
  20. change = output_deltas[k] * self.ah[j]
  21. self.wo[j][k] += N * change + M * self.co[j][k]
  22. self.co[j][k] = change
  23. # 计算隐藏层 deltas
  24. hidden_deltas = [0.0] * self.nh
  25. for j in range(self.nh):
  26. error = 0.0
  27. for k in range(self.no):
  28. error += output_deltas[k] * self.wo[j][k]
  29. hidden_deltas[j] = error * dsigmoid(self.ah[j])
  30. # 更新输入层权值
  31. for i in range(self.ni):
  32. for j in range(self.nh):
  33. change = hidden_deltas[j] * self.ai[i]
  34. # print ‘activation‘,self.ai[i],‘synapse‘,i,j,‘change‘,change
  35. self.wi[i][j] += N * change + M * self.ci[i][j]
  36. self.ci[i][j] = change
  37. # 计算误差平方和
  38. # 1/2 是为了好看,**2 是平方
  39. error = 0.0
  40. for k in range(len(targets)):
  41. error = 0.5 * (targets[k] - self.ao[k]) ** 2
  42. return error

注意不同于上文的单一学习率,这里有两个学习率N和M。N相当于上文的,而M则是在用上次训练的梯度更新权值时的学习率。这种同时考虑最近两次迭代得到的梯度的方法,可以看做是对单一学习率的改进。

另外,这里并没有出现任何更新偏置的操作,为什么?

因为这里的偏置是单独作为一个偏置节点放到输入层里的,它的值(输出,没有输入)固定为1,它的权值已经自动包含在上述权值调整中了。

如果将偏置作为分别绑定到所有神经元的许多值,那么则需要进行偏置调整,而不需要权值调整(此时没有偏置节点)。

哪个方便,当然是前者了,这也导致了大部分神经网络实现都采用前一种做法。

完整的实现

已开源到了Github上:https://github.com/hankcs/neural_net

这一模块的原作者是Neil Schemenauer,我做了些注释。

直接运行bpnn.py即可得到输出:

  1. Combined error 0.171204877501
  2. Combined error 0.190866985872
  3. Combined error 0.126126875154
  4. Combined error 0.0658488960415
  5. Combined error 0.0353249077599
  6. Combined error 0.0214428399072
  7. Combined error 0.0144886807614
  8. Combined error 0.0105787745309
  9. Combined error 0.00816264126944
  10. Combined error 0.00655731212209
  11. Combined error 0.00542964723539
  12. Combined error 0.00460235328667
  13. Combined error 0.00397407912435
  14. Combined error 0.00348339081276
  15. Combined error 0.00309120476889
  16. Combined error 0.00277163178862
  17. Combined error 0.00250692771135
  18. Combined error 0.00228457151714
  19. Combined error 0.00209550313514
  20. Combined error 0.00193302192499
  21. Inputs: [0, 0] --> [0.9982333356008245]  Target [1]
  22. Inputs: [0, 1] --> [0.9647325217906978]  Target [1]
  23. Inputs: [1, 0] --> [0.9627966274767186]  Target [1]
  24. Inputs: [1, 1] --> [0.05966109502803293]  Target [0]

IBM利用Neil Schemenauer的这一模块(旧版)做了一个识别代码语言的例子,我将其更新到新版,已经整合到了项目中。

要运行测试的话,执行命令

  1. code_recognizer.py testdata.200

即可得到输出:

  1. ERROR_CUTOFF = 0.01
  2. INPUTS = 20
  3. ITERATIONS = 1000
  4. MOMENTUM = 0.1
  5. TESTSIZE = 500
  6. OUTPUTS = 3
  7. TRAINSIZE = 500
  8. LEARNRATE = 0.5
  9. HIDDEN = 8
  10. Targets: [1, 0, 0] -- Errors: (0.000 OK)   (0.001 OK)   (0.000 OK)   -- SUCCESS!

值得一提的是,这里的HIDDEN = 8指的是隐藏层的节点个数,不是层数,层数多了就变成DeepLearning了。

时间: 2024-10-09 14:19:26

反向传播神经网络入门的相关文章

反向传播神经网络极简入门

反向传播神经网络极简入门 我一直在找一份简明的神经网络入门,然而在中文圈里并没有找到.直到我看到了这份162行的Python实现,以及对应的油管视频之后,我才觉得这就是我需要的极简入门资料.这份极简入门笔记不需要突触的图片做装饰,也不需要赘述神经网络的发展历史:要推导有推导,要代码有代码,关键是,它们还对得上.对于欠缺的背景知识,利用斯坦福大学的神经网络wiki进行了补全. 单个神经元 神经网络是多个“神经元”(感知机)的带权级联,神经网络算法可以提供非线性的复杂模型,它有两个参数:权值矩阵{W

【MLP】多层感知机网络——BPN反向传播神经网络

BPN(Back Propagation Net) 反向传播神经网络是对非线性可微分函数进行权值训练的多层网络,是前向神经网络的一种. BP网络主要用于: 1)函数逼近与预测分析:用输入矢量和相应的输出矢量训练一个网络,逼近一个函数或预测未知信息: 2)模式识别:用一个特定的输出矢量将它与输入矢量联系起来: 3)分类:把输入矢量以所定义的合适方式进行分类: 4)数据压缩:减少输出矢量维数以便于传输与存储. 比如,一个三层BPN结构如下: 由输入层.隐含层和输出层三层组成.其中每一层的单元与之相邻

BPN反向传播神经网络

BP算法细节 参数说明:假设有n层.J表示代价函数,和上面的E是同样的意思,只不过用不同的字母写而已. 分析:要想知道第l层的第i个结点的残差,必须知道该节点所连接的下一层的各个结点的权值,以及这些结点的残差,幸亏第l+1层已经计算出来了残差,你只要把后面一层的每个结点j的残差乘以该结点与这一层的结点i相连的权值,然后加和,最后别忘了乘以这一层的激活方式的导数. 最后说明一点,BP传播,计算各层的各点的残差是关键,残差是总的代价函数对于该点的net的偏导,从倒数第二层开始,求残差就要用到其后面的

反向传播神经网络(BP)

①输入.输出矢量及问题的阐述 由题意输入变量取值范围为e={-2,-1,0,1,2}和ec={-2,-1,0,1,2},则输入矢量有25种情况,分别如下所示: 则由T=int((e+ec)/2) ,采用向下取整,可得输出矢量T为: 该问题可描述为通过训练BP神经网络实现模糊控制规则T=int((e+ec)/2),并达到网络输出与期望值误差小于0.001.选取较好的BP神经网络参数,包括隐含层节点个数.学习速率等.同时对不同的学习训练算法进行比较,并通过内插方法测试网络. ②给出网络结构 由于有两

实现一个反向传播人工神经网络

为何实现一个BP神经网络? “What I cannot create, I do not understand” — Richard Feynman, February 1988 实现一个BP神经网络的7个步骤 选择神经网络 结构 随机 初始化权重 实现 前向传播 实现 成本函数 $J(\Theta)$ 实现反向传播算法并计算 偏微分 $\frac{\partial}{\partial\Theta_{jk}^{(i)}}J(\Theta)$ 使用 梯度检查 并在检查后关闭 使用梯度下降或其它优

神经网络入门——16实现一个反向传播

反向传播练习 现在你来实现一个通过反向传播训练的神经网络,数据集就是之前的研究生院录取数据.通过前面所学你现在有能力完成这个练习: 你的目标是: 实现一个正向传播 实现反向传播算法 更新权重 import numpy as np from data_prep import features, targets, features_test, targets_test np.random.seed(21) def sigmoid(x): """ Calculate sigmoid

神经网络入门——15反向传播

反向传播 如何让多层神经网络学习呢?我们已了解了使用梯度下降来更新权重,反向传播算法则是它的一个延伸.以一个两层神经网络为例,可以使用链式法则计算输入层-隐藏层间权重的误差. 要使用梯度下降法更新隐藏层的权重,你需要知道各隐藏层节点的误差对最终输出的影响.每层的输出是由两层间的权重决定的,两层之间产生的误差,按权重缩放后在网络中向前传播.既然我们知道输出误差,便可以用权重来反向传播到隐藏层. 例如,输出层每个输出节点 kk 的误差是 \delta^o_kδko? ,隐藏节点 jj 的误差即为输出

一文弄懂神经网络中的反向传播法——BackPropagation

最近在看深度学习的东西,一开始看的吴恩达的UFLDL教程,有中文版就直接看了,后来发现有些地方总是不是很明确,又去看英文版,然后又找了些资料看,才发现,中文版的译者在翻译的时候会对省略的公式推导过程进行补充,但是补充的又是错的,难怪觉得有问题.反向传播法其实是神经网络的基础了,但是很多人在学的时候总是会遇到一些问题,或者看到大篇的公式觉得好像很难就退缩了,其实不难,就是一个链式求导法则反复用.如果不想看公式,可以直接把数值带进去,实际的计算一下,体会一下这个过程之后再来推导公式,这样就会觉得很容

DL4NLP——神经网络(一)前馈神经网络的BP反向传播算法步骤整理

这里把按[1]推导的BP算法(Backpropagation)步骤整理一下,备忘使用.[1] 中直接使用矩阵微分的记号进行推导,整个过程十分简洁.而且这种矩阵形式有一个非常大的优势就是对照其进行编程实现时非常方便. 但其实用标量计算推导也有一定的好处,比如可以清楚地知道某个权重是被谁所影响的. 记号约定: $L$:神经网络的层数.输入层不算. $n^l$:第 $l$ 层神经元的个数.偏置神经元不算在内. $W^{l}\in\mathbb R^{n^l\times n^{l-1}}$:第 $l-1