1. 前言
1986年,由 David E. Rumelhart, Geoffrey E. Hinton, 和 Ronald J. Williams 共同撰写论文 《Learning representations by back-propagating errors》,并发表于《Nature》杂志上。这篇论文是深度学习领域中的里程碑之作,它详细介绍了反向传播算法(Backpropagation)的原理和应用。
反向传播(Backpropagation)是训练人工神经网络中广泛使用的一种算法,主要用于计算和更新模型参数以最小化预测输出与实际目标之间的损失函数。在深度学习框架下,神经网络由多层组成,每一层包含多个节点(或称为神经元)。在前向传播阶段,输入数据通过一系列非线性变换在网络中逐层传递,并最终生成一个预测输出。
当网络给出预测后,我们需要根据预测值与实际标签的差异来调整网络中的权重和偏置,以便模型在未来能够更准确地进行预测。这个调整过程就是通过反向传播算法实现的:
误差计算: 首先,计算模型在当前迭代中的预测输出与真实目标之间的差距,这通常转化为对损失函数(如交叉熵、均方误差等)的计算。
梯度计算: 使用链式法则从输出层开始,按相反的顺序逐层计算每个中间变量和参数对于总损失函数的梯度。也就是说,计算损失相对于网络中每层权重和偏置的导数。
参数更新: 根据计算出的梯度,利用优化算法(如梯度下降法、动量法、Adam等)来更新网络的权重和偏置。更新的方向与梯度方向相反,目的是减少损失函数的值。
简单来说,反向传播的核心原理是将误差从输出层沿着网络层层往回传播,并根据传播回来的误差信号调整各层的权重,使得整个网络对输入数据的学习能力逐步提高。这一过程在每次迭代训练中反复进行,直到模型收敛或者达到预定的训练轮数为止。
以上这些概述相信大家都已经耳熟能详了,知其然更要知其所以然,本文结合 《How the backpropagation algorithm works》 这篇剖析反向传播算法的好文章,讲解其中的来龙去脉,其中有很多数学计算推导,强烈建议拿出纸和笔跟着文章一起算算,相信会有收获!
2. 神经网络的结构
假设一共有
L
L
L 层网络,激活函数为
σ
\sigma
σ,
z
j
l
z_j^l
zjl 表示未激活之前的状态,
a
j
l
a_j^l
ajl 表示激活之后的状态。
损失函数:
C
=
1
2
∣
∣
y
−
a
L
∣
∣
2
=
1
2
∑
k
=
1
K
(
y
k
−
a
k
L
)
2
C = \frac{1}{2} ||y - a^L||^2 =\frac{1}{2}\sum_{k=1}^{K}(y_k-a_k^L)^2
C=21∣∣y−aL∣∣2=21k=1∑K(yk−akL)2
损失函数对
a
j
L
a_j^L
ajL 的偏导数:
∂
C
∂
a
j
L
=
a
j
L
−
y
j
\frac {\partial C} {\partial a_j^L} = a_j^L - y_j
∂ajL∂C=ajL−yj
这里建议暂停一下,先找纸笔,把图中的结构自己画一画,把上面公式推导一遍,尤其是理解下标 j、k 是什么含义,这样理解更深~
那么我们要达到什么目标呢?比如某次计算的损失函数误差依然很大,那么需要将误差从输出层沿着网络层层往回传播,并根据传播回来的误差信号调整各层的权重 ω \omega ω 及偏置 b b b,逐步调整直到最终的输出误差满足要求!
这其实就是论文《Learning representations by back-propagating errors》的思想,我们需要计算损失函数 C C C 对 ω \omega ω 及偏置 b b b 的偏导数,即 ∂ C / ∂ ω j k l \partial C/\partial \omega_{jk}^l ∂C/∂ωjkl 以及 ∂ C / ∂ b j l \partial C/\partial b_{j}^l ∂C/∂bjl。如果对导数的含义清楚的话,应该就知道,导数表达的是变化情况,即表示 ω \omega ω 及 b b b 对最终的误差的影响,如果误差大了,我们可以通过反方向改变 ω \omega ω 及 b b b,使得误差降低。
一个神经网络涉及到很多的参数 ω \omega ω 及 b b b,所以对于大模型,参数达到 10B、100B(B表示十亿)甚至更多,那么训练起来自然会很慢,这个是后话了。
3. 反向传播的四个基本方程
在读下面的内容之前,一定要把上面的过程推导理解一遍。
我们希望能计算出任意的
∂
C
/
∂
ω
j
k
l
\partial C/\partial \omega_{jk}^l
∂C/∂ωjkl 以及
∂
C
/
∂
b
j
l
\partial C/\partial b_{j}^l
∂C/∂bjl,由于涉及到激活函数,有
a
j
l
=
σ
(
z
j
l
)
a_j^l = \sigma(z_j^l)
ajl=σ(zjl) 这一层关系,我们先定义一个中间变量
δ
j
l
\delta_j^l
δjl:
δ
j
l
≡
∂
C
∂
z
j
l
\delta_j^l ≡ \frac {\partial C} {\partial z_j^l}
δjl≡∂zjl∂C
你可能会问,为什么不直接计算
∂
C
/
∂
a
j
k
l
\partial C/\partial a_{jk}^l
∂C/∂ajkl,这个不必太纠结,定义这个是为了简化后面的计算,接着看后面的计算过程。
3.1 方程式1
最后一层(输出层),即 L 层,我们来计算对应的
δ
j
L
\delta_j^L
δjL:
δ
j
L
=
∂
C
∂
z
j
L
=
∂
C
∂
a
j
L
∂
a
j
L
∂
z
j
L
=
∂
C
∂
a
j
L
σ
′
(
z
j
L
)
=
(
a
j
L
−
y
j
)
σ
′
(
z
j
L
)
\delta_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} = \frac {\partial C} {\partial a_j^L} \sigma'(z_j^L) = (a_j^L - y_j)\sigma'(z_j^L)
δjL=∂zjL∂C=∂ajL∂C∂zjL∂ajL=∂ajL∂Cσ′(zjL)=(ajL−yj)σ′(zjL)
我们推广一下
δ
j
L
\delta_j^L
δjL 到
δ
L
\delta^L
δL,即取 j=1到K:
δ
L
=
[
∂
C
∂
a
1
L
σ
′
(
z
1
L
)
∂
C
∂
a
2
L
σ
′
(
z
2
L
)
.
.
.
∂
C
∂
a
K
L
σ
′
(
z
K
L
)
]
=
[
∂
C
∂
a
1
L
∂
C
∂
a
2
L
.
.
.
∂
C
∂
a
K
L
]
⊙
[
σ
′
(
z
1
L
)
σ
′
(
z
2
L
)
.
.
.
σ
′
(
z
K
L
)
]
=
∇
a
C
⊙
σ
′
(
z
L
)
\delta^L = \begin{bmatrix} \frac {\partial C} {\partial a_1^L} \sigma'(z_1^L) \\ \frac {\partial C} {\partial a_2^L} \sigma'(z_2^L) \\ ...\\ \frac {\partial C} {\partial a_K^L} \sigma'(z_K^L) \end{bmatrix} =\begin{bmatrix} \frac {\partial C} {\partial a_1^L} \\ \frac {\partial C} {\partial a_2^L} \\ ...\\ \frac {\partial C} {\partial a_K^L} \end{bmatrix} \odot \begin{bmatrix} \sigma'(z_1^L) \\ \sigma'(z_2^L) \\ ...\\ \sigma'(z_K^L) \end{bmatrix} = \nabla_aC \odot \sigma'(z^L)
δL=
∂a1L∂Cσ′(z1L)∂a2L∂Cσ′(z2L)...∂aKL∂Cσ′(zKL)
=
∂a1L∂C∂a2L∂C...∂aKL∂C
⊙
σ′(z1L)σ′(z2L)...σ′(zKL)
=∇aC⊙σ′(zL)
3.2 方程式2
那么接下来继续计算对于 L-1 层:
δ
j
L
−
1
=
∂
C
∂
z
j
L
−
1
=
∂
C
∂
a
j
L
−
1
∂
a
j
L
−
1
∂
z
j
L
−
1
=
∂
C
∂
a
j
L
−
1
σ
′
(
z
j
L
−
1
)
\delta_j^{L-1} = \frac {\partial C} {\partial z_j^{L-1}} = \frac {\partial C} {\partial a_j^{L-1}} \frac {\partial a_j^{L-1}} {\partial z_j^{L-1}} = \frac {\partial C} {\partial a_j^{L-1}} \sigma'(z_j^{L-1})
δjL−1=∂zjL−1∂C=∂ajL−1∂C∂zjL−1∂ajL−1=∂ajL−1∂Cσ′(zjL−1)
相信到这里,大家都没有问题,那么
∂
C
∂
a
j
L
−
1
\frac {\partial C} {\partial a_j^{L-1}}
∂ajL−1∂C 如何计算呢?我们先来看下面这张图,假设我们需要计算
∂
C
∂
a
1
L
−
1
\frac {\partial C} {\partial a_1^{L-1}}
∂a1L−1∂C,那么可以看到红色路径都是对会最终结果的影响,也就是说我们把 C 看出是
(
z
1
L
z
2
L
,
.
.
.
,
z
K
L
)
(z_{1}^{L} z_{2}^{L},...,z_{K}^{L})
(z1Lz2L,...,zKL) 的函数,那么:
∂
C
∂
a
1
L
−
1
=
∂
C
(
z
1
L
z
2
L
,
.
.
.
,
z
K
L
)
∂
a
1
L
−
1
=
∂
C
∂
z
1
L
∂
z
1
L
∂
a
1
L
−
1
+
.
.
.
+
∂
C
∂
z
K
L
∂
z
K
L
∂
a
1
L
−
1
=
∑
k
=
1
K
∂
C
∂
z
k
L
∂
z
k
L
∂
a
1
L
−
1
\frac {\partial C} {\partial a_1^{L-1}} = \frac {\partial C(z_{1}^{L} z_{2}^{L},...,z_{K}^{L})} {\partial a_1^{L-1}} = \frac {\partial C} {\partial z_1^{L}} \frac {\partial z_1^L} {\partial a_1^{L-1}} + ...+ \frac {\partial C} {\partial z_K^{L}} \frac {\partial z_K^L} {\partial a_1^{L-1}} = \sum_{k=1}^K \frac {\partial C} {\partial z_k^{L}} \frac {\partial z_k^L} {\partial a_1^{L-1}}
∂a1L−1∂C=∂a1L−1∂C(z1Lz2L,...,zKL)=∂z1L∂C∂a1L−1∂z1L+...+∂zKL∂C∂a1L−1∂zKL=k=1∑K∂zkL∂C∂a1L−1∂zkL
根据第二章:
∂
z
k
L
∂
a
1
L
−
1
=
ω
k
1
L
\frac {\partial z_k^L} {\partial a_1^{L-1}} = \omega_{k1}^L
∂a1L−1∂zkL=ωk1L
同时根据定义:
∂
C
∂
z
k
L
=
δ
k
L
\frac {\partial C} {\partial z_k^{L}} = \delta_k^L
∂zkL∂C=δkL
代入得到:
∂
C
∂
a
1
L
−
1
=
∑
k
=
1
K
∂
C
∂
z
k
L
∂
z
k
L
∂
a
1
L
−
1
=
∑
k
=
1
K
ω
k
1
L
δ
k
L
\frac {\partial C} {\partial a_1^{L-1}} = \sum_{k=1}^K \frac {\partial C} {\partial z_k^{L}} \frac {\partial z_k^L} {\partial a_1^{L-1}} = \sum_{k=1}^K \omega_{k1}^L \delta_k^L
∂a1L−1∂C=k=1∑K∂zkL∂C∂a1L−1∂zkL=k=1∑Kωk1LδkL
同理,对于其他:
∂
C
∂
a
j
L
−
1
=
∑
k
=
1
K
ω
k
j
L
δ
k
L
\frac {\partial C} {\partial a_j^{L-1}} = \sum_{k=1}^K \omega_{kj}^L \delta_k^L
∂ajL−1∂C=k=1∑KωkjLδkL
还记得我们要计算
δ
j
L
−
1
\delta_j^{L-1}
δjL−1 吗,直接代入得到:
δ
j
L
−
1
=
∂
C
∂
a
j
L
−
1
σ
′
(
z
j
L
−
1
)
=
(
∑
k
=
1
K
ω
k
j
L
δ
k
L
)
∗
σ
′
(
z
j
L
−
1
)
\delta_j^{L-1} = \frac {\partial C} {\partial a_j^{L-1}} \sigma'(z_j^{L-1}) = (\sum_{k=1}^K \omega_{kj}^L \delta_k^L) * \sigma'(z_j^{L-1})
δjL−1=∂ajL−1∂Cσ′(zjL−1)=(k=1∑KωkjLδkL)∗σ′(zjL−1)
好了,那么对于任意 j 层,则有:
δ
j
l
=
∂
C
∂
a
j
l
σ
′
(
z
j
l
)
=
(
∑
k
=
1
K
ω
k
j
l
+
1
δ
k
l
+
1
)
∗
σ
′
(
z
j
l
)
\delta_j^{l} = \frac {\partial C} {\partial a_j^{l}} \sigma'(z_j^{l}) = (\sum_{k=1}^K \omega_{kj}^{l+1} \delta_k^{l+1}) * \sigma'(z_j^{l})
δjl=∂ajl∂Cσ′(zjl)=(k=1∑Kωkjl+1δkl+1)∗σ′(zjl)
同样的,我们推广一下
δ
j
l
\delta_j^l
δjl 到
δ
l
\delta^l
δl,即取 j=1到K:
δ
j
l
=
[
(
∑
k
=
1
K
ω
k
1
l
+
1
δ
k
l
+
1
)
σ
′
(
z
1
l
)
(
∑
k
=
1
K
ω
k
2
l
+
1
δ
k
l
+
1
)
σ
′
(
z
2
l
)
.
.
.
(
∑
k
=
1
K
ω
k
K
l
+
1
δ
k
l
+
1
)
σ
′
(
z
K
l
)
]
=
(
(
ω
l
+
1
)
T
δ
l
+
1
)
⊙
σ
′
(
z
l
)
\delta_j^{l} = \begin{bmatrix} (\sum_{k=1}^K \omega_{k1}^{l+1} \delta_k^{l+1}) \sigma'(z_1^l) \\ (\sum_{k=1}^K \omega_{k2}^{l+1} \delta_k^{l+1}) \sigma'(z_2^l) \\ ...\\ (\sum_{k=1}^K \omega_{kK}^{l+1} \delta_k^{l+1}) \sigma'(z_K^l) \end{bmatrix} = ((\omega^{l+1})^T\delta^{l+1}) \odot \sigma'(z^l)
δjl=
(∑k=1Kωk1l+1δkl+1)σ′(z1l)(∑k=1Kωk2l+1δkl+1)σ′(z2l)...(∑k=1KωkKl+1δkl+1)σ′(zKl)
=((ωl+1)Tδl+1)⊙σ′(zl)
3.3 方程式3
∂ C ∂ b j l = ∂ C ∂ z j l ∂ z j l ∂ b j l = ∂ C ∂ z j l = δ 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} = \frac {\partial C}{\partial z_j^l} = \delta_j^l ∂bjl∂C=∂zjl∂C∂bjl∂zjl=∂zjl∂C=δjl
ps: 在第二章有 z j l = ∑ k − 1 K ω j k l a k l − 1 + b j l z_j^l = \sum_{k-1}^K \omega_{jk}^la_k^{l-1} + b_j^l zjl=∑k−1Kωjklakl−1+bjl
3.4 方程式4
∂ C ∂ ω j k l = ∂ C ∂ z j l ∂ z j l ∂ ω j k l = δ j l a k l − 1 \frac {\partial C}{\partial \omega_{jk}^l} = \frac {\partial C}{\partial z_j^l} \frac {\partial z_j^l}{\partial \omega_{jk}^l} = \delta_j^l a_k^{l-1} ∂ωjkl∂C=∂zjl∂C∂ωjkl∂zjl=δjlakl−1
3.5 总结
方向传播是为了根据输出误差 C 来调整参数,涉及反向传播的四个基本方程式:
δ
L
=
∇
a
C
⊙
σ
′
(
z
L
)
\delta^L = \nabla_aC \odot \sigma'(z^L)
δL=∇aC⊙σ′(zL)
δ
j
l
=
(
(
ω
l
+
1
)
T
δ
l
+
1
)
⊙
σ
′
(
z
l
)
\delta_j^{l} = ((\omega^{l+1})^T\delta^{l+1}) \odot \sigma'(z^l)
δjl=((ωl+1)Tδl+1)⊙σ′(zl)
∂
C
∂
b
j
l
=
δ
j
l
\frac {\partial C}{\partial b_j^l} = \delta_j^l
∂bjl∂C=δjl
∂
C
∂
ω
j
k
l
=
δ
j
l
a
k
l
−
1
\frac {\partial C}{\partial \omega_{jk}^l} = \delta_j^l a_k^{l-1}
∂ωjkl∂C=δjlakl−1
4. 参考
《Learning representations by back-propagating errors》
《How the backpropagation algorithm works》
欢迎关注本人,我是喜欢搞事的程序猿; 一起进步,一起学习;
也欢迎关注我的wx公众号:一个比特定乾坤