Bootstrap

深度学习中的梯度计算与反向传播推导(一)DNN中单样本反向传播推导

前言

  • 这篇博客的初心 : 最近读的论文又用到LSTM了,发现对这些深度学习模型我还是只了解皮毛(前向传播),不了解其底层原理(如参数的更新),而我从接触深度学习开始就对反向传播充满了好奇,感觉这是个很难理解的事情。所以建立这篇博客慢慢从矩阵求导开始,慢慢推导所有深度学习模型的底层原理,从而加深自己的理解。
  • 这篇博客内容 : 包括部分深度学习所需数学知识,以及各种深度学习模型(DNN,RNN等)的原理推导

1 数学知识

注: 在本博客中,所有向量 v \bm{v} v默认都为列向量

1.1 深度学习中几种常见的梯度计算

在神经网络中,很常见的求梯度类型求一个向量 v \bm{v} v(如网络某一层的输出)或一个矩阵 W \bm{W} W(如网络中的参数)的梯度,他们的实质其实都是多元函数求偏导。其中这个多元函数 f f f(如损失函数)是以 v \bm{v} v或者 W \bm{W} W为自变量的多元函数。

1.1.1 求向量的梯度

若 v = ( v 1 v 2 . . . v n ) , 则 ∂ f v 等 价 于 ( ∂ f v 1 ∂ f v 2 . . . ∂ f v n ) 若\bm{v}=\begin{pmatrix} v_1 \\ v_2 \\ ... \\v_n \end{pmatrix},则\frac{\partial f}{\bm{v}}等价于\begin{pmatrix} \frac{\partial f}{v_1} \\ \frac{\partial f}{v_2} \\ ... \\\frac{\partial f}{v_n} \end{pmatrix} v=v1v2...vnvfv1fv2f...vnf

1.1.2 求矩阵的梯度

同1.1.1,即使用 f f f依次对矩阵的每一个元素求偏导,最后的结果仍然为一个矩阵。

2 深度神经网络(DNN)中反向传播的推导

2.1 变量定义

符号含义
x \bm{x} x一个输入样本,维度为( N 0 × 1 N_0\times 1 N0×1)
W j k l W_{jk}^l Wjkl l − 1 l-1 l1层第 k \bm{k} k个神经元到第 l l l层第 j \bm{j} j个神经元的连接权重
b j l b_j^l bjl l l l层第 j j j个神经元的偏置
z j l z_j^l zjl l l l层第 j j j个神经元的带权输入
σ l \sigma^l σl l l l层的激活函数
a j l a_j^l ajl l l l层第 j j j个神经元的输出
N l N_l Nl l l l层神经元数量

2.2 前向传播

为方便考虑传播过程,我们首先仅考虑一个样本 x \bm{x} x在DNN中的前向传播。

很容易发现,样本在每一层中流动的过程都是一样的,可以表示为:

z l = W l a l − 1 + b l a l = σ l ( z l ) \begin{aligned} &\bm{z^l}=\bm{W^la^{l-1}+b^l}\\ &\bm{a^l}=\sigma^l(\bm{z^l}) \end{aligned} zl=Wlal1+blal=σl(zl)

其中, a 0 = x \bm{a^0}=\bm{x} a0=x W l \bm{W^l} Wl的维度为 ( N l × N l − 1 ) (N_{l}\times N_{l-1}) (Nl×Nl1), a l − 1 \bm{a^{l-1}} al1的维度为 ( N l − 1 × 1 ) (N_{l-1}\times1) (Nl1×1), b l , z l , a l \bm{b^l,z^l,a^l} bl,zl,al的维度均为 ( N l × 1 ) (N_l\times1) (Nl×1) a L \bm{a^L} aL是DNN的输出

通过前向传播,样本 x \bm{x} x一层一层地通过DNN走到了输出层,并得到了 a L \bm{a^L} aL。那么我们就能够通过 a L \bm{a^L} aL和真实的标签 y \bm{y} y得到这个样本的损失,即 C = L o s s ( a L , y ) C=Loss(\bm{a^L,y}) C=Loss(aL,y)

2.3 接下来要做的事情

我们希望根据这个损失 C C C来不断的更新DNN的参数 [ W 1 , W 2 , . . . , W L 及 b 1 , b 2 , . . . , b l ] [\bm{W^1,W^2,...,W^L}及\bm{b^1,b^2,...,b^l}] [W1,W2,...,WLb1,b2,...,bl]。为了使用基于梯度的优化算法,很显然需要计算所有参数的梯度,也就是 [ ∂ C ∂ W 1 , ∂ C ∂ W 2 , . . . , ∂ C ∂ W L ] [\frac{\partial C}{\partial\bm{W^1}},\frac{\partial C}{\partial\bm{W^2}},...,\frac{\partial C}{\partial\bm{W^L}}] [W1C,W2C,...,WLC] [ ∂ C ∂ b 1 , ∂ C ∂ b 2 , . . . , ∂ C ∂ b L ] [\frac{\partial C}{\partial\bm{b^1}},\frac{\partial C}{\partial\bm{b^2}},...,\frac{\partial C}{\partial\bm{b^L}}] [b1C,b2C,...,bLC]

由1.1.1和1.1.2可知,求向量或矩阵的梯度,就是对其中的每一个元素分别求偏导,所以我们开始吧。

2.3.1 求 W l \bm{W^l} Wl的梯度

我们只考虑第 l l l层参数矩阵 W l \bm{W^l} Wl中的一个元素 W j k l W_{jk}^l Wjkl的梯度,来看看有什么规律。

W j k l W_{jk}^l Wjkl会对 C C C有什么影响呢?他只会与第 l − 1 l-1 l1层第 k \bm{k} k个节点的输出 a k l − 1 a_k^{l-1} akl1相乘,然后作为一部分汇聚到下一层,也就是第 l l l层的第 j \bm{j} j个节点上。如图所示:
DNN_W
所以根据链式法则,有 ∂ C ∂ W j k l = ∂ C ∂ z j l ∂ z j l ∂ W j k l \frac{\partial C}{\partial W_{jk}^l}=\frac{\partial C}{\partial z_j^l}\frac{\partial z_j^l}{\partial W_{jk}^l} WjklC=zjlCWjklzjl。其中, z j l = ∑ i = 1 N l − 1 a i l − 1 W j i l + b j l z_j^l=\sum_{i=1}^{N_{l-1}}a_i^{l-1}W_{ji}^l+b_j^l zjl=i=1Nl1ail1Wjil+bjl

所以, ∂ z j l ∂ W j k l = a k l − 1 \frac{\partial z_j^l}{\partial W_{jk}^l}=a_k^{l-1} Wjklzjl=akl1(仅当 i = k i=k i=k时,求和项不为0), 从而:

∂ C ∂ W j k l = ∂ C ∂ z j l a k l − 1 \frac{\partial C}{\partial W_{jk}^l}=\frac{\partial C}{\partial z_j^l}a_k^{l-1} WjklC=zjlCakl1

2.3.2 求 b l \bm{b^l} bl的梯度

同样,我们只考虑 b j l b_j^l bjl的梯度。

b j l b_j^l bjl会对 C C C有什么影响呢?他只会作用在第 l l l层的第 j \bm{j} j个节点上,作为一个小小的偏置(如上图)。所以我们依然可以根据链式法则得到 ∂ C ∂ b j l = ∂ C ∂ z j l ∂ z j l ∂ b j l \frac{\partial C}{\partial b_j^l}=\frac{\partial C}{\partial z_j^l}\frac{\partial z_j^l}{\partial b_j^l} bjlC=zjlCbjlzjl。其中, z j l = ∑ i = 1 N l − 1 a i l − 1 W j i l + b j l z_j^l=\sum_{i=1}^{N_{l-1}}a_i^{l-1}W_{ji}^l+b_j^l zjl=i=1Nl1ail1Wjil+bjl(与2.3.1中是一样的)

所以, ∂ z j l ∂ b j l = 1 \frac{\partial z_j^l}{\partial b_j^l}=1 bjlzjl=1,从而:

∂ C ∂ b j l = ∂ C ∂ z j l × 1 = ∂ C ∂ z j l \frac{\partial C}{\partial b_j^l}=\frac{\partial C}{\partial z_j^l}\times1=\frac{\partial C}{\partial z_j^l} bjlC=zjlC×1=zjlC

2.4 (误差的)反向传播

通过2.3节可以看到:如果想求 W l \bm{W^l} Wl b l \bm{b^l} bl中每一个元素的梯度,都需要求 ∂ C ∂ z j l \frac{\partial C}{\partial z_j^l} zjlC这一项。该怎么求呢?接下来就是反向传播的精髓了。

我小小的总结一下,前向传播是训练样本的前向传播,目的是使用 x \bm{x} x通过DNN得到 a L \bm{a^L} aL,从而计算 L o s s ( a L , y ) Loss(\bm{a^L,y}) Loss(aL,y)。那么反向传播是误差的反向传播,即首先根据 L o s s Loss Loss得到最后一层(第 L L L层)的误差,再反向计算每一层的误差。这里的误差,也就是我们刚刚注意到的 ∂ C ∂ z l \frac{\partial C}{\partial \bm{z^l}} zlC这一项。通过计算该项,我们便可以得到模型中所有参数的梯度,从而使用基于梯度的优化算法进行参数更新,这就是DNN完整的一轮迭代。

2.4.1 计算第 L L L层的误差 ∂ C ∂ z L \frac{\partial C}{\partial\bm{z^L}} zLC

同样的方法,我们还是先只考虑这一层中第 j \bm{j} j个神经元的误差 ∂ C ∂ z j L \frac{\partial C}{\partial z_j^L} zjLC

首先考虑 z j L z_j^L zjL会对 C C C产生什么影响。很简单的, z j L z_j^L zjL在被激活函数 σ L \sigma^L σL激活得到 a j L a_j^L ajL然后作为计算 C C C的一部分。

所以,又根据链式法则,得到 ∂ C ∂ z j L = ∂ C ∂ a j L ∂ a j L ∂ z j L \frac{\partial C}{\partial z_j^L}=\frac{\partial C}{\partial a_j^L}\frac{\partial a_j^L}{\partial z_j^L} zjLC=ajLCzjLajL.

其中 ∂ a j L ∂ z j L \frac{\partial a_j^L}{\partial z_j^L} zjLajL这一项等于 σ ′ L ( z j L ) \sigma'^L(z_j^L) σL(zjL).因为 a j L = σ L ( z j L ) a_j^L=\sigma^L(z_j^L) ajL=σL(zjL)

∂ C ∂ a j L \frac{\partial C}{\partial a_j^L} ajLC这一项需要根据具体的损失函数 L o s s ( ) Loss() Loss()来计算。我们以平方损失为例:

C = L o s s ( a L , y ) = 1 2 ∥ y − a L ∥ 2 = 1 2 ∑ j ( y j − a j L ) 2 C=Loss(\bm{a^L,y})=\frac{1}{2}\Vert\bm{y-a^L}\Vert^2=\frac{1}{2}\sum_j(y_j-a_j^L)^2 C=Loss(aL,y)=21yaL2=21j(yjajL)2

那么,我们便可以求得

∂ C ∂ a j L = ∂ 1 2 [ ( y 1 − a 1 L ) 2 + . . . + ( y N L − a N L L ) 2 ] ∂ a j L = 1 2 × 2 ( y j − a j L ) × − 1 = a j L − y j \frac{\partial C}{\partial a_j^L}=\frac{\partial \frac{1}{2}[(y_1-a_1^L)^2+...+(y_{N_L}-a_{N_L}^L)^2]}{{\partial a_j^L}}=\frac{1}{2}\times2(y_j-a_j^L)\times-1=a_j^L-y_j ajLC=ajL21[(y1a1L)2+...+(yNLaNLL)2]=21×2(yjajL)×1=ajLyj

可以看到 ∂ C ∂ a j L 和 ∂ a j L ∂ z j L \frac{\partial C}{\partial a_j^L}和\frac{\partial a_j^L}{\partial z_j^L} ajLCzjLajL都是只与下标 j \bm{j} j有关的,所以我们可以直接将其扩展成向量形式,即 ∂ C ∂ z L = ( a L − y ) ⨀ σ ′ L ( z L ) \frac{\partial C}{\partial\bm{z^L}}=(\bm{a^L-y})\bigodot\sigma'^L(\bm{z^L}) zLC=(aLy)σL(zL),其中 ⊙ \odot 是两个向量的按元素乘法

从平方损失函数扩展到其他各种损失函数,即

∂ C ∂ z L = ( ∂ C ∂ a L ) ⊙ σ ′ L ( z L ) \frac{\partial C}{\partial\bm{z^L}}=(\frac{\partial C}{\partial \bm{a^L}})\odot\sigma'^L(\bm{z^L}) zLC=(aLC)σL(zL)

2.4.1 误差从第 l + 1 l+1 l+1层传播到第 l l l

为了计算误差在两层之间是怎么流动的,我们首先需要观察一下两层之间 z \bm{z} z的关系,很简单,就是 z l + 1 = W l + 1 σ l ( z l ) + b l + 1 \bm{z^{l+1}}=\bm{W^{l+1}}\sigma^l(\bm{z^l})+\bm{b^{l+1}} zl+1=Wl+1σl(zl)+bl+1

我们可以从上面的式子观察一下第 l l l层的第 j \bm{j} j个神经元的 z j l z_j^l zjl是怎么作用到下一层的。同样很简单, z j l z_j^l zjl会先经过一个激活函数 σ \sigma σ得到 a j l a_j^l ajl,再乘上不同的权重,作用在下一层的每一个神经元上。
DNN_z
所以,根据链式法则(例:z为u,v的函数,u和v分别为x,y的函数),有 ∂ C ∂ z j l = ∑ k ∂ C ∂ z k l + 1 ∂ z k l + 1 ∂ z j l \frac{\partial C}{\partial z_j^l}=\sum_k\frac{\partial C}{\partial z_k^{l+1}}\frac{\partial z_k^{l+1}}{\partial z_j^l} zjlC=kzkl+1Czjlzkl+1

我们先看 ∂ z k l + 1 ∂ z j l \frac{\partial z_k^{l+1}}{\partial z_j^l} zjlzkl+1这一项。 z k l + 1 z_k^{l+1} zkl+1是怎么得到的呢?是上一层所有的 z i l z_i^l zil经过一个激活函数,再乘一个权重,最后加上一个偏置得到的,即 z k l + 1 = ∑ i N l σ l ( z i l ) × W k i l + 1 + b k l + 1 z_k^{l+1}=\sum_i^{N_l}\sigma^l(z_i^l)\times W_{ki}^{l+1}+b_k^{l+1} zkl+1=iNlσl(zil)×Wkil+1+bkl+1.(形式同2.3.1图)

所以 ∂ z k l + 1 ∂ z j l = σ ′ l ( z j l ) × W k j l + 1 \frac{\partial z_k^{l+1}}{\partial z_j^l}=\sigma'^l(z_j^l)\times W_{kj}^{l+1} zjlzkl+1=σl(zjl)×Wkjl+1(仅当 i = j i=j i=j时求和项不为0)

把他带回 ∂ C ∂ z j l \frac{\partial C}{\partial z_j^l} zjlC,得到 ∂ C ∂ z j l = ∑ k ∂ C ∂ z k l + 1 × W k j l + 1 × σ ′ l ( z j l ) \frac{\partial C}{\partial z_j^l}=\sum_k\frac{\partial C}{\partial z_k^{l+1}}\times W_{kj}^{l+1}\times\sigma'^l(z_j^l) zjlC=kzkl+1C×Wkjl+1×σl(zjl)

展开求和项,得到 ∂ C ∂ z j l = [ ∂ C ∂ z 1 l + 1 × W 1 j l + 1 + ∂ C ∂ z 2 l + 1 × W 2 j l + 1 + . . . + ∂ C ∂ z N l + 1 l + 1 × W N l + 1 j l + 1 ] × σ ′ l ( z j l ) \frac{\partial C}{\partial z_j^l}=[\frac{\partial C}{\partial z_1^{l+1}}\times W_{1j}^{l+1}+\frac{\partial C}{\partial z_2^{l+1}}\times W_{2j}^{l+1}+...+\frac{\partial C}{\partial z_{N_{l+1}}^{l+1}}\times W_{N_{l+1}j}^{l+1}]\times \sigma'^l(z_j^l) zjlC=[z1l+1C×W1jl+1+z2l+1C×W2jl+1+...+zNl+1l+1C×WNl+1jl+1]×σl(zjl)

仔细观察其向量表示!

很惊讶的发现,上式 = [ ( W l + 1 ) T ( ∂ C ∂ z l + 1 ) ] j × σ ′ l ( z j l ) =[\bm{(W^{l+1})}^\mathrm{T}(\frac{\partial C}{\partial \bm{z^{l+1}}})]_j\times \sigma'^l(z_j^l) =[(Wl+1)T(zl+1C)]j×σl(zjl)

因为只由下标 j \bm{j} j决定,所以又可以得到一个完美漂亮的向量表达~

∂ C ∂ z l = ( W l + 1 ) T ( ∂ C ∂ z l + 1 ) ⊙ σ ′ l ( z l ) \frac{\partial C}{\bm{\partial \bm{z^l}}}=\bm{(W^{l+1})}^\mathrm{T}(\frac{\partial C}{\partial \bm{z^{l+1}}})\odot\sigma'^l(\bm{z^l}) zlC=(Wl+1)T(zl+1C)σl(zl)

2.5 关于单样本反向传播的最后公式

为便于简洁表示,令每层的误差 ∂ C ∂ z l = δ l \frac{\partial C}{\partial \bm{z^l}}=\bm{\delta^l} zlC=δl

有以下公式:

∂ C ∂ W j k l = δ j l a k l − 1 ∂ C ∂ b j l = δ j l δ L = ( ∂ C ∂ a L ) ⊙ σ ′ L ( z L ) δ l = ( ( W l + 1 ) T δ l + 1 ) ⊙ σ ′ l ( z l ) \begin{aligned} &\frac{\partial C}{\partial W_{jk}^l}=\delta_j^la_k^{l-1}\\ &\frac{\partial C}{\partial b_j^l}=\delta_j^l\\ &\bm{\delta^L}=(\frac{\partial C}{\partial \bm{a^L}})\odot\sigma'^L(\bm{z^L})\\ &\bm{\delta^l}=(\bm{(W^{l+1})}^\mathrm{T}\bm{\delta^{l+1}})\odot\sigma'^l(\bm{z^l}) \end{aligned} WjklC=δjlakl1bjlC=δjlδL=(aLC)σL(zL)δl=((Wl+1)Tδl+1)σl(zl)

也可以按照以下流程编程实现(图片来自Neural Networks and Deep Learning, Michael Nielsen ):
method
自此,我对于单个样本 x \bm{x} x的反向传播推导就告一段落,已经可以凭借以上内容,实现一个使用随机梯度下降算法来优化的DNN啦!

2.6 多样本反向传播

参考文献

  1. Matrix Cookbook - Kaare Brandt Petersen, Michael Syskind Pedersen
  2. Neural Network and Deep Learning - Michael Nielsen
;