← 全部章节

P12 · 第 12 讲

反向传播

31:27

本章目录(8 节)
  1. 一、本节的问题与直观理解
  2. 二、核心知识:两个因子与一条递推
  3. 三、关键推导:一个 2-3-2 网络的局部导数(复习补充)
  4. 四、原讲义例子与结论
  5. 五、易错点
  6. 六、自测
  7. 七、30 秒速记
  8. 八、和其他 P 的关系
项 内容
课程版本 李宏毅 2016 秋季《Machine Learning》Lecture 7:Backpropagation
视频分 P https://www.bilibili.com/video/BV1ou411N7X3?p=12 (31:27;搬运合集)
官方讲义 BP.pdf,19 页
材料说明 对应官方讲义整理+必要补充,非视频逐字稿

页码为 PDF 物理页(封面第 1 页)。这一讲只讲「怎么算梯度」,不重复梯度下降本身;第 2 页开宗明义:参数有 millions 个,为了高效算梯度才用 backpropagation。

一、本节的问题与直观理解

网络有几十万个参数,损失是这些参数的高维函数。若对每个参数单独做一次「扰动—重算损失」,一次梯度要前向传播几十万次,训练不可行。反向传播要解决的就是这个算术问题:一次前向 + 一次反向,把所有参数的偏导一起算出来,代价只相当于两三次前向。

直观理解可以拆成两个因子。对某个权重 \(w\),它先影响所在神经元的输入 \(z\),\(z\) 再往后影响损失:

$$\frac{\partial C}{\partial w}=\frac{\partial z}{\partial w}\cdot\frac{\partial C}{\partial z}.$$

第一个因子是局部的、几乎不用算:\(z=wx+\dots\),所以 \(\partial z/\partial w\) 就是连在这个权重上的输入值(第 6 页:\(\partial z/\partial w_1=x_1\),\(\partial z/\partial w_2=x_2\))。第二个因子 \(\partial C/\partial z\) 是「这个神经元的输入每变一点,损失变多少」,它要看完所有下游路径,所以从输出层往回传。前向存下输入,反向传递误差信号,这就是整个算法。

二、核心知识:两个因子与一条递推

总损失拆成单样本。 第 4 页:

$$L(\boldsymbol{\theta})=\sum_{n=1}^{N}C^{n}(\boldsymbol{\theta}),\qquad \frac{\partial L}{\partial w}=\sum_{n=1}^{N}\frac{\partial C^{n}}{\partial w}.$$

所以只需推导单个样本的 \(\partial C/\partial w\),最后求和。

定义 \(\delta\)。 记第 \(l\) 层第 \(j\) 个神经元的输入为 \(z_j^{l}\),定义

$$\delta_j^{l}\equiv\frac{\partial C}{\partial z_j^{l}}.$$

第 8 页先给单神经元版本:

$$\frac{\partial C}{\partial z}=\frac{\partial a}{\partial z}\cdot\frac{\partial C}{\partial a}=\sigma'(z)\cdot\frac{\partial C}{\partial a},\qquad a=\sigma(z).$$

第 10–11 页再把 \(\partial C/\partial a\) 展开。一个神经元的输出 \(a\) 会喂给下一层多个神经元,所以要把每条路径加起来:

$$\frac{\partial C}{\partial a}=w_3\frac{\partial C}{\partial z'}+w_4\frac{\partial C}{\partial z''},\qquad \delta=\sigma'(z)\left(w_3\delta'+w_4\delta''\right).$$

第 13 页处理输出层:若 \(z'\) 直接产出 \(y_1\),则 \(\partial C/\partial z'=(\partial y_1/\partial z')(\partial C/\partial y_1)\),链路到此结束;第 14–16 页处理非输出层,把同样的式子继续往回套,直到抵达输出层。第 17–18 页把这个过程画成「从输出层往输入层逐层点亮」。

矩阵形式(复习补充的整理,讲义是标量写法)。 对第 \(l\) 层:

$$\mathbf{z}^{l}=W^{l}\mathbf{a}^{l-1}+\mathbf{b}^{l},\qquad \boldsymbol{\delta}^{l}=\sigma'(\mathbf{z}^{l})\odot\left(W^{l+1\top}\boldsymbol{\delta}^{l+1}\right),$$
$$\frac{\partial C}{\partial W^{l}}=\boldsymbol{\delta}^{l}\left(\mathbf{a}^{l-1}\right)^{\top},\qquad \frac{\partial C}{\partial \mathbf{b}^{l}}=\boldsymbol{\delta}^{l}.$$

维度必须能对上:\(\mathbf{a}^{l-1}\in\mathbb{R}^{n_{l-1}}\)、\(W^{l}\in\mathbb{R}^{n_l\times n_{l-1}}\)、\(\boldsymbol{\delta}^{l}\in\mathbb{R}^{n_l}\)、\(\partial C/\partial W^{l}\in\mathbb{R}^{n_l\times n_{l-1}}\)。\(\odot\) 是逐元素乘,\(\sigma'(\mathbf{z}^{l})\) 与 \(\boldsymbol{\delta}^{l+1}\) 过 \(W^{l+1\top}\) 之后的维度都是 \(n_l\)。

批量版:若一个 batch 的 \(\Delta^{l}\in\mathbb{R}^{B\times n_l}\)、\(A^{l-1}\in\mathbb{R}^{B\times n_{l-1}}\),则

$$\frac{\partial L}{\partial W^{l}}=\left(\Delta^{l}\right)^{\top}A^{l-1}\in\mathbb{R}^{n_l\times n_{l-1}}.$$

这里默认 \(L\) 是 batch 内求和;若损失定义成平均,再除以 \(B\)。求和还是平均,会让梯度差一个 \(B\) 倍,和 P1 里 \(1/N\) 的问题是同一条。

三、关键推导:一个 2-3-2 网络的局部导数(复习补充)

讲义第 7 页给了三个具体数:\(\partial z/\partial w=-1\)、\(0.12\)、\(0.11\),用来说明「前向因子的值就是输入」。但讲义没有把整条链算完,这里补一个可以手算、可以用代码复核的小例子。

网络:输入 2 维 → 隐藏层 3 个 sigmoid → 输出 2 个 sigmoid,损失 \(C=\frac12\sum_k(y_k-t_k)^2\)。取

$$\mathbf{x}=(0.5,-1.0),\quad W^{1}=\begin{bmatrix}0.2&-0.4\\0.7&0.1\\-0.3&0.5\end{bmatrix},\quad \mathbf{b}^{1}=(0.1,-0.2,0.05),$$
$$W^{2}=\begin{bmatrix}0.6&-0.5&0.2\\-0.1&0.8&0.4\end{bmatrix},\quad \mathbf{b}^{2}=(0,0.3),\quad \mathbf{t}=(1,0).$$

前向得 \(\mathbf{z}^{1}=(0.6,0.05,-0.6)\)、\(\mathbf{a}^{1}=(0.645656,0.512497,0.354344)\)、\(\mathbf{z}^{2}=(0.202014,0.787170)\)、\(\mathbf{y}=(0.550332,0.687223)\),损失 \(C=0.337238\)。

输出层用 MSE + sigmoid,局部导数恰好是「误差 × sigmoid 导数」:

$$\boldsymbol{\delta}^{2}=(\mathbf{y}-\mathbf{t})\odot\mathbf{y}\odot(1-\mathbf{y})=(-0.111278,\ 0.147717).$$

往回传:

$$\boldsymbol{\delta}^{1}=\left(W^{2\top}\boldsymbol{\delta}^{2}\right)\odot\mathbf{a}^{1}\odot(1-\mathbf{a}^{1}) =(-0.018655,\ 0.043426,\ 0.008426).$$

于是

$$\frac{\partial C}{\partial W^{1}}=\boldsymbol{\delta}^{1}\mathbf{x}^{\top} =\begin{bmatrix}-0.009327&0.018655\\0.021713&-0.043426\\0.004213&-0.008426\end{bmatrix},\qquad \frac{\partial C}{\partial \mathbf{b}^{1}}=\boldsymbol{\delta}^{1}.$$

用中心差分逐元素复核,四个参数块的最大绝对误差分别是 \(2.7\times10^{-11}\)、\(2.7\times10^{-11}\)、\(9.3\times10^{-11}\)、\(3.2\times10^{-11}\),纯浮点噪声。可复现的 NumPy 代码(不依赖 torch):

import numpy as np
sig = lambda z: 1 / (1 + np.exp(-z))
x  = np.array([0.5, -1.0]);  t  = np.array([1.0, 0.0])
W1 = np.array([[0.2, -0.4], [0.7, 0.1], [-0.3, 0.5]]); b1 = np.array([0.1, -0.2, 0.05])
W2 = np.array([[0.6, -0.5, 0.2], [-0.1, 0.8, 0.4]]);   b2 = np.array([0.0, 0.3])

a1 = sig(W1 @ x + b1)          # (3,)
y  = sig(W2 @ a1 + b2)         # (2,)
d2 = (y - t) * y * (1 - y)     # dC/dz2, (2,)
d1 = (W2.T @ d2) * a1 * (1 - a1)   # dC/dz1, (3,)
gW1, gW2 = np.outer(d1, x), np.outer(d2, a1)
print(d2, d1, gW1.shape, gW2.shape)   # (3,2) and (2,3), same as W1/W2

这个例子说明三件事:\(\delta\) 的维度与本层神经元数一致;\(\partial C/\partial W\) 的形状与 \(W\) 完全一致(否则没法更新);每一层的 \(\delta\) 都由「下一层的 \(\delta\) 过转置权重」再乘本层激活导数得到。

四、原讲义例子与结论

五、易错点

  1. BP 不是梯度下降。 BP 只负责算 \(\nabla L\);怎么用梯度(SGD、momentum、Adam)是另一层。说「用 BP 训练网络」是口语,严格说是「用 BP 算梯度、用某种优化器更新」。
  2. \(\delta\) 是从后往前递推的。 先算输出层 \(\delta^{L}\),再用 \(W^{l+1\top}\delta^{l+1}\) 往前推;方向搞反(用 \(W^{l}\) 往前乘)维度也许凑得上,但数学上完全错。
  3. \(\sigma'(z)\) 用的是前向时的 \(z\)。 所以必须在前向阶段把 \(z\) 或 \(a\) 存下来;反向时重新算一遍既浪费又容易和参数更新后的值不一致。
  4. 一个神经元的下游有多条路径时要相加。 第 10 页的 \(w_3\delta'+w_4\delta''\) 就是这件事;只取一条会漏梯度。
  5. 共享权重要把各处的梯度累加。 讲义这一讲是全连接网络,每个权重只出现一次;换成 CNN(卷积核共享)或 RNN(时间步共享)时,同一个参数在多处使用,\(\partial C/\partial w\) 必须是各处之和。
  6. 输出层的 \(\delta\) 依赖损失函数。 MSE + sigmoid 是 \((\mathbf{y}-\mathbf{t})\odot\mathbf{y}\odot(1-\mathbf{y})\);softmax + cross-entropy 化简后直接是 \(\mathbf{y}-\mathbf{t}\),少一个 \(\sigma'\)。换损失不换这行代码,梯度就错了。
  7. 批量求和与平均。 见第二节;差一个 \(B\) 倍,表现为学习率「突然不灵」。

六、自测

Q1. 为什么 \(\partial C/\partial W^{l}\) 的形状一定和 \(W^{l}\) 一样?

参考答案

因为损失对某个参数的偏导只是一个标量,把所有参数排成与 \(W^{l}\) 相同的矩阵,就得到同形状的梯度。从公式看 \(\boldsymbol{\delta}^{l}(\mathbf{a}^{l-1})^\top\) 是 \((n_l\times1)(1\times n_{l-1})\),正好是 \(n_l\times n_{l-1}\),与 \(W^{l}\) 一致;形状不一致就无法做 \(W\leftarrow W-\eta G\)。

Q2. 某神经元输出 \(a\) 同时喂给下一层 3 个神经元,\(\partial C/\partial a\) 怎么写?

参考答案

把三条路径相加:\(\partial C/\partial a=\sum_{k=1}^{3}w_k\,\partial C/\partial z_k\),\(w_k\) 是 \(a\) 到第 \(k\) 个神经元的权重。这就是多元链式法则,漏掉任何一条都会低估梯度。

Q3. 把输出层从 MSE 换成 softmax + cross-entropy,\(\delta^{L}\) 会怎么变?

参考答案

对 one-hot target,\(\delta^{L}=\mathbf{y}-\mathbf{t}\),不再乘 \(\sigma'\)。原因是 softmax 的雅可比与 cross-entropy 的导数相消;这也是分类任务几乎都用这一对的原因——梯度不会因为输出饱和而消失。

Q4. 训练时发现梯度数值和中心差分对不上,最可能检查哪三处?

参考答案

一、\(\delta\) 的递推方向与转置(\(W^{\top}\delta\) 而非 \(W\delta\));二、激活导数是否用了前向存的 \(z\)、是否漏乘;三、损失是求和还是平均、共享权重是否累加。数值校验时记得用双精度、\(\epsilon\) 取 \(10^{-6}\) 左右,太大截断误差、太小学到舍入噪声。

七、30 秒速记

\(\partial L/\partial w=\sum_n\partial C^n/\partial w\) → 每个权重拆成「前向因子 \(\partial z/\partial w=a\)」×「反向因子 \(\delta=\partial C/\partial z\)」→ \(\delta^{l}=\sigma'(z^{l})\odot(W^{l+1\top}\delta^{l+1})\) → 输出层 \(\delta=(\mathbf{y}-\mathbf{t})\odot\sigma'\)(MSE)或 \(\mathbf{y}-\mathbf{t}\)(softmax+CE)→ \(\partial C/\partial W^{l}=\delta^{l}(a^{l-1})^\top\),形状与本层权重一致 → BP 只算梯度,更新是优化器的事。

八、和其他 P 的关系

来源与说明

基于对应官方讲义整理,未取到完整视频字幕;不冒充逐字稿。