反向传播算法的理解(Nielsen版)

在学习standford大学机器学习在coursera上的公开课中,对于其中讲授的神经网络的反向传播算法不是很清楚,经过网上查找资料,觉得Michael Nielsen的「Neural Networks and Deep Learning」中的解释特别清楚,于是这份材料为主经过学习,现在说一下我的理解。

记忆BP算法的窍门

我总结记住反向传播算法的关键要素可以用三个数字来代表:214

  • 两个假设
  • 一个中间变量
  • 四个公式

代价函数

在阐述反向传播算法之前,我们还是先明确代价函数的定义,我们定义代价函数的形式如下:

C=12nx||y(x)aL(x)||2()

其中n是样本的总数,L是神经网络的总层数,y(x)=y是样本的值,aL(x)=x是自变量带入hypothesis计算的结果,aL(x)=x也可以看成hw,b(x),这个式子也可以写成C=12nx||yaL||2,吴恩达的版本是:J(W,b;x,y)=12||hw,b(x)y||2

两个假设

  1. 总体的代价函数可以写成全部单个样本的代价总和的平均值。
    也就是C=1nxCx,其中Cx=12||yaL||2

  2. 代价函数可以认为是神经网络输出的函数。
    比如对于单独的一个样本来说,代价函数可以写成C=12||yaL||=12j(yjaLj)2

一个中间变量

我们定义神经网络中第l层的第j个神经元上的误差δlj为:

δlj=Czlj()

其中zlj是这个神经元上的输入,这里我们用zlj表示神经元上的输入,用alj表示这个神经元的输出。
凭什么这么定义?凭什么这么定义?凭什么这么定义?(重要的事情说三遍)
我认为理解这点是整个算法的核心,算法的全部内容都是基于这个假设展开的,如果这点不成立后面推导出花也没用!不幸的是,网上大多数文章都在这个问题都是一笔带过,在Nielsen的书里有对这点进行描述,我结合他的阐述以及我的理解表达一下我对这个假设的看法::如果我们在第l层的第j个神经元的输入zlj上增加一个微小的变化Δzlj,根据误差传递法则,这个微小的变化最终会导致代价C产生CzljΔzlj的改变,其实按理来说CzljΔzlj这个东西才是所谓的误差。但是在这个算法里我们并没有把整个式子定义为误差,而是取其中的偏导数部分为误差。偏导部分其实是C这个函数对于zlj这个变量的梯度,它反映了这个函数对于这个变量变化的敏感程度(CS231n),如果敏感程度高那么即使是自变量非常微小的变化也会引起整个函数值很大的变化,反之如果非常不敏感,即使是自变量很大的变化也不会另函数整体发生多少改变(按Nielsen的说法就是这个神经元已经接近最优了,所谓的神经元接近最优我理解的是在这个神经元上的激励函数σ已经非常接近真实的情况,也就是hypothesis无限的接近true,如果每个神经元的激励函数都是最优的那么整体的神经网络就是最优的)。所以偏导数部分在整个式子中是最重要的部分,为了简化运算,我们就把这部分作为第l层的第j个神经元上的误差。另外这种设定只能在Δzlj非常微小的情况下才管用。(理解可能不一定准确,求批评求指正,给我讲明白了我给您发红包
如果这个解释你还是理解不了,你就把它单纯的理解为是一种数学技巧吧,数学中我们经常会引入一些中间变量来方便计算。
我们为什么要引入这个中间变量,它非常重要,BP算法就是通过它来间接的计算CWljkCblj的。

四个公式

BP1:输出层误差公式

δLj=Caljσ(zLj)(BP-1)

δLj=aCσ(zLj)(BP-1a)

δLj=(aLy)σ(zLj)(BP-1b)

推导过程:

  • 我们之前定义了中间变量 δlj=Czlj
    因此对于最后一层(输出层)则有 δLj=CzLj
  • CaL的函数(第二个假设C=12||yaL||=12j(yjaLj)2),而az的函数(ali=σ(zli)),根据链式求导法则有δLj=kCaLkaLkzLj;
  • 为什么会有一个k呢?这是由于第二个假设C可以看成aL的函数C=12||yaL||=12j(yjaLj)2,也就是C是由L层全部的输出构成的,因此在求导的时候需要对每一个L层的激励元进行求导,假设L层有k个,再加上链式求导法则于是CzLj就写成kCaLkaLkzLj;
  • 要注意aLkzLj的关系,一个是L层某个神经元的输出部分,一个是L层某个神经元的输入部分,只当这两个部分作用于同一神经元的时候才有意义,也就是k=j的时候aLk=σ(zLj)这个式子才成立,或者说对于L层的第k个神经元来说,连到其他神经元的输入对它的输出没有意义啊,还可以这么理解对于z = x + 1 这样一个函数来说,你非要对y(一个不存在的自变量)求导(这个在复合函数求导中特别常见,经过第一次求偏导之后,第二次求偏导的自变量就没了因此得0),所以对于kjaLkzLj都等于0,所以最后式子化为δLj=CaLjaLjzLj;
  • 又因为aLj=σ(zLj),所以式子就变成δLj=CaLjσ(zLj) 这就是BP-1,BP-1是分量(elementwise)形式的表达方式。
    再进一步由于C=12j(yjaLj)2,所以CaLj=j(yjaLj)=j(aLjyj),所以BP-1又可以写成δLj=j(aLjyj)σ(zLj),这个就是BP-1b的分量形式。
  • BP-1a是BP-1的向量形式,aC表示Ca上的梯度,是梯度符号,梯度是一个向量,其中的每个分量就是CaLj
  • 而中间的普通乘积为什么会变成Hadamard乘积,这个其实就是公式从标量形式变成矢量形式的时候根据需要调整的,前两课的笔记里面有我是如何把通过标量形式推导出矢量形式的过程(基本上就是用几个小的数带进去观察一下规律总结出来的),吴恩达在standford大学机器学习讲义(CS229-notes-deeplearning)里面公式3.29到公式3.30也有类似的描述;

直观的解释
上面是从数学细节来证明的,我们再从直观的角度理解一下BP-1的含义:
Calj表示的是代价在第L层第j个神经元输出上的变化速度,如果C不太依赖这个神经元,那么Calj的值理论上接近0,也就是整个式子为0,也就是说这个神经元基本上没有误差,是非常理想的情况。而σ(zLj)提现了这个**函数在zLj这一点上的变化速度。

BP2:相邻两层之间的误差关系

δl=((wl+1)Tδl+1))σ(zl)(BP2)

推导过程:

  • 我们还是中间变量开始δlj=Czlj
  • 这一步利用了一些数学技巧:对于第l+1层来说有δl+1k=Czlj,我们可以推导出,第l层的第j个神经元上的输入zlj与第l+1层的的第k个神经元的输入zl+1k之间存在着这样的关系,zl+1k=jwl+1kjσ(zlj)+bl+1k,于是zl+1k可以看成zlj的函数,根据链式求导法则,我们重新改写一下δlj=Czlj这个公式为δlj=Czlj=kCzl+1kzl+1kzlj,而δl+1k=Czlj,于是kCzl+1kzl+1kzlj=kzl+1kzljδl+1k
  • zl+1kzlj=wl+1kjσ(zlj)
    为什么zl+1k明明是求和公式怎么一求偏导就没有累加符号了?符号太多看着有点眼晕,那么我们用一个简化问题的大招——代几个小数进去把问题具体化再观察其规律。
    比如这样的一个结构
    反向传播算法的理解(Nielsen版)
    j在l层,其取值范围为{1,2,3},k在l+1层,取值范围为{1,2},那么zl+1k=jwl+1kjσ(zlj)+bl+1k=wl+1k1σ(zl1)+wl+1k2σ(zl2)+wl+1k3σ(zl3)+bl+1k,对于zl+1kzlj我们令j=1那么zl+1kzl1相当于用上面展开的式子对zl1求偏导,那么对于不含有zl1的项自然为零了,结果就是wl+1k1σ(zl1)
    所以zl+1kzlj=wl+1kjσ(zlj)
  • 再回到式子kzl+1kzljδl+1k中把上一步的结果代入,得到δlj=kwl+1kjσ(zlj),这个就是公式BP-2的分量形式。

直观的解释
还是以上面那个图为例
反向传播算法的理解(Nielsen版)
在原有的结构中,假设数据从输出层往输入层方向流动,我们可以知道利用原有的系数矩阵,将其转置就能计算从l+1层的输出到l层的输入。
反向传播算法的理解(Nielsen版)
注意这里visio画不出,所以左下角那个公式用“.*”来代替

BP3: 代价函数对任意一个神经元的偏置的偏导:

Cblj=δlj(BP3)

推导过程:

  • 之前我们在推导相邻两层误差关系的时候利用了这个公式zl+1k=jwl+1kjσ(zlj)+bl+1k,所以有zlj=kwljkσ(zl1k)+blj,或者zlj=kwljkal1k+blj(这里把l+1换成l,把j,k调换一下看着方便)。由这个公式我们可以知道zljblj的函数。
  • 我们依然利用链式求导法则改写Cblj,则有Cblj=Czljzljblj
  • 注意到Czlj其实就是δlj ,而zljblj为1,也就是那一大堆被当成常数求导为0。所以Cblj=δlj

直观的解释:
代价函数对某个神经元偏置的偏导就是这个神经元上的误差,这是一个特别好的性质。

BP4:代价函数对任意一个权重的偏导

Cwljk=al1kδlj(BP4)

推导过程:

  • 我们依然使用 zlj=kwljkal1k+blj 这个公式,这个公式我们可以知道zljwljk的函数
  • 于是Cwljk=al1kδlj。注意这里依然涉及到累和求导的问题,最后只会留下一个。

直观解释
反向传播算法的理解(Nielsen版)
Cwljk=al1kδlj这个公式的下标太多以至于让人觉得很乱,于是我们还是用上面一个简单的结构具体化一下,最后我们再推出一般的情况。
最后我们得到这样一个结论——代价函数对于某一条弧上的权重的偏导数等于这条弧左端的神经元的输出与弧右端神经元的误差的乘积。所以BP4有的时候也可以表示成Cw=ainδout
从BP4我们可以知道当**值(神经元的输出值)ain很小的时候,整个偏导数的值就很小,偏导数的值就是梯度的值,梯度小就说明梯度下降步子小,也就是说要经过多轮迭代才能得到最优的结果,这种情况也被成为“学习缓慢”。

学习缓慢的问题

下面我们我们从“学习缓慢”的角度来审视BP1到BP4这四个公式。
对于BP1,其中包含有σ(zlj)这一项,如果σ是sigmoid函数,σ(zlj)的值再接近1或者0的时候会变得很平,函数图形变得很平的时候意味着导数也接近于0,也就是σ(zlj)0,那么输出层的误差就接近于0,由于权重的梯度和偏置的梯度的计算都依赖与误差,所以梯度也都接近与0,这也就是说输出层的学习会很缓慢。
对于BP2也有类似的结果,因为BP2中也含有σ(zlj)项。
对于这种由于高**值(σ(zlj)的值接近1)或者低**值(σ(zlj)的值接近0)而导致学习缓慢的神经元,我们称这个神经元饱和了。
了解了BP算法的这种特性,有什么帮助呢?由于我们上面的推导其实不依赖于任何具体的函数,所以,我们可以设计一些具有特殊特性的学习函数来避免学习缓慢的情况。说白了还是为了优化计算速度。

小结一下反向传播的四个方程式

δLj=aCσ(zLj)(BP-1a)

δl=((wl+1)Tδl+1))σ(zl)(BP2)

Cblj=δlj(BP3)

Cwljk=al1kδlj(BP4)

反向传播算法

  1. 输入x : 求输入层神经元的输出a1 (这个一般来说就是样本输入)
  2. 向前传播: 从第二层开始到最后一层,依次的计算各层的zlal
    for l in [2,L]
    compute zl (zl=wlal1+bl)
    compute al (al=σ(zl))
  3. 计算输出层误差: 利用公式δLj=aCσ(zLj)计算出输出层的误差
  4. 反向传播误差:从L-1层开始一直到第二层,利用公式δl=((wl+1)Tδl+1))σ(zl)计算每一层的误差
  5. 输出:利用公式Cblj=δljCwljk=al1kδlj 计算代价函数对所有未知参数的偏导数

参考文献:

  1. How the backpropagation algorithm works
  2. Backpropagation Algorithm
  3. CS231n:Backpropagation, Intuitions
  4. CS229: Deeplearning classnotes