反向传播(Backpropagation)

反向传播封面

反向传播是神经网络训练时用来「算出每个参数该怎么调」的核心算法。它把网络最终的预测误差,从输出层一层层往回传,告诉路上每一个参数:你对这个误差该负多少责任、下次该往哪个方向调。

流水线返工:一个贯穿全文的类比

想象一条流水线:原料先过加工站 A,半成品再到 B,再到 C,最后出成品。质检员发现成品不达标(误差),这份「不达标」的信息会从最后一站往回传——

  • 先告诉 C 站「你的输出偏了多少」,C 据此知道自己该怎么调;
  • 同时 C 算出「那 B 交给我的料子也有问题」,把这条消息往回递给 B;
  • B 再照样追溯给 A……

每个站收到的「调整指令」,就是梯度(gradient,一个表示「该往哪个方向调、调多少」的数)。神经网络就是这条流水线,反向传播就是「误差从后往前、逐站分摊责任」的过程。

它在 AI 体系中的位置

反向传播属于深度学习训练的「中枢」环节。一次完整的训练由四步循环组成,反向传播负责其中关键的第三步:

  1. 前向传播(forward):数据从前往后走,得出网络的预测。
  2. 算损失(loss):拿预测值和真实答案比对,得出误差有多大——这正是损失函数干的事。
  3. 反向传播(backward):误差从后往前传,算出每个参数的梯度。
  4. 更新参数:用梯度下降,按梯度方向把每个参数调一点,让下一次更准。

四步不断循环,网络就一点点「学」会了任务。可以说,没有反向传播高效地算出梯度,训练多层网络几乎寸步难行。

核心数学:链式法则

反向传播的数学基础就是微积分里的链式法则(chain rule)。神经网络的本质是一个层层嵌套的复合函数:输入经过第一层得到中间结果,再喂进第二层,再喂进第三层……最后算出损失。链式法则告诉我们怎么对这种「函数套函数」的结构求导。

以最简单的一段为例——输入 xx 经过一个权重 ww 算出 z=wxz = wx,再过一个激活函数得到 a=σ(z)a = \sigma(z),最后和真实值 yy 比较得到损失 LL。要求「ww 该负多少责任」,就把这三个环节的导数乘起来:

Lw=La输出端误差az激活函数的传导zw权重的影响\frac{\partial L}{\partial w} = \underbrace{\frac{\partial L}{\partial a}}_{\text{输出端误差}} \cdot \underbrace{\frac{\partial a}{\partial z}}_{\text{激活函数的传导}} \cdot \underbrace{\frac{\partial z}{\partial w}}_{\text{权重的影响}}

符号逐项解读

  • \partial —— 偏导数符号,读作"partial",L/w\partial L/\partial w 整个读作"LLww 的偏导数",表示 ww 动一点时 LL 跟着变多少,即梯度
  • Lw\frac{\partial L}{\partial w} —— 损失 LL 对权重 ww 的梯度,也就是「ww 该往哪调、调多少才能减小误差」。这是反向传播最终想算出来的东西。
  • La\frac{\partial L}{\partial a} —— 误差信号从输出端出发的初始值(损失对网络输出的敏感度)。
  • σ\sigma —— 激活函数的记号,本文里就是 sigmoid,把 zz 压到 0~1
  • σ(z)\sigma'(z) —— 上标撇号 ' 是求导记号,σ(z)\sigma'(z) 表示激活函数 σ\sigmazz 的导数
  • az\frac{\partial a}{\partial z} —— 信号经过激活函数这一关时,被放大或缩小了多少。
  • zw\frac{\partial z}{\partial w} —— 权重 ww 对中间值 zz 的影响(在这个例子里它就等于输入 xx)。

通俗理解:每个参数的「责任」= 输出端的误差 × 信号往回传时每一关的「传导系数」连乘。回到流水线类比——C 站的调整力度,等于「最终成品的偏差」乘上「B 到 C 之间信号衰减」再乘上「A 到 B 之间信号衰减」。这正是「反向传播」名字的由来:信号是反向流动的。

链式法则三因子相乘

图 1:权重的梯度 = 输出端误差 × 激活函数传导 × 权重影响,三个因子从右往左连乘。

算一个小例子

w=0.5w = 0.5x=2x = 2,激活函数用 sigmoid,真实值 y=1y = 1

  • z=wx=0.5×2=1.0z = wx = 0.5 \times 2 = 1.0
  • a=σ(z)=11+e10.731a = \sigma(z) = \frac{1}{1+e^{-1}} \approx 0.731
  • L=12(ay)2=12(0.7311)20.0362L = \frac{1}{2}(a - y)^2 = \frac{1}{2}(0.731 - 1)^2 \approx 0.0362

(式中的 ee 是自然对数的底,约等于 2.718,是一个数学常数。)

现在用链式法则往回算 Lw\frac{\partial L}{\partial w}

步骤计算结果
输出端误差 La\frac{\partial L}{\partial a}aya - y0.7311=0.2690.731 - 1 = -0.269
激活函数传导 az\frac{\partial a}{\partial z}σ(z)=a(1a)\sigma'(z) = a(1-a)0.731×0.2690.1970.731 \times 0.269 \approx 0.197
权重影响 zw\frac{\partial z}{\partial w}xx22
三者相乘得 Lw\frac{\partial L}{\partial w}0.269×0.197×2-0.269 \times 0.197 \times 20.106\approx -0.106

自检:梯度为负,说明稍微增大 ww 就能让损失变小——这合理吗?目标 y=1y=1,而当前预测 a=0.731a=0.731 偏小,增大 ww 会让 zz 变大、aa 更接近 1、损失下降。方向正确。做梯度下降时 w=wlr×(0.106)w_{\text{新}} = w - \text{lr} \times (-0.106),会把 ww 往大调,正是我们要的。

符号解读:

  • lr\text{lr} —— 学习率,每次调权重的步伐大小
  • ww_{\text{新}} —— 更新后的新权重
梯度逐步乘出来

图 2:把小例子的三个数 −0.269、0.197、2 连乘,得到权重梯度 −0.106。

为什么它这么重要

在反向传播被广泛使用之前,训练多层神经网络极其困难——人们不知道怎么高效地算出成千上万个参数各自的梯度。反向传播(本质上是反向模式的自动微分)把这个计算组织成一次从后往前的扫掠,效率极高:网络再深,算梯度也只需走一遍。

正是有了它,深度学习才从「理论可行」变成「实际可训」,今天的大模型才训练得动。

反向扫掠一次算出全部梯度

图 3:反向传播只需从输出往输入扫一遍,沿途顺手收集每一层权重的梯度。

完整代码

下面用 PyTorch 写一个能真学会任务的小网络。整体流程是「造大脑 → 备数据 → 反复练习(前向 → 算误差 → 反向 → 更新)→ 考试」——训练循环里每一步的顺序不能乱:先看预测准不准(前向 + 损失),再回头分摊责任(反向传播),最后才动参数(更新)。

python
import torch
import torch.nn as nn  # nn = neural network,PyTorch 搭神经网络用的工具箱

# ===== 任务:教神经网络学会「复习时长 → 考试分数」=====
# 准备 4 组有真实含义的数据(小时数 → 分数),趋势是学得越久分越高
hours = torch.tensor([[1.0], [2.0], [3.0], [4.0]])        # torch.tensor:把数据变成 PyTorch 的多维数组(张量)
scores = torch.tensor([[52.0], [65.0], [78.0], [91.0]])   # 目标分数

# ===== 定义两层小网络:信号要穿过两层,「反向」才有链条可言 =====
class ScoreNet(nn.Module):              # nn.Module:所有神经网络模块的基类,继承它 PyTorch 才能帮你管理参数
    def __init__(self):
        super().__init__()
        self.layer1 = nn.Linear(1, 8)   # 第一层:1 个输入 → 8 个中间值(nn.Linear = 全连接层)
        self.act = nn.ReLU()            # 激活函数:把负数压成 0,给网络「非线性」能力
        self.layer2 = nn.Linear(8, 1)   # 第二层:8 个中间值 → 1 个输出(预测分数)
    def forward(self, x):              # forward:定义数据「从前往后」怎么走,即前向传播
        out = self.layer1(x)           # 先过第一层
        out = self.act(out)            # 再过激活函数
        out = self.layer2(out)         # 再过第二层,得到预测分数
        return out

torch.manual_seed(42)                  # 固定随机数种子,保证每次运行结果可复现
net = ScoreNet()
loss_fn = nn.MSELoss()                 # 均方误差:衡量预测分数和真实分数差多少(损失函数)
opt = torch.optim.Adam(net.parameters(), lr=0.1)  # Adam 优化器:自适应学习率,比普通梯度下降收敛更快更稳,lr 是学习率(每次调整的步伐大小)

# ===== 训练循环:反复「前向 → 算误差 → 反向传播 → 更新参数」=====
for epoch in range(500):               # 把 4 组数据反复看 500 遍
    pred = net(hours)                  # ① 前向传播:输入复习时长,得到预测分数
    loss = loss_fn(pred, scores)       # ② 算损失:预测分数离真实分数有多远
    opt.zero_grad()                    # ③ 清空上一轮残留的梯度(不清空会累加,方向就算错)
    loss.backward()                    # ④ 反向传播!自动用链式法则,从损失往回算出每个参数的梯度
    opt.step()                         # ⑤ 梯度下降:按梯度把每个参数往「让损失更小」的方向调一点

# ===== 考试:网络学会了吗?预测「复习 5 小时」能考多少分 =====
test_hour = torch.tensor([[5.0]])
print(f"复习 5 小时,网络预测能考:{net(test_hour).item():.1f} 分")
print(f"训练损失:{loss.item():.4f}(越小越准)")

运行后会看到网络预测「复习 5 小时」能考约 104 分——它真的从 4 组数据里学到了「每小时约涨 13 分」的趋势(52 → 65 → 78 → 91 → 104,正好接上)。其中 loss.backward() 这一行,就是反向传播在代码里的化身:框架自动用链式法则,把穿过两层的梯度一口气算好,你完全不用手写求导。

小结

前向传播让数据从前往后走出一个预测,反向传播则让误差从后往前传回去,算清每个参数该怎么调。一个是「往前走」,一个是「往回传」,两者一去一回,神经网络才真正「学」起来。

参考资料

  1. Backpropagation calculus — 3Blue1Brown(深度学习系列第 4 章,最直观的反向传播可视化讲解) https://www.3blue1brown.com/lessons/backpropagation-calculus
  2. Neural Networks: Backpropagation — CS231n(斯坦福深度学习课程笔记,含计算图与梯度推导) https://cs231n.github.io/optimization-2/
  3. 反向传播算法 — 维基百科 https://zh.wikipedia.org/zh-cn/反向传播算法