all_lessons/数学的逻辑/26第 27 课 / 共 44 课

第八部分 · 学习就是优化

反向传播:神经网络如何算梯度

梯度是训练的指南针,可几百万个偏导,照定义一个一个数值地算会慢到训练不动。深度学习真正的引擎,是一招把第 7 课的链式法则反着跑的算法——一次遍历,所有梯度全部到手。

线性回顾
上一课:我们把第 7 课的导数推广到多变量——偏导量"只动一个参数时损失的变化率",把所有偏导排成向量就是梯度 ∇L,它指向上坡最快,−∇L 指向下坡最快。这是训练的指南针。
留下的问题:指南针好,可怎么算得动?神经网络的损失是一长串函数套函数套函数(层叠层),参数上百万。若对每个参数都"扰动一下、看损失变化"去逼近偏导,要把整个网络前向跑上百万遍——根本不现实。
本课新增:反向传播。它的核心是第 7 课就埋下、却没充分使用的链式法则,加上一张计算图。先前向跑一遍算出每个中间值;再反向跑一遍,把每条边上的局部导数沿路相乘、在汇合处相加——一趟下来,所有参数的偏导同时算出。这就是深度网络之所以能被训练的根本原因。
本课路线
(1) 复合函数的导数:链式法则回顾(第 7 课);(2) 把计算画成图——节点是运算,边是数据流;(3) 前向传播:算出每个节点的值;(4) 反向传播:从损失倒着走,局部导数沿边相乘;(5) 分叉处相加、复用中间结果——为什么一趟就够;(6) 这就是 gpt_mini 里训练发生的事;(7) 梯度到手——下一步该怎么用它更新参数。

链式法则:函数套函数怎么求导

神经网络说到底是函数的层层嵌套:输入经过第一层变成中间结果,再经过第二层、第三层……最后吐出一个损失。要对最里层的参数求导,绕不开第 7 课提过的链式法则。它讲的是一件很自然的事:若 a 影响 z、z 又影响 L,那么 a 对 L 的影响,等于一路上各段影响率相乘。

∂L/∂a = ∂L/∂z · ∂z/∂a
读作"L 对 a 的导数,等于 L 对 z 的导数,乘以 z 对 a 的导数"。直觉:a 动一点,z 跟着动 ∂z/∂a 倍;z 动一点,L 跟着动 ∂L/∂z 倍;连起来,a 动一点 L 就动这两者的乘积。

链条更长也一样,只是多乘几段:

∂L/∂a = ∂L/∂z₃ · ∂z₃/∂z₂ · ∂z₂/∂z₁ · ∂z₁/∂a
读作"沿着 a → z₁ → z₂ → z₃ → L 这条路,把每一段的局部变化率相乘"。这就是链式法则的全部秘密——把一长串导数乘起来

问题是,网络里这样的"链条"有几百万条(每个参数到损失都有一条甚至多条路)。如果对每个参数都从头乘一遍,会有海量的重复计算。反向传播的聪明之处,正是把这些乘法组织得不重复——而组织它们的脚手架,叫计算图。

计算图:把算式画成一张图

任何复杂表达式都能拆成一连串最基本的运算(加、乘、平方、sigmoid……),画成一张图:节点是运算或变量,边表示"谁喂给谁"。拿一个小例子贯穿全课:

L = (a · b + c)²
读作"L 等于 a 乘 b、再加 c,最后整体平方"。这里 a, b, c 当作"参数",我们要算 ∂L/∂a、∂L/∂b、∂L/∂c

把它拆开,引入两个中间量:先算 u = a · b,再算 v = u + c,最后 L = v²。于是计算图是一条主干带一个分叉:

a, b乘法 → u = a·b
u, c加法 → v = u + c
v平方 → L = v²

关键在于:每个基本运算的导数我们都背得出。乘法 u=a·b∂u/∂a = b∂u/∂b = a。加法 v=u+c∂v/∂u = 1∂v/∂c = 1。平方 L=v²∂L/∂v = 2v。每条边上都贴着这样一个简单的局部导数。剩下要做的,只是把它们沿正确的路径乘起来——这就是反向传播。

前向传播:先把每个值算出来

第一趟是前向:从输入出发,顺着图把每个节点的数值算出来并记下(一会儿反向要用)。取一组具体数 a=2, b=3, c=1

u = a·b = 2×3 = 6 ; v = u+c = 6+1 = 7 ; L = v² = 49
读作"u 等于 6,v 等于 7,损失 L 等于 49"。这就是前向传播:一路算到底,顺手把每个中间值缓存起来。

注意每条边上的局部导数现在也有了具体值:乘法节点处 ∂u/∂a = b = 3∂u/∂b = a = 2;平方节点处 ∂L/∂v = 2v = 14。它们都依赖前向算出的值——这就是为什么必须先前向、再反向。

反向传播:从损失倒着乘回去

第二趟是反向。我们给每个节点配一个"它对损失的敏感度",记作 ∂L/∂(该节点),行话叫这个节点的"梯度"。从终点 L 出发,它对自己的敏感度当然是 1(∂L/∂L = 1),然后逆着每条边,把上游已经算好的敏感度乘上这条边的局部导数,传给下游:

∂L/∂v = ∂L/∂L · ∂L/∂v = 1 × 14 = 14
读作"L 对 v 的敏感度 = 14"。这是平方节点回传给 v 的。
∂L/∂u = ∂L/∂v · ∂v/∂u = 14 × 1 = 14 ; ∂L/∂c = ∂L/∂v · ∂v/∂c = 14 × 1 = 14
读作"加法节点把上游的 14 原样分给它的两个输入 u 和 c"(因为加法的局部导数都是 1)。∂L/∂c = 14 已经是我们要的答案之一。
∂L/∂a = ∂L/∂u · ∂u/∂a = 14 × 3 = 42 ; ∂L/∂b = ∂L/∂u · ∂u/∂b = 14 × 2 = 28
读作"乘法节点把上游的 14,分别乘以另一条输入的值(b=3、a=2),得到对 a、对 b 的梯度"。

三个答案 ∂L/∂a = 42, ∂L/∂b = 28, ∂L/∂c = 14 一次反向遍历就全有了。可以用前向定义验一下:L = (ab+c)² 对 a 求偏导是 2(ab+c)·b = 2×7×3 = 42,分毫不差。下面的小实验把这两趟画给你看——改 a、b、c,先看绿色的前向值,再点"反向传播"看红色的敏感度怎样从 L 一格一格倒着乘回去。

计算图:前向算值,反向算梯度 L = (a·b + c)²
改动 a、b、c 后先看每个节点的前向值(绿)。点"反向传播一步",从 L 开始逐边回传敏感度 ∂L/∂(节点)(红):每条边把上游敏感度乘以该边的局部导数。走到底,a、b、c 的梯度就全有了。"重置"回到只有前向。
∂L/∂a
∂L/∂b
∂L/∂c

两条规则,就能让一趟反向覆盖整张图

上面的小图藏着反向传播的全部规则,再大的网络也只靠这两条:

为什么一趟就够、不会重复?因为每个节点的敏感度只算一次、存下来,下游全都复用它。一个有 d 个参数的网络,前向一趟 + 反向一趟,总代价只跟网络规模成正比——而不是像数值逼近那样要 d 次前向。把"逐个参数 d 次"压成"两趟",这就是反向传播让百万参数训练成为可能的全部魔法。

这就是 gpt_mini 里发生的事

你在 gpt_mini 看到的训练循环,每一步底下跑的正是这套:先前向把一批文本喂进网络、算出预测和损失(损失用的是第 33 课的交叉熵);再反向传播,从损失沿计算图倒着乘回去,一次得到上亿个参数各自的梯度 ∇L;最后用这些梯度去更新参数(下一课)。框架里那行 loss.backward(),做的就是本课的反向遍历;它能自动完成,是因为框架替你记下了整张计算图和每条边的局部导数。强化学习(RL)、扩散模型(扩散)的训练,地基都是这同一招。

历史上,反向传播的现代形式在 1986 年由鲁梅尔哈特、辛顿、威廉斯一篇论文推广开来,让多层网络第一次能被有效训练——它没有发明新数学,只是把链式法则用对了顺序、且不重复。一个看似平凡的"算导数的技巧",撑起了整个深度学习时代。

常见误解

一句话带走
反向传播 = 链式法则 + 计算图 + 不重复地复用中间结果。前向算出每个节点的值,反向从损失倒着把局部导数沿边相乘、在分叉处相加——一次遍历就拿到全部参数的梯度。它没造新数学,却让百万、上亿参数的网络第一次训练得动。
下一步
梯度算出来了(指南针有了,而且算得动了)。可有了 −∇L 这个最快下坡方向,到底该怎么走?一步迈多大?太大会冲过头甚至发散,太小要走到天荒地老。沿着坡一步步往谷底挪的规则,叫梯度下降——它是几乎所有训练的内核。→ 第 27 课《梯度下降:沿着坡一步步走到底》。