从零开始学习大模型(十):手算一个神经网络的反向传播

反向传播的核心是沿计算图应用链式法则。网络复杂时,框架负责记录依赖与复用中间结果;理解一个小例子,可以帮助排查损失不下降、梯度为零或参数未更新的问题。

本文选用平方损失与一个 ReLU 隐藏单元,所有输入和参数均为教学值,不使用真实业务数据。

1. 定义网络与输入

网络包含两步变换:z = w₁x₁ + w₂x₂ + b,h = ReLU(z);输出为 ŷ = vh + c,损失为 L = 0.5(ŷ − y)²。

量 取值 含义
输入 x₁、x₂ 1、2 固定输入
权重 w₁、w₂ 0.5、−0.5 输入到隐藏层
偏置 b 1 隐藏层偏置
权重 v、偏置 c 2、0 隐藏层到输出
目标 y 0 本次训练标签

这里需要训练五个标量参数。输入与标签可以参与函数计算,但通常不作为待更新的模型参数。

2. 先完成前向计算

代入得到 z = 0.5×1 − 0.5×2 + 1 = 0.5,因此 h = 0.5。输出为 2×0.5 + 0 = 1,损失为 0.5。

前向阶段的中间结果会被用于求导。不能在计算完梯度之前任意覆盖这些参数或激活值,否则会破坏导数所对应的计算过程。

损失沿输出层、激活函数和隐藏线性层反向传递梯度的计算图示意

图 1:反向传播沿计算依赖求导,方向与前向生成激活的方向相反。

3. 从损失逐层求导

输出误差为 e = ŷ − y = 1,因此 ∂L/∂ŷ = 1。输出层梯度为 ∂L/∂v = eh = 0.5、∂L/∂c = e = 1,同时传给隐藏单元的梯度是 ∂L/∂h = ev = 2。

本例 z 大于零,ReLU 在该点的导数为 1,因此 ∂L/∂z = 2。继续向输入层求导:

参数 链式法则 梯度
w₁ (∂L/∂z) × x₁ 2
w₂ (∂L/∂z) × x₂ 4
b ∂L/∂z 2
v (∂L/∂ŷ) × h 0.5
c ∂L/∂ŷ 1

如果同一个中间变量通过多条路径影响损失,需要把各路径贡献相加。反向传播不是在分叉处随意选择一条路径。

4. 用有限差分独立核对梯度

中心差分用 [L(θ+ε) − L(θ−ε)] / (2ε) 近似导数。它适合检查小型网络,但每个参数都需要额外前向计算,不能替代大型模型的高效训练。

from math import isclose

def loss(p):
    w1, w2, b, v, c = p
    h = max(0.0, w1 + 2.0 * w2 + b)
    return 0.5 * (v * h + c) ** 2

parameters = [0.5, -0.5, 1.0, 2.0, 0.0]
expected = [2.0, 4.0, 2.0, 0.5, 1.0]
epsilon = 1e-5
for i, gradient in enumerate(expected):
    plus, minus = parameters.copy(), parameters.copy()
    plus[i] += epsilon
    minus[i] -= epsilon
    numeric = (loss(plus) - loss(minus)) / (2 * epsilon)
    assert isclose(numeric, gradient, rel_tol=1e-7)

updated = [p - 0.1 * g for p, g in zip(parameters, expected)]
assert isclose(loss(parameters), 0.5)
assert isclose(loss(updated), 0.005)
print(round(loss(updated), 4))

本例使用学习率 0.1 后损失下降,但这不保证任意网络和学习率都如此。有限差分也受舍入误差与步长选择影响;在 ReLU 的零点附近,函数不可导,不能要求数值近似与框架约定始终一致。

5. 自动微分实际负责什么

常见反向模式自动微分根据实际执行的运算建立依赖,计算损失相对于叶子参数的梯度。它计算的是程序定义的函数,并不检查目标设计是否符合真实需求。

反向传播不等于更新参数。优化器读取梯度后,才按梯度下降、动量或 Adam 等规则调整权重。许多框架会累加梯度,因此新一轮训练前需要显式清理,或有意按照梯度累积策略处理。

还要注意张量形状。广播可能使程序正常运行,却计算了不符合预期的损失;梯度存在也不证明训练逻辑正确。检查预测与标签维度、损失归约方式及参数是否被优化器管理,是必要步骤。

6. 小结

先确定前向依赖,再从标量损失反向应用链式法则,可以逐项得到参数梯度。数值检查用于核对局部实现,验证集则用于检验训练结果,两者解决不同问题。

下一篇使用 PyTorch 完成可运行的训练循环。更完整的训练流程可参考 PyTorch:Optimizing Model Parameters。

本阶段目录

  1. 神经网络为什么需要非线性?
  2. 手算一个神经网络的反向传播
  3. 用 PyTorch 训练第一个神经网络
  4. CNN 如何通过卷积提取图像特征?
  5. 从经典 CNN 到 ResNet,深层网络如何训练?

系列学习路线

上一篇:神经网络为什么需要非线性?

下一篇:用 PyTorch 训练第一个神经网络