在《神经网络的梯度推导与代码验证》之数学基础篇:矩阵微分与求导中,我们总结了一些用于推导神经网络反向梯度求导的重要的数学技巧。此外,通过一个简单的demo,我们初步了解了使用矩阵求导来批量求神经网络参数的做法。在本篇章,我们将专门针对DNN/FNN这种网络结构进行前向传播介绍和反向梯度推导。更多相关内容请见《神经网络的梯度推导与代码验证》系列介绍。
注意:
- 本系列的关注点主要在反向梯度推导以及代码上的验证,涉及到的前向传播相对而言不会做太详细的介绍。
- 反向梯度求导涉及到矩阵微分和求导的相关知识,请见《神经网络的梯度推导与代码验证》之数学基础篇:矩阵微分与求导,内含手把手教学级的内容。
目录
- 2.1 FNN(DNN)的前向传播
- 2.2 FNN(DNN)的反向传播
- 2.3 总结
- 参考资料
提醒:
- 后续会反复出现$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)