把黑箱拆开:手搓一个大语言模型 书架

第 07 章 / 共 16 章

第七章 · 训练就是下山:损失、梯度、反向传播

假设你被蒙上眼睛,扔在一座大山的半山腰,任务是走到山谷最低点。你看不见地形,没有地图,只能靠脚底板感觉:往前迈半步,脚下是上坡还是下坡?哪个方向最陡地往下掉,你就往那个方向挪一小步。挪完再感觉一次,再挪一步。重复几千几万次,你多半就摸到谷底了。

你可能觉得这太笨了。但整个大语言模型的"学习",从头到尾,干的就是这么一件事。上一章我们把 Transformer 从输入到输出词概率的流水线拆完了,里面有几十亿甚至上万亿个数字旋钮(就是那些矩阵里的权重)。这一章要回答的问题只有一个:这堆旋钮一开始全是随机的,它们到底是怎么被调成"对"的?

答案就是蒙眼下山。我们要把这个类比里的每一样东西,翻译成模型里真实发生的事。

先得知道"错多少":损失

下山之前,你得先定义什么叫"低"。对模型来说,"低"就是预测得准。可"准不准"得变成一个具体的数字,不然没法比较、没法优化。这个数字就是损失(loss)——衡量模型这次答得有多离谱的一个分数,越大越错,越小越好。

拿第一章那个老例子:输入"今天天气很",正确的下一个词是"好"。模型不会直接吐一个词,它吐的是一整张概率表——词表里每个词的可能性。比如它说"好"的概率是 0.6,"热"是 0.2,"冷"是 0.1,剩下几万个词分掉最后 0.1。

现在标准答案告诉你:这次真正出现的词是"好"。那模型给"好"打了 0.6 分,还算不错,但离满分 1.0 还差着。损失要做的,就是把"你给正确答案的概率是 0.6 而不是 1.0"这件事,换算成一个惩罚分。

常用的换算方式叫交叉熵(cross-entropy),名字唬人,做的事特别简单:惩罚 = 对模型给正确答案的概率取负对数,写成 loss = -log(p正确)

为什么用负对数?因为它的脾气正好符合我们想要的赏罚:你给正确答案的概率越接近 1,惩罚越接近 0(答对了几乎不罚);概率越接近 0,惩罚飙到无穷大(你把正确答案说成绝无可能,罪该万死)。它温柔地奖励自信的正确,狠狠地暴打自信的错误。

套进例子:-log(0.6) ≈ 0.51。如果模型学得更好,给"好"打了 0.95,损失就降到 -log(0.95) ≈ 0.05。如果它蠢到只给"好"打 0.01,损失就飙到 -log(0.01) ≈ 4.6。你看,一个数字就把"答得好不好"量化了。真实训练时,模型要同时处理一大批句子里的每一个位置,把所有这些惩罚分平均一下,就是这一步的总损失。训练的全部目标,就是让这个数字变小。

往哪个方向调:梯度

现在山谷有了——损失就是海拔,我们要走到损失最低的地方。可问题来了:模型有几十亿个旋钮,你怎么知道该把哪个旋钮往哪拧、拧多少,才能让损失下降?

这就是蒙眼下山里"用脚感觉坡度"那一步。对某一个旋钮,你可以做个思想实验:把这个旋钮往上拧一丁点,看损失是变大还是变小、变了多少。如果往上拧损失就变大,那说明你该往下拧;如果往上拧损失反而变小,那就继续往上拧。这个"某个旋钮动一点点、损失跟着变多少"的比值,就是梯度(gradient)

梯度说白了就是敏感度加方向:它告诉你"这个旋钮对最终错误负多大责任、该往哪边动"。梯度大,说明这个旋钮是当前错误的主要责任人,得使劲调;梯度接近 0,说明动它几乎不影响结果,那就别管它。

把所有旋钮的梯度凑在一起,就得到了一个指向"损失上升最快方向"的巨大箭头。我们想下山,所以往相反方向走——这就是梯度下降(gradient descent):算出上坡最陡的方向,然后反着迈一步。每个旋钮的新值 = 旧值 − 学习率 × 它的梯度。

这里冒出个新旋钮:学习率(learning rate)——你每一步迈多大。这是整个训练里最要命的一个设置。步子太小,下山慢得让人绝望;步子太大,你可能一脚跨过谷底,蹦到对面山坡上,甚至越蹦越高,损失直接爆炸。实践中它通常是个很小的数,比如 0.0001 这个量级,而且往往训练途中还会慢慢变小——一开始大步流星,快到谷底了再小心挪。

关键一问:几十亿个梯度怎么算?

到这儿有个坎必须迈过去。刚才说"把一个旋钮动一点点看损失变多少",这在数学上叫求导,没问题。但如果真的一个一个旋钮去试——动一下、跑一遍整个网络看损失、记下来、复原、再动下一个——几十亿个旋钮就得把整个网络前向跑几十亿遍。这慢得没有意义,一辈子也训不出一个模型。

救命的算法叫反向传播(backpropagation)。它的牛处在于:只要前向算一遍、再反向算一遍,就能一次性把所有旋钮的梯度全求出来。不是几十亿遍,是两遍。

它凭什么能这么快?靠的是一条你高中就学过的规则——链式法则。网络是一层套一层的:输入喂给第一层,第一层的输出喂给第二层,一直到最后算出损失。这是一根很长的因果链条。某个深处的旋钮怎么影响最终损失,是通过它后面那一层层"接力"传过去的。

反向传播就利用了这个接力结构,反着走一遍:

  1. 先从终点开始——算出"损失对最后一层输出"的敏感度,这个最容易,因为损失就是拿最后一层的输出直接算的。
  2. 然后往回退一层。用刚才那个敏感度,乘上"最后一层输出对倒数第二层输出"的敏感度,就得到了"损失对倒数第二层"的敏感度。
  3. 就这么一层一层往回传,每退一层,就把已经算好的结果拿来乘一下,接着往前递。

关键在于复用:后面层算出来的敏感度,前面层能直接拿来接着用,不用从头再算。就像发工资,公司先把总账算清,再一级一级往下分摊到每个部门、每个人,而不是给每个员工单独重跑一遍全公司的账。反向传播就是把"损失该由谁负责"这笔账,沿着网络高效地摊派到每一个旋钮头上。

所以"反向传播"这个词,字面意思就是把误差从输出端反着推回输入端,一路上给每个旋钮派好它该背的锅(梯度)。前向传播是"数据往前流、算出预测",反向传播是"错误往后流、算出该怎么改"。一前一后,配成一整个训练循环。

把一整圈连起来

现在我们能把模型学习的一个完整回合,从头到尾走一遍了。这个循环会重复成千上万甚至上百万次:

  1. 前向:喂进一批文本,让网络照着当前旋钮算出每个位置"下一个词"的概率表。
  2. 算损失:拿概率表和真实的下一个词比,用交叉熵算出这批数据错得有多离谱,得到一个损失数字。
  3. 反向:用反向传播,一次性算出每个旋钮的梯度——谁该为这次的错误负多大责任、该往哪边调。
  4. 更新:每个旋钮沿着自己梯度的反方向,挪一小步(步长由学习率定)。
  5. 回到第一步,换一批新数据,再来一圈。

就这么简单,也就这么暴力。没有哪一步藏着"灵光一现",没有谁在中间"想明白了道理"。整个过程就是:算错了多少 → 算出每个旋钮的责任 → 各自挪一小步 → 再来。你以为的"模型在理解语言",在数学上不过是一个几十亿维的损失曲面上,一个蒙眼的人在往下挪脚。

顺便戳破一个常见的迷思:模型训练不是"记住了正确答案",而是"被无数次纠错,逼着把旋钮调到能碰巧对大多数情况都答得还行"的状态。它从来没被告诉过任何一条规则,它只是被损失反复推搡,最后停在了一个损失比较低的坑里。

你大概会追问:这么笨的下山,凭什么能爬出语法、常识、甚至推理?两件事在暗中帮忙。一是曲面本身——它不是光滑的一个大碗,而是几十亿维里布满沟壑的诡异地形,某些坑底恰好对应着"抓住了语言的规律"。二是喂进去的数据量大到离谱。这两件事怎么把"猜下一个词"这个笨目标,喂成一个像样的模型,正是下一章"先读半个互联网"要拆的。

所以本质上,训练一个大语言模型,就是:给几十亿个随机旋钮定一个"错多少"的分数,然后让它们蒙着眼、顺着最陡的下坡,一小步一小步地挪,挪上亿次,直到不那么错为止。没有魔法,只有下山。

把黑箱拆开:手搓一个大语言模型 · 王建硕
面向对世界有好奇心的人 · 费曼式写法 · 由多个 AI 代理撰写与互相审校