精华内容
下载资源
问答
  • 详解BP算法之链式求导法则

    千次阅读 2020-12-11 22:57:01
    包括Hinton关于BP网络的原始论文,对链式求导法则也只是一带而过。 文章先从简化版本的链式法则讲起,再将其应用到BP算法中。 简化版本的链式法则 两层嵌套(复合)函数 如上图所示,E是A1,A2,A3的函数,A1,A2,A3都...

    BP算法的文章很多,但是说明白BP算法中的链式求导法则应该只此一家了。
    西瓜书,李宏毅的网课,考研时的高数资料,高赞博客,甚至Hinton的原始论文,对链式求导法则也只是一带而过。

    文章先从简化版本的链式法则讲起,再将其应用到BP算法中。

    简化版本的链式法则

    两层嵌套(复合)函数
    在这里插入图片描述

    如上图所示,E是A1,A2,A3的函数,A1,A2,A3都是B1函数。此时,简单的运用链式求导法则即可求得E关于B1的偏导:
    d E d B 1 = d E d A 1 ∗ d A 1 d B 1 + d E d A 2 ∗ d A 2 d B 1 + d E d A 3 ∗ d A 3 d B 1 \frac{dE}{dB_1}=\frac{dE}{dA_1}*\frac{dA_1}{dB_1}+\frac{dE}{dA_2}*\frac{dA_2}{dB_1}+\frac{dE}{dA_3}*\frac{dA_3}{dB_1} dB1dE=dA1dEdB1dA1+dA2dEdB1dA2+dA3dEdB1dA3

    但是当函数复合了三层以后,又该怎么处理呢?
    三层嵌套(复合)函数

    在这里插入图片描述
    如上图所示,E是A1,A2,A3的函数,A1,A2,A3都是B1,B2,B3函数,B1,B2,B3都是 C 1 C_1 C1的函数。那么E关于 C 1 C_1 C1的偏导该怎么求呢?
    根据函数的嵌套关系,我们很容易就能写出 d E d A i ∗ d A i d B j ∗ d B j d C 1 ( i , j = 1 , 2 , 3 ) \frac{dE}{dA_i}*\frac{dA_i}{dB_j}*\frac{dB_j}{dC_1}(i,j=1,2,3) dAidEdBjdAidC1dBj(i,j=1,2,3)这样的式子,如 d E d A i ∗ d A 1 d B 1 ∗ d B 1 d C 1 , d E d A 1 ∗ d A 1 d B 2 ∗ d B 2 d C 1 . . . \frac{dE}{dA_i}*\frac{dA_1}{dB_1}*\frac{dB_1}{dC_1},\frac{dE}{dA_1}*\frac{dA_1}{dB_2}*\frac{dB_2}{dC_1}... dAidEdB1dA1dC1dB1,dA1dEdB2dA1dC1dB2...。我们可以轻而易举的把i,j=1,2,3的式子穷举出来。但是随之困扰我们的一个问题就是,该用什么符号把这些式子连接起来,是加号,减号或者乘号?
    这个问题困扰了我很久,查了西瓜书,考研时的高数资料,网上的博客,甚至悲催的读了Hinton的原始论文,但是遗憾的是,所有的资料都只描述了两层嵌套的链式法则。因此,用两层嵌套嵌套的链式法则就能解决多层嵌套的求导问题,应当是业界共识。
    思索之后,答案呼之欲出:可以把前面N-1层嵌套压缩成一层嵌套。举例来说,如上图的三层嵌套函数E可以描述为:E是 B 1 , B 2 , B 3 B_1,B_2,B_3 B1,B2,B3的函数, B 1 , B 2 , B 3 B_1,B_2,B_3 B1,B2,B3 C 1 C_1 C1的函数;而E关于 B i B_i Bi的偏导,就是我们在二层嵌套中已经求过的偏导数。这样就可以使用链式法则了,具体如下图所示:在这里插入图片描述

    链式法则在BP中的应用

    如果掌握了上述多层嵌套链式法则,不妨把它运用的实际的BP算法中。
    首先看一个简单的BP网络,为了简化问题,该网络只包含接近输出层的部分: θ \theta θ代表阈值,w代表连接权,x代表输入,y代表输出,激活函数f是sigmoid函数 1 1 + e − x \frac{1}{1+e^{-x}} 1+ex1。另外, y i ^ \hat{y_i} yi^表示样例的第i个输出,E是偏差。在这里插入图片描述
    再用具体的数学表达式明确表示出嵌套关系:

    在这里插入图片描述

    然后我们就可以开始计算了。在BP算法中,从输出层往输入层计算。在本例中,首先从输出层往输入层计算出偏差关于输入的偏导:在这里插入图片描述
    然后再从输出层往输入层计算出偏差关于阈值和连接权的偏导数:在这里插入图片描述

    展开全文
  • 第五章Neural Networks由网神主讲,精彩内容有:神经网络做回归和分类的训练目标函数、BP误差后向传播的链式求导法则、正则化、卷积网络等。

    主讲人 网神

    (新浪微博:@豆角茄子麻酱凉面

    网神(66707180) 18:55:06

    那我们开始了啊,前面第3,4章讲了回归和分类问题,他们应用的主要限制是维度灾难问题。今天的第5章神经网络的内容:
    1. 神经网络的定义
    2. 训练方法:error函数,梯度下降,后向传导
    3. 正则化:几种主要方法,重点讲卷积网络

    书上提到的这些内容今天先不讲了,以后有时间再讲:BP在Jacobian和Hessian矩阵中求导的应用;
    混合密度网络;贝叶斯解释神经网络。

    首先是神经网络的定义,先看一个最简单的神经网络,只有一个神经元:

    这个神经元是一个以x1,x2,x3和截距1为输入的运算单元,其输出是:

    其中函数f成为"激活函数" , activation function.激活函数根据实际应用确定,经常选择sigmoid函数.如果是sigmoid函数,这个神经元的输入-输出的映射就是一个logistic回归问题。

    神经网络就是将许多个神经元连接在一起组成的网络,如图:

    神经网络的图跟后面第八章图模型不同, 图模型中,每个节点都是一个随机变量,符合某个分布。神经网络中的节点是确定的值,由与其相连的节点唯一确定。

    上图这个神经网络包含三层,左边是输入层,x1...xd节点是输入节点, x0是截距项。最右边是输出层,y1,...,yk是输出节点. 中间是隐藏层hidden level, z1,...,zM是隐藏节点,因为不能从训练样本中观察到他们的值,所以叫隐藏层。

    可以把神经网络描述为一系列的函数转换。首先,构造M个输入节点的线性组合:

    上式中,j=1,...,M,对应M个隐藏节点. 上标(1)表示这是第一层的参数。wji是权重weight,wj0是偏置.  aj 叫做activation. 中文叫激活吧,感觉有点别扭。

    把activation aj输入到一个非线性激活函数h(.)就得到了隐藏节点的值zj。

    在这个从输入层到隐藏层的转换中,这个激活函数不能是线性的,接下来,将隐藏节点的值线性组合得到输出节点的activation: 

    每个ak输入一个输出层激活函数,就得到了输出值。

    这个从隐藏层到输出层的激活函数σ,根据不同应用,有不同的选择,例如回归问题的激活函数是identity函数,既y = a.分类问题的激活函数选择sigmoid函数,multiclass分类选择是softmax函数.

    把上面各阶段的计算连起来,神经网络整个的计算过程就是:

    上面计算过程是以2层神经网络为例。实际使用的NN可能有多层,记得听一个报告现在很火的deep learning的隐藏层有5-9层.这个计算过程forward propagation,从前向后计算。与此相反,训练时,会用back propagation从后向前来计算偏导数。

    神经网络有很强的拟合能力,可以拟合很多种的曲线,这个图是书上的一个例子,对四种曲线的拟合:

    第一部分神经网络的定义就这么多,大家有没有什么问题啊?

    ============================讨论=================================

    阳阳(236995728) 19:20:43

    输入层到隐藏层使用的激活函数与隐藏层到输出层的激活函数是否要一致?

    网神(66707180) 19:21:11

    两层激活函数不一定一致,输入层到隐藏层 经常是sigmoid函数。 而隐藏层到输出层,根据应用不同,选择不同.

    牧云(1106207961) 19:22:30

    一般的算法,比如神经网络,都有分类和拟合的功能,分类和拟合有共同点吗?为什么能拟合,这个问题我没有找到地方清晰的解释。

    独孤圣者(303957511) 19:23:47

    拟合和分类,在我看来实际上是一样的,分类无非是只有2个或多个值的拟合而已。2个值的拟合,被称为二值分类

    ant/wxony(824976094) 19:24:30

    不是吧,svm做二值分类,就不是以拟合为目标。二值拟合可以用做分类。

    独孤圣者(303957511) 19:29:42

    可以作为概率来理解吧,我个人认为啊,sigmoid函数,是将分类结果做一个概率的计算。

    阳阳(236995728) 19:24:07

    输入层到隐藏层 经常是sigmoid函数 那么也就是特征值变成了【0-1】之间的数了?

    网神(66707180) 19:25:19

    是的,我认为是.机器学习经常需要做特征归一化,也是把特征归一化到[0,1]范围。当然这里不只是归一化。

    阳阳(236995728) 19:26:56

    这里应该不是归一化的作用@网神 

    网神(66707180) 19:27:33

    嗯,不是归一化,我觉得神经网络的这些做法,没有一个科学的解释。不像SVM每一步都有严谨的理论。

    罗杰兔(38900407) 19:29:00

    参数W,b是算出来的,还是有些技巧得到的。因为常听人说,神经网络难就难在调试参数上。

    阳阳(236995728) 19:29:11

    我觉得神经网络也是在学习新的特征 而这些特征比较抽象难于理解@牧云 @网神 

    网神(66707180) 19:29:48

    对,我理解隐藏层的目的就是学习特征

    阳阳(236995728) 19:30:24

    是的 但是这些特征难于理解较抽象@网神 

    网神(66707180) 19:30:47

    最后一个隐藏层到输出层之间,就是做分类或回归。前面的不同隐藏层,则是提取特征。

    独孤圣者(303957511) 19:30:52

    隐藏层的目的就是学习特征,这个表示赞同

    网神(66707180) 19:31:33

    后面讲到卷积网络时,可以很明显感受到,隐藏层的目的就是学习特征。

    阳阳(236995728) 19:31:59

    但是我不理解为什么隐藏层学习到的特征值介于【0-1】之间是合理的?是归一化的作用?

    牧云(1106207961) 19:32:47

    归一化映射

    Wilbur_中博(1954123) 19:33:59

    要归一化也是对每一个输入变量做吧。。各个输入变量都组合在一起了,归一化干嘛。我理解就是一个非线性变换,让神经网络具有非线性能力。sigmoid之外,其他非线性变换用的也蛮多的。和归一化没什么关系。不一定要在[0, 1]之间。

    独孤圣者(303957511) 19:39:50

    这个解释我很同意,tanh函数也经常替换sigmoid函数,在神经网络中普遍使用。

    网神(66707180) 19:39:59

    嗯,让NN具有非线性能力是主要原因之一。

    牧云(1106207961) 19:40:03

    这个1要的干嘛

    网神(66707180) 19:40:18

    这个是偏置项,y = wx+b, 那个+1就是为了把b 合入w,变成 y=wx

    罗杰兔(38900407) 19:41:36

    Ng好象讲b是截距

    网神(66707180) 19:43:16

    截距和偏执项是一个意思。 放在坐标系上,b就是截距

    阳阳(236995728) 19:40:33

    它可以逼近任意的非线性函数吗@网神 

    网神(66707180) 19:42:13

    书上没说可以逼近任意曲线,说的是逼近很多曲线

    阳阳(236995728) 19:42:58

    它到底是怎么逼近的啊@网神  我想看看多个函数叠加的?

    网神(66707180) 19:44:30


    就是多个函数叠加。这个图上那个抛物线就是最终的曲线,由另外三条曲线叠加而成,另外那三条,每条是一个隐藏节点生成的曲线。

    阳阳(236995728) 19:45:05

    这三条曲线用的函数形式是不是一样的

    zeno(117127143) 19:45:31

    理解nn怎么逼近xor函数,就知道怎么非线性的了

    HX(458728037) 19:45:56

    问一句,你现在讲的是BP网络吧?

    网神(66707180) 19:46:07

    是BP网络

    ========================讨论结束=================================

    网神(66707180) 19:46:59

    接下来是神经网络的训练。神经网络训练的套路与第三,四章相同:确定目标变量的分布函数和似然函数,
    取负对数,得到error函数,用梯度下降法求使error函数最小的参数w和b。

    NN的应用有:回归、binary分类、K个独立binary分类、multiclass分类。不同的应用决定了不同的激活函数、不同的目标变量的条件分布,不同的error函数.

    首先,对于回归问题,输出变量t的条件概率符合高斯分布:

    因此,给定一个训练样本集合,其似然函数是:

    error函数是对似然函数取 负对数,得到:

    最小化这个error函数,只需要最小化第一项,所以回归问题的errro函数定义是:

    回归问题的隐藏层到输出层的激活函数是identity函数,也就是

    ak就是对隐藏层节点的线性组合:

    这样有了error函数,就可以同梯度下降法求极值点.

    再说说binary分类和multiclass分类的error函数:

    binary分类的目标变量条件分布式伯努利分布:

    这样,通过对似然函数取负对数,得到error函数如下:

    另外binary分类,隐藏层到输出层的激活函数是sigmoid函数。对multiclass问题,也就是结果是K个互斥的类别中的一个.类似思路可以得到其error函数.这里就不说了。书上都有.

    知道了error函数,和激活函数,这里先做一个计算,为后面的BP做准备,将E(w)对激活ak求导,可得:

    这里有意思的是,不管是回归、binary分类还是multiclass分类,尽管其error函数不同,这个求导得出的结果都是yk-tk

    回归问题这个求导很明显,下式中,y = a, 所以对a求导就是 y - t.

    其他错误函数,需要推导一下,这里就不推了。有了error函数,用梯度下降法求其极小值点。

    因为输入层到隐藏层的激活函数是非线性函数,所以error函数是非凸函数,所以可能存在很多个局部极值点。

    两栖动物(9219642) 20:07:40

    你这里说的回归问题是用神经网络拟合随机变量?

    网神(66707180) 20:07:56

    是的,就是做线性回归要做的事.只是用神经网络的方法,可以拟合更复杂的曲线。这是我的理解。

    梯度下降法的思路就是对error函数求导,然后按下式对参数作调整:

    一直到导数为0,就找到了极值点.

    这是基本思路,实际上很多算法,如共轭梯度法,拟牛顿法等,可以更有效的寻找极值点。因为梯度下降是机器学习中求极值点的常用方法,这些算法不是本章的内容,就不讲了,有专门的章节讲这些算法。 

    这里要讲的是error函数对参数w的求导方法,也就是back propagation后向传导法。
    可以高效的计算这个导数。

    这个导数的计算利用的 求导数的链式法则,

    把E对w的求导,转换成E对activation a的求导 和 a 对w 的求导. 这个地方因为涉及的式子比较多,我在纸上写了一下,我整个贴上吧:

    大家看看,可以讨论一下

    阳阳(236995728) 20:21:30

    error function是负对数似然函数吧

    网神(66707180) 20:21:38

    是的

    天上的月亮(785011830) 20:22:35

    更新hidden层的w,为什么利用偏导的chain rule呢?

    网神(66707180) 20:22:49

    最主要的结论就是:

    对于输出层的节点,

    对于隐藏层的节点,

    E对隐藏层节点w的导数,通过上式,就由输出层节点的导数 求得了.

    有了上面求偏导数的方法,整个训练过程就是:
    1.输入一个样本Xn,根据forward propagation得到每个hidden单元和output单元的激活值a.
    2.计算每个output单元的偏导数

    3.根据反向传导公式,计算每个hidden单元的

    4.计算偏导数。
    5.用一定的步长更新参数w。
    循环往复,一直找到满意的w。

    独孤圣者(303957511) 20:32:09

    BP时,是不是每个样本都要反馈一次?

    网神(66707180) 20:33:32

    应该是每个样本都反馈一次,batch也是.batch形式的每个样本反馈一次,将每个样本的偏导数求和,如下式,得到所有样本的偏导数,然后做一次参数w的调整.

    接下来就讲正则化,神经网络参数多,训练复杂,所以正则化方法也被研究的很多,书上讲了很多种的正则化方法. 第一个,就是在error函数上,加上正则项。
    第三章回归的正则项如下:

    后面那一项对过大的w做惩罚。

    但是对神经网络,正则项需要满足 一个缩放不变性,所以正则项的形式如下:

    这个形式怎么推导出来的,大家看书吧,这里不说了。

    书上讲到的第二种正则化方法是early stop,我理解的思路就是 在训练集上每调整一次w, 得到新的参数,就在验证集上验证一下。 开始在验证集上的error是逐渐下降的,后来就不下降了,会上升。那么在开始上升之前,就停止训练,那个参数就是最佳情况。

    忘了说一个正则化注意对象了,就是隐藏节点的数量M。这个M是预先人为确定的,他的大小决定了整个模型的参数多少。所以是影响 欠拟合还是过拟合的 关键因素。

    monica(909117539) 20:42:38

    开始上升是出现了过拟合么?节点数量和层数都是怎样选择的呀?

    网神(66707180) 20:42:56

    开始上升就是过拟合了,是的,在训练集上,error还是下降的,但验证集上已经上升了。节点数量M是人为确定,我想这个数怎么定,是NN调参的重点。

    monica(909117539) 20:44:36

    :)

    网神(66707180) 20:44:38

    ML牛的人,叫做 调得一手好参,我觉得这里最能体现。

    这个图是不同的M值对拟合情况的影响:

    另外,因为NN又不是凸函数,导致train更麻烦

    monica(909117539) 20:47:01

    嗯,这个能理解,前面模型太简单,欠拟合了,后面模型太复杂,就过拟合了。

    网神(66707180) 20:47:08

    需要尝试不同的M值,对于每一个M值,又需要train多次,每次随即初始化w的值,然后尝试找到不同的局部极值点。

    上面说了三种正则化方法。接下来,因为神经网络在图像识别中,应用广泛,所以图像识别对正则化有些特殊的需求,就是 平移、缩放、旋转不变性。不知道这个需求在其他应用中,是否存在,例如NLP中,反正书上都是以图像处理为例讲的.

    这些不变形,就是图像中的物体位置移动、大小变化、或者一定程度的旋转, 对ouput层的结果应该没有影响。

    这里主要讲卷积神经网络。这个东东好像很有用,我看在Ng的deep learning教程中也重点讲了,当图像是大图时,如100x100像素以上,卷积网络可以大大减少参数w的个数.

    卷积网络的input层的unit是图像的像素值.卷积网络的hidden层由卷积层和子采样层组成,如图:

    一个卷及网络可能包括多个卷积层和子采样层,如下图有两对卷积/子采样层:

    卷积层的units被划分成多个planes,每个plane是一个feature map,一个feature map里的一个unit只连接input层的一个小区域,一个feature map里的所有unit使用相同的weight值。
    卷积层的作用可以认为是提取特征。每个plane就是一个特征滤波器,通过卷积的方式提取图像的一种特征,过滤掉不是本特征的信息。比如提取100种特征,就会有100个plane,上图中,提取6个特征,第二层有6个平面。
    一个plane里面的一个unit与图像的一个子区域相连,unit的值表示从该区域提取的一个特征值。共有多少个子区域,plane里就有多少个unit。
    以上图为例,输入图像是32x32像素,所以input层有32x32个unit. 取子区域的大小为4x4,,那么共有28x28个子区域。每个子区域提取得到6个特征,对应第二层有6个plane, 每个plane有28x28个unit. 

    这样一个plane里的一个unit与输入层的4x4个unit相连,有16个权重weight值,这个plane里的其他unit都公用16个weight值.
    这样,6个平面,共有16x6=96个weight参数和6个bias参数.

    下面说子采样层,上面说道,6个平面,每个平面有28x28个unit,所以第二层共有6x28x28个unit. 这是从图像中提取出来的特征,作为后一层的输入,用于分类或回归。
    但实际中,6个特征远远不够,图像也不一定只有32x32像素。假如从96x96像素中提取400个特征,子区域还是4x4,则第二层的unit会有 400 x (96-4) x (96-4) = 300多万。 超过300多万输入的分类器很难训练,也容易过拟合。
    这就通过子采样来减少特征。子采样就是把卷积层的一个平面划分成几部分,每个部分取最大值或平均值,作为子采样层的一个unit值.如下图:

    左边表示卷积层的一个plane,右边是这个plane的子采样结果。一个格子表示一个unit。
    把左边整个plane分成4部分,互相不重合,每个部分取最大值或平均值,作为右边子采样层的一个unit。这样子采样层一个plane只有4个unit。

    通过卷积和子采样:得到的特征作为下一层分类或回归的输入。
    主要好处:
    a. 这样输入层的各种变形在后面就会变得不敏感。如果有多对 卷积/子采样,每一对就将不变形更深入一层。
    b. 减少参数的个数。就这么多了,大家慢慢看。

    阳阳(236995728) 21:01:30

    关于卷积神经网络的材料有吗?@网神 

    阿邦(1549614810) 21:01:44

    问个问题,cnn怎么做的normalization?

    网神(66707180) 21:03:26

    卷积网络的材料,我看了书上的,还有Andrew Ng的deep learning里讲的,另外网上搜了一篇文章

    这里我贴下链接。

    罗杰兔(38900407) 21:04:19

    不大明白为什么可以采样,怎么保证采样得到的信息正好是我们需要的

    阿邦(1549614810) 21:05:01

    采样是max pooling,用于不变性

    网神(66707180) 21:05:02

    http://ibillxia.github.io/blog/2013/04/06/Convolutional-Neural-Networks/

    牧云(1106207961) 21:05:02

    采样是随机的吗?

    网神(66707180) 21:06:40

    传了个deep learning教程。这是Andrew Ng上课的notes, 网上一些人众包翻译的,我觉得翻译的不错。里面讲了神经网络、卷积网络,和其他深度网络的东西。

    采样不是随机的。

    牧云(1106207961) 21:08:04

    卷积提取的特征具有稳定性吗

    阿邦(1549614810) 21:08:56

    据说要数据量很大才可以

    网神(66707180) 21:09:03

    卷积层和子采样层主要是 解决 不变性问题 和减少参数w数量问题,所以可以起到泛化作用.

     

     

    PRML读书会讲稿PDF版本以及更多资源下载地址:http://vdisk.weibo.com/u/1841149974

    展开全文
  • 摘要: 说到BP(Back Propagation)算法,人们通常强调的是反向传播,其实它是一个双向算法:正向传播输入信号,反向传播误差信息。接下来,你将看到的,可能是史上最为通俗易懂的BP图文讲解,不信?来瞅瞅并吐吐槽呗...

    摘抄自:https://www.cnblogs.com/jzy996492849/p/7080491.html

    系列文章:

    https://yq.aliyun.com/articles/86580?spm=5176.100239.blogcont110025.12.falo6d&do=login&accounttraceid=afa7aaf5-0b81-431a-ba99-7d69981887f3

    摘要: 说到BP(Back Propagation)算法,人们通常强调的是反向传播,其实它是一个双向算法:正向传播输入信号,反向传播误差信息。接下来,你将看到的,可能是史上最为通俗易懂的BP图文讲解,不信?来瞅瞅并吐吐槽呗!

    更多深度文章,请关注:https://yq.aliyun.com/cloud


    系列文章:

    人工“碳”索意犹尽,智能“硅”来未可知(深度学习入门系列之二)


    https://yq.aliyun.com/articles/86580?spm=5176.100239.blogcont110025.12.falo6d&do=login&accounttraceid=afa7aaf5-0b81-431a-ba99-7d69981887f3




    8.1 BP神经网络极简史

    在神经网络(甚至深度学习)参数训练中,BP(Back Propagation)算法非常重要,它都占据举足轻重的地位。在提及BP算法时,我们常将它与杰弗里•辛顿(Geoffrey Hinton)的名字联系在一起。但实际上,辛顿还真不是第一个提出BP算法的人,就像爱迪生不是第一个发明电灯的人一样。但人们记住的,永远都是那个让电灯“飞入平常百姓家”的功勋人物爱迪生,而不是它的第一发明人美国人亨利·戈培尔(Henry Goebel)。
    如果说辛顿就是BP算法的“爱迪生”,那谁是BP算法的“戈培尔”呢?他就是保罗·沃伯斯(Paul Werbos)。1974年,沃伯斯在哈佛大学博士毕业。在他的博士论文里,首次提出了通过误差的反向传播来训练人工神经网络[1]。事实上,这位沃伯斯不光是BP算法的开创者,他还是循环神经网络(Recurrent Neural Network,RNN)的早期开拓者之一。在后期的系列入门文章中,我们还会详细介绍RNN,这里暂且不表。
    说到BP算法,我们通常强调的是反向传播,但其实呢,它是一个典型的双向算法。更确切来说,它的工作流程是分两大步走:(1)正向传播输入信号,输出分类信息(对于有监督学习而言,基本上都可归属于分类算法);(2)反向传播误差信息,调整全网权值(通过微调网络参数,让下一轮的输出更加准确)。
    下面我们分别举例,说明这两个流程。为了简化问题描述,我们使用如图8-1所示的最朴素三层神经网络。在这个网络中,假设输入层的信号向量是[-1, 1],输出层的目标向量为[1, 0],“学习率”η为0.1,权值是随机给的,这里为了演示方便,分别给予或“1”或“-1”的值。下面我们就详细看看BP算法是如何运作的?

    a37d182a966571c11bcf7c67901d95385c4b2797
    图8-1 简易的三层神经网络

    8.2.1正向传播信息

    正向传播信息,简单说来,就是把信号通过激活函数的加工,一层一层的向前“蔓延”,直到抵达输出层。在这里,假设神经元内部的激活函数为Sigmod([Math Processing Error]f(x)=11+e−x)。之所以选用这个激活函数,主要因为它的求导形式非常简洁而优美:
    [Math Processing Error](8.1)f′(x)=f(x)(1−f(x))

    事实上,类似于感知机,每一个神经元的功能都可细分两大部分:(1)汇集各路链接带来的加权信息;(2)加权信息在激活函数的“加工”下,神经元给出相应的输出,如图8-2所示。

    e799875d1ae2221de6408d195b1148e1b7665699
    图8-2 单个神经元的两部分功能

    于是,在正向传播过程中,对于[Math Processing Error]f1(e)神经元的更新如图8-3所示,其计算过程如下所示:
    [Math Processing Error]f1(e)=f1(w11x1+w21x2)=f1((−1)×1+1×(−1))=f1(−2)=11+e−(−2)=0.12


    15e16a639205944383b5dbed20d10b016a4721e5
    图8-3 神经元信息前向更新神经元1的f1(e)

    接着,在同一层更新[Math Processing Error]f2(e)的值,过程和计算步骤类似于[Math Processing Error]f1(e),如图8-4所示:
    [Math Processing Error]f2(e)=f2(w12x1+w22x2)=f2(1×1+1×(−1))=f2(0)=11+e0=0.5

    3162a0faa5695dc6019a9f81d6a59ffebfd33f9e
    图8-4 神经元信息前向更新神经元2的f2(e)

    接下来,信息正向传播到下一层(即输出层),更新神经元3的[Math Processing Error]f3(e)(即输出[Math Processing Error]y1的值),如图8-5所示。
    [Math Processing Error]y1=f3(e)=f3(w13f1+w23f2)=f3(1×0.12+1×0.5)=f3(0.62)=11+e−0.62=0.65

    e0b50156560626a2171ecefcd0106af95a993721
    图8-5 神经元信息前向更新神经元3的f3(e)

    然后,类似地,计算同在输出层求神经元[Math Processing Error]f4(e)(即输出[Math Processing Error]y2)的值,如图8-6所示。
    [Math Processing Error]y2=f4(e)=f4(w14f1+w24f2)=f4((−1)×0.12+1×0.5)=f4(0.38)=11+e−0.38=0.59

    c70fef942c8a8512f172d0d4e7ee3dc6ca646b4c
    图8-6神经元信息前向更新f4(e)

    到此,在第一轮信号前向传播中,实际输出向量已计算得到[Math Processing Error]y′=[0.65,0.59]T,但我们预期输出的向量(即教师信号)是[Math Processing Error]y=[1,0]T,这二者之间是存在“误差”的。于是,重点来了,下面我们就用“误差”信息反向传播,来逐层调整网络参数。为了提高权值更新效率,这里就要用到下文即将提到的“反向模式微分法则(chain rule)”。

    8.2.2 求导中的链式法则

    (砰!砰!砰!敲黑板!请注意:如下部分是BP算法最为精妙之处,值得细细品味!)
    在前面信号正向传播的示例中,为了方便读者理解,我们把所有的权值都暂时给予了确定之值。而实际上,这些值都是可以调整的,也就是说其实它们都是变量,除掉图8-1中的所有确定的权值,把其视为变量,得就到更为一般化的神经网络示意图8-7。

    bf27a24908edd07a5e3338e984749d96d3e5613d
    图8-7 带权重变量的神经网络

    这里为了简化理解,我们暂时假设神经元没有激活函数(或称激活函数为[Math Processing Error]y=x),于是对于隐含层神经元,它的输出可分别表示为:
    [Math Processing Error]f1=x1w11+x2w21f2=x1w12+x2w22

    然后,对于输出层神经元有:
    [Math Processing Error]f3=f1w13+f2w23=(x1w11+x2w21)w13+(x1w12+x2w22)w23=x1w11w13+x2w21w13+x1w12w23+x2w22w23

    [Math Processing Error]f4=f1w14+f2w24=(x1w11+x2w21)w14+(x1w12+x2w22)w24=x1w11w14+x2w21w14+x1w12w24+x2w22w24

    于是,损失函数[Math Processing Error]L可表示为公式(8.2):
    [Math Processing Error](8.2)L(w11,w12,...,wij,...,wmn)=12(yi−fi(w11,w12,...,wij,...,wmn))2

    这里[Math Processing Error]Y为预期输出值向量,为实际输出向量[Math Processing Error]fi(w11,w12,...,wij,...,wmn)。对于有监督学习而言,在特定训练集合下,输入元素[Math Processing Error]xi和预期输出[Math Processing Error]yi都可视为常量。由此可以看到,损失函数[Math Processing Error]L,在本质上,就是一个单纯与权值[Math Processing Error]wij相关的函数(即使把原本的激活函数作用加上去,除了使得损失函数的形式表现得更加复杂外,并不影响这个结论)。
    于是,损失函数[Math Processing Error]L梯度向量可表示为公式(8.3):
    [Math Processing Error](8.3)∇L=(∂L∂w11,∂L∂w12,...,∂L∂wmn)=∂L∂w11e11+∂L∂w12e12+...+∂L∂wmnemn
    其中,这里的[Math Processing Error]eij是正交单位向量。为了求出这个梯度,需要求出损失函数[Math Processing Error]L对每一个权值[Math Processing Error]wij的偏导数。
    BP算法之所以经典,部分原因在于,它是求解这类“层层累进”的复合函数偏导数的利器。为啥这么说呢?下面,我们就列举一个更为简化但不失一般性的例子来说明这个观点(以下案例参考了Chris Olah的博客文章[3])。
    假设有如下一个三层但仅仅包括[Math Processing Error]a、[Math Processing Error]b、[Math Processing Error]c、[Math Processing Error]d和[Math Processing Error]e等5个神经元的网络,在传递过程中,[Math Processing Error]c、[Math Processing Error]d和[Math Processing Error]e神经元对输入信号做了简单的加工,如图8-8所示。

    f80bcb9f278fa1ab5986ad3a94c346b53f974eb4
    图8-8 简易的神经网络

    假设变量[Math Processing Error]a影响[Math Processing Error]c,此时我们想弄清楚[Math Processing Error]a是如何影响[Math Processing Error]c的。于是我们考虑这么一个问题,如果[Math Processing Error]a变化一点点,那么[Math Processing Error]c是如何变化的呢?我们把这种影响关系定义为变量[Math Processing Error]c相对于变量[Math Processing Error]a的偏导数,记做[Math Processing Error]∂c∂a。
    利用高等数学的知识,我们很容易求得,对于直接相连的神经元(如[Math Processing Error]a对[Math Processing Error]c,或[Math Processing Error]b对[Math Processing Error]d),可利用“加法规则”或“乘法规则”直接求出。例如,利用加法规则,[Math Processing Error]∂c∂a可表示为:
    [Math Processing Error]∂c∂a=∂(a+b)∂a=∂a∂a+∂b∂a=1
    而对于表达式为乘法的求偏导规则为:
    [Math Processing Error]∂∂uuv=u∂v∂u+v∂u∂u=v
    那么,对于间接相连的神经元,比如[Math Processing Error]a对[Math Processing Error]e,如果我们也想知道[Math Processing Error]a变化一点点时[Math Processing Error]e变化多少,怎么办呢?也就是说,偏导数[Math Processing Error]∂e∂a该如何求呢?这时,我们就需要用到链式法则了。
    这里假设[Math Processing Error]a=2,[Math Processing Error]b=1。如果[Math Processing Error]a的变化速率是1,那么[Math Processing Error]c的变化速率也是1(因为[Math Processing Error]∂c∂a=1)。类似地,如果[Math Processing Error]c的变化速率为1,那么[Math Processing Error]e的变化速率为2(因为[Math Processing Error]∂e∂c=d=2)。所以相对于[Math Processing Error]a变化1,[Math Processing Error]e的变化为[Math Processing Error]1×2=2。这个过程就是我们常说的“链式法则”,其更为形式化的表达为(如图8-9所示):

    [Math Processing Error]∂e∂a=∂e∂c⋅∂c∂a=d×1=2×1=2

    bd608c17923213e77e038f54d9e5ff4aefd14c44
    图8-9 链式法则示意图

    [Math Processing Error]a对[Math Processing Error]e的“影响”属于单路径的影响,还比较容易求得,但这并不是我们关注的重点。因为在神经网络中,神经元对神经元的连接,阡陌纵横,其影响也是通过多条路径“交织”在一起的。在图8-9中,我们研究一下[Math Processing Error]b对[Math Processing Error]e的影响,就能比较好理解这一工作机理。显然,[Math Processing Error]b对[Math Processing Error]e的影响,也可表达为一个偏导关系:
    [Math Processing Error]∂e∂b=∂cd∂b=d∂c∂b+c∂d∂b=d×1+c×1=2×1+3×1=5
    从图8-9可以看出,[Math Processing Error]b对[Math Processing Error]e影响,其实是“兵分两路”:(1)[Math Processing Error]b通过影响[Math Processing Error]c,然后通过[Math Processing Error]c影响[Math Processing Error]e;(2)[Math Processing Error]b通过影响[Math Processing Error]d,然后通过[Math Processing Error]d影响[Math Processing Error]e。这就是多维变量(这里“多”仅为2)链式法则的“路径加和”原则。
    这个原则,看起来简单明了,但其实蕴藏着巨大代价。因为当网络结构庞大时,这样的“路径加和”原则,很容易产生组合爆炸问题。例如,在如图8-10所示的有向图中,如果[Math Processing Error]X到[Math Processing Error]Y有三条路径(即[Math Processing Error]X分别以[Math Processing Error]α、[Math Processing Error]β和[Math Processing Error]χ的比率影响[Math Processing Error]Y),[Math Processing Error]Y到[Math Processing Error]Z也有三条路径([Math Processing Error]Y分别以[Math Processing Error]δ、[Math Processing Error]ε和[Math Processing Error]ξ的比率影响[Math Processing Error]Z)。

    1fca895b8fe24798d5f97198c0f1522d1db790c9
    图8-10 路径加和规则演示

    于是,很容易根据路径加和原则得到[Math Processing Error]X对[Math Processing Error]Z的偏导数:
    [Math Processing Error](8.4)∂Z∂X=(αδ+αε+αξ)+(βδ+βε+βξ)+(χδ+χε+χξ)

    上面用到的求偏导数方法,可称之为“前向模式微分(forward-mode differentiation)”,如图8-11-(a)所示。当网络结构简单时,即使[Math Processing Error]X到[Math Processing Error]Z的每一个路径都被“临幸(遍历)”一遍,总共才有 [Math Processing Error]3×3=9 条路径,但一旦网络结构的复杂度上去了,这种“前向模式微分”,就会让求偏导数的次数和神经元个数的平方成正比。这个计算量,就很可能是成为机器“难以承受的计算之重”。
    有道是“东方不亮西方亮”。为了避免这种海量求导模式,数学家们另辟蹊径,提出了一种称之为“反向模式微分(reverse-mode differentiation)”。取代公式(8.4)的那种简易的表达方式,我们用公式(8.5)的表达方式来求[Math Processing Error]X对[Math Processing Error]Z的偏导:
    [Math Processing Error](8.5)∂Z∂X=(α+β+ξ)(δ+ε+ξ)

    或许你会不屑一顾,这又是在搞什么鬼?把公式(8.4)恒等变换为公式(8.5)又有什么用呢?

    cdc9891dae5c330f5e372a8fedeb82d8f028c316
    图8-11 前向与反向微分方法对比

    你可别急,这背后大有玄机,且听我慢慢道来。
    前文提到的前向模式微分方法,其实就是我们在高数课堂上学习的求导方式。在这种求导模式中,强调的是某一个输入(比如[Math Processing Error]X)对某一个节点(如神经元)的影响。因此,在求导过程中,偏导数的分子部分,总是根据不同的节点总是不断变化,而分母则锁定为偏导变量“[Math Processing Error]∂X”,保持定不变(见图8-11-(a))。
    相比而言,反向模式微分方法则有很大不同。首先在求导方向上,它是从输出端(output)到输入端进行逐层求导。其次,在求导方法上,它不再是对每一条“路径”加权相乘然后求和,而是针对节点采纳“合并同类路径”和“分阶段求解”的策略。
    拿8-11-(b)的例子来说,先求[Math Processing Error]Y节点对[Math Processing Error]Z节点的"总影响"(反向第一层): 
    [Math Processing Error]∂Z∂Y=δ+ε+ξ
    然后,再求节点[Math Processing Error]X对节点[Math Processing Error]Y的总影响(反向第二层):
    [Math Processing Error]∂Z∂Y=α+β+χ
    然后利用乘法规则求得[Math Processing Error]X对[Math Processing Error]Z的整体影响[Math Processing Error](α+β+ξ)(δ+ε+ξ)。在求导形式上,偏导数的分子部分(节点)不变,而分母部分总是随着节点不同而变化,即[Math Processing Error][∂Z∂]。
    这样说来说去,好像还是不太明白!下面我们还是用图8-9所示的原始例子,对比二者的求导过程,一遍走下来,你就能明白其中的差异。为了进一步方便读者理解,我们将图8-9重新绘制为图8-12的样子。

    f50147c3e6759f5fc8b3dc9545d3a961e9e2dcef
    图8-12 前向模式微分方法

    以求变量[Math Processing Error]b偏导数的流程为例,我们先用前向模式微分法,来说明这种方法的求导过程。根据加法规则,对于求偏导值[Math Processing Error]∂e∂b的步骤可以分两步走:(1)求得所有输入(包括[Math Processing Error]a和[Math Processing Error]b)到终点[Math Processing Error]e的每条路径上的偏导值乘积;(2)对所有条路径的导数值进行加和操作。
    从8-12所示的图中,对于两个输入[Math Processing Error]a和[Math Processing Error]b,它们共有3条路径抵达终点[Math Processing Error]e(分别计为①、②和③)。
    对于第①条路径而言,输入[Math Processing Error]a对[Math Processing Error]e的影响为:
    [Math Processing Error]∂e∂b=0×1×1×2=0
    对于第②条路径而言,输入[Math Processing Error]b对[Math Processing Error]e的影响为:
    [Math Processing Error]∂e∂b=1×1×1×2=2
    对于第③条路径而言,输入[Math Processing Error]b对[Math Processing Error]e的影响为:
    [Math Processing Error]∂e∂b=1×1×1×3=3
    所以在整体上,输入[Math Processing Error]a和[Math Processing Error]b从三条路径上对e施加的“总影响”为:0+2+3=5.
    或许读者已经注意到了,有些路径已经被冗余遍历了,比如在图8-12所示中,[Math Processing Error]a→c→e(第①条路)和[Math Processing Error]b→c→e(第②条路)就都走了路径[Math Processing Error]c→e。
    此外,对于求[Math Processing Error]∂e∂a,上述三条路径,它们同样还是“一个都不能少”地走一遍,这到底得有多少冗余啊!
    可能你会疑问,对于[Math Processing Error]∂e∂b([Math Processing Error]∂e∂a),第①(③)条路径明明可以不走的嘛?这种明智,是对人的观察而言的,且是对于简单网络而言的。因为,如果网络及其复杂,人们可能就没有这么“慧眼识珠”,识别其中的路径冗余。
    此外,对于计算机而言,有些时候,局部操作的“优化”,相对于整体操作的“规范化”,顶多算得上“奇淫巧技”,其优势可谓是“荡然无存”。如果有过大规模并行编程经验的读者,可能对这个观点会有更深的认知。
    然而,同样是利用链式法则,反向模式微分方法就能非常机智地避开了这种冗余(下面即将讲到的BP算法,正是由于这么干,才有其优势的)。在这种方法中,它能做到对每一条路径只“临幸”一次,这是何等的节俭!下面我们来看看它是如何工作的。

    98d8179592100a1d3e6f9091c68085d3f0754f10
    图8-13反向模式微分方法

    相比于“前向模式微分法”是以输入(如图8-13-(a)所示的[Math Processing Error]a和[Math Processing Error]b)为锚点,正向遍历每一条可能的路径。反向模式微分法是以节点(或说神经元,如图8-13-(a)所示的[Math Processing Error]e、[Math Processing Error]c和[Math Processing Error]d)为锚点,逐层分阶段求导,然后汇集每一个节点的外部路径(合并同类路径)。
    如图8-13-(b)所示,在反向求导的第1层,对于节点[Math Processing Error]c有:

    [Math Processing Error]∂e∂c=∂(c×d)∂c=d=2
    类似的,对于节点[Math Processing Error]d有:
    [Math Processing Error]∂e∂d=∂(c×d)∂d=c=3
    在阶段性的求解完毕第一层的导数之后,下面开始求解第二层神经元变量的偏导。如图8-13-(c)所示,在反向第2层,对于节点[Math Processing Error]a有如图8-14-(Ⅰ)所示的求导模式。

    a8223852797a6456a4659c694bc3e81dbcf02213
    图8-14反向模式微分方法的推演

    特别需要注意的是,8-14-(Ⅰ)所示的表达式“[Math Processing Error]∂e∂c∂c∂a”中左部“[Math Processing Error]∂e∂c”,已经在第1层求解过了,并“存储”在神经元[Math Processing Error]c中。此时,采用“拿来主义”,拿来就能用!这就是反向模式微分的精华所在!
    类似地,在反向求导第2层,对于节点[Math Processing Error]b,由于它汇集“两路兵马”的影响,所以需要合并同类路径,有如图8-14-(Ⅱ)所示结果。
    这样一来,如图8-13反向模式微分方法,每个路径仅仅遍历一次,就可以求得所有输出(如[Math Processing Error]e节点)对输入(如[Math Processing Error]a或[Math Processing Error]b节点)的偏导,干净利落,没有任何冗余!
    在第七章,我们提到“BP算法把网络权值纠错的运算量,从原来的与神经元数目的平方成正比,下降到只和神经元数目本身成正比。”其功劳,正是得益于这个反向模式微分方法节省的计算冗余!

    8.2.3 误差反向传播

    有了前面“链式求导”的知识铺垫,下面我们就来细细讲解误差反向传播的过程。鉴于我们的系列文章是写给初学者(实践者)看的,下面我们尽量省略了其中较为复杂的推导公式,对该部分感兴趣的读者可参阅卡内基梅隆大学Tom Mitchell教授的经典著作《机器学习》[3](对公式不感冒的读者,第一遍阅读可以直接跳过公式,直达图文解释部分)。
    误差反向传播通过梯度下降算法,迭代处理训练集合中的样例,一次处理一个样例。对于样例[Math Processing Error]d,如果它的预期输出(即教师信号)和实际输出有“误差”,BP算法抓住这个误差信号[Math Processing Error]Ld,以“梯度递减”的模式修改权值。也就是说,对于每个训练样例[Math Processing Error]d,权值[Math Processing Error]wji的校正幅度为[Math Processing Error]Δwji(需要说明的是,[Math Processing Error]wji和[Math Processing Error]wij其实都是同一个权值,[Math Processing Error]wji表示的是神经元[Math Processing Error]j的第[Math Processing Error]i个输入相关的权值,这里之所以把下标“[Math Processing Error]j”置于“[Math Processing Error]i”之前,仅仅表示这是一个反向更新过程而已):
    [Math Processing Error](8.6)Δwji=−η∂Ld∂wji
    在这里,[Math Processing Error]Ld表示的是训练集合中样例[Math Processing Error]d的误差,分解到输出层的所有输出向量,[Math Processing Error]Ld可表示为:
    [Math Processing Error](8.7)Ld(w)=12∑j∈outputs(yj−yj′)2
    其中:
    [Math Processing Error]yj表示的是第[Math Processing Error]j个神经单元的预期输出值。
    [Math Processing Error]yj′表示的[Math Processing Error]j个神经单元的实际输出值。
    [Math Processing Error]outputs的范围是网络最后一层的神经元集合。
    下面我们推导出[Math Processing Error]∂Ld∂wji的一个表达式,以便在公式(8.7)中使用梯度下降规则。首先,我们注意到,权值[Math Processing Error]wji仅仅能通过[Math Processing Error]netj影响其他相连的神经元。因此利用链式法则有:
    [Math Processing Error](8.8)∂Ld∂wji=∂Ld∂netj∂netj∂wji =∂Ld∂netjxji 
    在这里,[Math Processing Error]netj=∑iwjixji,也就是神经元[Math Processing Error]j输入的加权和。[Math Processing Error]xji表示的神经[Math Processing Error]j的第[Math Processing Error]i个输入。需要注意的是,这里的[Math Processing Error]xji是个统称,实际上,在反向传播过程中,在经历输出层、隐含层和输入层时,它的标记可能有所不同。
    由于在输出层和隐含层的神经元对“纠偏”工作,承担的“责任”是不同的,至少是形式不同,所以需要我们分别给出推导。
    (1)在输出层,对第[Math Processing Error]i个神经元而言,省略部分推导过程,公式(8.8)的左侧第一项为:
    [Math Processing Error](8.9)∂Ld∂netj=(yj−yj′)yj′(1−yj′)
    为了方便表达,我们用该神经元的纠偏“责任(responsibility)”[Math Processing Error]δj(1)描述这个偏导,即:
    [Math Processing Error](8.10)δj(1)=∂Ld∂netj
    这里[Math Processing Error]δj(1)的上标“(1)”,表示的是第1类(即输出层)神经元的责任。如果上标为“(2)”,这表示的是第2类(即隐含层)神经元的责任,见下面的描述。
    (2)对隐含层神经元[Math Processing Error]j的梯度法则(省略了部分推导过程),有:

    6a9327f760512be7d30d5cfa67beffb521a0ffa1

    其中:
    [Math Processing Error]fj表示神经单元[Math Processing Error]j的计算输出。
    [Math Processing Error]netj表示的是神经单元[Math Processing Error]j的加权之和。
    [Math Processing Error]Downstream(j)表示的是在网络中神经单元[Math Processing Error]j的直接下游单元集合。
    隐含层神经元的纠差职责,是通过计算前一步输出神经元的“责任”来实现的。
    这里说的每层神经元“责任”,或者更为确切来说是“纠偏责任”,其实就是在8.2.2节讲到的“分阶段求解”策略。
    在明确了各个神经元“纠偏”的职责之后,下面就可以依据类似于感知机学习,通过如下加法法则更新权值:
    对于输出层神经元有:

    [Math Processing Error](8.12)wji(1)=wji(1)+ηΔwji(1)=wji(1)+ηδi(1)hj

    对于隐含层神经元有:

    157bc6e688ceb01f69bf1be562b8fe9489cd884d

    在这里,[Math Processing Error]η∈(0,1)表示学习率。在实际操作过程中,为了防止错过极值,[Math Processing Error]η通常取小于0.1的值。[Math Processing Error]hj为神经元j的输出。[Math Processing Error]xjk表示的是神经单元[Math Processing Error]j的第[Math Processing Error]k个输入。
    上面的公式依然比较抽象,难以理解。下面我们还是以前面的神经网络拓扑结构为例,用实际运算过程给予详细说明,以期望给与读者感性认识。
    从上面的描述可以得知,针对输出层的神经元3,它的输出值[Math Processing Error]y1′为0.65,而期望输出值[Math Processing Error]y1为1,二者存在“误差”:[Math Processing Error]e1=y1−y1′=1−0.65=0.35。
    在这里,我们把每个神经元根据误差调参的“责任”记为[Math Processing Error]δ,那么,根据公式(8.9)和(8.10),神经元3的“责任”可表示为:

    6964297cea5a50ec227b4ac2a71bcc64cb09fc6f
    d8cd63c1a8be530de844d920da2a58832da2f192
    图8-15 误差反向传播计算神经元3的“责任”

    从上面分析可知,我们很容易计算出[Math Processing Error]δ3(1)的值:

    20273a3f7bb409ad549d48120d883b3202e0664b

    于是,可以反向更新[Math Processing Error]w31(1)的权值为:

    cd70193c0b854a50fc60532ebc10ca0afb8ced62

    在这里,image(具体推导见公式8.8-8.10),[Math Processing Error]η为学习率,此处取值为0.1。[Math Processing Error]f1为神经元1的输出(即[Math Processing Error]y1′)。
    类似地,我们可以反向更新[Math Processing Error]w32(1))的权值:
    [Math Processing Error]w32(1)=w32(1)+ηΔw32(1)=w32(1)+ηδ3(1)f2=1+0.1×0.0796×0.5=1.00398

    同样的操作,我们可以计算出[Math Processing Error]δ4(1)的值,如图8-16所示。
    image

    9c9beef8f394408598407fe4afb75f76ba9e8322
    图8-16 误差反向传播计算神经元4的责任

    从而可以反向更新[Math Processing Error]w41(1)的权值:
    [Math Processing Error]w41(1)=w41(1)+ηΔw41(1)=w41(1)+ηδ4(1)f1=−1+0.1×(−0.1427)×0.12=−1.0017

    类似地,我们可以反向更新[Math Processing Error]w42(1)的权值:
    [Math Processing Error]w42(1)=w42(1)+ηΔw42(1)=w42(1)+ηδ4(1)f2=1+0.1×(−0.1427)×0.5=0.9929

    在反向更新完毕输出层的权值后,下面。我们开始反向更新隐含层的网络权值,示意图如图8-17所示。

    193a0967798f791c13a415611a44fba12c8d39b2
    图8-17 误差反向传播计算神经元1的责任

    如果我们把反向传播误差的“职责(即[Math Processing Error]δj)”,也看做一种特殊信息的话,那么在隐藏层的每个神经元都会有一个加权和影响,记为[Math Processing Error]Δj,实际上,这里的[Math Processing Error]Δj,就是公式(8.11)的加权求和[Math Processing Error]Downstream(j)(其实也就是8.2.2所提及的“合并同类路径”)。
    对于隐含层神经1,则有:

    e5f6fa245e1d097dcc148c977bad60cf6296107a

    有了这个权值影响,我们就可以很容易计算出神经元1承担的“责任”[Math Processing Error]δ1(2):
    [Math Processing Error]δ1(2)=f1(1−f1)Δ1=0.12×(1−0.12)×0.2223=−0.0235
    在计算出计算神经元1承担的“责任”之后,我们就可以更新与神经元1相连的两个输入变量权值:

    eb6787f8a46de50545b78e496139bda9489596de

    类似的流程(示意图如图8-18所示),可以求得神经元2的累计加权影响有:

    db9b4d050581c47aa9fc1993e836ad567f00924a
    f1ac6fca6f125fc58e0d95b8ee3f389063254724
    图8-18误差反向传播计算神经元2的责任

    于是,计算神经元2承担的“责任”[Math Processing Error]δ2(2):
    [Math Processing Error]δ2(2)=f2(1−f2)Δ2=0.5×(1−0.5)×(−0.0631)=0.0158

    同样,计算出计算神经元2承担的“责任”之后,我们就可以更新与神经元2相连的两个输入变量权值:

    58df7c93d49b2118c57af68ce86ad6db3fd56a47

    从上面的推导过程,我们可以看到,经过一轮的误差逆传播,神经网络的权值前后确有不同。但由于学习率(即步长)较小(为0.1),所以前后的权值变化并不大(括号内的数值为原始权值),如图8-19所示。

    8b8a641197e07e451bf88f2e927994eb037ca8a7
    图8-19 一轮BP算法之后前后的权值对比

    如此一来,整个网络的权值就全部得以更新。接下来,网络就可接受下一个训练样例,接着往下训练了,直到输出层的误差小于预设的容忍度。
    BP算法,在很多场所的确非常有用。例如,1989年,Yann Lecun(又一位当下的深度学习大牛)等人就用BP算法,在手写邮政编码识别上有着非常成功的应用[5],训练好的系统,手写数字错误率只有5%。Lecun借此还申请了美国专利,开了公司,发了一笔小财。
    在发表BP算法之后的30年,2006年,辛顿等人在发表的有关“深度信念网”的经典论文中指出[8],深度信念网(DBN)的组成元件就是受限玻尔兹曼机 (Restricted Boltzmann Machines, RBM)。而DBN的构建其实分两步走的:(1)单独“无监督”地训练每一层RBM网络,以确保特征向量在映射到不同特征空间时,能够尽可能多地保留特征信息;(2)在DBN的最后一层,设置BP网络,用以接收RBM的输出特征向量作为它的输入特征向量,然后“有监督”地训练实体关系分类器,对网络权值实施微调(Fine-Tune)。
    现在你看到了,BP算法的影响力,一直渗透到“深度学习”骨子里!这就是为什么在讲深度学习时,我们绕不过BP算法的原因。

    8.4 小结

    在本章中,我们详细解释了反向传播(BP)算法,通过学习我们知道,BP算法其实并不仅仅是个反向算法,而是一个双向算法。也就是说,它其实是分两大步走:(1)正向传播信号,输出分类信息;(2)反向传播误差,调整网络权值。如果没有达到预期目的,重走回头路(1)和(2)。
    BP算法很成功。但我们也要看到BP算法的不足,比如说会存在“梯度扩散(Gradient Diffusion)”现象,其根源在于对于非凸函数,梯度一旦消失,就没有指导意义,导致它可能限于局部最优。而且“梯度扩散”现象会随着网络层数增加而愈发严重,也就是说,随着梯度的逐层消减,导致它对调整网络权值的调整效益,作用越来越小,故此BP算法多用于浅层网络结构(通常小于等于3),这就限制了BP算法的数据表征能力,从而也就限制了BP的性能上限。
    再比如说,虽然相比于原生态的BP算法,虽然它降低了网络参数的训练量,但其网络参数的训练代价还是不小,耗时非常“可观”。就拿LeCun的识别手写邮编的案例说事,其训练耗时就达3天之久。
    再后来,与LeCun同在一个贝尔实验室的同事Vladimir Vapnik(弗拉基米尔·万普尼克),提出并发扬光大了支持向量机 (Support Vector Machine) 算法。
    SVM作为一种分类算法,对于线性分类,自然不在话下。在数据样本线性不可分时,它使用了所谓“核机制(kernel trick)”,将线性不可分的样本,映射到高维特征空间 (high-dimensional feature space),从而使其线性可分。自上世纪九十年代初开始,SVM逐渐大显神威,在图像和语音识别等领域,获得了广泛而成功的应用。
    在手写邮政编码的识别问题上,LeCun利用BP算法,把错误率整到5%左右,而SVM在1998年就把错误率降到低至0.8%。这远超越同期的传统神经网络算法。
    就这样,万普尼克又把神经网络研究送到了一个新的低潮! 
    在下一章中,我们将来聊聊“卷积神经网络”,这可是深度学习发展过程中的一个重要里程碑,希望你能关注。

    8.5 请你思考

    通过本章的学习,请你思考如下问题:
    (1)正是由于神经网络具有强大的表示能力,“成也萧何,败也萧何”,BP算法常常遭遇“过拟合(overfitting)”,它可能会把噪音当做有效信号,你知道有什么策略来避免过拟合吗?
    (2)利用梯度递减策略,BP算法常停止于局部最优解,而非全局最优解,这好比“只因身在此山中”,却不知“人外有人,山外有山”,你知道有什么方法可以改善这一状况吗?
    (3)在BP算法流程中,我们看到的是反反复复的权值调整,而杰弗里•辛顿在文献[4]中提到的特征表示(representation),体现在什么地方?

    【参考文献】

    [1] Paul J. Werbos. Beyond Regression: New Tools for Prediction and Analysis in the Behavioral Sciences. PhD thesis, Harvard University, 1974
    [2] Christopher Olah. Calculus on Computational Graphs: Backpropagation 
    [3]Tom Mitchell著.曾华军等译. 机器学习. 机器工业出版社. 2007.4
    [4] Williams D, Hinton G. Learning representations by back-propagating errors[J]. Nature, 1986, 323(6088): 533-538.
    [5] LeCun Y, Boser B, Denker J S, et al. Backpropagation applied to handwritten zip code recognition[J]. Neural computation, 1989, 1(4): 541-551.
    [6]周志华. 机器学习. 清华大学出版社. 2016.1
    [7] Mirosav Kubat著. 王勇等译. 机器学习导论. 机械工业出版社. 2016.11
    [8] Hinton G E, Osindero S, Teh Y W. A fast learning algorithm for deep belief nets[J]. Neural computation, 2006, 18(7): 1527-1554.

    文章作者:张玉宏,著有《品味大数据》一书。审校:我是主题曲哥哥。

     

    (未完待续)

    用云栖社区APP,舒服~


    展开全文
  • 反向传播、链式求导

    2020-02-06 20:32:25
    nltk 处理文本 注意力机制 SENet、CBAM ...反向传播、链式求导 梯度下降 最小二乘法(LS算法):实际为L2范数的一个具体应用(计算残差平方和) 线性回归 例子 【程序3-3】开头部分 import ...

    日萌社

    人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)


    nltk 处理文本

    注意力机制 SENet、CBAM

    卷积神经网络 处理文本:word2vec、TF-IDF、TextRank、字符卷积、词卷积、卷积神经网络文本分类模型的实现(Conv1D一维卷积、Conv2D二维卷积)

    反向传播、链式求导

    梯度下降

    最小二乘法(LS算法):实际为L2范数的一个具体应用(计算残差平方和)

    线性回归 例子


    【程序3-3】开头部分
    import numpy as np
    import math
    import random
    
    def rand(a, b):
        return (b - a) * random.random() + a
    
    def make_matrix(m, n, fill=0.0):
        mat = []
        for i in range(m):
            mat.append([fill] * n)
        return mat
    
    def sigmoid(x):
        return 1.0 / (1.0 + math.exp(-x))
    
    def sigmod_derivate(x):
        return x * (1 - x)
    
    class BPNeuralNetwork:
        def __init__(self):
            self.input_n = 0
            self.hidden_n = 0
            self.output_n = 0
            self.input_cells = []
            self.hidden_cells = []
            self.output_cells = []
            self.input_weights = []
            self.output_weights = []
    
        def setup(self, ni, nh, no):
            self.input_n = ni + 1
            self.hidden_n = nh
            self.output_n = no
            self.input_cells = [1.0] * self.input_n
            self.hidden_cells = [1.0] * self.hidden_n
            self.output_cells = [1.0] * self.output_n
            self.input_weights = make_matrix(self.input_n, self.hidden_n)
            self.output_weights = make_matrix(self.hidden_n, self.output_n)
            # random activate
            for i in range(self.input_n):
                for h in range(self.hidden_n):
                    self.input_weights[i][h] = rand(-0.2, 0.2)
            for h in range(self.hidden_n):
                for o in range(self.output_n):
                    self.output_weights[h][o] = rand(-2.0, 2.0)
        # 前向传播
        def predict(self, inputs):
            for i in range(self.input_n - 1):
                self.input_cells[i] = inputs[i]
            for j in range(self.hidden_n):
                total = 0.0
                for i in range(self.input_n):
                    total += self.input_cells[i] * self.input_weights[i][j]
                self.hidden_cells[j] = sigmoid(total)
            for k in range(self.output_n):
                total = 0.0
                for j in range(self.hidden_n):
                    total += self.hidden_cells[j] * self.output_weights[j][k]
                self.output_cells[k] = sigmoid(total)
            return self.output_cells[:]
    
        # 反向传播
        def back_propagate(self, case, label, learn):
            self.predict(case) # 前向传播
    
            ### 计算输出层的误差 ###
            #先零初始化输出层中每个神经元的梯度值,实际为输出层的输出值对于误差的梯度值
            output_deltas = [0.0] * self.output_n 
            for k in range(self.output_n):
                #想要求出输出层的输出值对于误差的梯度值,首先要求出输出层每个神经元的误差,而输出层每个神经元的误差实际为真实值与模型预测值之间的差值。
                #即输出层每个神经元的误差值,实际为输出层中每个神经元的输出值和每个真实值之间的差值。
                error = label[k] - self.output_cells[k] 
                #首先把输出层该神经元中已经经过了激活函数的输出值先通过sigmod导函数求导,然后乘以该神经元的误差值,最终得出该神经元的梯度值
                output_deltas[k] = sigmod_derivate(self.output_cells[k]) * error 
    
            ### 计算隐藏层(全连接层)的误差 ###
            #先零初始化隐藏层(全连接层)中每个神经元的梯度值,实际为隐藏层的输出值对于误差的梯度值
            hidden_deltas = [0.0] * self.hidden_n 
            for j in range(self.hidden_n):
                #想要求出隐藏层的输出值对于误差的梯度值,首先要求出隐藏层每个神经元的误差,但是对于隐藏层,由于缺少直接的目标值来计算隐藏层中每个神经元的误差,
                #因此需要通过间接的方式计算隐藏层的误差项,首先通过下一层输出层已知的每个神经元的梯度值乘以隐藏层中二维权重矩阵中第j个神经元的output_n个权重值,
                #即加权求和得出隐藏层中第j个神经元的误差。
                error = 0.0
                for k in range(self.output_n):
                    #通过下一层输出层已知的每个神经元的梯度值乘以隐藏层中二维权重矩阵中第j个神经元的output_n个权重值,即加权求和得出隐藏层中第j个神经元的误差
                    error += output_deltas[k] * self.output_weights[j][k] 
                #首先把隐藏层该神经元中已经经过了激活函数的输出值先通过sigmod导函数求导,然后乘以该神经元的误差值,最终得出该神经元的梯度值
                hidden_deltas[j] = sigmod_derivate(self.hidden_cells[j]) * error
    
            ### 更新输出层权重 ###
            for j in range(self.hidden_n):
                for k in range(self.output_n):
                    #θ-= lr * 输出层的梯度值 * 输出层的输入
                    self.output_weights[j][k] += learn * output_deltas[k] * self.hidden_cells[j]
    
            ### 更新隐藏层权重 ###
            for i in range(self.input_n):
                for j in range(self.hidden_n):
                    #θ-= lr * 隐藏层的梯度值 * 隐藏层的输入
                    self.input_weights[i][j] += learn * hidden_deltas[j] * self.input_cells[i]
    
            error = 0
            for o in range(len(label)):
                error += 0.5 * (label[o] - self.output_cells[o]) ** 2 #计算均方误差
            return error
    
        def train(self, cases, labels, limit=100, learn=0.05):
            for i in range(limit):
                error = 0
                for i in range(len(cases)):
                    label = labels[i]
                    case = cases[i]
                    error += self.back_propagate(case, label, learn)
            pass
    
        def test(self):
            cases = [
                [0, 0],
                [0, 1],
                [1, 0],
                [1, 1],
            ]
            labels = [[0], [1], [1], [0]]
            self.setup(2, 5, 1)
            self.train(cases, labels, 10000, 0.05)
            for case in cases:
                print(self.predict(case))
    
    if __name__ == '__main__':
        nn = BPNeuralNetwork()
        nn.test()
    

    计算每一层(输出层/隐藏层)的误差和梯度

            ### 计算输出层的误差 ###
            #先零初始化输出层中每个神经元的梯度值,实际为输出层的输出值对于误差的梯度值
            output_deltas = [0.0] * self.output_n 
            for k in range(self.output_n):
                #想要求出输出层的输出值对于误差的梯度值,首先要求出输出层每个神经元的误差,而输出层每个神经元的误差实际为真实值与模型预测值之间的差值。
                #即输出层每个神经元的误差值,实际为输出层中每个神经元的输出值和每个真实值之间的差值。
                error = label[k] - self.output_cells[k] 
                #首先把输出层该神经元中已经经过了激活函数的输出值先通过sigmod导函数求导,然后乘以该神经元的误差值,最终得出该神经元的梯度值
                output_deltas[k] = sigmod_derivate(self.output_cells[k]) * error 
    
            ### 计算隐藏层(全连接层)的误差 ###
            #先零初始化隐藏层(全连接层)中每个神经元的梯度值,实际为隐藏层的输出值对于误差的梯度值
            hidden_deltas = [0.0] * self.hidden_n 
            for j in range(self.hidden_n):
                #想要求出隐藏层的输出值对于误差的梯度值,首先要求出隐藏层每个神经元的误差,但是对于隐藏层,由于缺少直接的目标值来计算隐藏层中每个神经元的误差,
                #因此需要通过间接的方式计算隐藏层的误差项,首先通过下一层输出层已知的每个神经元的梯度值乘以隐藏层中二维权重矩阵中第j个神经元的output_n个权重值,
                #即加权求和得出隐藏层中第j个神经元的误差。
                error = 0.0
                for k in range(self.output_n):
                    #通过下一层输出层已知的每个神经元的梯度值乘以隐藏层中二维权重矩阵中第j个神经元的output_n个权重值,即加权求和得出隐藏层中第j个神经元的误差
                    error += output_deltas[k] * self.output_weights[j][k] 
                #首先把隐藏层该神经元中已经经过了激活函数的输出值先通过sigmod导函数求导,然后乘以该神经元的误差值,最终得出该神经元的梯度值
                hidden_deltas[j] = sigmod_derivate(self.hidden_cells[j]) * error
    

    每一层的权重更新 

            ### 更新输出层权重 ###
            for j in range(self.hidden_n):
                for k in range(self.output_n):
                    #θ-= lr * 输出层的梯度值 * 输出层的输入
                    self.output_weights[j][k] += learn * output_deltas[k] * self.hidden_cells[j]
    
            ### 更新隐藏层权重 ###
            for i in range(self.input_n):
                for j in range(self.hidden_n):
                    #θ-= lr * 隐藏层的梯度值 * 隐藏层的输入
                    self.input_weights[i][j] += learn * hidden_deltas[j] * self.input_cells[i]
    

    展开全文
  • 这里写自定义目录标题BP算法四个基本方程链式法则一、完备表达如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表...
  • 摘要: 说到BP(Back Propagation)算法,人们通常强调的是反向传播,其实它是一个双向算法:正向传播输入信号,反向传播误差信息。接下来,你将看到的,可能是史上最为通俗易懂的BP图文讲解,不信?来瞅瞅并吐吐槽呗...
  • 8.1 BP神经网络极简史在神经网络(甚至深度学习)参数训练中,BP(Back Propagation)算法非常重要,它都占据举足轻重的地位。在提及BP算法时,我们常将它与杰弗里•辛顿(Geoffrey Hinton)的名字联系在一起。但实际...
  • 摘要:说到BP(Back Propagation)算法,人们通常强调的是反向传播,其实它是一个双向算法:正向传播输入信号,反向传播误差信息。接下来,你将看到的,可能是史上最为通俗易懂的BP图文讲解,不信?来瞅瞅并吐吐槽呗!...
  • 主讲人 网神 (新浪微博:@豆角茄子麻酱凉面) 网神(66707180) 18:55:06 那我们开始了啊,前面第3,4章讲了回归和分类问题,他们应用的主要限制是维度...书上提到的这些内容今天先不讲了,以后有时间再讲:BP在...
  • 文章目录矩阵向量求导说明1说明21.定义法1. 用定义法求解标量对向量求导举个例子2.用定义法求解标量对矩阵求导举个例子3.用定义法求解向量对向量的...标量对多个向量的链式求导法则例子3.标量对多个矩阵的链式求导法则
  • BP:矩阵求导

    2018-12-27 14:07:57
    知乎:如何理解矩阵对矩阵求导 affine/linear(仿射/线性)变换函数详解及全连接层反向传播的梯度求导 简书:机器学习-矩阵求导 5分钟学会矩阵求导 矩阵求导计算法则 例题 矩阵求导公式 矩阵求导总结 ...
  • 通俗理解神经网络BP传播算法 在学习深度学习相关知识,无疑都是从神经网络开始入手,在神经网络对参数的学习算法bp算法,接触了很多次,每一次查找资料学习,都有着似懂非懂的感觉,这次趁着思路比较清楚,也...
  • 【Autograd】深入理解BP与自动求导

    千次阅读 2017-10-20 12:53:23
    微积分中的链式法则(为了不与概率中的链式法则相混淆)用于计算复合函数的导数。反向传播是一种计算链式法则的算法,使用高效的特定运算顺序。 设 x x 是实数, f f 和 g g 是从实数映射到实数的函数。假设...
  • 本文以neural network中最常见的一种计算单元为例,详细介绍了BP算法中涉及到的矩阵求导过程。刚接触机器学习时,曾被BP算法中的矩阵求导困扰好久,写下这篇总结,希望能对后来学习的同学有所帮助。文章中的错误,...
  • [BP求导]BP反向传播理解

    千次阅读 2019-01-28 15:02:44
    BP反向传播其实就是通过链式法则,从后往前一次计算出损失函数对所有参数的偏导,而前向求导的话每次只能求出某一个特定参数关于损失函数的导数。 十分钟看懂神经网络反向传输算法 昨天面试被问...
  • DL之BP:神经网络算法简介之BP算法简介(链式法则/计算图解释)、案例应用之详细攻略 相关文章:DL之DNN之BP:神经网络算法简介之BP算法/GD算法之不需要额外任何文字,只需要八张图讲清楚BP类神经网络的工作原理 ...
  • 神经网络--BP算法详解

    2019-08-24 21:20:39
    BP算法,全称是BackPropagation算法,也可以叫做反向传播算法,是一种应用在神经网络中的梯度下降算法。 算法核心:Chain rule----链式求导法则 对于复合函数的求导,我们易知的有以下求导法则: 即可以...
  • 神经网络的严格矩阵求导

    千次阅读 2016-07-20 14:59:05
    玩机器学习的人基本功,就不解释了,BP的核心思想是cost对z和a求导,然后利用链式求导法则从后往前推,然后利用这些中间变量求对theta的导数(也就是梯度)。。。。 写的累死了,顺便晒下薄扶林政治学院的Main ...
  • 什么是梯度下降和链式求导法则神经网络的结构BP算法中的执行流程(前向传递和逆向更新)输出层和隐藏层权重以及偏置更新的推导Python 实现源码解析手写数字识别实例训练神经网络中有哪些难点(TODO) 梯度下降和...
  • 一文搞定BP神经网络——从原理到应用(原理篇)

    万次阅读 多人点赞 2017-10-11 09:31:25
    神经网络结构以及前向传播过程 损失函数和代价函数 反向传播 1 矩阵补充知识 11 矩阵求梯度 12 海塞矩阵 13 总结 2 矩阵乘积和对应元素相乘 3 反向传播原理四个基础等式 4 反向传播总结 41 单样本输入公式表 42 多...
  • BP网络-梯度下降

    2019-09-27 15:23:21
    学习资料: 初学比较懵逼,大脑的缓存不够(估计大部分人都不够),最好跟着教程动手在纸上画(算)一遍,就比较清晰了,如果我把每一细节与想法都写在纸上,一般都能...-用实际的计算给出一个具体的反向求导过程...
  • 1.softmax函数的正推原理,softmax的代数和几何意义,softmax为什么能用作分类预测,softmax链式求导的过程。 2.从数学的角度上研究了神经网络为什么能通过反向传播来训练网络的原理。 3.结合信息熵理论...
  • 【神经网络BP算法

    2020-02-25 21:13:02
    链式求导法则 张量求导 原理推到 代码展示 import numpy as np import pickle def sigmoid(z): return 1/(1+np.exp(-z)) def sigmoid_derivative(x): return sigmoid(x)*(1-sigmoid(x)...
  • 点上方计算机视觉联盟获取更多干货仅作学术分享,不代表本公众号立场,侵权联系删除转载于:作者丨Criss来源丨机器学习与生成对抗网络AI博士笔记系列推荐周志华《机器学习》手推笔记正式开源!可...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,086
精华内容 1,234
热门标签
关键字:

bp网络链式求导