神经网络和深度学习(三)——反向传播工作原理
反向传播算法工作原理
在上一篇文章,我们看到了神经网络如何通过梯度下降算法学习,从而改变权重和偏差。但是,前面我们并没有讨论如何计算代价函数的梯度,这是一个很大的遗憾。这一篇文章,我们将介绍一种称为反向传播的快速计算梯度的算法。
使用反向传播算法学习的神经网络比其他早期的方法要快很多,这使得使用神经网络可以解决之前不能解决的问题。如今,反向传播算法是神经网络中最重要的组成部分。
你完全可以忽略反向传播算法,把它当作一个黑盒子去使用神经网络。那么我们为什需要详细的了解这个算法呢?这当然是为了理解神经网络的学习过程。反向传播算法的核心是一个对于任何权重或者偏差计算其关于代价函数的偏导数的表达式。这个表达式告诉我们改变权重和偏差时,代价函数的变化快慢。虽然这个表达式很复杂,但是它却有自己的数学之美,每一个元素拥有一个自然的直观解释。因此反向传播不仅仅是为了学习的一个快速算法,它实际上给了我们一个视角去深入了解在改变权重和偏差时,网络整体的行为的变化。这才是我们详细了解反向传播算法的意义所在。
-
热身:一种基于矩阵计算神经网络输出的快速方法
在讨论反向传播算法之前,我们使用一种基于矩阵的计算神经网络输出的快速方法来做预热。这可以让我们在学习反向传播时更加熟悉各种记号和表示方法。
首先我们给出权重一个清楚的记号。我们将使用
wljk 来定义(l−1) 层第k 个神经元到第l 层第j 个神经元之间的权重。比如,下图展示了第2层第4个神经元到第3层第2个神经元之间的权重:这个记号刚开始很绕,需要一点时间熟悉。经过一段时间的掌握一会发现这样的表达很简单自然。这个记号很别扭的地方在于j和k的顺序,你可能和我一样刚开始觉得j和k的位置需要换一下更舒服,但是下面我将告诉你为什么这样做。
我们对于网络的**值(神经元的输出值)和偏差采用类似的记号。我们使用
blj 来表示第l 层第j 个神经元的偏差,用alj 来表示第l 层第j 个神经元的**值。下图展示了一个例子:有了这些记号,**值
alj 可以由第l−1 层的**值来计算:其中求和是对第
l−1 层所有神经元进行的。为了使用矩阵重写上面的表达式,我们对每一层l ,都定义了一个权值矩阵wl ,矩阵中的每一个元素表示的是连接到第l 层神经元的权值,也就是说第j 行k 列的元素的值正是wljk 。同样的,我们对每一层都定义一个偏差矩阵(向量)bl 。我想你可以自己给出它的定义,显然它是一个只有一列多行的矩阵,它的组成元素表达的值就是上段中的blj 。最后我们同样给出**矩阵(向量)al ,它的每一个元素的值为alj 。最后我们需要引入向量化函数(如
σ ),用矩阵的方式重写公式(23)。其实在上一篇文章中我们已经接触到向量化了,它的思想就是应用函数到向量中的每一个元素中。前面我们曾使用σ(v) 来表示这种对每个元素进行函数求值的计算过程。举个例子,如果我们给定一个函数f(x)=x2 ,那么对给定向量之后的计算过程为:即,向量化
f 就是对向量中所有元素进行平方计算。有了这些定义,公式(23)就可以重写为下面这个美妙而简介的向量形式了:
这个表达式让我们可以在全局范围内审视某一层的**值与其前一层**值的关系:我们仅仅对前一层的**值乘以权值,并加上偏差,最后在应用
σ 函数。(这里其实可以解释前面j和k的顺序的问题,如果我们对调j和k的顺序,那么这里在作wlal−1 时,就应该是wl 矩阵的转置矩阵来运算)。这种全局层面的表达式要比使用神经元层面的表达式更加简单简洁。在实践中,表达式同样很有⽤,因为⼤多数矩阵库提供了实现矩阵 乘法、向量加法和向量化的快速方法。实际上,上篇文章的代码其实已经隐式使用了这种表达式来计算网络行为。当使用公式(25)去计算
al 时,我们需要计算中间量zl=wlal−1+bl 。这个值是非常有用的,我们称之为:zl 为第l 层神经元的带权输入。在后面的章节中,我们将大量使用带权输入zl 。等式(25)因此可以简写为al=σ(zl) 。我们需要知道zl 中的每个元素为zlj=∑kwljkal−1k+blj ,即表示l 层中第j 个神经元的**函数的带权输入值。 -
关于代价函数的两个假设
反向传播的目标是计算代价函数关于权值w 和偏差b 的偏导数∂C/∂w 和∂C/∂b 。为了使反向传播函数可以工作,我们需要对代价函数做出两点假设。在给出假设之前,我们需要再看看代价函数。我们继续使用二次代价函数。根据上文的表示,代价函数可以写为:其中
n 表示总的训练样本数量;求和是对所有单个训练样本x 而言;y=y(x) 是对应的目标输出;L 表示网络的层数;aL=aL(x) 是当网络的输入为x 时,网络输出的**值向量。那么是代价函数可以工作的两个假设是什么呢?首先我们需要假设代价函数可以写成对于每一个训练样本
x 的代价值Cx 的均值,即C=1n∑xCx 。其中Cx 使用二次代价函数Cx=12||y−aL|| 只是一个例子,也就是说其他形式的代价函数也要满足该假设。我们需要这个假设的原因是,反向传播实际上是计算单个训练样本的偏导数值
∂Cx/∂w 和∂Cx/∂b 。然后我们通过在所有样本上进行平均后才得到了∂C/∂w 和∂C/∂b 。第二个假设是代价可以写成神经网络输出的函数:
比如,二次代价函数满足这个需求,因为二次代价函数对一个单个的训练样本
x 可以写作:代价函数确实是是输出**值的函数。虽然我们看到代价函数依赖期望输出
y ,但是y 其实是一个固定值,因为训练样本x 是固定的个,y 的值不会随着权重偏差的改变而改变。因此,将C 看成输出**值aL 的函数是合理的假设。 -
Hadamard 乘积,
s⊙t
反向传播算法基于常规的的线性代数运算,比如向量加法,向量矩阵乘法等。但是有一个操作不常见。我们假设有两个同纬度的向量s 和t ,那么s⊙t 就可以用来表示这两个向量之间按元素乘积,即(s⊙t)j=sjtj ,举个例子:这种按元素乘积的操作有时被称作
Hadamard 乘积或者Schur 乘积。 -
反向传播的四个基本方程
反向传播是关于理解如何改变权值和偏差从而改变代价函数。最终的意义在于计算偏导数
∂C/∂wljk 和∂C/∂blj 。但是为了计算这些值,我们先引入一个中间量δlj ,它表示l 层第j 个神经元的误差。反向传播将会提供计算误差的流程,然后关联误差δlj 到∂C/∂wljk 和∂C/∂blj 上。为了理解误差的定义,我们假设在神经网络中有一个恶魔:
这个恶魔在
l 层的第j 个神经元上。当有输入来到这个神经元,这个恶魔妨碍了这个神经元的操作。它会在神经元的带权输入上加上一个Δzlj ,使得该神经元的输出由σ(zlj) 变为σ(zlj+Δzlj) 。这个改变传播到后面的神经元中,最终到是整个代价产生∂C∂zljΔzlj 的改变。现在这个恶魔是一个好的恶魔,它尝试着帮你优化代价,也就是试着找到一个
Δzlj ,使得代价更小。假设∂C∂zlj 是一个很大的值(不管是正还是负)。然后这个恶魔可以通过选择Δzlj 与∂C∂zlj 符号相反来使得代价更小。相反,如果∂C∂zlj 接近0,然后恶魔不能通过改变带权输入值来优化代价,在恶魔看来,这个时候神经元已经接近最优了。所以这里有一种启发式的认识,∂C∂zlj 是神经元误差的度量。我们定义
l 层第j 个神经元的误差为δlj :按照我们的习惯,我们使用
δl 表示关联l 层的误差向量。反向传播将会提供计算每一层误差δl 的方法,然后将这些误差和我们真正关心的∂C/∂wljk 和∂C/∂blj 关联起来。你可能会好奇为什么恶魔要改变权重输入
zlj 。其实我们假设恶魔改变的是神经元的**输出alj 可能更加自然,即使用∂C∂alj 作为我们的误差。但是如果你这么做的话,会使得反向传播的表达变得十分复杂,所以我们使用∂C∂zlj 作为神经元的误差。解决方案:反向传播基于四个基本方程。这些方程给了我们计算误差
δl 和代价函数梯度的方法(别忘了我们的目的是计算代价函数关于权重和偏差的梯度,但是这些都是基于计算误差所求得,后面会讲述的很明白)。后面的公式其实不难,你第一眼看上起可能很复杂,当你静下心看完后面的证明你会觉得这些公式很简洁,而且用代码很容易实现。首先,我们会证明这些公式的正确性,然后以为代码的形式给出它们的算法形式;然后用python代码实现它们;在本文的最后,我们会发展出⼀个关于反向传播⽅程含义的直觉画⾯,以及人们如何能够从零开始发现这个规律。-
方程1:输出层误差的方程,
δL :δL 每个元素定义如下:这是一个很自然的表达式,至于每一项怎么来的,后面会给出证明。这里的每一项是很好计算的,右边第一项虽然和代价函数
C 的方程形式有关,但是当我们确定了代价函数之后,比如我们使用的二次代价函数,那么右边第一次项就是对C=12∑j(yj−aLj)2 求偏导,结果∂C/∂aLj=(alj−yj) ,这是很好计算的。对于右边第二项,也只是对**函数的求导而已。我们可以把上面的按分量表示的表达式改写成矩阵形式:
这里
∇aC 中是向量,其中的元素为:∂C/∂aLj ,如果C 为二次代价函数,那么上式就可以写为:证明:这里先给出(BP1)的证明(实在是不想用latex敲公式了,直接参考了其他人翻译的):
-
方程2:使用前面层的误差
δl+1 计算当前层的误差δl :其中
(wl+1)T 是l+1 层的权重矩阵wl+1 的转置矩阵。这个公式看似很复杂,其实你可以想象这个网络反过来传播,即输入值是误差δ ,也就是说这里的δl+1 看成l+1 层到l 层的输入,那么权重矩阵转置后乘以这个误差输入,就能得到l 层的误差变化速度,再作Hadamard积就可以得到l 层的误差了。通过前面两个公式,我们就可以计算任何层的误差了。证明:
-
方程3: 代价函数关于任意偏差的改变率(偏导数)(反向传播函数的返回值之一):
我们可以看到代价函数该神经元偏差求偏导数的值和该神经元的误差值一样的。这是一个很好的性质,因为我们通过前面两个公式已经可以计算
δlj 了。我们可以简记(BP3)为:其中
δ 和偏差b 都是针对同一个神经元的。这个公式的证明通过链式法则也可以很轻松的证明,就不再详述了。 -
方程4: 代价函数关于任意权重的改变率(偏导数)(反向传播函数的返回值之一):
同样这里公式右边的量都可以通过前面的公式求出,上式可以简写为:
下面总结一下这四个方程:
-
-
反向传播算法
反向传播算法给出了一种计算代价函数梯度的方法,我们用算法的形式明确地写出来:
- 输入
x :为输入层设置对应的**值a1 。 - 向前传播:对所有的
l=2,3,4...,L ,计算zl=wlal−1+bl 和al=σ(zl) - 输出误差:计算向量
δl=∇aC⊙σ'(zl) - 误差逆传播:对所有的
l=L−1,L−2,...,2 计算δl=((wl+1)Tδl+1)⊙σ'(zl) - 输出:代价函数的梯度:
这个算法是针对一个输入样本
x 时,计算代价函数梯度,但是我们前面讲过,我们在标准的梯度下降算法中,是对所有样本的梯度值求和取平均,在随机梯度算法中我们是对一个小批量数据的梯度值求和取平均。下面假设给定小批量样本大小为m ,则随机梯度下降算法为:代码改进:我们对于随机梯度下降的实现是对⼀ 个小批量数据中的训练样本进行遍历。所以也可以更改反向传播算法使得它同时对⼀个小批量数据中的所有样本进行梯度计算。这个想法其实就是我们可以用⼀个矩阵
X=[x1,x2,...,xm] ,其中每列就是在小批量数据中的向量,而不是单个的输⼊向量x 。 我们通过乘权重矩阵,加上对应的偏置进行前向传播,在所有地方应用S型函数。然后按照类似的过程进行反向传播,这会提高代码的速度(在我的笔记本电脑上,在MNIST分类问题上,我相较于上一篇文章的实现获得了2倍的速度提升)。 - 输入
参考原文:http://neuralnetworksanddeeplearning.com/chap2.html
以上是作者对原文的翻译和理解,有不对的地方请指正。
翻译参考:译者: FreemanZhang、 XiaohuZhu
注:转载请注明原文出处:
作者:CUG_UESTC
出处:http://blog.****.net/qq_31192383/article/details/77429409