2.5 反向传播:梯度的流水线
千亿个梯度,谁来算?
梯度下降的口号很响亮:「朝坡度反方向走」。但它悄悄回避了一个问题——坡度从哪来?
最笨的办法确实存在:把某个参数挪动一点点,重新跑一遍模型,看损失变了多少——这就是数值近似。对 2 个参数没问题;对 GPT 的几千亿个参数,每算一次完整梯度就要把整个模型正向跑几千亿遍。宇宙热寂了都算不完。
我们需要的是一条流水线:把模型正向算一遍、再反向扫一遍,一次拿到全部参 数的梯度。这条流水线就是反向传播,深度学习真正的发动机舱。
它的全部原理浓缩成一句口诀:
「损失对我的影响」=「损失对我下一站的影响」×「我对下一站的影响」。像接力赛:每一站只需要知道自己这一棒的情况(本地导数),把上游传来的数一乘,继续往回递。
一图看清两个方向——蓝色是前向算出预测和损失,橙色是梯度沿原路反向流回每个参数:
前向一遍(蓝,实线)+ 反向一遍(橙,虚线),全部参数的梯度就一次到手。
亲手走一遍流水线
下面是能画出来的最小网络:输入 x 经过两个权重、一个 ReLU,输出 ŷ,和目标 y = 1 比较得到损失。蓝色数字是前向算好的值。
请这样玩:
第一步:走完反向 5 步。 点「▶ 开始反向传播」,然后一路「👣 下一步」。盯着每张红框公式卡——你会发现 5 步全是同一个动作:上游梯度 × 本地导数。第 1 步从损失自己出发(∂L/∂ŷ = −1.20),之后每站接力:算 w₂ 的梯度时乘的是 a,算 a 的梯度时乘的是 w₂,过 ReLU 时乘 1(因为 z = 0.80 大于 0,闸门开着),最后到 w₁。
第二步:用梯度干活。 5 步走完,两个权重的梯度都到手了(−0.96 和 −1.20),点「⚙️ 应用更新」——这就是上一节的梯度下降迈了一步。损失从 0.360 掉到 0.145。
第三步:多来几轮。 再跑两三轮「反向 → 更新」,看底部的损失链条一轮比一轮短,直到 🎉 提示出现。恭喜,你手动完成了一次完整的神经网络训练。
为什么这条流水线如此高效?
注意一个细节:第 1 步算出的 ∂L/∂ŷ = −1.20,在第 2 步(算 w₂)和第 3 步(算 a)里被复用了两次。反向传播的精髓就在这里——从损失出发往回走,每个中间结果只算一次、处处复用。代价几乎只是把网络反着再算一遍,一趟下来所有参数的梯度全部到手。正向跑一遍 + 反向 扫一遍 ≈ 两倍模型计算,换来几千亿个梯度:这笔账,是深度学习在计算上可行的根本原因。
更妙的是,今天没有任何工程师手推这些公式。你用 PyTorch 写好前向计算,框架会自动记下计算图,然后:
loss.backward() # 反向传播:一行代码,所有参数的梯度已就位
这套机制叫自动微分。框架把每种基本运算的「本地导数」都内置好了,反向扫描时逐站套用口诀——你在实验里手动干的事,它每秒干几十亿次。
实验里的网络是一条单行道,真 实网络是有分叉和汇合的图:同一个中间变量往往被多条路径使用(就像 ∂L/∂ŷ 被两步复用那样,规模大得多),此时它的梯度要把每条路径传回来的量加起来。另外,这里只喂了一条数据 (x=2, y=1);真实训练是对一批数据分别算梯度再取平均(上一节的 mini-batch)。原理一字不改,只是账目多了求和。
小结
- 反向传播解决「梯度谁来算」:前向一遍 + 反向一遍,全部参数的梯度一次到手。
- 每一站都是同一口诀:下游梯度 = 上游梯度 × 本地导数(链式法则)。
- 中间梯度处处复用,总代价约等于两遍模型计算——这是深度学习可行的根本。
- 现代框架用自动微分把这一切藏进 loss.backward(),但出了 bug 还得靠你懂原理。
延伸阅读
- 3Blue1Brown:《反向传播究竟在做什么》(B 站官方中字)——口诀的动画版。
- Karpathy: micrograd——100 行 Python 从零实现自动微分,读完这节代码就能看懂。
- 下一节 2.6 训练实验室:三件套合体,点火。