损失函数告诉你"错了多少",但没告诉你该改哪个参数。 一个模型可能有几千亿个参数,逐个试探是天文数字。 反向传播就是回答这个问题的办法:先正着跑一遍(前向)、再倒着走一遍(反向), 一遍拿到全部参数的梯度——每个参数一个数,说它往上调一点,损失会变大还是变小、变多快。 它靠的是链式法则(一个复合的变化,等于每一小段放大倍数的乘积)加一点记账技巧,不是玄学。
最朴素的办法是逐个试探:把第 i 个参数加上一点点,跑一遍前向算出损失;再减掉一点点,
再跑一遍。
这个办法在数学上完全正确,它叫数值微分。问题是代价:每试一个参数,都要把整张网络从头跑一遍,
前面几十层白算了。
另一种办法叫
蛮力法 vs 反向传播:真实的前向次数对比
产品出问题了,要查是哪个环节的责任。
逐个试探像把每个工人单独换掉一次,看产品会不会变好,有多少工人就要重开多少次产线。
反向传播像从最后一道工序往前倒推:每道工序只回答「我往下传的责任要打几折」,
走一遍就把所有环节都算清楚了。
这个类比里唯一对不上的地方:工厂的责任是分摊的,这里是相乘的。 数学那节那张公式卡会告诉你为什么。
反向传播只负责算出梯度。拿到梯度之后怎么走,是另一件事: 想象你被空投到山上,蒙着眼,只能感觉到脚下的倾斜。想去山谷, 那就每一步都朝最陡的下坡方向挪一小步。 这一章只用最朴素的走法演示梯度怎么用;怎么走得更快、更稳,是下一章《优化器》的事。
为什么朝最陡的下坡走,损失就会变小?梯度说的就是「往哪个方向动损失变化最快」; 步子不太大时,沿它的反方向走一小步就往下。
互动 · 拖学习率,看小球怎么走
| 学习率 | 会发生什么 | 现象 |
|---|---|---|
| 太小 (0.01) | 走得太慢 | 几十步才挪到谷底 |
| 合适 (0.05 ~ 0.2) | 稳定下降 | 几步到十几步到底 ✅ |
| 偏大 (0.25 ~ 0.3) | 还能到底,但开始绕 | 步数成倍变多:0.3 要三十多步 |
| 更偏大 (0.31 ~ 0.33) | 拖很久,或在两个坡之间弹 | 0.32 要一两百步;0.33 一直在弹 |
| 再大 (0.34 ~ 0.5) | 一直弹下去 | 损失上下交替,再也降不下去 |
把
反向传播算出梯度,梯度下降用这个梯度:一个指方向,一个迈步子。 这一章的重点是前一个;第 2 节只是用最朴素的走法演示后者,怎么走得更好是下一章《优化器》。
上一节说「沿着梯度往下走」。但那个梯度是从哪来的?
反向传播的数学只有一句话:把沿途每一小段的局部导数乘起来。
先说记号:∂L/∂a 读作「a 动一点,L 跟着动几倍」,∂ 的意思是「只让这一个量动」。
图里的激活写成 σ(z):σ 就是《激活函数》那章的 Sigmoid,局部导数是 a(1 − a)——拖滑块可以拿 a 的读数自己验算。
下面这张图就是这句话的全部,数字全是当场算的。
拖 w 看它怎么变;第 5 节用同一套乘法算一个真网络,数学那节还有一张图让你亲手把这串乘法乘塌。
一条链,把责任往回传(拖权重看全过程)
开头那个「工厂追责」在这里对上了:每一段都在上一段放大过的结果上再放大一次,所以是乘不是加。
公式里的每一项,在图上都是哪一段
这张图比第 3 节多一个节点:把 z = w·x + b 拆成两步——
u = w·x,再 z = u + b。
u 只是中间积;拆开是为了看清乘法和加法各自的导数规则。
公式里每一项下面都有一条虚线,鼠标划过它就等于划过图上那一段:两边是同一个高亮状态。
互动 · 一个最小的计算图
| 节点 | 局部导数 | 梯度怎么传下去 |
|---|---|---|
加法 c = p + q |
∂c/∂p = 1 | 原样分发给两个输入 |
乘法 c = p·q |
∂c/∂p = q | 乘上「另一个输入」 |
激活 a = tanh(z) |
∂a/∂z = 1 − a² | 乘上一个衰减因子:a 越接近 ±1,传下去的越少 |
整套反向传播,就是这三种节点按顺序反复执行。框架里的 autograd 做的事,
就是给每种运算预先写好一份「倒着走」的规则。
能,手推就是那么干的。但网络一深,整个表达式会长到写不出来。 计算图让你不用展开整个式子,只按局部规则一步步走。框架走的也是这条路。
下面是一个真实的
互动 · 逐步走一遍前向与反向
自己手推反向传播时,写错一个符号或者漏掉一项,前向完全看不出来: 损失照样在降,只是降得慢,或者停在错误的地方。 数值梯度就是一把标尺,它慢得没法用来训练,但足够精确,可以当标准答案。 工业界实现一个新算子时,第一步永远是用它做梯度检查(gradient check)。
但标尺不能当引擎:扰动量取得太大,截断误差大;取得太小,浮点数的舍入误差吃掉一切。 双精度下最佳扰动量在 1e-5 ~ 1e-6,能做到的精度也就 1e-9 ~ 1e-10 左右。 而且它要跑 2N 次前向,第 1 节已经量过那个代价了。
前面都是具体数字。这一节把它写成一般形式。这是全站三处完整推导之一 (另两处是注意力的 QKV、以及 softmax 与交叉熵的求导)。 它们之所以值得推,是因为后面所有内容都是这三块的组合。
至于「为什么是乘不是加」,把沿途每一小段的「敏感度」乘起来,就是整条路的敏感度。 不是加起来,因为传导是叠加的放大,不是并列的分摊。
假设有一台机器,把输入放大 3 倍。再来一台,把输入放大 5 倍。串起来是多少倍? 15 倍,就是 3 × 5。放大倍数是要相乘的。
导数就是「局部的放大倍数」。∂z/∂w 说的是「w 动一点,z 动几倍」;
∂L/∂a 说的是「a 动一点,L 动几倍」。
一路串下来,总倍数是每一段的倍数相乘,这就是链式法则。
| 如果改成加 | 会得到什么(错的) |
|---|---|
∂L/∂w = ∂L/∂a + ∂a/∂z + ∂z/∂w |
量纲都不对。三项的单位互不相同,加起来没有意义 |
拿第 3 节那条链的当前值验算:— |
≈ —,而真实梯度是 —,连正负号都不对 |
用乘法验算:— |
= — ✅ 和数值微分对得上 |
互动 · 每层放大一点,乘起来会怎样
导数的定义是「让它动一点点,看结果动多少」:
df/dx = limh→0 (f(x+h) − f(x)) / h。
把这条定义套到复合函数上,推导只有三行。
第 2 行那个关键的「先乘后除」:把分子分母同时乘上 g(x+h) − g(x)。
这是唯一的一步技巧,剩下的全是在消项。
最后一步之所以能写,是因为 h→0 时 g(x+h) → g(x)(g 连续)。
上面这个"分子分母同乘一项"的写法在数学上不严谨,中间会出现 0/0。
严格的证明要用到 ε-δ 语言,写出来大概一章。
但本项目不这么写:你不是在做数学作业,你是在理解一台机器怎么转。 上面这段推导给出了正确的直觉和正确的结论,而严谨版本不会让你多懂一点点。
诚实标注:此处牺牲了严谨性,换取了可读性。
链式法则只是数学,让它变成可行算法的是计算顺序。 这是一条用时间换空间的界线:算得快,代价是要把前向时每一层的中间结果都存下来。
| 做法 | 要算多少次 | 为什么 |
|---|---|---|
| 逐参数暴力 | O(N²) | 对 N 个参数中的每一个,都要把整张图重新跑一遍。第 1 节已经量过这个代价 |
| 反向传播 | O(N) | 从右往左走一遍:每个边只算一次局部导数,每条边的结果被所有上游共用 |
| 那个"共用"是什么 | — | 算到 ∂L/∂z 时,无论后面要推 ∂L/∂w 还是 ∂L/∂b,
都要用它。存一次、用两次,这就是省下来的全部 |
公司里有 500 个人要报销。做法一:每个人单独跑一趟财务,财务每次都要重新翻一遍所有单据,跑 500 趟。
做法二:先按部门把单据汇总成一张总表,每个部门算一次,每个人再把自己那份接上去,一遍走完。
中间那张「部门总表」就是 ∂L/∂z。反向传播省下的钱,全在这张表被复用了。
所以它才敢叫「反向」,而不是「对每个参数各算一次」。
反向传播给了你"一个样本的梯度"。但训练集有几十万条。 是全部用上,还是一条一条来?
互动 · 同一片地形上的三种策略
| 比什么 | 批量梯度下降 BGD | 随机梯度下降 SGD | 小批量 Mini-batch |
|---|---|---|---|
| 每次用多少数据 | 全部数据 | 1 条 | B 条(32~4096) |
| 梯度准不准 | 完全准确 | 噪声很大 | 噪声 ~ 1/√B |
| 一轮要算几次 | 1 次 | M 次(M = 训练数据条数) | M/B 次 |
| 能否并行 | 能,但 GPU 吃不饱 | 几乎不能 | 能,且利用率高 |
| 能不能跳出局部最优 | 不能,容易卡住 | 噪声帮它跳出去 | 兼顾两者 |
| 实际用吗 | 小数据集偶尔用 | 在线学习用 | 实际训练的标准做法 |
SGD 的噪声不是缺点,是特性。 没有噪声,梯度下降滑进局部极小就再出不来;有噪声,它有概率被抖出来。 小批量往往比全批量泛化更好,这现象被反复验证过,但至今没有满意的理论解释。
为什么不直接把学习率调大?噪声不是步子大小带来的,是「只抽了一部分数据」带来的; 学习率把噪声也一起放大,反而更容易被带偏。降噪声只有一个办法:一次多抽几条数据。
互动 · 批大小一变,梯度抖得多厉害(当场抽样本算)
代价是批大小和学习率被绑在了一起。经验规则是:batch 放大 k 倍,学习率也放大 k 倍, 这条叫「线性缩放规则」(Goyal 等 2017);大 batch 时改用平方根缩放更稳。 很多论文只报了 batch size 没提学习率怎么调,复现就会很难。
上面那张表比的是「一次用多少数据」;这张表比的是梯度本身怎么算出来。 反向传播不是唯一的路,只是唯一能撑起深度学习的路。区别都在公式那一列。
| 方法 | 一行公式 | 成本 | 特点 |
|---|---|---|---|
| 反向传播 反向模式自动微分 |
∂L/∂w = (∂L/∂a)·(∂a/∂w) | 一次前向 + 一次反向 | 本页讲的就是它。深度学习能跑起来的地基,输出是标量时效率最优 |
| 前向模式自动微分 | ∂y/∂w = (∂y/∂z)·(∂z/∂w) 同一个式子,只是从输入那端往前乘 |
每个输入变量都要跑一遍 | 输入维度远小于输出时才好用。物理仿真里常见 |
| 数值微分 | (L(w+h) − L(w−h)) / (2h) | 2N 次前向(中心差分) | 慢到不能训练,但可以当标尺,用来做梯度检查 |
| 符号微分 | ∂(x²+3x)/∂x → 2x+3 先化简,再算 |
表达式会指数膨胀 | Mathematica 那类符号计算系统用的。深度学习里不实用 |
| 伴随法 | λ = ∂L/∂x(t) 伴随变量;反向解一个 ODE(微分方程) |
一次前向 + 一次反向 | 神经 ODE、物理仿真里都有它 |
| 直通估计器 STE | ∂L/∂x ≔ ∂L/∂y 硬当导数是 1 |
便宜 | 数学上不对,工程上有效:把权重压成整数、中间结果离散成编号的模型都靠它 |
| 梯度检查点 | 只存 √层数 个激活,其余反向时重算 | 时间多 ~25%,显存省 60%+ | 长序列、大模型训练的标配 |
分工要记清楚:反向传播只负责算出梯度,它没说怎么用这个梯度。 往哪走、走多大一步是优化器的事; 梯度能不能传回去,由初始化决定。 这三件事经常被混在一起说成「训练」,但它们是三个独立的问题。
把第 3 节那条只算一个神经元的链,换成几十层、每层几百个神经元的网络,流程完全一样,只是链条长得多。
互动 · 前向流动 vs 反向流动
前向(绿):数据从左往右流,每一层把中间结果存下来。
反向(粉):梯度从右往左流,每一层用当年存下的中间结果,算出自己的局部梯度。
注意那个「存」字,它是训练比推理贵得多的根源。
互动 ·
把鼠标停在下面公式里的 batch / 序列长度 / 层数 / d 上,对应的那根滑杆会亮起来。
推理用完就扔;训练必须把每层激活留到反向,链越长留得越多——训练显存可能是推理的十几倍。 算力是另一笔:一次训练步 ≈ 3 次前向(反向 ≈ 两遍前向)。 梯度检查点只存少数"检查点"、其余重算,用 20~30% 的速度换 60% 以上的显存。
爆炸好治,消失难治。因为已经乘成 0 的东西,你没法再乘回来。
残差连接 · 给梯度修一条近路
前向时输入绕过这一层、直接加到输出上;反向时梯度沿近路原样回到输入。
只能减缓,治不好。只要链够长,一长串小于 1 的因子照样连乘到零。 这就是残差连接存在的理由:它给梯度修了一条不用连乘的近路(就是上面那张图)。
| 代价 | 什么时候真的会痛 | 说明 |
|---|---|---|
| 必须保存中间激活 | 序列长度上到几千、层数上到几十的时候 | 链越长占用越大。小模型里激活是显存的大头;大语言模型里,优化器状态和梯度常常更大 |
| 不可导的操作用不了 | 你在做量化、采样,或者要从输出里挑一个类别 | 取整、量化、采样、离散选择,这些地方要么绕开,要么用 STE 硬撑 |
| 它是局部方法 | 参数少、地形坑洼的小网络上 | 只保证走到附近的最低点,不保证是全局最优 |
| 梯度 ≠ 好的更新方向 | 损失地形是又长又窄的山谷时 | 梯度只说明"哪里下降最快",但它在狭长山谷里会来回震荡、在最优点附近会慢得像爬 → 优化器那一章 |
| 它不是大脑的工作方式 | 有人跟你说「大脑就是这么学的」的时候 | 反向传播要求前向、反向用的是同一套权重,还要有一条把权重送回去的通路;真实的神经元没有这条通路。 「大脑也是这样学的」是一个常见的误解 |
这一章没什么新东西要记。它只属于七条线里的一条,但那条线在这里第一次露出牙齿。
w 拖到饱和区
回到第 3 节那张链图。把 w 拖到 3 附近,看 ∂a/∂z 那个因子。
它会掉到 0 附近,然后整条链乘出来也是 0。那个瞬间你看到的就是「算得出梯度,却会被饱和掐死」。
回到图上再拖一次。
| 线 | 什么关系 |
|---|---|
| ⑦ 拧得动 | ✅ 这章真正的位置:它为"拧"提供了工具——能算梯度,才知道往哪个方向走。 只有能算梯度,才能发现"卡住"的点梯度并不为零,只是某个方向为零;第 3 节的饱和实验是它的反面 |
| ⑥ 层层组合 | 沾边:链式法则就是把一次巨大的乘法拆成一串小乘法,一层管一段。但它讲的是「怎么算」,不是「为什么深」 |
| ③ 规模会赢 | 沾边,正因为它是 O(N) 而不是 O(N²),大模型才可能被训练出来。但它本身是数学,不是规模现象 |
下面就是这一章的答案。你读后面的章时会发现,每章都有这么一块。
| 暗线 | 这一章的回答 |
|---|---|
| A 信息流动 | 数据形状:前向 x → z → a → L,形状一路"塌"到标量; 反向走同一批边,方向相反,形状必须原路对得上,对不上就会报形状错误 |
| B 什么被牺牲了 | 内存换时间:为了 O(N) 的梯度,必须把前向每一层的激活都存下来。 小模型里它是训练显存的大头;大语言模型里优化器状态和梯度常常更大 |
| C 参数账本 | 算力 ≈ 3 × 一次前向(1 次前向 + 反向≈2 次前向)。 一个 2→3→1 网络只有 13 个参数;换成 525,825 个参数时,这个倍数不变 |
| D 跑在什么上 | 逐元素算子(激活、反向时的激活读写)是带宽受限,不是算力受限:每读一个存下的激活只做几次乘法, 所以常常在等显存而不是等 GPU 核心(占 FLOPs 大头的矩阵乘仍是算力受限)。 这就是「训练比推理贵」的算力那一半:一次训练步 ≈ 3 次前向。 完整的算术强度账在 《硬件与算力账本》 |
| E 它假设了什么 | 假设每个运算的导数都写得出来(可微)。真实的网络里有些地方不可微 (ReLU 在 0、argmax),工程上的做法是绕过它:约定一个次梯度,或者改用可微的替代 |
| F 违背了哪个直觉 | "都怪那个参数"的直觉是错的。一个参数没学好,经常不是它自己的问题, 而是它前面某一环把梯度掐死了(饱和的激活、太小的初始化)。 所以调试时要往回看,不是往下压 |
它接住了上一章的什么:损失函数(《损失函数》)定义了"错多少",但没说"怎么改"。 这一章回答了"怎么改"。
它给下一章留了什么:现在你有了梯度——一个方向。 但往这个方向迈多大一步?《优化器谱系》回答的就是这个。
反向传播 = 链式法则 + 中间结果复用。
从前向后算一遍值、从后向前乘一遍局部导数,
一次前向 + 一次反向就拿到所有参数的梯度。模型越大,它比逐个试探快得越多——本页最大的一档已经差近九千万倍。
梯度下降负责用这个梯度走:学习率太小走不动,太大在谷底两边弹。
三种变体里小批量是标准做法;SGD 的噪声不是缺点,它帮模型跳出局部极小。
代价是训练必须存下每层激活,梯度检查点就是拿时间换这部分空间。
而"
上面讲的都是「够用」的版本。想往下挖,这里有三个入口, 它们不是必修内容,是给想再往前走一步的读者准备的。