AI 课程网站首页
返回知识库

反向传播:一次反向计算得到全部梯度

矩阵形式的链式法则、它的运算量与内存开销,以及它对初始化的启示。

本篇目录
  1. 为什么需要一个算法
  2. 一条函数链
  3. 一般规则:计算图
  4. 矩阵形式的反向传播
  5. 批量、运算量与内存
  6. 反向计算的起点:损失
  7. 自动微分
  8. 反向计算告诉我们的深层网络训练问题
  9. 小结
  10. 注释
  11. 练习
  12. 参考文献

梯度下降需要损失对每个参数的导数(第 5 篇)。第 5 篇手工算出了只有一个隐藏单元的网络的导数,而第 7 篇引入的深层网络动辄有几十万个参数。本文完整推导一次算出全部导数的算法——反向传播。我们先用具体数字在一条标量函数链上运行它,再给出适用于任何计算的一般规则,然后推导深层网络的矩阵形式并做一个算例。接着我们计算它的运算量和内存开销,说明反向计算如何从损失开始,把它放到自动微分的框架中,最后用反向传播的方程解释:为什么初始权重的尺度决定了深层网络能否训练。

1. 为什么需要一个算法

估计导数最简单的办法,是把参数挪动一点,看损失变化多少。中心差分

∂ℓ∂ϕ≈ℓ(ϕ+ϵ)−ℓ(ϕ−ϵ)2ϵ(8.1)\frac{\partial \ell}{\partial \phi} \approx \frac{\ell(\phi + \epsilon) - \ell(\phi - \epsilon)}{2\epsilon} \tag{8.1}

对每个参数需要计算两次损失,每次都是一遍完整的前向计算。第 7 篇中 (784,256,256,10)(784, 256, 256, 10) 的分类器有 269,322 个参数,算一次梯度就要做 538,644 次前向计算。而梯度下降要走成千上万步,这完全不可行。

两个观察指向更好的方法。第一,权重乘的是激活值,所以它对损失的影响与那个激活值成正比:激活值为零时,改变权重毫无作用;激活值很大时,权重的微小变化影响很大。第二,前面某层的参数只能通过它之后的所有层影响损失。所有前面的参数共享同一条下游路径,所以这条路径上的导数可以只算一次、反复使用。反向传播同时利用了这两点:它从损失出发、反向计算损失对各中间值的导数,然后用每个参数所作用的中间值和它所乘的激活值,直接读出该参数的导数。

2. 一条函数链

我们从一个由标量运算串成的模型开始,这是包含反向传播全部步骤的最小情形。网络版本留到第 4 节。模型只有一个输入 xx,它计算:

f0=β0+ω0x,h1=sin⁡(f0),f1=β1+ω1h1,h2=exp⁡(f1),f2=β2+ω2h2,h3=cos⁡(f2),f3=β3+ω3h3,ℓ=(f3−y)2(8.2)\begin{aligned} f_0 &= \beta_0 + \omega_0 x, & h_1 &= \sin(f_0), \\ f_1 &= \beta_1 + \omega_1 h_1, & h_2 &= \exp(f_1), \\ f_2 &= \beta_2 + \omega_2 h_2, & h_3 &= \cos(f_2), \\ f_3 &= \beta_3 + \omega_3 h_3, & \ell &= (f_3 - y)^2 \end{aligned} \tag{8.2}

它的结构就是每层只有一个单元的深层网络。每个 fkf_k 是带偏置 βk\beta_k 和权重 ωk\omega_k(读作“omega”)的仿射函数,每个 hk+1h_{k+1} 对它施加一个非线性函数。这里用三个不同的非线性函数代替 ReLU,是为了让推导的每一步看起来都不一样。最后一行是单个训练样本 (x,y)(x, y) 的平方误差。模型共有八个参数。

取 x=0.6x = 0.6、y=0.4y = 0.4,参数值为 β0=0.1\beta_0 = 0.1、ω0=1.2\omega_0 = 1.2、β1=−0.3\beta_1 = -0.3、ω1=0.8\omega_1 = 0.8、β2=0.2\beta_2 = 0.2、ω2=0.5\omega_2 = 0.5、β3=0.1\beta_3 = 0.1、ω3=1.5\omega_3 = 1.5。

前向计算从上到下计算式 8.2,并保存每个中间值:f0=0.1+1.2×0.6=0.82f_0 = 0.1 + 1.2 \times 0.6 = 0.82,h1=sin⁡0.82=0.7311h_1 = \sin 0.82 = 0.7311,f1=−0.3+0.8×0.7311=0.2849f_1 = -0.3 + 0.8 \times 0.7311 = 0.2849,h2=e0.2849=1.3297h_2 = e^{0.2849} = 1.3297,f2=0.8648f_2 = 0.8648,h3=cos⁡0.8648=0.6488h_3 = \cos 0.8648 = 0.6488,f3=1.0732f_3 = 1.0732,最后 ℓ=(1.0732−0.4)2=0.4531\ell = (1.0732 - 0.4)^2 = 0.4531。

反向计算按相反的顺序,算出损失对每个中间值的导数。我们从末端开始:

∂ℓ∂f3=2(f3−y)=1.3463\frac{\partial \ell}{\partial f_3} = 2(f_3 - y) = 1.3463

h3h_3 只通过 f3=β3+ω3h3f_3 = \beta_3 + \omega_3 h_3 影响损失,h3h_3 每增加一个单位,f3f_3 增加 ω3\omega_3。由链式法则:

∂ℓ∂h3=∂f3∂h3 ∂ℓ∂f3=ω3×1.3463=2.0195\frac{\partial \ell}{\partial h_3} = \frac{\partial f_3}{\partial h_3}\,\frac{\partial \ell}{\partial f_3} = \omega_3 \times 1.3463 = 2.0195

接着,f2f_2 只通过 h3=cos⁡f2h_3 = \cos f_2 影响损失,后者的导数是 −sin⁡f2-\sin f_2:

∂ℓ∂f2=∂h3∂f2 ∂ℓ∂h3=−sin⁡(0.8648)×2.0195=−1.5368\frac{\partial \ell}{\partial f_2} = \frac{\partial h_3}{\partial f_2}\,\frac{\partial \ell}{\partial h_3} = -\sin(0.8648) \times 2.0195 = -1.5368

沿着链往回,规律不断重复。每一步都把上一步算出的导数乘以一个局部导数,即单个运算对其输入的导数:

∂ℓ∂hk=ωk∂ℓ∂fk,∂ℓ∂fk−1=ak′(fk−1) ∂ℓ∂hk(8.3)\frac{\partial \ell}{\partial h_k} = \omega_k \frac{\partial \ell}{\partial f_k}, \qquad \frac{\partial \ell}{\partial f_{k-1}} = a_k'(f_{k-1})\,\frac{\partial \ell}{\partial h_k} \tag{8.3}

其中 aka_k 是第 kk 步的非线性函数(这里是 sin、exp 或 cos)。用文字说:反向越过一个仿射步骤时,乘以它的权重;反向越过一个非线性步骤时,乘以该函数在前向计算中所收到的那个值处的斜率。第二条规则正是前向计算要保存 fkf_k 的原因。继续下去得到 ∂ℓ/∂h2=−0.7684\partial\ell/\partial h_2 = -0.7684,∂ℓ/∂f1=e0.2849×(−0.7684)=−1.0217\partial\ell/\partial f_1 = e^{0.2849} \times (-0.7684) = -1.0217,∂ℓ/∂h1=−0.8174\partial\ell/\partial h_1 = -0.8174,∂ℓ/∂f0=cos⁡(0.82)×(−0.8174)=−0.5576\partial\ell/\partial f_0 = \cos(0.82) \times (-0.8174) = -0.5576。图 8.1 列出了全部数值。

一排九个方框,依次标为 x、f0、h1、f1、h2、f2、h3、f3 和损失,由向右的箭头相连,每个箭头上方写着它执行的运算。每个方框下方有两个数:深蓝色的是前向值,橙色的是损失的导数,并有向左的橙色箭头。

图 8.1 函数链上的前向与反向计算。 式 8.2 的模型,x=0.6x = 0.6,y=0.4y = 0.4。前向计算(深色箭头)依次执行箭头上方的运算,并保存结果(深色数字)。反向计算(橙色箭头)从 ∂ℓ/∂ℓ=1\partial\ell/\partial\ell = 1 开始,每一步乘以一个局部导数(式 8.3),得到损失对每个中间值的导数(橙色数字)。

参数的导数几乎是顺手得到的。偏置 βk\beta_k 只通过 fkf_k 起作用,局部导数为 1;权重 ωk\omega_k 通过 fkf_k 起作用,并乘以 hkh_k(约定 h0=xh_0 = x):

∂ℓ∂βk=∂ℓ∂fk,∂ℓ∂ωk=hk∂ℓ∂fk(8.4)\frac{\partial \ell}{\partial \beta_k} = \frac{\partial \ell}{\partial f_k}, \qquad \frac{\partial \ell}{\partial \omega_k} = h_k \frac{\partial \ell}{\partial f_k} \tag{8.4}

这正是第 1 节的两个观察写成的式子:权重的导数,等于它作用处的导数乘以它所乘的激活值。代入我们的数值:

kk ∂ℓ/∂βk\partial\ell/\partial\beta_k ∂ℓ/∂ωk\partial\ell/\partial\omega_k
0 −0.5576-0.5576 0.6×(−0.5576)=−0.33460.6 \times (-0.5576) = -0.3346
1 −1.0217-1.0217 0.7311×(−1.0217)=−0.74700.7311 \times (-1.0217) = -0.7470
2 −1.5368-1.5368 1.3297×(−1.5368)=−2.04341.3297 \times (-1.5368) = -2.0434
3 1.34631.3463 0.6488×1.3463=0.87350.6488 \times 1.3463 = 0.8735

这八个值与 ϵ=10−6\epsilon = 10^{-6} 的中心差分(式 8.1)至少在小数点后六位一致。反向计算只用了十来次乘法,大致相当于一次前向计算的工作量;而中心差分需要十六次前向计算。

3. 一般规则:计算图

在函数链中,每个值到损失只有一条路径。真实网络是有分叉的:一个隐藏单元会送往下一层的每个单元,一个值也可能被使用多次。一般规则可以处理这种情况。

任何计算都可以画成一张计算图:每个节点是一个值,从节点 uu 指向节点 vv 的箭头表示 vv 是直接由 uu(以及其他输入)算出的。如果 uu 被几个节点 v1,…,vmv_1, \ldots, v_m 使用,uu 的微小变化会改变所有这些节点,它们对损失的影响相加。多元链式法则给出:

∂ℓ∂u=∑j=1m∂vj∂u ∂ℓ∂vj(8.5)\frac{\partial \ell}{\partial u} = \sum_{j=1}^{m} \frac{\partial v_j}{\partial u}\,\frac{\partial \ell}{\partial v_j} \tag{8.5}

这个式子可以分成两部分。导数 ∂ℓ/∂vj\partial\ell/\partial v_j 属于图中更靠后的节点,只要我们从损失开始反向处理节点,它们就已经算好了。因子 ∂vj/∂u\partial v_j / \partial u 是局部的:每个只取决于计算 vjv_j 的那一个运算,在保存的前向值处求值。用文字说:损失对一个值的导数,等于所有使用这个值的节点的导数,乘以各自的局部导数,再求和。

一个很小的例子可以说明这个求和。设 u=3u = 3 被两个运算使用,v1=2u=6v_1 = 2u = 6,v2=u2=9v_2 = u^2 = 9,并令 ℓ=v1+v2=15\ell = v_1 + v_2 = 15。于是 ∂ℓ/∂v1=∂ℓ/∂v2=1\partial\ell/\partial v_1 = \partial\ell/\partial v_2 = 1,由式 8.5 得 ∂ℓ/∂u=2×1+2u×1=8\partial\ell/\partial u = 2 \times 1 + 2u \times 1 = 8。直接把损失写成 ℓ=2u+u2\ell = 2u + u^2,导数 2+2u=82 + 2u = 8,结果相同。手写梯度时常见的错误是漏掉其中一条路径,那样会得到 2 或 6。

于是算法就是:做前向计算,保存每个节点的值;令 ∂ℓ/∂ℓ=1\partial\ell/\partial\ell = 1;按计算顺序的逆序访问各节点,对每个节点使用式 8.5。图中每条箭头在两个方向上各用一次,所以无论有多少参数,反向计算的开销都只是前向计算的一个小常数倍。这就是反向传播的全部。本文其余部分把它具体用于深层网络,并考察它带来的结论。

4. 矩阵形式的反向传播

现在把一般规则用于第 7 篇的深层网络(式 7.4)。为方便起见,我们给预激活命名。令 h0=x\mathbf{h}_0 = \mathbf{x},对 k=0,…,Kk = 0, \ldots, K,前向计算为:

fk=βk+Ωkhk,hk+1=a(fk)  (k<K)(8.6)\mathbf{f}_k = \boldsymbol{\beta}_k + \boldsymbol{\Omega}_k \mathbf{h}_k, \qquad \mathbf{h}_{k+1} = a(\mathbf{f}_k) \ \ (k < K) \tag{8.6}

输出是 fK\mathbf{f}_K。我们把第 kk 层的误差信号定义为损失对该层预激活的导数 δk=∂ℓ/∂fk\boldsymbol{\delta}_k = \partial\ell/\partial\mathbf{f}_k,它是有 Dk+1D_{k+1} 个元素的向量。

参数。 fk\mathbf{f}_k 的第 dd 个元素是 fk,d=βk,d+∑jΩk,djhk,jf_{k,d} = \beta_{k,d} + \sum_j \Omega_{k,dj} h_{k,j}。权重 Ωk,dj\Omega_{k,dj} 只出现在这个元素中,并乘以 hk,jh_{k,j},所以 ∂ℓ/∂Ωk,dj=δk,d hk,j\partial\ell/\partial\Omega_{k,dj} = \delta_{k,d}\,h_{k,j},与式 8.4 完全一样。把所有 dd 和 jj 合在一起:

∂ℓ∂βk=δk,∂ℓ∂Ωk=δkhk⊤(8.7)\frac{\partial \ell}{\partial \boldsymbol{\beta}_k} = \boldsymbol{\delta}_k, \qquad \frac{\partial \ell}{\partial \boldsymbol{\Omega}_k} = \boldsymbol{\delta}_k \mathbf{h}_k^\top \tag{8.7}

权重梯度是一个外积:有 Dk+1D_{k+1} 个元素的列向量乘以有 DkD_k 个元素的行向量,得到 Dk+1×DkD_{k+1} \times D_k 矩阵,与 Ωk\boldsymbol{\Omega}_k 形状相同。用文字说:从单元 jj 到单元 dd 的权重,其梯度等于单元 dd 的误差信号乘以单元 jj 的激活值。

反向越过权重矩阵。 hk\mathbf{h}_k 的单元 jj 被 fk\mathbf{f}_k 的每个元素使用,局部导数为 ∂fk,d/∂hk,j=Ωk,dj\partial f_{k,d}/\partial h_{k,j} = \Omega_{k,dj}。式 8.5 对这些使用求和:∂ℓ/∂hk,j=∑dΩk,dj δk,d\partial\ell/\partial h_{k,j} = \sum_d \Omega_{k,dj}\,\delta_{k,d}。这个和沿着 Ωk\boldsymbol{\Omega}_k 的第 jj 列进行,所以写成矩阵形式就是乘以转置:

∂ℓ∂hk=Ωk⊤δk(8.8)\frac{\partial \ell}{\partial \mathbf{h}_k} = \boldsymbol{\Omega}_k^\top \boldsymbol{\delta}_k \tag{8.8}

形状正确:(Dk×Dk+1)(D_k \times D_{k+1}) 乘以 Dk+1D_{k+1} 个元素,得到 DkD_k 个元素,hk\mathbf{h}_k 的每个单元一个。前向计算乘以 Ωk\boldsymbol{\Omega}_k,反向计算乘以 Ωk⊤\boldsymbol{\Omega}_k^\top,把每个误差信号沿着同样的连接送回去。

反向越过激活函数。 hk\mathbf{h}_k 的第 jj 个元素只依赖 fk−1\mathbf{f}_{k-1} 的第 jj 个元素,所以局部导数就是激活函数在那里的斜率:

δk−1=a′(fk−1)⊙∂ℓ∂hk(8.9)\boldsymbol{\delta}_{k-1} = a'(\mathbf{f}_{k-1}) \odot \frac{\partial \ell}{\partial \mathbf{h}_k} \tag{8.9}

其中 ⊙\odot 表示两个向量逐元素相乘。对 ReLU,预激活为正处斜率为 1,为负处斜率为 0,所以式 8.9 就是把每个未激活单元的误差信号置零。(恰好为零处导数没有定义,深度学习库取 0。)

式 8.7–8.9 合在一起,就是深层网络的反向计算:

  1. 做前向计算(式 8.6),保存每个 hk\mathbf{h}_k 和 fk\mathbf{f}_k(对 ReLU,只需保存 fk\mathbf{f}_k 的符号)。
  2. 由损失算出 δK=∂ℓ/∂fK\boldsymbol{\delta}_K = \partial\ell/\partial\mathbf{f}_K(第 6 节)。
  3. 对 k=K,K−1,…,0k = K, K-1, \ldots, 0:用式 8.7 算出第 kk 层的参数梯度;若 k>0k > 0,再用式 8.8 和 8.9 算出 δk−1\boldsymbol{\delta}_{k-1}。

我们来算一个各层大小为 (2,2,2,1)(2, 2, 2, 1) 的网络,它有 15 个参数,使用 ReLU 激活和平方误差。输入为 x=(1,2)⊤\mathbf{x} = (1, 2)^\top,目标为 y=1y = 1,参数为:

Ω0=[1−10.51], β0=[0−1],Ω1=[12−1−1], β1=[0.50],Ω2=[0.5−1], β2=0.5\boldsymbol{\Omega}_0 = \begin{bmatrix} 1 & -1 \\ 0.5 & 1 \end{bmatrix},\ \boldsymbol{\beta}_0 = \begin{bmatrix} 0 \\ -1 \end{bmatrix},\quad \boldsymbol{\Omega}_1 = \begin{bmatrix} 1 & 2 \\ -1 & -1 \end{bmatrix},\ \boldsymbol{\beta}_1 = \begin{bmatrix} 0.5 \\ 0 \end{bmatrix},\quad \boldsymbol{\Omega}_2 = \begin{bmatrix} 0.5 & -1 \end{bmatrix},\ \boldsymbol{\beta}_2 = 0.5

前向计算得到 f0=(1−2, −1+0.5+2)=(−1,1.5)\mathbf{f}_0 = (1 - 2,\ -1 + 0.5 + 2) = (-1, 1.5),所以 h1=(0,1.5)\mathbf{h}_1 = (0, 1.5):第 1 层的第一个单元未激活。接着 f1=(0.5+3, −1.5)=(3.5,−1.5)\mathbf{f}_1 = (0.5 + 3,\ -1.5) = (3.5, -1.5),h2=(3.5,0)\mathbf{h}_2 = (3.5, 0):第 2 层的第二个单元未激活。输出为 f2=0.5+0.5×3.5=2.25f_2 = 0.5 + 0.5 \times 3.5 = 2.25,损失为 (2.25−1)2=1.5625(2.25 - 1)^2 = 1.5625。

反向计算从 δ2=2(2.25−1)=2.5\delta_2 = 2(2.25 - 1) = 2.5 开始。由式 8.7 得 ∂ℓ/∂Ω2=2.5×(3.5,0)=(8.75,0)\partial\ell/\partial\boldsymbol{\Omega}_2 = 2.5 \times (3.5, 0) = (8.75, 0)。由式 8.8 得 ∂ℓ/∂h2=Ω2⊤×2.5=(1.25,−2.5)\partial\ell/\partial\mathbf{h}_2 = \boldsymbol{\Omega}_2^\top \times 2.5 = (1.25, -2.5),式 8.9 把未激活的第二个元素置零:δ1=(1.25,0)\boldsymbol{\delta}_1 = (1.25, 0)。再往回一层:

∂ℓ∂Ω1=[1.250][01.5]=[01.87500],∂ℓ∂h1=Ω1⊤[1.250]=[1.252.5]\frac{\partial \ell}{\partial \boldsymbol{\Omega}_1} = \begin{bmatrix} 1.25 \\ 0 \end{bmatrix} \begin{bmatrix} 0 & 1.5 \end{bmatrix} = \begin{bmatrix} 0 & 1.875 \\ 0 & 0 \end{bmatrix}, \qquad \frac{\partial \ell}{\partial \mathbf{h}_1} = \boldsymbol{\Omega}_1^\top \begin{bmatrix} 1.25 \\ 0 \end{bmatrix} = \begin{bmatrix} 1.25 \\ 2.5 \end{bmatrix}

把未激活的第一个单元置零,得到 δ0=(0,2.5)\boldsymbol{\delta}_0 = (0, 2.5),所以 ∂ℓ/∂Ω0=δ0x⊤\partial\ell/\partial\boldsymbol{\Omega}_0 = \boldsymbol{\delta}_0\mathbf{x}^\top 的两行分别是 (0,0)(0, 0) 和 (2.5,5)(2.5, 5)。全部 15 个导数都与中心差分一致。图 8.2 把这些值标在网络上。

一个网络示意图:两个输入、两个各有两个单元的隐藏层、一个输出,最后是损失。每个单元标有预激活、激活值和误差信号。每个隐藏层中都有一个单元呈灰色并标为未激活,进出它的连接是虚线。

图 8.2 在一个小 ReLU 网络中做反向传播。 各层大小为 (2,2,2,1)(2, 2, 2, 1),输入 x=(1,2)\mathbf{x} = (1, 2),目标 y=1y = 1,参数见第 4 节。每个单元标出前向计算得到的预激活 ff 和激活值 hh(深色),以及反向计算得到的误差信号 δ=∂ℓ/∂f\delta = \partial\ell/\partial f(橙色)。灰色单元未激活(f<0f < 0):它们不向前传递任何东西,误差信号为零,对这个样本而言,进出它们的所有权重梯度都为零(虚线)。

这个例子显示了两个普遍成立的性质。第一,对这个样本,未激活的 ReLU 单元在两个方向上都阻断梯度:它的输入权重得到 δ=0\delta = 0,它的输出权重乘的是 h=0h = 0。如果一个单元对所有训练样本都不激活,它就永远不会改变,这称为死亡 ReLU。第二,单个样本的权重梯度 δkhk⊤\boldsymbol{\delta}_k\mathbf{h}_k^\top 的秩为一。一批样本的梯度是这些外积之和,秩通常更高,但不超过批大小。

5. 批量、运算量与内存

训练使用一批 BB 个样本和平均损失 L=1B∑iℓiL = \frac{1}{B}\sum_i \ell_i(第 5 篇)。每个样本各有一次前向和反向计算,LL 的梯度是各样本梯度的平均。像第 7 篇那样把一批的激活值作为各列,Hk∈RDk×B\mathbf{H}_k \in \mathbb{R}^{D_k \times B},并把误差信号作为 Δk∈RDk+1×B\boldsymbol{\Delta}_k \in \mathbb{R}^{D_{k+1} \times B} 的各列,那么式 8.7 中外积对样本的求和就是一次矩阵乘法:

∂L∂Ωk=1B ΔkHk⊤,∂L∂βk=1B Δk1(8.10)\frac{\partial L}{\partial \boldsymbol{\Omega}_k} = \frac{1}{B}\,\boldsymbol{\Delta}_k \mathbf{H}_k^\top, \qquad \frac{\partial L}{\partial \boldsymbol{\beta}_k} = \frac{1}{B}\,\boldsymbol{\Delta}_k \mathbf{1} \tag{8.10}

形状为 (Dk+1×B)(B×Dk)=Dk+1×Dk(D_{k+1} \times B)(B \times D_k) = D_{k+1} \times D_k:这个乘积对批维度求和。式 8.8 变成 Ωk⊤Δk\boldsymbol{\Omega}_k^\top\boldsymbol{\Delta}_k,整批只需一次乘法。

运算量。 每一层的前向计算做一次矩阵乘法 ΩkHk\boldsymbol{\Omega}_k\mathbf{H}_k,需要 Dk+1DkBD_{k+1} D_k B 次乘加。反向计算做两次同样大小的乘法:ΔkHk⊤\boldsymbol{\Delta}_k\mathbf{H}_k^\top 求权重梯度,Ωk⊤Δk\boldsymbol{\Omega}_k^\top\boldsymbol{\Delta}_k 继续往回传。第一层可以省掉第二次乘法,因为没有谁需要 ∂ℓ/∂x\partial\ell/\partial\mathbf{x}。所以反向计算的开销约为前向计算的两倍,一个训练步约为三倍。对 (784,256,256,10)(784, 256, 256, 10) 分类器,每个样本前向需要 268,800 次乘加,反向需要 336,896 次;比值只有 1.25,因为很大的第一层省掉了它的第二次乘法。

内存。 式 8.10 需要 Hk\mathbf{H}_k,式 8.9 需要 fk−1\mathbf{f}_{k-1} 的符号,它们都来自前向计算。因此训练必须为整批样本保留每一层的激活值,直到反向计算到达该层,所需内存与 B∑kDkB \sum_k D_k 成正比。预测时没有反向计算,每层的激活值在下一层算完后就可以丢弃。这就是训练比预测需要多得多的内存、批大小常常受内存限制的原因。梯度检查点(gradient checkpointing)[7] 用计算换内存:它只保存部分层的激活值,在反向计算时重新算出其余各层;对 KK 层的链,它把激活值内存降到大约 K\sqrt{K} 层的量,代价约为多做一次前向计算。

6. 反向计算的起点:损失

反向计算从 δK=∂ℓ/∂fK\boldsymbol{\delta}_K = \partial\ell/\partial\mathbf{f}_K 开始,它取决于损失。

平方误差。 对 ℓ=∑k(fK,k−yk)2\ell = \sum_k (f_{K,k} - y_k)^2,导数为 δK=2(fK−y)\boldsymbol{\delta}_K = 2(\mathbf{f}_K - \mathbf{y}):就是误差本身的两倍。如果损失是 II 个样本的平均,每个样本的信号再除以 II。

softmax 与交叉熵。 做分类时,网络输出分数 z=fK\mathbf{z} = \mathbf{f}_K,softmax 把它们变成概率 pk=ezk/∑jezjp_k = e^{z_k}/\sum_j e^{z_j},损失是正确类别 cc 的 ℓ=−log⁡pc\ell = -\log p_c(第 4 篇)。代入 softmax,并利用 log⁡(ezc)=zc\log(e^{z_c}) = z_c:

ℓ=−zc+log⁡∑jezj\ell = -z_c + \log \sum_{j} e^{z_j}

其中 jj 遍历所有类别。第一项只依赖 zcz_c:对 k=ck = c 导数为 −1-1,否则为 0。第二项对 zkz_k 的导数是 ezk/∑jezj=pke^{z_k}/\sum_j e^{z_j} = p_k。合起来:

∂ℓ∂zk=pk−1[k=c](8.11)\frac{\partial \ell}{\partial z_k} = p_k - \mathbb{1}[k = c] \tag{8.11}

其中 1[k=c]\mathbb{1}[k = c] 在正确类别处为 1,其他处为 0。用文字说:误差信号就是预测概率减去目标概率。对第 4 篇的分数 z=(2.0,0.5,−1.0)\mathbf{z} = (2.0, 0.5, -1.0),概率为 (0.786,0.175,0.039)(0.786, 0.175, 0.039);如果照片是猫,误差信号为 (−0.214,0.175,0.039)(-0.214, 0.175, 0.039)。梯度下降逆着它走:提高猫的分数、降低另外两个,幅度与各自概率错得多少成正比。各元素之和总是零,因为概率之和为 1。

式 8.11 比分开计算两步更简单,数值上也更稳定。先算 pcp_c 再算 −log⁡pc-\log p_c,当 pcp_c 在浮点数中下溢为零时就会失败;而且 −log⁡p-\log p 单独的导数 −1/p-1/p,恰恰在 softmax 自身导数极小的地方变得极大。因此深度学习库提供融合的“由分数直接计算交叉熵”:用 m+log⁡∑jezj−mm + \log\sum_j e^{z_j - m}(其中 m=max⁡jzjm = \max_j z_j)计算 log⁡∑jezj\log\sum_j e^{z_j},保证指数不会溢出,并在反向计算中使用式 8.11。

7. 自动微分

第 3–6 节的内容并不限于神经网络。任何由已知局部导数的运算(加法、乘法、矩阵乘法、指数、ReLU 等)组成的程序都定义了一张计算图,第 3 节的反向计算能算出程序输出对其全部输入的导数。这就是反向模式自动微分,反向传播是它在神经网络上的特例。PyTorch、JAX 等库在前向计算运行时记录运算(或预先追踪程序),并为每个基本运算提供一条规则,把对输出的导数映射为对输入的导数。用户只需写前向计算。

理解为什么反向才是正确的方向,会很有帮助。对一条层链,链式法则把梯度写成一串雅可比矩阵的乘积:∂ℓ/∂x⊤=∂ℓ/∂fK⊤ JK⋯J1\partial\ell/\partial\mathbf{x}^\top = \partial\ell/\partial\mathbf{f}_K^\top\,\mathbf{J}_K \cdots \mathbf{J}_1,其中 Jk\mathbf{J}_k 是第 kk 层输出对输入的导数矩阵。最左边的因子是行向量,因为损失只是一个数。从左往右乘,每个中间结果都保持为向量,每一步只是一次向量乘矩阵。反过来从右往左乘,就要在整条链上携带完整的矩阵。前向模式自动微分就是这样一次算一列:它把对某一个输入的导数随着数值一起向前推,每个输入或参数需要一遍。对标量损失和大量参数,反向模式的开销是一次前向计算的一个小常数倍,与参数个数无关 [5];前向模式的开销则是参数个数的倍数。在相反的情形,即输入少、输出多时,前向模式更好。

手写或自定义的梯度应当用中心差分(式 8.1)检查。中心差分的误差与 ϵ2\epsilon^2 成正比,单边差分则与 ϵ\epsilon 成正比。在 64 位浮点运算中,ϵ\epsilon 取 10−610^{-6} 左右、两个梯度之间的相对误差低于约 10−610^{-6} 是典型情况;32 位浮点精度不够,无法可靠地检查。距离 ReLU 拐点不到 ϵ\epsilon 的点可能出现合理的不一致,因为两次求值落在了不同的线性段上。

8. 反向计算告诉我们的深层网络训练问题

把式 8.8 和 8.9 从输出一直展开到第一层,可以看到误差信号的结构:

δ0=a′(f0)⊙Ω1⊤(a′(f1)⊙Ω2⊤(⋯(a′(fK−1)⊙ΩK⊤δK)⋯))(8.12)\boldsymbol{\delta}_0 = a'(\mathbf{f}_0) \odot \boldsymbol{\Omega}_1^\top \Big( a'(\mathbf{f}_1) \odot \boldsymbol{\Omega}_2^\top \big( \cdots \big( a'(\mathbf{f}_{K-1}) \odot \boldsymbol{\Omega}_K^\top \boldsymbol{\delta}_K \big) \cdots \big) \Big) \tag{8.12}

到达前面某层的信号,每经过一层就乘了一个权重矩阵和一个激活斜率。许多因子的乘积往往随长度呈指数增长或衰减;前向计算也一样,每层乘一个权重矩阵、施加一次激活。权重的大小决定了是哪种情况。这正是第 5 篇未加证明给出的初始化规则背后的原因,现在可以推导它了。

前向。 设第 kk 层的权重独立抽取,均值为 0、方差为 σ2\sigma^2,偏置为 0,且上一层的预激活关于零对称。fk\mathbf{f}_k 的第 dd 个元素是 DkD_k 个独立项 Ωk,djhk,j\Omega_{k,dj}h_{k,j} 之和,每项均值为 0、方差为 σ2 E[hk,j2]\sigma^2\,\mathbb{E}[h_{k,j}^2]。对称变量经过 ReLU 恰好保留一半的平方量,所以 E[hk,j2]=12Var(fk−1,j)\mathbb{E}[h_{k,j}^2] = \frac{1}{2}\mathrm{Var}(f_{k-1,j})。因此:

Var(fk)=Dk σ22 Var(fk−1)(8.13)\mathrm{Var}(f_k) = \frac{D_k\,\sigma^2}{2}\,\mathrm{Var}(f_{k-1}) \tag{8.13}

反向。 式 8.8 的第 jj 个元素是 Dk+1D_{k+1} 项 Ωk,djδk,d\Omega_{k,dj}\delta_{k,d} 之和,式 8.9 大约保留其中一半的结果。把权重、误差信号和激活模式视为相互独立(这是近似,但在初始化时相当好),同样的论证给出:

Var(δk−1)=Dk+1 σ22 Var(δk)(8.14)\mathrm{Var}(\delta_{k-1}) = \frac{D_{k+1}\,\sigma^2}{2}\,\mathrm{Var}(\delta_k) \tag{8.14}

用文字说:每一层把前向信号的方差乘以 Dkσ2/2D_k\sigma^2/2,把反向信号的方差乘以 Dk+1σ2/2D_{k+1}\sigma^2/2。这些因子小于 1 时,两个信号都随深度指数消失;大于 1 时,两者都指数爆炸。当 σ2=2/Dk\sigma^2 = 2/D_k(前向)或 σ2=2/Dk+1\sigma^2 = 2/D_{k+1}(反向)时因子等于 1,这就是 He 初始化 [6]。所有隐藏层宽度都是 DD 时,两个条件都是 σ2=2/D\sigma^2 = 2/D。

图 8.3 用模拟来验证:50 个宽度为 100 的 ReLU 层,没有偏置,一批 256 个随机输入,权重方差为 c/Dc/D(c=1,2,3c = 1, 2, 3),顶层给一个随机的误差信号。c=2c = 2 时,预激活和误差信号在全部 50 层中大小基本不变。c=1c = 1 时,每层把标准差乘以约 1/2\sqrt{1/2}:50 层后,预激活缩小到原来的 1.45×10−81.45 \times 10^{-8},到达第一层的误差信号缩小到 1.58×10−81.58 \times 10^{-8}。c=3c = 3 时,两者每层增大约 3/2\sqrt{3/2} 倍,分别达到 2.9×1042.9 \times 10^{4} 和 1.6×1041.6 \times 10^{4}。

两幅以层号 1 到 50 为横轴、纵轴为对数刻度的图。a)预激活的大小:绿线保持在 1 附近,橙线稳定上升到约一万,蓝线稳定下降到约一亿分之一。b)误差信号的大小:绿线保持在 1 附近,橙线从第 1 层的约一万降到第 50 层的约 1,蓝线从第 1 层的约一亿分之一升到第 50 层的约 1。

图 8.3 初始权重的尺度决定信号能否穿过 50 层。 一个有 50 个宽度为 100 的层的 ReLU 网络,权重方差 σ2=c/D\sigma^2 = c/D,一批 256 个随机输入,最后一层给一个随机误差信号。a)前向计算中预激活 fk\mathbf{f}_k 的均方根大小。b)反向计算(从第 50 层到第 1 层)中误差信号 δk\boldsymbol{\delta}_k 的大小。c=2c = 2(绿色,He 初始化)时两者都保持在 1 附近,与式 8.13 和 8.14 的预测一致。c=1c = 1(蓝色)时两者每层缩小约 2\sqrt{2} 倍,c=3c = 3(橙色)时每层增大约 1.5\sqrt{1.5} 倍。

看权重梯度本身时,会发现一个微妙之处。由式 8.7,第 kk 层的梯度是 δkhk⊤\boldsymbol{\delta}_k\mathbf{h}_k^\top。c=1c = 1 时,靠后的层激活值小,靠前的层误差信号小,两者的乘积在每一层都小了大致相同的总倍数 (c/2)K/2≈3×10−8(c/2)^{K/2} \approx 3 \times 10^{-8};模拟测得第 1、25、50 层的权重梯度范数都在 5×10−85 \times 10^{-8} 到 10−710^{-7} 之间。所以在宽度相同的普通 ReLU 网络中,错误的尺度并不会让前面的层比后面的层学得更慢;它让整个网络的输出和梯度都失去意义,而输出只有应有大小的 10−810^{-8} 倍,任何单一的学习率都无法补救。在别的情形下,层与层之间的失衡是真实存在的。sigmoid 或 tanh 单元的斜率几乎处处小于 1,循环网络在每个时间步都乘以同一个矩阵,这时误差信号在往回传时缩小而激活值不缩小,于是前面的层或前面的时间步只得到极小的梯度。这就是经典意义上的梯度消失问题 [8]。

初始化让信号在训练开始时保持合适的大小,但不能保证权重变化后仍然如此,也解决不了另一个困难:深的普通网络比浅的训练得更差(第 7 篇第 5 节)。第 11 篇介绍的残差连接为每一层加上一条恒等路径,使式 8.12 不再只是一串乘积;归一化层则在每一层重新设定激活值的尺度。

小结

反向传播用一次反向计算算出损失对每个参数的导数,开销只是前向计算的一个小倍数。前向计算保存中间值。反向计算从损失的导数开始,按逆序访问各个值,每经过一个运算乘以一个局部导数,并对一个值的所有使用求和(式 8.5)。对深层网络,这给出三个方程:权重梯度是误差信号乘以它所乘的激活值,是一个外积;误差信号反向越过权重矩阵时乘以其转置;反向越过 ReLU 时把未激活单元置零。反向计算的开销约为前向的两倍,而且训练必须保留每层的激活值,所以训练比预测需要多得多的内存。对 softmax 加交叉熵,反向计算的起点是预测概率减去目标。把同一算法用于任何程序,就是反向模式自动微分。最后,反向计算是逐层因子的乘积,分析它的方差就得到 He 初始化;训练非常深的网络的其余困难留给第 11 篇。第 9 篇接下来从拟合训练数据转向在训练数据之外的泛化。

注释

历史。 Linnainmaa 在 1970 年描述了反向模式求导 [3],用来计算长串运算的舍入误差;Werbos 在 1974 年的博士论文中提出把它用于神经网络 [4]。Rumelhart、Hinton 和 Williams [2] 在 1986 年表明,它能让多层网络学到有用的内部表示,这篇论文使它得到广泛使用。Baydin 等人 [9] 综述了机器学习中的自动微分。

二阶导数。 反向传播给出梯度。二阶导数即海森矩阵有 P2P^2 个元素,神经网络很少显式构造它。海森矩阵与一个向量的乘积,可以通过对反向计算再做前向模式求导,以几次梯度计算的代价得到;许多关于损失曲率的分析就是这样做的。

其他初始化。 对在零附近近似线性的激活函数(如 tanh),去掉式 8.13 中的因子 1/21/2,就得到 σ2=1/D\sigma^2 = 1/D。Glorot 和 Bengio [10] 在前向与反向两个条件之间折中,取 σ2=2/(Dk+Dk+1)\sigma^2 = 2/(D_k + D_{k+1}),常称为 Xavier 初始化。这些推导假设权重相互独立;正交初始化把每个权重矩阵取为随机旋转,在线性网络中能精确保持每个向量的长度。

不可导点。 ReLU 在 0 处没有导数,取最大值和取绝对值等运算也有类似的拐点。自动微分在那里选取一个导数值(对 ReLU 取 0)。由于预激活恰好为零的概率可以忽略,这个选择在实践中很少有影响。

延伸阅读。 Understanding Deep Learning [1] 第 7 章对同一个玩具模型和深层网络推导了反向传播,并讲解了初始化。Griewank 和 Walther [5] 是自动微分的标准参考书。

练习

练习 8.1 对第 2 节的函数链,计算 ∂ℓ/∂x\partial\ell/\partial x。为什么训练从来不需要它?(答案:ω0 ∂ℓ/∂f0=1.2×(−0.5576)=−0.6691\omega_0\,\partial\ell/\partial f_0 = 1.2 \times (-0.5576) = -0.6691。输入是数据而不是参数,梯度下降从不更新它。)

练习 8.2 在第 4 节的网络中,把目标改为 y=3y = 3,其他不变。新的梯度是多少?(答案:前向计算不变,δ2=2(2.25−3)=−1.5\delta_2 = 2(2.25 - 3) = -1.5。所有梯度都与 δ2\delta_2 成线性关系,所以全部乘以 −1.5/2.5=−0.6-1.5/2.5 = -0.6:例如 ∂ℓ/∂Ω2=(−5.25,0)\partial\ell/\partial\boldsymbol{\Omega}_2 = (-5.25, 0),∂ℓ/∂Ω0\partial\ell/\partial\boldsymbol{\Omega}_0 的两行为 (0,0)(0, 0) 和 (−1.5,−3)(-1.5, -3)。)

练习 8.3 对分数 z=(2.0,0.5,−1.0)\mathbf{z} = (2.0, 0.5, -1.0),当正确类别是第二类时,计算式 8.11 的误差信号。证明式 8.11 各元素之和总是零。(答案:(0.786,−0.825,0.039)(0.786, -0.825, 0.039)。和为 ∑kpk−1=0\sum_k p_k - 1 = 0。)

练习 8.4 设 u=1u = 1,v1=2uv_1 = 2u,v2=u2v_2 = u^2,ℓ=v1v2\ell = v_1 v_2。用式 8.5 计算 ∂ℓ/∂u\partial\ell/\partial u,并把 ℓ\ell 写成 uu 的函数加以验证。(答案:∂ℓ/∂v1=v2=1\partial\ell/\partial v_1 = v_2 = 1,∂ℓ/∂v2=v1=2\partial\ell/\partial v_2 = v_1 = 2,所以 ∂ℓ/∂u=2×1+2u×2=6\partial\ell/\partial u = 2 \times 1 + 2u \times 2 = 6。直接计算,ℓ=2u3\ell = 2u^3,导数为 6u2=66u^2 = 6。)

练习 8.5 对一批 256 个样本,(784,256,256,10)(784, 256, 256, 10) 分类器的一个训练步(前向加反向)需要多少次乘加?(答案:256×(268,800+336,896)=155,058,176256 \times (268{,}800 + 336{,}896) = 155{,}058{,}176,约 1.55 亿次。)

练习 8.6 对恒等激活函数 a(z)=za(z) = z 重做第 8 节的前向方差推导。什么样的权重方差能让预激活保持大小不变?(答案:没有 ReLU 时 E[h2]=Var(f)\mathbb{E}[h^2] = \mathrm{Var}(f),所以 Var(fk)=Dkσ2 Var(fk−1)\mathrm{Var}(f_k) = D_k\sigma^2\,\mathrm{Var}(f_{k-1}),σ2=1/Dk\sigma^2 = 1/D_k。)

练习 8.7 在图 8.3 中 c=1c = 1 的模拟里,按式 8.13,预激活从第 1 层到第 50 层大约应缩小多少倍?与实测的 0.9880.988 和 1.45×10−81.45 \times 10^{-8} 比较。(答案:(1/2)49/2≈4.2×10−8(1/2)^{49/2} \approx 4.2 \times 10^{-8};实测比值为 1.47×10−81.47 \times 10^{-8},数量级相同。差别来自在 49 层中不断累积的随机波动。)

参考文献