《神经网络的梯度推导与代码验证》之FNN(DNN)的前向传播和反向梯度推导

《神经网络的梯度推导与代码验证》之数学基础篇:矩阵微分与求导中,我们总结了一些用于推导神经网络反向梯度求导的重要的数学技巧。此外,通过一个简单的demo,我们初步了解了使用矩阵求导来批量求神经网络参数的做法。在本篇章,我们将专门针对DNN/FNN这种网络结构进行前向传播介绍和反向梯度推导。更多相关内容请见《神经网络的梯度推导与代码验证》系列介绍

 

注意:


目录

提醒:

  • 后续会反复出现$oldsymbol{delta}^{l}$这个(类)符号,它的定义为$oldsymbol{delta}^{l} = frac{partial l}{partialoldsymbol{z}^{oldsymbol{l}}}$,即loss $l$对$oldsymbol{z}^{oldsymbol{l}}$的导数
  • 其中$oldsymbol{z}^{oldsymbol{l}}$表示第$l$层(DNN,CNN,RNN或其他例如max pooling层等)未经过激活函数的输出。
  • $oldsymbol{a}^{oldsymbol{l}}$则表示$oldsymbol{z}^{oldsymbol{l}}$经过激活函数后的输出。

这些符号会贯穿整个系列,还请留意。


2.1 FNN(DNN)的前向传播

下面是两张DNN的示意图:

 

我们用$w_{24}^{3}$来表示第2层的第4个神经元与第三层第2个神经元之间的参数。

我们用$b_{3}^{2}$表示第2层的第3个神经元的偏置。用$a_{1}^{3}$表示第3层的第1个神经元的输出(注意是经过激活函数后的)。

 

上图的从第一层到第二层的参数计算公式如下:

$a_{1}^{2} = sigmaleft( {w_{11}^{2}x_{1} + w_{12}^{2}x_{2} + w_{13}^{2}x_{3} + b_{1}^{2}} ight)$

$a_{2}^{2} = sigmaleft( {w_{21}^{2}x_{1} + w_{22}^{2}x_{2} + w_{23}^{2}x_{3} + b_{2}^{2}} ight)$

$a_{3}^{2} = sigmaleft( {w_{31}^{2}x_{1} + w_{32}^{2}x_{2} + w_{33}^{2}x_{3} + b_{3}^{2}} ight)$

$a_{4}^{2} = sigmaleft( {w_{41}^{2}x_{1} + w_{42}^{2}x_{2} + w_{43}^{2}x_{3} + b_{4}^{2}} ight)$

其中$sigmaleft( ~ ight)$表示激活函数。

 

将上图写成矩阵的编排方式就是下面这样:

$leftlbrack egin{array}{l} egin{array}{l} a_{1}^{2} \ a_{2}^{2} \ end{array} \ a_{3}^{2} \ a_{4}^{2} \ end{array} ight brack = sigmaleft( {leftlbrack egin{array}{lll} egin{array}{l} w_{11}^{2} \ w_{21}^{2} \ end{array} & egin{array}{l} w_{12}^{2} \ w_{22}^{2} \ end{array} & egin{array}{l} w_{13}^{2} \ w_{23}^{2} \ end{array} \ w_{31}^{2} & w_{32}^{2} & w_{33}^{2} \ w_{41}^{2} & w_{42}^{2} & w_{43}^{2} \ end{array} ight brackleftlbrack egin{array}{l} x_{1} \ x_{2} \ x_{3} \ end{array} ight brack + leftlbrack egin{array}{l} egin{array}{l} b_{1}^{2} \ b_{2}^{2} \ end{array} \ b_{3}^{2} \ b_{4}^{2} \ end{array} ight brack} ight)$

$oldsymbol{a}^{2} = sigmaleft( {oldsymbol{W}^{2}oldsymbol{x} + oldsymbol{b}^{2}} ight)$

同理得到第二层到第三层的计算公式:

$oldsymbol{a}^{3} = sigmaleft( {oldsymbol{W}^{3}oldsymbol{a}^{2} + oldsymbol{b}^{3}} ight)$

于是总结下来,DNN的层间关系如下:

$oldsymbol{a}^{oldsymbol{l}} = sigmaleft( {oldsymbol{W}^{l}oldsymbol{a}^{l - 1} + oldsymbol{b}^{l}} ight)$

所以DNN的前向传播逻辑如下:

输入:总层数L,所有隐藏层和输出层对应的参数矩阵$oldsymbol{W}$,偏置向量$oldsymbol{b}$和输入向量$oldsymbol{x}$

输出:$oldsymbol{a}^{L}$

1) 初始化$oldsymbol{a}^{1} = oldsymbol{x}$

2) for $l = 2$ to L,计算:$oldsymbol{a}^{l} = sigmaleft( {oldsymbol{W}^{l}oldsymbol{a}^{l - 1} + oldsymbol{b}^{l}} ight)$

最后的结果即为输出$oldsymbol{a}^{L}$


2.2 FNN(DNN)的反向梯度求导

在进行DNN反向传播算法前,我们需要选择一个损失函数,来度量训练样本计算出的输出和真实的训练样本输出之间的损失。这里用mse作为损失函数,则每一条样本的loss计算公式如下:

$l = frac{1}{2}left| {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight|_{2}^{2} = frac{1}{2}left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)^{T}left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)$

其中,$oldsymbol{y}$就是样本标签$oldsymbol{y}\_oldsymbol{t}oldsymbol{u}oldsymbol{r}oldsymbol{e}$,而$oldsymbol{a}^{oldsymbol{L}}$就是预测值$oldsymbol{y}\_oldsymbol{p}oldsymbol{r}oldsymbol{e}oldsymbol{d}oldsymbol{i}oldsymbol{c}oldsymbol{t}$。

预测值$oldsymbol{a}^{oldsymbol{L}}$和输入$oldsymbol{x}$满足$oldsymbol{a}^{oldsymbol{L}} = oldsymbol{D}oldsymbol{N}oldsymbol{N}left( {oldsymbol{x};oldsymbol{W},oldsymbol{b}} ight)$,这就是2.1中提到的DNN的前向传播过程,这么看来,所谓前向传播,不过是一个复杂的函数罢了。

 

于是写得再全一点,某条样本$oldsymbol{a}^{oldsymbol{L}} = oldsymbol{D}oldsymbol{N}oldsymbol{N}left( {oldsymbol{x};oldsymbol{W},oldsymbol{b}} ight)$根据mse计算出来的loss就是下面这样:

$lleft( {oldsymbol{x},oldsymbol{y},oldsymbol{W},oldsymbol{b}} ight) = frac{1}{2}left| {oldsymbol{D}oldsymbol{N}oldsymbol{N}left| {oldsymbol{x};oldsymbol{W},oldsymbol{b}} ight| - oldsymbol{y}} ight|_{2}^{2} = frac{1}{2}left( {oldsymbol{D}oldsymbol{N}oldsymbol{N}left( {oldsymbol{x};oldsymbol{W},oldsymbol{b}} ight) - oldsymbol{y}} ight)^{T}left( {oldsymbol{D}oldsymbol{N}oldsymbol{N}left( {oldsymbol{x};oldsymbol{W},oldsymbol{b}} ight) - oldsymbol{y}} ight)$

 

铺垫了这么多接下来正式开始求梯度。

我们先求$frac{partial l}{partialoldsymbol{a}^{oldsymbol{L}}}$,

$dl = frac{1}{2}left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)^{T}dleft( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight) + frac{1}{2}dleft( left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)^{T} ight)left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight) = frac{1}{2}left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)^{T}doldsymbol{a}^{oldsymbol{L}} + frac{1}{2}dleft( left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)^{T} ight)left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)$

对$frac{1}{2}left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)dleft( left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)^{T} ight)$使用迹技巧,有:

$frac{1}{2}dleft( left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)^{T} ight)left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight) = frac{1}{2}trleft( {dleft( left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)^{T} ight)left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)} ight) = frac{1}{2}trleft( {left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)^{oldsymbol{T}}dleft( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)} ight) = frac{1}{2}trleft( {left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)^{oldsymbol{T}}doldsymbol{a}^{oldsymbol{L}}} ight) = frac{1}{2}left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)^{oldsymbol{T}}doldsymbol{a}^{oldsymbol{L}}$

所以有:

$dl = frac{1}{2}left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)^{T}doldsymbol{a}^{oldsymbol{L}} + frac{1}{2}left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)^{oldsymbol{T}}doldsymbol{a}^{oldsymbol{L}} = left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight)^{oldsymbol{T}}doldsymbol{a}^{oldsymbol{L}}$

$frac{partial l}{partialoldsymbol{a}^{oldsymbol{L}}} = oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}$

我们令$oldsymbol{z}^{L} = oldsymbol{W}^{oldsymbol{L}}oldsymbol{a}^{oldsymbol{L} - 1} + oldsymbol{b}^{oldsymbol{L}}$

可求得$frac{partial l}{partialoldsymbol{z}^{oldsymbol{L}}} = left( frac{partialoldsymbol{a}^{oldsymbol{L}}}{partialoldsymbol{a}^{oldsymbol{L}}} ight)^{T}frac{partial l}{partialoldsymbol{a}^{oldsymbol{L}}}$

因为$doldsymbol{a}^{oldsymbol{L}} = dsigmaleft( oldsymbol{z}^{L} ight) = sigma^{'}left( oldsymbol{z}^{L} ight) odot doldsymbol{z}^{L} = diagleft( {sigma^{'}left( oldsymbol{z}^{L} ight)} ight)doldsymbol{z}^{L}$

所以$frac{partial l}{partialoldsymbol{z}^{oldsymbol{L}}} = diagleft( {sigma^{'}left( oldsymbol{z}^{L} ight)} ight)left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight) = left( {oldsymbol{a}^{oldsymbol{L}} - oldsymbol{y}} ight) odot sigma^{'}left( oldsymbol{z}^{L} ight)$

有了$frac{partial l}{partialoldsymbol{z}^{oldsymbol{L}}}$,那么求L层的$oldsymbol{W}^{oldsymbol{L}}$和$oldsymbol{b}^{oldsymbol{L}}$的梯度就非常容易了,根据标量对线性变换的求导结论,直接得到:

$frac{partial l}{partialoldsymbol{W}^{oldsymbol{L}}} = frac{partial l}{partialoldsymbol{z}^{oldsymbol{L}}}left( oldsymbol{a}^{oldsymbol{L} - 1} ight)^{T}$

$frac{partial l}{partialoldsymbol{b}^{oldsymbol{L}}} = frac{partial l}{partialoldsymbol{z}^{oldsymbol{L}}}$

这样第L层的所有参数的梯度就得到了。

为了方便起见,今后用$delta^{l}$表示$frac{partial l}{partialoldsymbol{z}^{oldsymbol{l}}}$。

有上面求L层参数的梯度的思路,可以发现,如果我们想求出第$l$层的参数的梯度,我们可以先求出$delta^{l}$然后直接套用标量对线性变换的求导结论就可以快速求得结果了。因此,这里我们用数学归纳法,第L层的$delta^{L}$我们已经求出来了,假设第$l+1$层的$delta^{l + 1}$已求出来,那我们如何求$delta^{l}$呢?

 

根据链式法则,有$frac{partial l}{partialoldsymbol{z}^{oldsymbol{l}}} = left( frac{partialoldsymbol{z}^{oldsymbol{l} + 1}}{partialoldsymbol{z}^{oldsymbol{l}}} ight)^{T}frac{partial l}{partialoldsymbol{z}^{oldsymbol{l} + 1}}$

现在问题转到求$frac{partialoldsymbol{z}^{oldsymbol{l} + 1}}{partialoldsymbol{z}^{oldsymbol{l}}}$上。

我们注意到有$oldsymbol{z}^{l + 1} = oldsymbol{W}^{l + 1}sigmaleft( oldsymbol{z}^{oldsymbol{l}} ight) + oldsymbol{b}^{l + 1}$成立,

所以$doldsymbol{z}^{l + 1} = oldsymbol{W}^{l + 1}dsigmaleft( oldsymbol{z}^{oldsymbol{l}} ight) = oldsymbol{W}^{l + 1}left( {sigma^{'}left( oldsymbol{z}^{l} ight) odot doldsymbol{z}^{l}} ight) = oldsymbol{W}^{l + 1}diagleft( {sigma^{'}left( oldsymbol{z}^{oldsymbol{l}} ight)} ight)doldsymbol{z}^{oldsymbol{l}}$

所以$frac{partialoldsymbol{z}^{l + 1}}{partialoldsymbol{z}^{l}} = oldsymbol{W}^{l + 1}diagleft( {sigma^{'}left( oldsymbol{z}^{oldsymbol{l}} ight)} ight)$

于是通过$delta^{l + 1}$,我们可以求得:

$delta^{l} = diagleft( {sigma^{'}left( oldsymbol{z}^{oldsymbol{l}} ight)} ight)left( oldsymbol{W}^{l + 1} ight)^{T}delta^{l + 1} = left( oldsymbol{W}^{l + 1} ight)^{T}delta^{l + 1} odot sigma^{'}left( oldsymbol{z}^{oldsymbol{l}} ight)$

同理,根据$delta^{l + 1}$可以秒求出$oldsymbol{W}^{l} = delta^{l}left( oldsymbol{a}^{oldsymbol{l} - 1} ight)^{T}$,$oldsymbol{b}^{oldsymbol{l}} = delta^{l}$

 


 

2.3 总结

在求神经网络某一层的参数的梯度时,先求出$delta^{l}$是一种比较合理的策略,因为借助标量对线性变换的求导结论可以快速通过$delta^{l}$求得参数的梯度;通过推导出$delta^{l}$与$delta^{l+1}$的关系,可以将这种求参数梯度的模式推广到其他层上。

 

同时我们也可以发现,对参数梯度造成影响的因素主要有以下几个:

  • 损失函数的选取,它决定了最初的$frac{partial l}{partialoldsymbol{a}^{oldsymbol{L}}}$
  • 激活函数的选取,它决定了层间$delta^{l} = oldsymbol{W}^{oldsymbol{l} + 1}diagleft( {sigma^{'}left( oldsymbol{z}^{oldsymbol{l}} ight)} ight)delta^{l + 1}$的递推计算
  • 神经网络的参数,例如每一层的神经元个数影响了$oldsymbol{W}$的尺寸;而整体深度则影响了神经网络隐藏层(尤其是靠前的隐藏层)的梯度稳定性(靠前的隐藏层可能会发生梯度消失或梯度爆炸)。
  • 神经网络的结构,因为显然它会直接影响反向梯度的推导方式(在LSTM的反向梯度推导中大家会有更深的体会)。

如果本文对您有所帮助的话,不妨点下“推荐”让它能帮到更多的人,谢谢。


参考资料

  • https://www.cnblogs.com/pinard/p/6418668.html
  • https://www.cnblogs.com/pinard/p/6422831.html

(欢迎转载,转载请注明出处。欢迎留言或沟通交流: lxwalyw@gmail.com)

原文地址:https://www.cnblogs.com/sumwailiu/p/13600984.html