HelloAI
L3 第 2 篇 🐣 难度 🕒 8 分钟

反向传播详解:链式法则的实战

Backpropagation 让神经网络真的能学。这一篇拆解它的数学、直觉、和实现。

阿莱
2026/9/2

L3-01 我们看到 MLP 怎么把多层神经元堆起来。 这一篇讲:堆起来之后,它怎么”学”

没有反向传播 —— 神经网络只是个昂贵的随机函数。

一句话定位

反向传播 = 用链式法则把 loss 的梯度从输出层一路传回每一层参数

每层都拿到自己”应该往哪个方向、走多大步”才能让 loss 下降一点点。 然后跟着梯度下降走 —— 模型变好了。

数学回忆:链式法则

L1-06 讲过链式法则。一句话回顾:

ddxf(g(x))=f(g(x))g(x)\frac{d}{dx} f(g(x)) = f'(g(x)) \cdot g'(x)

复合函数的导数 = 外层在内层值处的导数 × 内层的导数

把这个推广到多个变量、多个层 —— 就是反向传播。

一个最小神经网络

考虑 1 个输入、1 个隐藏层(2 个神经元)、1 个输出的小网络:

x ──W1──> h ──W2──> y_pred ──Loss──> L

                            y_true

参数:W1,W2W_1, W_2(这里忽略 bias 简化) 激活:h=σ(W1x)h = \sigma(W_1 x)σ\sigma 是 sigmoid 预测:y^=W2h\hat{y} = W_2 h 损失:L=12(y^y)2L = \frac{1}{2}(\hat{y} - y)^2

问题:怎么算 LW1\frac{\partial L}{\partial W_1}

链式法则一层层算

从最外层开始往内传:

LW1=Ly^y^hhW1\frac{\partial L}{\partial W_1} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial h} \cdot \frac{\partial h}{\partial W_1}

每一项是局部导数:

  • Ly^=y^y\frac{\partial L}{\partial \hat{y}} = \hat{y} - y (loss 对预测的导)
  • y^h=W2\frac{\partial \hat{y}}{\partial h} = W_2 (线性层对输入的导)
  • hW1=σ(W1x)x\frac{\partial h}{\partial W_1} = \sigma'(W_1 x) \cdot x (激活后对权重的导)

代回去:

LW1=(y^y)W2σ(W1x)x\frac{\partial L}{\partial W_1} = (\hat{y} - y) \cdot W_2 \cdot \sigma'(W_1 x) \cdot x

就这么简单——每一项都是局部的微分。

“反向”在哪里反向

正向传播(forward):从 xx 走到 LL,每层算输出。 反向传播(backward):从 LL 走回 xx,每层算梯度

正向: x → h → y_pred → L
反向: L ← h ← y_pred ← (gradient 累乘回去)

反向这个词来自这个回流方向。

为什么不直接数值微分

数值微分(有限差分)也能算梯度:

LW1L(W1+ϵ)L(W1)ϵ\frac{\partial L}{\partial W_1} \approx \frac{L(W_1 + \epsilon) - L(W_1)}{\epsilon}

为啥不用?

数值微分反向传播
精度取决于 ϵ\epsilon解析精确
每个参数代价一次完整 forward共享一次 forward
百万参数模型一百万次 forward 才一个 gradient一次 forward + 一次 backward 就够了

数值微分理论上对、实际算不起。反向传播让大模型成为可能

计算图视角

现代深度学习框架(PyTorch、TensorFlow)把模型表示成计算图

       ┌──────┐
       │  x   │
       └──┬───┘

       ┌──▼───┐    ┌────┐
       │ mul  │◄───│ W1 │
       └──┬───┘    └────┘

       ┌──▼───┐
       │sigma │
       └──┬───┘

       ┌──▼───┐    ┌────┐
       │ mul  │◄───│ W2 │
       └──┬───┘    └────┘

       ┌──▼───┐
       │ y_p  │
       └──┬───┘

       ┌──▼───┐    ┌────┐
       │ loss │◄───│ y  │
       └──────┘    └────┘

每个节点知道:

  1. 它的 forward 公式(用输入算输出)
  2. 它的 backward 公式(已知 L输出\frac{\partial L}{\partial 输出},算 L输入\frac{\partial L}{\partial 输入}L参数\frac{\partial L}{\partial 参数}

整个 backward = 沿图反向遍历,每节点用 chain rule 把梯度往后传。

PyTorch 实战

PyTorch 的 autograd 自动构建计算图、自动反向:

import torch

# 输入和真值
x = torch.tensor(2.0)
y = torch.tensor(1.0)

# 参数 —— requires_grad=True 让 autograd 跟踪
W1 = torch.tensor(0.5, requires_grad=True)
W2 = torch.tensor(0.8, requires_grad=True)

# Forward
h = torch.sigmoid(W1 * x)
y_pred = W2 * h
loss = 0.5 * (y_pred - y) ** 2

print(f"loss = {loss.item():.4f}")

# Backward —— 一行
loss.backward()

# 梯度直接在参数上
print(f"dL/dW1 = {W1.grad:.4f}")
print(f"dL/dW2 = {W2.grad:.4f}")

loss.backward() —— 这一句话内部做了上面整套链式法则。

几个常见坑

1. 梯度累加,不是替换

loss.backward()
# W1.grad 是 0.3

loss.backward()
# W1.grad 是 0.6 —— 累加了!

所以训练循环里一定要:

optimizer.zero_grad()   # 清零旧梯度
loss.backward()         # 算新梯度
optimizer.step()        # 沿梯度更新

忘记 zero_grad() 是新手常犯的 bug

2. 梯度消失 / 爆炸

链式法则是连乘。如果每层的局部梯度都很小(< 1),层数一多——梯度乘到底就接近 0(消失)。如果都很大(> 1)——梯度爆炸。

历史上:

  • Sigmoid + 深层 → 梯度消失 → 训不动深网络
  • 解决方案:ReLU 激活、BatchNorm、残差连接(ResNet)、梯度裁剪

L3-03 训练技巧 会详讲。

3. 不可微的操作

argmaxif x > 0、采样等操作没有导数——backprop 走不过。

解决:

  • Gumbel-Softmax:连续松弛 argmax
  • Straight-Through Estimator:forward 用离散,backward 假装恒等
  • Reparameterization(VAE):把随机性外置

反向传播的历史

  • 1960s:控制论里就有类似想法
  • 1986:Rumelhart、Hinton、Williams 把它系统化用在神经网络
  • 2012:AlexNet 用 GPU + backprop 在 ImageNet 杀疯了 —— 深度学习爆发
  • 今天:autograd 让每个 PyTorch 用户都”免费”用上

反向传播是 AI 史上最重要的算法之一—— 没有它,“训练神经网络”这件事根本不成立。

💡 一句要记住

反向传播 = 链式法则 + 计算图 + 梯度从后往前传

学神经网络可以不会推 backward 公式(autograd 帮你算)—— 但得理解它在做什么:每层都拿到一个”你这一步该走多远”的信号。

这个信号的质量(不消失、不爆炸、不被噪声主导)—— 决定了模型能不能学。

下一篇推荐:L3-03 训练技巧(BatchNorm、Dropout、初始化) —— 让 backprop 在深层网络里实际能用。

🔗 被以下 1 篇文章引用
📬

读到这里说明你认真在学 🎯

订阅每周精选 —— 下一篇新文章 / 新可视化第一时间送到邮箱。

💬

讨论区

· 用 GitHub 账号登录评论
⚠️ Giscus 评论未配置 —— 在 src/components/Comments.astro 顶部填入 仓库 ID 和分类 ID(见组件注释里的配置步骤)。