拧旋钮不靠运气,每个旋钮该往哪拧,是算出来的
上一篇留下的黑盒
上一篇的训练循环里,有两行是当黑盒用的:loss.backward() 和 optimizer.step()。我们只说它们负责"按错误方向拧旋钮",没说怎么拧。
但这正是整件事最神奇、也最该弄懂的地方。一个有几百万个旋钮的模型,凭什么知道每一个旋钮该往左拧还是往右拧、拧多少?如果靠瞎试,几百万个旋钮的组合是个天文数字,试到宇宙毁灭也试不完。
它不靠试,靠算。这一篇就把这个黑盒彻底拆开。这是整个模型训练的心脏,值得花一篇讲透。
损失是一座山,我们要下山
先建立一个画面。
把模型所有旋钮的当前位置,看成你站立的位置。把损失——也就是"模型猜得有多差"——看成你所在位置的海拔高度。旋钮拧一拧,位置就变,海拔(损失)也跟着变。
训练的目标是让损失变小,翻译过来就是:从当前位置出发,往低处走,最好走到整座山的谷底。
现在设想你被扔在这座山的某处,四周全是浓雾,看不见谷底在哪。你只能做一件事:感受脚下这一小块地面的坡度,朝着最陡的下坡方向,迈一小步。然后在新位置再感受一次坡度,再迈一步。一直这样,你就能慢慢挪到谷底。
模型训练用的就是这个笨办法,它有个正式名字叫梯度下降(gradient descent)。整件事的关键,就落在"怎么感受脚下的坡度"上。
梯度:每个旋钮的下坡方向
"脚下的坡度",对一个旋钮来说,是个能精确定义的东西:把这个旋钮往上拧一丁点,损失会变大还是变小、变化多快。
这个"损失对某个旋钮的变化率",就叫梯度(gradient)。
梯度是个有方向、有大小的数。如果某个旋钮的梯度是正的,意味着"把它往上拧,损失会变大"——那我们就该反过来,把它往下拧。如果梯度是负的,往上拧损失会变小,那就往上拧。一句话:旋钮要往梯度的反方向动。梯度的大小,还告诉我们这个旋钮对损失的影响有多猛——影响猛的多拧些,影响小的少拧些。
模型有几百万个旋钮,每个旋钮都有自己的梯度。把它们全算出来,我们就得到了一张完整的"下坡指南":每个旋钮各自该往哪个方向、动多少。optimizer.step() 做的,就是照着这张指南,把所有旋钮一起动一小步。
所以问题收窄成了一个:这几百万个梯度,是怎么算出来的?
用 PyTorch 亲手算一次梯度
先把规模缩到最小——只有一个旋钮,亲手算一次,把概念坐实。
假设模型只有一个旋钮 w,损失的算法是 loss = (w - 3) ** 2。这个损失什么时候最小?显然 w 等于 3 的时候,损失是 0。我们来看 PyTorch 能不能自己找到 3。
import torch
# 旋钮初始值设为 5。requires_grad=True 告诉 PyTorch:盯住这个数,要算它的梯度
w = torch.tensor(5.0, requires_grad=True)
loss = (w - 3.0) ** 2 # 前向:算损失
loss.backward() # 反向:算梯度
print(f"当前 loss = {loss.item()}") # (5-3)^2 = 4
print(f"w 的梯度 = {w.grad.item()}") # 4
loss.backward() 跑完,w.grad 里就出现了梯度值 4。
这个 4 怎么验证?loss = (w-3)²,用初中的求导,损失对 w 的变化率是 2 × (w-3),代入 w=5,得 2 × 2 = 4。和 PyTorch 算的一模一样。梯度是 4,是正的,说明把 w 往上拧损失会变大——所以 w 该往下走,朝 3 的方向。方向对了。
现在把"迈步"也加上,让这一个旋钮自己训练起来:
w = torch.tensor(5.0, requires_grad=True)
lr = 0.1 # 学习率:每步迈多大
for step in range(20):
loss = (w - 3.0) ** 2 # 前向
loss.backward() # 反向,算出 w.grad
with torch.no_grad(): # 这段不参与梯度计算
w -= lr * w.grad # 往梯度反方向迈一步
w.grad.zero_() # 清空梯度,准备下一轮
print(f"step {step}: w = {w.item():.4f}, loss = {loss.item():.6f}")
跑一下,你会看到 w 从 5 出发,一步步逼近 3,损失一步步逼近 0。
停下来体会一下:这就是一次完整的训练,只不过模型只有一个旋钮。上一篇那个有几百万旋钮的训练循环,骨架和这个一字不差——前向算损失、反向算梯度、往反方向迈步。区别只是旋钮的数量。
反向传播:怎么一次性算出几百万个梯度
一个旋钮,靠求导手算梯度很轻松。但真实模型有几百万个旋钮,而且损失不是 (w-3)² 这么简单——数据要穿过层层叠叠的运算,才最后变成损失。怎么一次性把所有旋钮的梯度都算出来?
答案是反向传播(backpropagation),loss.backward() 里的 back 就是它。
它的思路是这样。模型从输入算到损失,是一连串运算一环扣一环,像一条链子:输入经过第一层运算,结果再经过第二层,再第三层……最后得到损失。要算"最前面某个旋钮对损失的影响",可以用一个数学上的链式法则:把这条链子从损失端往回走,一环一环地把影响传递回去。
打个比方。一家工厂,原料经过很多道工序变成产品,最后产品有个质量分。现在想知道"第一道工序的某个参数,对最终质量分影响多大"。你不需要重头模拟整条产线,只要从最后一道工序往前问:最后一道工序,它的输入变一点,质量分变多少?倒数第二道,它的输入变一点,最后一道的输入变多少?这样一道道往前传,乘起来,就得到了第一道工序那个参数的影响。
反向传播就是这么干的:从损失出发,沿着运算链反向走一遍,每经过一环就把梯度往前传一层。走完一遍,所有旋钮的梯度就全有了。它最聪明的地方是中间结果能复用——靠后的旋钮算梯度时得到的中间量,靠前的旋钮直接拿来接着用,不重复计算。所以哪怕几百万个旋钮,一次反向传播也就和一次前向差不多快。
更省心的是,这些你完全不用自己实现。PyTorch 有个叫 autograd 的机制:你做前向计算时,它在背后悄悄把整条运算链记录下来;你一调 loss.backward(),它就沿着这条链自动跑反向传播,把每个标了 requires_grad=True 的旋钮的梯度都填好。上一篇 Bigram 模型那张表,是用 nn.Embedding 建的,PyTorch 默认就把它登记成了需要算梯度的旋钮,所以 loss.backward() 一句话就够了。
学习率:步子迈多大
回到下山的画面。梯度告诉了你下坡的方向,但每一步迈多大,是另一个要定的事。这个步长,就是学习率(learning rate),上一篇代码里的 lr。
学习率太小,每步挪一点点,要走很久才到谷底,训练慢。学习率太大,一步迈过头,可能直接从这个山坡蹦到对面山坡上,损失不降反升,甚至来回乱跳收不住。
你可以拿上面那个单旋钮的例子试:把 lr 改成 1.5 跑跑看,w 不会收敛到 3,而是越跳越远。学习率是训练里最需要手感的参数之一,第七篇正式训练时会再调它。常用的起点是 1e-3 这个量级。
为什么每轮要先清空梯度
上一篇训练循环里有行 optimizer.zero_grad(),单旋钮例子里也有 w.grad.zero_(),现在能解释了。
PyTorch 有个设定:loss.backward() 算出的梯度,是累加到 .grad 上的,不是覆盖。如果你不手动清零,这一轮的梯度会和上一轮的叠在一起,越积越多,迈步就完全错了。
所以每一轮的标准动作是:清空梯度、前向、反向、迈步。漏掉清空这一步,是新手最常见的 bug 之一——程序不报错,但损失就是降不下去。
优化器在梯度下降之上做的事
最后说说 optimizer。上面单旋钮例子里,我们用最朴素的方式迈步:w -= lr * w.grad,往梯度反方向、按固定步长走。这种最基础的做法叫 SGD(随机梯度下降)。
上一篇用的 AdamW 是更聪明的优化器。它在 SGD 之上多做了两件事。一是带惯性:参考前几步的方向,像下山时带点冲劲,能更快地穿过平缓地带、也更不容易卡在小坑里。二是每个旋钮用各自的步长:根据每个旋钮过往梯度的大小,自动给它配一个合适的步长,而不是所有旋钮一个 lr 一刀切。
实践中,AdamW 几乎是训练 GPT 这类模型的默认选择,省心、收敛快。你知道它"在梯度下降的基础上,加了惯性和自适应步长"就够了,不必自己实现。
到这里,训练的心脏就讲完了。回头看那个循环:前向算出损失(你在山上的海拔),反向传播算出每个旋钮的梯度(脚下的坡度),优化器照着梯度把所有旋钮往下坡方向迈一步。几百万个旋钮,就是这样被一步步、自动地、拧到谷底的。
本篇要点
- 训练是"下山":损失是海拔,旋钮的位置是站立点,目标是走到损失的谷底。
- 梯度是"损失对某个旋钮的变化率",指明该旋钮往哪个方向、影响多大;旋钮要往梯度的反方向动。
- 反向传播用链式法则,从损失端反向走一遍运算链,一次算出所有旋钮的梯度,且复用中间结果。
- PyTorch 的 autograd 自动记录运算链,
loss.backward()一句就完成反向传播。 - 学习率是每步的步长,太小训练慢、太大会发散,常用起点是
1e-3量级。 - 梯度会累加,每轮必须先清零;
AdamW在梯度下降上加了惯性和每个旋钮自适应的步长。
下一篇
到这里,"怎么训练"已经讲透了——它对任何模型都一样。接下来几篇换一条线:把模型本身从"只看一个字"的笨 Bigram,升级成看得见整段上下文的 GPT。下一篇登场的是 GPT 最核心的零件——注意力机制。
参考资料
- PyTorch Autograd 入门
- 3Blue1Brown:什么是梯度下降(视频)
- 博客内 ml-basics 系列《反向传播》——更系统的数学推导
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:反向传播:模型怎么知道该往哪改
本文链接:https://www.sshipanoo.com/blog/ai/mini-gpt/04-反向传播模型怎么知道该往哪改/
本文最后一次更新为 天前,文章中的某些内容可能已过时!