反向传播:链式法则的优雅
上百万个权重,每个都该为最终的错误负多少责?反向传播用一条两百年前的数学法则,把这笔账算得又快又准。这一章我们亲手把这笔账算一遍。
5.1 问题回顾:百万权重,谁来背锅
上一章我们有了损失函数:它把「网络现在有多差」压缩成一个数字 L。但光知道总分差并没有用——真正的问题是:网络里躺着上百万个权重,每个权重该为这份「差」负多大责任?
有个笨办法:每次把一个权重稍微扰动一点点,重新跑一遍前向传播,看损失变了多少,变化幅度就是这个权重的责任。但一次前向只能测一个权重,一百万个权重就要跑一百万次前向——按今天的算力也得算到天荒地老。
我们需要的是:每个权重对损失的「责任率」,也就是偏导数 ∂L/∂w。而反向传播(Backpropagation)给出的承诺近乎奢侈:只跑一遍反向,就同时算出一百万个权重的责任,总代价大约只是两遍前向传播。
项目搞砸了,CEO(损失)要追责。高效的做法不是把每个员工单独叫来试一遍,而是沿着汇报链逐级分解:CEO 问部门总监,总监问组长,组长问组员。每一级只需要回答一个问题——「我的直接上级受我多大影响?」
5.2 链式法则:下游梯度 × 局部导数
反向传播的全部数学根基,是微积分里的链式法则。设 x 影响 y,y 又影响 L,那么 x 对 L 的影响就是两段影响的乘积:
∂L/∂x = (∂L/∂y)·(∂y/∂x)读法很直白:x 对最终损失的责任 = 下游传回来的梯度 × 本地的局部导数。举个最小例子:y = 3x,L = y2,取 x = 2,则 y = 6。下游梯度 ∂L/∂y = 2y = 12,局部导数 ∂y/∂x = 3,于是 ∂L/∂x = 12 × 3 = 36——x 每增加一点点,损失大约增加 36 倍那么多。
关键在于:每一层只需要知道自己的局部导数(比如「我是乘 3 的,我的局部导数就是 3」),再从下游接过梯度相乘,就能把自己的那份责任继续往上游传。责任像接力棒一样,一层一层往回传,这就是「反向传播」这个名字的由来。
为什么必须「从下游往回传」而不能正着来?因为 ∂L/∂y 里已经累积了 y 之后所有层的影响。只有最靠近损失的层先算出自己的责任,更前面的层才有东西可接——顺序只能是反向的。
5.3 计算图实战:一笔账算到底
把计算画成一张图就清楚了:每个节点是一个中间变量,每条边是一次运算,边上标注的是这次运算的局部导数。前向传播沿箭头算出数值;反向传播逆着箭头,在每个节点把「收到的梯度 × 边的局部导数」继续往上传。
下面这张图实现的是 e = (a + b) · c。注意两条经验:加法节点把梯度原样平分(∂s/∂a 和 ∂s/∂b 都是 1,来多少传多少);乘法节点把梯度乘以「另一个操作数」(∂e/∂s = c,∂e/∂c = s,像交换了手里的牌)。
演示 1 · 计算图:前向与反向
可点击节点点击任意节点,查看它的局部导数与梯度来源。
试试:把 c 拖到很大(比如 2.8),再微调 a——∂e/∂a 恒等于 c,a 的责任被 c 放大了。这就是为什么上游的梯度大小,取决于下游的数值。
把每个节点换成一层网络、每条边换成一次矩阵乘法,这张小图就是一个真实的神经网络——反向传播的做法一模一样,只是节点和边多了几百万倍。
5.4 梯度消失与爆炸:连乘的毁灭与疯狂
现在把链式法则用到 N 层的深网络上。反向传播时,梯度每穿过一层都要乘上这一层的「梯度因子」g(权重、激活函数导数等的综合效果)。传到第 1 层时,梯度已经被连乘了 N 次:
第 1 层拿到的梯度 ≈ g × g × … × g = gN连乘是最不讲情面的运算。g 只要比 1 小一点点,比如 0.9,乘 50 次就只剩约 0.005——前面几十层的权重几乎收不到任何更新信号,这就是梯度消失。还记得第 2 章的 sigmoid 吗?它进入饱和区后导数趋近于 0,不饱和时最大也只有 0.25,几十层 sigmoid 叠起来,梯度还没传到家门口就蒸发干净了。
反过来,g 只要比 1 大一点点,比如 1.5,乘 50 次就是 6 亿多——权重更新一步就飞出十万八千里,损失直接爆炸成 NaN,这就是梯度爆炸。
演示 2 · 连乘的毁灭与疯狂
对数坐标y 轴是对数坐标:每格相差 10 倍。虚线是 gn = 1 的「不增不减」基准。这就是深网络难训练的根源,也是残差连接、批归一化这些技术存在的理由——它们千方百计把每层的 g 维持在 1 附近。
注意一个残酷的不对称:g 偏离 1 只要一点点,结果就天差地别,而且层数越深惩罚越狠。这就是为什么浅层网络随便训都能动,而深网络在没有现代技巧护航的年代几乎无法训练。
5.5 反向传播 = 自动微分:框架替你记账
回头总结一下我们到底做了什么:在计算图上,对每条边用了一次链式法则。没有更多,也没有更少。这套机制有个更正式的名字——自动微分(Automatic Differentiation,反向模式)。
PyTorch、TensorFlow 这些框架做的事情,本质就是替你维护这本账:你写前向代码,它默默记下每个节点的局部导数;你调用一行 loss.backward(),它就沿着图反向走一遍,把每个参数的梯度算好放在那里。框架可以替你记账,但你必须懂账本的结构——否则面对「梯度为什么是 0」「损失为什么是 NaN」这类问题时,你连账都查不了。
反向传播不是一种独立于梯度下降的学习算法,它既不更新权重,也不决定学习率——它只是一种高效计算梯度的记账法。真正的「学习」发生在梯度算完之后:由梯度下降(下一章的主角)拿着这些梯度去更新权重。反向传播负责算账,梯度下降负责动手,别把它们混为一谈。
反向传播 = 在计算图上对每条边用一次链式法则:下游梯度 × 局部导数,逐层回传。一次反向就能算出所有权重的梯度,代价仅约两倍前向。加法节点平分梯度,乘法节点交换操作数。深网络里梯度因子连乘,g 略小于 1 则消失、略大于 1 则爆炸——这决定了整个深度学习工程的主旋律:把每层的 g 维持在 1 附近。