外观
反向传播与自动微分
一句话定义:反向传播(backpropagation)是用链式法则高效计算损失对每个参数梯度的算法——深度学习的一切训练,本质都是"先反向传播算出梯度,再沿梯度下降更新参数"(见优化与梯度下降)。而自动微分(automatic differentiation)是把这套算法系统化、机器化后的工程实现,PyTorch/TensorFlow 的 backward() 背后就是它。
一、为什么需要反向传播
训练神经网络要做两件事:前向传播算出损失,反向传播算出梯度。前向传播我们在神经网络基础里讲过:z = Wx + b → 激活 → … → 损失 L。
问题来了:怎么知道每个参数该往哪个方向调?答案是算 ∂L/∂W——损失对每个权重的偏导数,即梯度。有了梯度,参数更新就是:
W ← W − η · ∂L/∂W其中 η 是学习率。这就是梯度下降,细节见优化与梯度下降。
那么梯度怎么算?最笨的办法是对每个参数做数值差分:
∂L/∂Wᵢ ≈ (L(Wᵢ+ε) − L(Wᵢ−ε)) / 2ε一个 1 亿参数模型就要跑 2 亿次前向传播,完全不现实。反向传播利用链式法则,一次前向 + 一次反向就得到全部梯度——计算量只相当于两次前向传播,这是它能统治深度学习的根本原因。
为什么反向传播这么重要
反向传播 1986 年由 Rumelhart、Hinton、Williams 系统化提出并引爆深度学习,但其核心思想(逆序链式求导)早在 1970 年代由 Seppo Linnainmaa 在自动微分的论文中就已出现。它把"梯度计算"从 O(参数×前向成本) 降到了 O(前向成本),才让"学亿级参数"成为可能。
二、链式法则:数学内核
链式法则(chain rule)是微积分里一个"一句话"定理:复合函数求导等于逐层导数相乘。
若 L = f(g(h(x))),则:
∂L/∂x = f′(g(h(x))) · g′(h(x)) · h′(x)注意方向:外层函数先被计算(先有 f),但求导时我们从最外层 L 往回乘。这就是"反向"二字的含义。数学基础可查数学基础速查。
把它套在网络上:损失 L 是最后一层输出的函数,最后一层输出是倒数第二层的函数……所以:
∂L/∂W⁽ˡ⁾ = ∂L/∂a⁽ᴸ⁾ · ∂a⁽ᴸ⁾/∂a⁽ᴸ⁻¹⁾ · … · ∂a⁽ˡ⁺¹⁾/∂a⁽ˡ⁾ · ∂a⁽ˡ⁾/∂W⁽ˡ⁾写成通用形式,每个中间变量 z 保存一个"上游梯度" ∂L/∂z(也叫梯度信号),反向传播就是把上游梯度沿着计算图一层层传回去。
三、计算图:前向图与反向图
任何网络都可以画成一张计算图(computational graph):节点是数据(张量)与运算,边是依赖关系。前向传播按拓扑序从输入算到输出;反向传播按逆拓扑序从输出往回求梯度。
x ──(Linear)──▶ z ──(ReLU)──▶ a ──(Linear)──▶ ŷ ──(MSE)──▶ L
▲ ▲
W1,b1 W2,b2前向图存下每个中间结果(z、a)与各运算的局部导数;反向图用链式法则把这些局部导数串起来,得到 ∂L/∂W1、∂L/∂W2。
关键工程点:前向传播的内存开销必须保留(因为反向需要 z、a 的值)。这就是为什么显存占用约等于"激活值"而非只有参数——训练时显存远大于推理时,也解释了梯度检查点(gradient checkpointing)这类省显存技巧的原理。
四、手推一个小网络:从零算一遍
让我们手推一个最简单的标量网络:输入 x=1,一个隐藏神经元(权重 w1=2,无偏置),ReLU 激活,输出权重 w2=3,均方误差损失,目标 y=10。
前向传播:
z1 = w1·x = 2×1 = 2
a1 = ReLU(z1) = 2
ŷ = w2·a1 = 3×2 = 6
L = ½(ŷ − y)² = ½(6−10)² = 8反向传播(从 L 往回):
∂L/∂ŷ = ŷ − y = 6 − 10 = −4
∂ŷ/∂w2 = a1 = 2 → ∂L/∂w2 = (−4)×2 = −8
∂ŷ/∂a1 = w2 = 3 → ∂L/∂a1 = (−4)×3 = −12
∂a1/∂z1 = 1 (ReLU 正区间导数为 1)
∂z1/∂w1 = x = 1 → ∂L/∂w1 = (−12)×1×1 = −12所以两个梯度分别是 ∂L/∂w2 = −8、∂L/∂w1 = −12。若学习率 η=0.1,则 w2 ← 3+0.8 = 3.8、w1 ← 2+1.2 = 3.2。可以看到损失会下降——这正是神经网络"学会"的最小闭环。把这张表从标量推广到矩阵(每层都有 batch 维),就是真实框架里 backward() 做的一切。
五、梯度消失与梯度爆炸:连乘的宿命
反向传播的每一步都是"乘法"。看链式法则:
∂L/∂W⁽¹⁾ = ∂L/∂a⁽ᴸ⁾ · (Πₗ ∂a⁽ˡ⁺¹⁾/∂a⁽ˡ⁾) · ∂a⁽¹⁾/∂W⁽¹⁾如果每层导数的范数都小于 1(例如 Sigmoid 导数最大 0.25),连乘 30 层后梯度 ≈ 0.25³⁰,直接下溢为 0——梯度消失,浅层参数几乎收不到更新信号。反过来若每层导数大于 1,连乘指数爆炸——梯度爆炸,参数一步跳到 NaN。
工程要点
训练中 90% 的"梯度爆炸/消失"问题,都能在反向传播的链式乘法里找到根源。缓解手段包括:ReLU 系激活、合适的初始化(初始化与归一化)、归一化层、残差连接、梯度裁剪,排查方法见调试与诊断。
梯度消失的经典受害者是早期 RNN——跨时间步的连乘让它几乎记不住长距离依赖,这也是后来 LSTM、GRU 门控设计、以及最终 Transformer 全面取代 RNN 的深层原因,演进脉络见RNN 与序列建模。
六、自动微分:前向模式 vs 反向模式
自动微分不是数值差分(近似),也不是符号求导(展开表达式),而是在计算图上精确地应用链式法则,只是把求导过程程序化。它有两种基本模式:
- 前向模式(forward mode):从输入出发,同时对每个中间变量求"方向导数"。计算
n个输入的梯度需要n次前向。适合"输入少、输出多"(如标量函数对向量参数)的场景,Jacobian-vector product 高效。 - 反向模式(reverse mode):就是反向传播。一次前向 + 一次反向,得到损失对所有参数的梯度。适合"输入多、输出一"的典型神经网络场景(损失是标量,参数百万计)——这正是反向传播统治深度学习的原因。
两种模式各有适用场景:反向模式省一次算全部参数梯度,但必须保存中间激活,内存贵;前向模式无需保存中间结果、内存友好,常用于梯度本身参与计算(如求 Hessian 向量积)。选择逻辑与代价对比是框架与工具怎么选里可实操的考点。
七、PyTorch autograd 的正确用法
PyTorch 的自动微分由 torch.autograd 驱动。核心规则只有几条:
python
import torch
x = torch.tensor([1.0], requires_grad=True) # 需要梯度的叶子张量
w = torch.tensor([2.0], requires_grad=True)
b = torch.tensor([0.5], requires_grad=False) # 不追踪梯度
z = x * w + b # 记录运算
L = z.square().mean() # 损失(标量)
L.backward() # 反向传播,填充 .grad
print(w.grad) # tensor([2.0]) —— ∂L/∂w几个必须掌握的语义:
requires_grad=True:该张量及其所有衍生张量都被追踪进计算图。默认参数张量需要梯度,数据张量不需要。detach():从计算图中"摘除"该张量——返回一个不追踪梯度、不共享记录的新张量。典型用途:在某个中间特征处切断梯度流(如冻结特征提取器、对比学习中 stop-gradient),或者在torch.no_grad()之外单独禁用某条支路的梯度。torch.no_grad():上下文管理器,块内所有运算不建图、不存激活,省显存、省时间。推理时必须用,因为推理不需要梯度。torch.set_grad_enabled(False):以编程方式全局开关,训练脚本里按阶段切换的惯用写法。
detach() 与 no_grad() 的区别一句话:detach() 是"某个张量退出图",no_grad() 是"整段代码不建图"。
八、常见误区
反向传播 + autograd 是训练事故的高发区,这里列四个最常见的坑:
- 原地操作(in-place)破坏计算图:
w += 1、x.add_(1)会改写张量的值,而 autograd 反向时需要读取操作前的旧值,会直接报错a leaf Variable that requires grad is being used in an in-place operation。正确写法:w = w + 1(创建新张量)或用with torch.no_grad(): w.add_(1)更新参数。 - 在
no_grad()块内更新参数:with torch.no_grad(): w -= lr * w.grad是合法的(参数更新本来就不需要梯度),但如果在no_grad()里执行了前向 +backward(),梯度根本不会计算,模型永远不更新——这是"训练了一晚上 loss 纹丝不动"的头号原因。 - 每步不清零梯度:
loss.backward()默认是累加梯度。正确流程是每步先optimizer.zero_grad()(或loss.backward()前手动置零),否则梯度累加导致训练震荡。 - 对非标量直接 backward:
backward()要求输出是标量(或传入与输出同形状的梯度权重)。损失多输出时先.mean()或.sum()再 backward。
九、混合精度与梯度缩放
现代 GPU(如 NVIDIA A100)对 FP16/BF16 运算远快于 FP32,于是有了混合精度训练:参数和主梯度存 FP32,前向/反向用 FP16 加速。但 FP16 可表示范围小,梯度可能下溢到 0。解决方案是梯度缩放(gradient scaling):
loss_scaled = loss × scale # 放大损失
loss_scaled.backward() # 反向得到放大后的梯度
grad = grad / scale # 反向传播后还原PyTorch 的 torch.cuda.amp.GradScaler 自动处理"溢出就跳过该步、减小 scale"的流程。BF16 因为与 FP32 指数位相同、几乎不丢范围,成了 LLM 训练的默认选择。这一整套技巧在生产训练里几乎是标配,落地方案见训练配方与调参。
十、权衡与取舍
权衡与取舍
反向模式省算力、费显存 vs 前向模式省显存、费算力:标准训练用反向模式;当显存是瓶颈且梯度维数低时,前向模式或梯度检查点更划算。
保存全部激活 vs 检查点/重计算:保存激活反向最快但显存随深度线性增长;梯度检查点每层只存少量中间值、反向时重算,显存显著下降、时间约翻倍——在大模型训练里是"用时间换显存"的标准操作。
autograd 的便利 vs 可控性:自动微分让写模型像写数学公式一样自由,但代价是"魔法感"——出问题难以定位。理解手推示例中的每一步(第四节),是日后调试任何梯度问题的底气。
自动微分是深度学习这座大厦的地基:它决定了我们能训多大模型、多深网络、多长序列。把这一节吃透,配合神经网络基础中的前向传播,你就掌握了训练循环的全部核心;剩下的只是"梯度怎么用",交给优化与梯度下降章节。
延伸阅读
- 神经网络基础——前向传播与网络结构
- 深度学习总体架构解剖——数据、模型、损失、优化四大件全景
- 初始化与归一化——缓解梯度消失/爆炸的另一半
- 损失函数与输出层——梯度信号的质量源头
- 深度学习评估与实验——实验与调参中的梯度诊断
- 数学基础速查——链式法则、Jacobian 等数学基础
参考资料
- Rumelhart, Hinton, Williams. Learning representations by back-propagating errors (Nature, 1986)
- Linnainmaa. The representation of the cumulative rounding error of an algorithm as a Taylor expansion of the local rounding errors (1970)
- Baydin, Pearlmutter, Radul, Siskind. Automatic Differentiation in Machine Learning: a Survey (2018)
- Paszke et al. Automatic differentiation in PyTorch (2017)
- PyTorch autograd 官方文档