背景
去年看了《神经网络与深度学习》的前几章,了解了反向传播算法的一些皮毛,当时想自己实现一下,但是由于事情多,就放下了。现在有时间,也是由于想理清这算法就动手推公式和写代码了。
------这里只以全连接层作为例子,主要是我最近的一些理解------
定义全连接网络

上图所示,说明一下参数:
wijl:表示第l层中的第i个神经元与第l+1层中第j个神经元的权重
bil:表示第l层中的第i个神经元的偏向值
zil:表示第l层中的第i个神经元的输入值,它由前一层对应权重与偏向和。
ail:表示第l层中的第i个神经元的输出值,它是输入值经**函数计算所得。
这里每个神经元所用**函数为sigmoid函数 s(x)=1+e−x1,顺便算下s(x)对x求导的结果为:s′(x)=(1+e−x)2e−x=s(x)(1−s(x))。
这个网络输出是两个值,可以代表一个二分类网络每个类别的概率。输入是两个值,可以想象每一个样本有两个特征值。
接下来我们举个例子,看网络的前向运算。
前向运算
输入一个样本X,它有两个特征值如下表示:
X={x1,x2}
两个特征值进入输入层,也就是第一层的输出值,于是可以依次计算出第二层每个神经元的输入值和**值(对下一层的输出值):
z12=w111⋅x1+w211⋅x2+b12
z22=w121⋅x1+w221⋅x2+b22
z32=w131⋅x1+w231⋅x2+b32
a12=s(z12)
a22=s(z22)
a32=s(z32)
接下来算出第三层每个神经元的输入值和对下一层的输出值:
z13=w112⋅a12+w212⋅a22+w312⋅a32+b13
z23=w122⋅a12+w222⋅a22+w322⋅a32+b23
z33=w132⋅a12+w232⋅a22+w332⋅a32+b33
a13=s(z13)
a23=s(z23)
a33=s(z33)
有了第三层**值,那么可以算出第四层,也就是输出层的值:
z14=w113⋅a13+w213⋅a23+w313⋅a33+b14
z24=w123⋅a13+w223⋅a23+w323⋅a33+b24
a14=s(z14)
a24=s(z24)
得到网络的输出值a14,a24,我们与真实值相比较,设对于样本X的标签为Y={y1,y2}。那么算出网络计算值与真实值的差距loss,这里用平方差:
loss=2(y1−a14)2+(y2−a24)2
有了损失值,那么,我们应该优化网络参数,不断降低损失值。这里采用最常用的梯度下降法,来求loss的最小值。因为,沿梯度相反的方向,就是函数值下降最快的方向。那么接下来就是求每个l关于w,b的梯度,然后按照一定的学习率lr更新这些参数,如下:
w=w−lr⋅dwdloss(1)
b=b−lr⋅dbdloss(2)
,总有一天,loss会降到最低,令我们满意。
那么,计算每个w,b的梯度,这和前向计算一样,是一件体力活,接下来就采用链式求导来依次计算出dwdloss、dbdloss
链式求导
从最后一层开始,求dw113dloss、dw123dloss、dw213dloss、dw223dloss、dw313dloss、dw323dloss以及db14dloss、db24dloss:
参照上面前向计算式子,从后往前看,直到遇见 b14为止:
loss=2(y1−a14)2+(y2−a24)2
a14=s(z14)
z14=w113⋅a13+w213⋅a23+w313⋅a33+b14
那么可以依照链式求导法则来求 loss对b14的偏导数:
db14dloss=da14dloss⋅dz14da14⋅db14dz14=−21⋅2⋅(y1−a14)⋅s(z14)⋅(1−s(z14))=−(y1−a14)⋅s(z14)⋅(1−s(z14))
同理可以得到下面:
db24dloss=da24dloss⋅dz24da24⋅db24dz24=−21⋅2⋅(y2−a24)⋅s(z24)⋅(1−s(z24))=−(y2−a24)⋅s(z24)⋅(1−s(z24))
dw113dloss=da14dloss⋅dz14da14⋅dw113dz14
dw123dloss=da24dloss⋅dz24da24⋅dw123dz24
......照这样计算下去就可以把这一层参数偏导数全求出来。
最后一层求出之后,再求倒数第二层dw112dloss、dw122dloss、dw132dloss、dw212dloss、dw222dloss、dw232dloss、dw312dloss、dw322dloss、dw332dloss以及db13dloss、db23dloss、db23dloss:
这一层有点深,求db13dloss,从后往前看:
loss=2(y1−a14)2+(y2−a24)2
a14=s(z14)
a24=s(z24)
z14=w113⋅a13+w213⋅a23+w313⋅a33+b14
z24=w123⋅a13+w223⋅a23+w323⋅a33+b24
a13=s(z13)
z13=w112⋅a12+w212⋅a22+w312⋅a32+b13
直到出现b13,然后求偏导数:
db13dloss=da14dloss⋅dz14da14⋅da13dz14⋅dz13da13⋅db13dz13+da24dloss⋅dz24da24⋅da13dz24⋅dz13da13⋅db13dz13
好了,接下来看dw112dloss:
loss=2(y1−a14)2+(y2−a24)2
a14=s(z14)
a24=s(z24)
z14=w113⋅a13+w213⋅a23+w313⋅a33+b14
z24=w123⋅a13+w223⋅a23+w323⋅a33+b24
a13=s(z13)
z13=w112⋅a12+w212⋅a22+w312⋅a32+b13
看到了w112,那就求导:
dw112dloss=da14dloss⋅dz14da14⋅da13dz14⋅dz13da13⋅dw112dz13+da24dloss⋅dz24da24⋅da13dz24⋅dz13da13⋅dw112dz13
接下来,算其它的也是一样的方法,这里就不赘述了!
求出所有层的参数,然后按照梯度下降法的公式(1)、(2),更新一次参数。再不断重复这个前向运算和后向求偏导并更新参数过程,使得loss降到最低。
这里大家可能就发现问题了,这样求导,越往深处求,越发现,有些偏导数前面的都是一样的,而且已经求过了,在求所有偏导数时,存在大量的不必要的重复计算。那怎么才能优化它呢?接下来就介绍反向传播算法来加速网络求梯度。
反向传播算法
反向传播算法,我的理解就是引入δ,从后往前计算梯度时,每计算完一个参数梯度,就先保存下来,然后再计算前面梯度时,直接用先前保存下来的梯度值继续计算。这样避免重复计算!
ok,下面讲的是算法思想:
我们先定义δ:
δil=dzildloss
明天再写。