25

反向传播——梯度是怎么一层一层算回来的

阅读约 6 分钟第 25/60 课

前向传播算出了 loss。现在问题是:十亿个参数中,每一个参数应该往哪个方向调、调多少?

答案是:算梯度。

梯度是什么

梯度就是「loss 对某个参数的偏导数」——如果把这个参数增大一点点,loss 会变大还是变小,变多少。

梯度为正 → 参数增大会让 loss 增大 → 所以应该减小这个参数
梯度为负 → 参数增大会让 loss 减小 → 所以应该增大这个参数
梯度接近零 → 这个参数对 loss 影响很小 → 不太需要调

梯度就像山坡上的指南针——它告诉你「往哪个方向走是下坡」。训练就是顺着这个方向一步一步走,让 loss 越来越低。

为什么不能直接算

你可能会想:直接把每个参数微调一点(比如加 0.0001),算一次 loss,看看变了多少,不就知道梯度了吗?

理论上可以,但计算量完全不可承受。Sophia 有十亿个参数。如果对每个参数都做一次微调并算一次 loss,就需要做十亿次前向传播——而一次前向传播本身就需要几十亿次浮点运算。总计算量是 10⁹ × 10⁹ = 10¹⁸ 次运算。

反向传播的巧妙之处在于:只需要一次前向传播 + 一次反向传播,就能同时算出所有十亿个参数的梯度。 计算量大约是前向传播的两倍——而不是十亿倍。

链式法则

反向传播的数学基础是链式法则(chain rule)。

假设有一个简单的计算链:

a → b = f(a) → c = g(b) → loss = h(c)

loss 对 a 的梯度可以这样算:

∂loss/∂a = ∂loss/∂c × ∂c/∂b × ∂b/∂a

每一步的局部梯度相乘,就得到了从 loss 到任意参数的梯度。

在实际的神经网络中,计算链有几百步(28 层,每层好几个操作),涉及矩阵乘法、激活函数、softmax 等。但原理完全一样——从 loss 开始,逐步往回算每一步的局部梯度,通过链式法则连乘得到每个参数的梯度。

反向传播的流程

前向传播从输入到 loss:

嵌入 → 层1 → 层2 → ... → 层28 → 输出层 → loss

反向传播从 loss 往回走:

loss → 输出层 → 层28 → ... → 层2 → 层1 → 嵌入

每经过一层,反向传播做两件事:

  1. 把梯度传给上一层(继续往回传)。
  2. 计算这一层自己的参数的梯度(存起来,等一下用来更新参数)。

当反向传播走完整个网络之后,每一个参数都有了自己的梯度——一个数字,告诉你把这个参数往哪个方向调能减小 loss,以及调的幅度应该多大。

一个数字例子

用一个两层的迷你网络来走一遍。假设:

输入 x = 2
第一层:y = w₁ × x      (w₁ = 3)
第二层:z = w₂ × y      (w₂ = 4)
loss = (z - 目标)²       (目标 = 20)

前向传播:

y = 3 × 2 = 6
z = 4 × 6 = 24
loss = (24 - 20)² = 16

反向传播(从 loss 往回):

∂loss/∂z = 2 × (z - 目标) = 2 × (24 - 20) = 8

∂z/∂w₂ = y = 6
∂loss/∂w₂ = ∂loss/∂z × ∂z/∂w₂ = 8 × 6 = 48
→ w₂ 的梯度是 48(正数,说明 w₂ 应该减小)

∂z/∂y = w₂ = 4
∂loss/∂y = ∂loss/∂z × ∂z/∂y = 8 × 4 = 32

∂y/∂w₁ = x = 2
∂loss/∂w₁ = ∂loss/∂y × ∂y/∂w₁ = 32 × 2 = 64
→ w₁ 的梯度是 64(正数,说明 w₁ 应该减小)

两个参数的梯度在一次反向传播中全部算出来了。

更新参数

有了梯度之后,更新规则很简单:

新参数 = 旧参数 − 学习率 × 梯度

学习率是一个很小的正数(比如 0.001),控制每一步走多远。太大会跳过最优点,太小会走得极慢。

w₂_new = 4 − 0.001 × 48 = 3.952
w₁_new = 3 − 0.001 × 64 = 2.936

两个参数都变小了一点——因为它们的梯度都是正的,说明它们太大了。调小之后,z 会更接近目标值 20,loss 会减小。

这个「前向传播 → 算 loss → 反向传播 → 更新参数」的循环重复几十亿次,就是训练。

下一章节来看 PyTorch 怎么自动帮你完成反向传播——你不需要手写链式法则,只需要定义前向传播,梯度就自动算出来了。