上一篇结束时,模型手里已经拿到一张概率表:五万多个候选词,每个词一个概率,加起来等于 1。但一个刚初始化、参数全是随机数的模型,吐出来的这张表基本是乱猜。要让它从乱猜变成会写文章,得先有一个能衡量「这次猜得多离谱」的数字,而且这个数字要能告诉每个参数该往哪边挪。
打勾打叉为什么不行
最自然的想法是判对错:正确答案是「跑」,你猜的是「猫」,扣分。
这个标准在语言模型上失效,原因有两个。一是太稀疏:词表五万个词,随机猜猜中的概率约五万分之一,训练早期几乎每个位置都错,所有错误看起来一样糟,模型得不到方向感。二是不看信心:给出 和 ,在「猜对了吗」这个标准下没有区别,可这两次预测的质量差得远。
所以要衡量的不是「猜中没有」,而是 你把多少概率放在了正确答案上。
只盯住正确答案那一格
规则可以写得极简:这一次预测的罚分只看模型给正确词分配的概率 ,
越大罚得越少,越小罚得越重,而且重得很快。代入几个数: 时 ; 时 ; 时 ; 时 。概率越接近 0,罚分涨得越陡,趋向无穷——模型把 99% 的信心押在错误答案上,比它犹犹豫豫地错更应该被纠正。这个性质用 得不到: 得 0.99, 得 0.999,几乎看不出差别。
取对数还有一个附带好处:两个独立的预测,概率相乘,取负对数就变成相加。一整句话的总罚分因此可以直接把各位置的罚分加起来——这是后面一条句子能同时算几百道题的前提。
回到那四个词,手算一次
接着上一篇的例子:词表只有四个词(猫、狗、鱼、跑),某个位置算出的概率是
如果真实出现的下一个词就是「跑」,。如果正确答案是「猫」,。同一张概率表,答案不同,罚分差四倍。
有个容易误会的地方值得停一下:即使猜中了,罚分也不是零。「猫」是概率最高的词,可它只给了 53% 的信心,仍被罚 0.627。这是对的——训练不希望模型停在「大概吧」。
正式的名字,和两个自检数字
把真实答案写成一个只在一格取 1 的向量 ,上面这件事的一般写法是
因为 只有一格是 1,求和里其它项全被 0 乘掉,结果还是 。这个量叫 交叉熵(cross-entropy)。PyTorch 的 CrossEntropyLoss 直接吃未归一化的 logits,内部做完 log_softmax 再取正确答案那一格的负对数 1;实际训练代码常写成一行,把 batch 和序列长度一起拉平:
1loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1))这是 nanoGPT 里原样的一行 3。 的实在读法是「平均每猜一个词要花多少信息量」,换成比特就是除以 :2.631 nat 等于 3.80 比特。取指数得到 困惑度 ,上例是 ,读作「模型的犹豫程度相当于在 13.9 个词里瞎猜」。
这些换算给出两把尺子。开局应该多差? 随机初始化时模型对五万个词几乎一视同仁,。训练脚本第一步 loss 落在 10.8 附近才算对;显示 2.3 或 60,那是算错了,不是学得快。训好了有多好? GPT-2 论文里最小的模型(117M)在 WikiText-2 上困惑度 29.41 2,即 、每词 4.88 比特:五万多个候选,平均只花不到 5 比特就定位到下一个,相当于把范围压到 30 个词左右。从 10.82 降到 3.38,就是整个预训练在做的事。
一条句子怎么变成几百道题
现在说清「和什么比」。答案不是人工标签,而是语料里本来就有的东西:输入是一段长度为 的文本,目标是把同一段文本 右移一位——第 1 个位置预测第 2 个 token,第 2 个预测第 3 个,依此类推 4。
于是「下一个词是什么」这一个问题,在一条 1000 token 的文档里变成 1000 道题,任何原始文本都能用,不需要标注。这 1000 道题在一次前向里全部算完(因果掩码保证第 个位置看不到第 个 token),每道题各得一个 ,再取平均:
取平均而不是求和,是为了让 loss 的尺度不随序列长度和 batch 大小漂移。CrossEntropyLoss 默认的 reduction='mean' 就是这个意思 1。
这个数字怎么真的挪动参数
要能改参数,靠的是它对每个分数的梯度有个异常干净的形式:
用上例代入,正确是「跑」,梯度就是 。梯度下降是减梯度,所以这一步之后:猫的分数降 ,狗和鱼各降 ,跑的分数升 ( 是学习率)。
读法很直白:把正确答案的分数往上抬,把所有其他词的分数往下压,压的力度正好等于它当前的概率。 已经只有 0.001 的词几乎不用管,那个抢走 53% 概率的错误答案才要被按住。这里的「所有其他词」不是巧合——softmax 的分母让抬高一格就挤压其余所有格,竞争关系直接写进了损失里。这也是 softmax 和交叉熵总成对出现的原因:二者的组合给出这个 ,换掉任何一个,求导都会麻烦得多。
这么笨的目标,凭什么够用
的物理意义是「平均每词多少比特」。要把它压低,模型必须真的利用文本里的规律:语法让某些词必然跟随另一些词;事实让「法国的首都是」后面几乎必然是「巴黎」;体裁让一段代码的下一行大概率是缩进和括号;论证让「因此」前面通常有个已成立的前提。任何能减少预测误差的内部结构都会降低 loss,因而都会被保留下来。 在这个意义上,loss 是一本总账,为学好语言付出的一切表征都在为它服务。
模型也可以靠死记硬背降 loss:见过整段,下次直接背出下一句。但语料有几十 GB、参数只有上亿时,背并不划算。GPT-2 论文的观察支持这一点:只在 WebText 上做下一词预测,模型零样本就能在问答、翻译、阅读理解这些从未被标注的任务上做出像样的成绩,而且模型越大越好 2。
几个容易推过头的地方
它只和一个答案比。 语料里每个位置只有一个真实的下一位,模型提出另一种同样合理的续写仍会被罚。学的是「这份语料里的下一位」,不是「所有说得通的下一位」。
loss 低不等于事实对。 语料里写错的,模型学的就是错的;靠背训练文本也能把 loss 压得很低,但那是记忆不是理解。看到异常低的 loss,先怀疑数据泄漏。
loss 有下界,不会归零。 有些位置本来就无法预测(下一句谁先开口、某个编号是多少),这部分不确定性是文本自带的。后期下降变慢不是方法失效。
压低 loss 不等于变听话。 网络文本里「提问」和「像助手一样回答」的比例不受控,模型学到的下一位更可能是继续写网文,而不是回答你。所以预训练之后还需要指令微调和对齐——那是后面的话题。
走完这一步
链路闭合了:随机初始化的模型对一段文本的每个位置给出概率表;真实的下一词由同一段文本右移一位得到;每个位置算一个 ,全 batch 平均成一个数;这个数对每个分数的梯度是 ,把正确答案往上抬、其余往下压;重复几十亿次之后,loss 从 10.8 掉到 3 附近。
下一步要问的是更底层的一件事:这个数怎么一路传回前面几十层注意力里的每一个参数上?那是反向传播的话题。