前言
- 这篇博客的初心 : 最近读的论文又用到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...vn⎠⎟⎟⎞,则v∂f等价于⎝⎜⎜⎜⎛v1∂fv2∂f...vn∂f⎠⎟⎟⎟⎞
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 l−1层第 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=Wlal−1+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×Nl−1), a l − 1 \bm{a^{l-1}} al−1的维度为 ( N l − 1 × 1 ) (N_{l-1}\times1) (Nl−1×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,...,WL及b1,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}}] [∂W1∂C,∂W2∂C,...,∂WL∂C]及 [ ∂ 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}}] [∂b1∂C,∂b2∂C,...,∂bL∂C]。
由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
l−1层第
k
\bm{k}
k个节点的输出
a
k
l
−
1
a_k^{l-1}
akl−1相乘,然后作为一部分汇聚到下一层,也就是第
l
l
l层的第
j
\bm{j}
j个节点上。如图所示:
所以根据链式法则,有
∂
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}
∂Wjkl∂C=∂zjl∂C∂Wjkl∂zjl。其中,
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=1Nl−1ail−1Wjil+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} ∂Wjkl∂zjl=akl−1(仅当 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} ∂Wjkl∂C=∂zjl∂Cakl−1
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} ∂bjl∂C=∂zjl∂C∂bjl∂zjl。其中, 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=1Nl−1ail−1Wjil+bjl(与2.3.1中是一样的)
所以, ∂ z j l ∂ b j l = 1 \frac{\partial z_j^l}{\partial b_j^l}=1 ∂bjl∂zjl=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} ∂bjl∂C=∂zjl∂C×1=∂zjl∂C
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} ∂zjl∂C这一项。该怎么求呢?接下来就是反向传播的精髓了。
我小小的总结一下,前向传播是训练样本的前向传播,目的是使用 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}} ∂zl∂C这一项。通过计算该项,我们便可以得到模型中所有参数的梯度,从而使用基于梯度的优化算法进行参数更新,这就是DNN完整的一轮迭代。
2.4.1 计算第 L L L层的误差 ∂ C ∂ z L \frac{\partial C}{\partial\bm{z^L}} ∂zL∂C
同样的方法,我们还是先只考虑这一层中第 j \bm{j} j个神经元的误差 ∂ C ∂ z j L \frac{\partial C}{\partial z_j^L} ∂zjL∂C。
首先考虑 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} ∂zjL∂C=∂ajL∂C∂zjL∂ajL.
其中 ∂ a j L ∂ z j L \frac{\partial a_j^L}{\partial z_j^L} ∂zjL∂ajL这一项等于 σ ′ 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} ∂ajL∂C这一项需要根据具体的损失函数 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)=21∥y−aL∥2=21j∑(yj−ajL)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 ∂ajL∂C=∂ajL∂21[(y1−a1L)2+...+(yNL−aNLL)2]=21×2(yj−ajL)×−1=ajL−yj
可以看到 ∂ 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} ∂ajL∂C和∂zjL∂ajL都是只与下标 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}) ∂zL∂C=(aL−y)⨀σ′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}) ∂zL∂C=(∂aL∂C)⊙σ′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,再乘上不同的权重,作用在下一层的每一个神经元上。
所以,根据链式法则(例: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}
∂zjl∂C=∑k∂zkl+1∂C∂zjl∂zkl+1
我们先看 ∂ z k l + 1 ∂ z j l \frac{\partial z_k^{l+1}}{\partial z_j^l} ∂zjl∂zkl+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} ∂zjl∂zkl+1=σ′l(zjl)×Wkjl+1(仅当 i = j i=j i=j时求和项不为0)
把他带回 ∂ C ∂ z j l \frac{\partial C}{\partial z_j^l} ∂zjl∂C,得到 ∂ 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) ∂zjl∂C=∑k∂zkl+1∂C×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) ∂zjl∂C=[∂z1l+1∂C×W1jl+1+∂z2l+1∂C×W2jl+1+...+∂zNl+1l+1∂C×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+1∂C)]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}) ∂zl∂C=(Wl+1)T(∂zl+1∂C)⊙σ′l(zl)
2.5 关于单样本反向传播的最后公式
为便于简洁表示,令每层的误差 ∂ C ∂ z l = δ l \frac{\partial C}{\partial \bm{z^l}}=\bm{\delta^l} ∂zl∂C=δ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} ∂Wjkl∂C=δjlakl−1∂bjl∂C=δjlδL=(∂aL∂C)⊙σ′L(zL)δl=((Wl+1)Tδl+1)⊙σ′l(zl)
也可以按照以下流程编程实现(图片来自Neural Networks and Deep Learning, Michael Nielsen ):
自此,我对于单个样本
x
\bm{x}
x的反向传播推导就告一段落,已经可以凭借以上内容,实现一个使用随机梯度下降算法来优化的DNN啦!