第13章 反向传播:误差怎样穿过多层网络
假设一个网络要识别手写数字。输入是几百个像素,输出是十个类别概率,中间有许多层和成千上万个参数。模型把一张“7”认成“1”后,只得到一个最终损失。
这个损失怎样告诉第一层的某个权重应该增大还是减小?中间隐藏单元没有人工标签,我们也不能为每个参数分别试遍所有改动。参数越多,逐个试错越不可能。
反向传播(backpropagation)解决的正是这个计算问题:已知整个网络怎样从输入算到损失,就利用链式法则,从损失沿计算路径反向计算每个参数的梯度。
它没有替模型选择目标,也没有亲自更新参数。反向传播负责回答“损失对每个参数有多敏感”;梯度下降、Adam 等优化方法再依据这些梯度决定参数怎样移动。
多层网络先向前计算
一个最简单的全连接层可以写成:
\[ z=Wx+b \]
\(x\) 是输入向量,\(W\) 是权重矩阵,\(b\) 是偏置,\(z\) 是线性组合。若只连续堆叠线性层,最终仍可合并成一个线性变换:
\[ W_2(W_1x+b_1)+b_2=W'x+b' \]
层数增加了,能画出的边界却没有发生本质变化。因此每层通常还要经过激活函数(activation function):
\[ h=\phi(z) \]
Sigmoid、tanh 和 ReLU 都是常见激活。非线性使多层组合能形成弯曲、分段和层次化的函数。
以两层网络为例:
\[ \begin{aligned} z_1&=W_1x+b_1 \\ h&=\phi(z_1) \\ z_2&=W_2h+b_2 \\ \hat y&=g(z_2) \end{aligned} \]
输入依次经过第一层、激活和第二层得到预测,再由损失函数计算 \(L(\hat y,y)\)。这条从输入到损失的路线叫作前向传播(forward pass)。
前向传播会保存一些中间量,例如 \(x,z_1,h,z_2\)。它们不只是临时结果;反向计算需要知道每一步当时的输入,才能求出局部导数。
最终标签怎样影响隐藏层
监督数据只给最终答案 \(y\)。隐藏层中的第 37 个单元没有“正确激活值”。它是否有用,要看它怎样影响后续层,后续层又怎样影响最终损失。
如果某个隐藏值稍微增加会使损失明显上升,损失对它的导数为正;若增加它会降低损失,导数为负;若当前输出几乎不受它影响,导数接近零。
因此,反向传播送回的不是“正确隐藏值”,而是敏感度:一个中间量发生微小变化时,最终损失怎样变化。
这与工厂追查质量问题有些相似。最终产品偏差不直接说明第一道工序要改多少;需要知道每道工序怎样影响下一道,再把影响关系从终点逐段传回起点。
计算图:把复杂公式拆成局部步骤
神经网络可以表示成计算图(computational graph)。节点是中间量或运算,边表示数据依赖。例如一个神经元:
x --乘以 w-->
z = wx + b -> a = sigmoid(z) -> 损失 L(a, y)
b ------------>
整条链看起来复杂,每个节点只需要回答两个局部问题:
- 前向时,怎样从输入算出输出?
- 反向时,已知损失对本节点输出的梯度,怎样得到对各输入的梯度?
加法节点把上游梯度原样传给两个输入;乘法节点把梯度乘以另一个输入;激活节点乘以自身导数;矩阵乘法节点还要处理转置与批次维度。
大网络因此不需要手工展开一条巨长导数。只要每种基本运算知道自己的局部导数,就能沿图反向组合。
链式法则:影响沿路径相乘
设 \(a=\sigma(z)\),\(z=wx+b\),损失为平方误差:
\[ L=\frac{1}{2}(a-y)^2 \]
我们想知道 \(w\) 改一点会怎样影响 \(L\)。\(w\) 不直接进入损失,而是先改变 \(z\),再改变 \(a\),最后改变 \(L\)。链式法则写成:
\[ \frac{\partial L}{\partial w} =\frac{\partial L}{\partial a} \frac{\partial a}{\partial z} \frac{\partial z}{\partial w} \]
三段局部导数分别是:
\[ \begin{aligned} \frac{\partial L}{\partial a}&=a-y \\ \frac{\partial a}{\partial z}&=\sigma(z)(1-\sigma(z)) \\ \frac{\partial z}{\partial w}&=x \end{aligned} \]
合在一起:
\[ \frac{\partial L}{\partial w} =(a-y)\,\sigma(z)(1-\sigma(z))\,x \]
这四个因子各有含义:预测误差、激活对输入的敏感度、当前输入大小,以及它们共同决定的参数梯度。若输入为零,该样本不会更新这条权重;若 sigmoid 已经饱和,中间导数很小,即使预测错误,梯度也可能难以传回。
核心机制
前向传播保存每个运算的输入与输出;反向传播从损失梯度 1 开始,在每个节点把收到的梯度乘以局部导数,并把来自多条后续路径的贡献相加。最终得到损失对全部参数的梯度,优化器再用它们更新参数。
为什么要从后往前
若网络只有一个输出损失,却有一百万个参数,可以对每个参数分别从头求一次导数,但会重复计算大量公共路径。
反向传播采用反向模式自动微分(reverse-mode automatic differentiation):先算一次前向图,再从单个标量损失反向累计。一个中间节点的下游敏感度只需算一次,就能复用于所有进入该节点的参数。
若一个中间量同时影响两条后续路径,梯度不是任选一条,而是相加:
\[ \frac{\partial L}{\partial u} =\sum_j \frac{\partial L}{\partial v_j} \frac{\partial v_j}{\partial u} \]
残差连接、分支网络和参数共享都依赖这条规则。只要一个量通过多条路径影响损失,各路径贡献就会在反向阶段汇合。
自动微分与符号求导不同。符号系统尝试生成一个新的代数表达式;自动微分则在具体计算执行时,按基本运算精确应用链式法则。它也不同于数值差分:自动微分在浮点误差范围内得到解析链式法则的结果,不需要反复扰动每个参数。
技术深潜:矩阵层的梯度形状
设一批输入为 \(X\in\mathbb{R}^{B\times d_{in}}\),权重为 \(W\in\mathbb{R}^{d_{in}\times d_{out}}\),偏置为 \(b\in\mathbb{R}^{d_{out}}\):
\[ Z=XW+b \]
假设反向阶段已经收到 \(G_Z=\partial L/\partial Z\),形状是 \([B,d_{out}]\)。三个梯度为:
\[ \begin{aligned} G_X&=G_ZW^\mathsf{T} \\ G_W&=X^\mathsf{T}G_Z \\ G_b&=\sum_{i=1}^{B}G_{Z,i} \end{aligned} \]
形状检查能提前发现许多错误:
| 梯度 | 形状 | 含义 |
|---|---|---|
| \(G_X\) | \([B,d_{in}]\) | 损失对上一层输出的敏感度 |
| \(G_W\) | \([d_{in},d_{out}]\) | 每条权重的梯度 |
| \(G_b\) | \([d_{out}]\) | 批次中偏置梯度之和 |
\(G_X\) 继续传向前一层,\(G_W\) 与 \(G_b\) 留给优化器更新本层参数。偏置在批次维度上被广播,所以反向时要把该维度求和。
深度学习框架会自动完成这些运算,但广播、转置或批次平均写错时,代码可能仍能运行。理解形状比只会调用 .backward() 更可靠。
一次反向传播究竟做什么
以两层网络为例,训练一个批次时大致经历:
- 读取输入 \(X\) 与标签 \(Y\);
- 前向计算隐藏表示 \(H\) 与预测 \(\hat Y\);
- 把预测与标签压成一个标量损失 \(L\);
- 从 \(\partial L/\partial L=1\) 开始反向;
- 计算第二层参数梯度,并得到对 \(H\) 的梯度;
- 梯度穿过激活函数,得到对第一层线性输出的梯度;
- 计算第一层参数梯度;
- 优化器读取全部参数梯度并执行更新;
- 清空旧梯度,开始下一个批次。
许多框架默认把多次反向得到的梯度累加,而不是自动覆盖。忘记清零会让更新包含旧批次贡献;有时这是错误,有时又被故意用于梯度累积,以多个小批次模拟较大批次。
反向传播通常与前向传播具有相近量级的计算成本,却还要保存中间激活,因此训练比只做推理需要更多内存。推理时参数固定,不要求保留整张反向图。
它不是 1986 年突然出现的单一发明
反向传播的历史比一个年份复杂。Seppo Linnainmaa 在 1970 年系统描述了对复合计算进行反向累积的方法。(Linnainmaa 1970年) Paul Werbos 在 1974 年的博士论文中讨论了把这种思想用于动态系统和神经网络。(Werbos 1974年)
1986 年,David Rumelhart、Geoffrey Hinton 与 Ronald Williams 的论文用多组实验清楚展示了反向传播怎样让网络学习有用的内部表示,并使方法在连接主义研究中广为人知。(Rumelhart 等 1986年)
随后,Yann LeCun 等人把反向传播用于手写邮政编码识别,展示了端到端训练在现实视觉任务中的潜力。(LeCun 等 1989年)
因此,更准确的说法是:链式法则很早就存在,反向累积也有多条发展路线;1980 年代的工作把它与多层神经网络、实验结果和可用计算结合,推动了广泛采用。
梯度为什么会消失或爆炸
多层链式法则包含许多导数的乘积。若每层局部导数的典型大小小于 1,向前传播得越远,乘积越可能接近零;若持续大于 1,梯度可能迅速增大。
Sigmoid 的导数最大只有 \(1/4\),输入绝对值很大时更接近零。深层网络若大量单元进入饱和区,前面层几乎收不到学习信号。这是梯度消失(vanishing gradients)。
反过来,权重和局部导数的组合若反复放大,会出现梯度爆炸(exploding gradients):损失震荡,参数变成极大值,甚至出现非有限数。
ReLU 定义为:
\[ \operatorname{ReLU}(z)=\max(0,z) \]
正半轴导数为 1,减轻了饱和造成的梯度衰减,也便于计算。Nair 与 Hinton 的工作推动了 ReLU 在深度模型中的应用。(Nair 和 Hinton 2010年) 但负半轴梯度为零,若单元长期落在负区间,可能成为不再更新的“死亡 ReLU”。
激活函数只是条件之一。网络深度、权重尺度、数据尺度、归一化、残差连接和优化器都会共同改变梯度流。
初始化不能随便填一个小数
若所有隐藏单元用完全相同的权重初始化,它们会得到相同输出和相同梯度,之后仍同步变化,多个单元等于一个单元。随机初始化首先要打破这种对称。
但随机范围也不能任意。权重过小,信号与梯度逐层缩弱;权重过大,激活可能饱和或数值爆炸。
Xavier/Glorot 初始化根据输入与输出宽度设置方差,希望前向激活和反向梯度的尺度在多层间大致保持。(Glorot 和 Bengio 2010年) 对 ReLU,He 初始化会根据整流激活改变方差传播的特点采用不同尺度。(He 等 2015年)
这些方法不是保证收敛的魔法。它们建立在输入近似独立、分布相对稳定等简化假设上;卷积、残差、注意力和不同激活还会改变合适尺度。但良好初始化能让训练从“几乎没有信号”变成“优化器有机会工作”。
批次、随机梯度与优化器
在全部训练集上计算一次精确梯度可能非常昂贵。小批量随机梯度下降(mini-batch SGD)每次抽取一批样本,用批次平均损失近似全数据梯度。
较小批次更新更频繁、梯度噪声更大,内存需求较低;较大批次能提高硬件吞吐,梯度更稳定,却可能需要调整学习率并占用更多内存。批次大小改变的不只是运行速度,也改变优化轨迹。
动量方法会积累过去梯度方向,减小狭长损失谷中的来回震荡。Adam 对不同参数维护梯度的一阶与二阶统计,自适应调整步长。(Kingma 和 Ba 2015年) 它常提供方便起点,但学习率、权重衰减和最终泛化仍需验证。
再次强调:反向传播产生梯度,优化器使用梯度。两者可以替换组合。相同反向传播可以配 SGD、带动量 SGD 或 Adam;相同优化器也能用于不同可微模型。
技术深潜:用有限差分检查梯度
自己实现反向传播时,可以用数值差分抽查某个参数 \(\theta_j\):
\[ g_j^{num}\approx \frac{L(\theta_j+\varepsilon)-L(\theta_j-\varepsilon)}{2\varepsilon} \]
把它与反向传播得到的 \(g_j^{back}\) 比较。常用相对误差形式是:
\[ \frac{|g_j^{num}-g_j^{back}|} {\max(1,|g_j^{num}|,|g_j^{back}|)} \]
差异很大时,应检查导数符号、广播求和、转置、批次平均和正则化项。\(\varepsilon\) 太大导致近似粗糙,太小又受浮点舍入影响,通常要尝试几个数量级。
梯度检查很慢,因为每个被查参数都需要额外前向计算;它适合小网络与少量参数抽样,不用于正式训练。还应暂时关闭 dropout 等随机操作,避免两次损失来自不同随机路径。
会求梯度,不等于一定学得好
反向传播只忠实计算当前模型和损失的局部梯度。以下问题不会自动消失:
- 标签或奖励是否对应真实目标;
- 网络结构是否能表示所需关系;
- 损失表面是否存在平坦区、鞍点或不稳定方向;
- 数据是否足够、是否有泄漏和偏差;
- 训练分布与部署分布是否一致;
- 最终参数是否泛化,而不只是降低训练损失。
梯度为零也有多种解释:可能到达局部平稳点,可能激活饱和,可能 ReLU 死亡,可能计算图被错误截断,也可能该参数确实不影响当前损失。不能只看一个数字下结论。
P04 将在后续实践部分不依赖自动微分,从零实现线性层、激活、损失和反向节点,并用有限差分做梯度检查。实践仍然后置,不影响下一章阅读。
下一章把这套训练机制用于图像。若把每个像素与每个隐藏单元全连接,参数量巨大,也忽略了图像最重要的结构:相邻像素关系密切,同一种局部图案可能出现在不同位置。卷积网络会把这些假设直接写进连接方式。
本章小结
- 多层网络先通过前向传播计算中间表示、预测与标量损失,并保存反向所需的中间量。
- 反向传播利用链式法则计算损失对全部参数的梯度;梯度下降或其他优化器再执行更新。
- 计算图把复杂函数拆成基本运算,每个节点只需提供前向规则和局部导数。
- 反向模式自动微分复用公共下游路径,适合从一个标量损失高效计算大量参数梯度。
- 多层导数相乘可能造成梯度消失或爆炸;激活、初始化、归一化和残差结构共同影响梯度流。
- 批次大小与优化器改变计算效率和优化轨迹,但不能替代数据、目标与泛化检查。
- 数值有限差分可以抽查手写反向传播,尤其适合发现符号、转置、广播和平均方式错误。
- 能精确计算梯度不保证目标正确、优化成功或模型在新数据上可靠。
思考问题
- 为什么隐藏层不需要人工提供“正确激活值”,仍能根据最终标签更新?
- 反向传播、梯度下降和自动微分分别解决什么问题?
- 一个网络训练损失完全不下降时,怎样区分梯度实现错误、激活饱和和学习率不合适?
延伸阅读
- Rumelhart、Hinton 与 Williams 1986 年的论文适合理解“内部表示由误差塑造”的早期实验。(Rumelhart 等 1986年)
- Linnainmaa 与 Werbos 的工作帮助还原反向累积和神经网络训练的多线历史。(Linnainmaa 1970年; Werbos 1974年)
- LeCun 等人的邮政编码识别论文展示了反向传播走向实际视觉系统的早期路径。(LeCun 等 1989年)
- Glorot 与 Bengio 的论文从激活与梯度尺度分析深层训练困难。(Glorot 和 Bengio 2010年)
