English interface selected · this article is currently written in Chinese.
语言模型究竟在预测什么:标签错位、交叉熵与训练损失
English overview →具备清晰骨架、初步数学推导或工程验证,持续补充论据与实测。
数字花园
🌿 什么是数字花园与思想演进?
遵循 Maggie Appleton 与 Andy Matuschak 的常青笔记理念:这里不是按时间沉底的流水账,而是持续修剪打磨的知识生态。文章按成熟度经历:
- 🌱 萌芽 (Seedling): 发散灵感、读书摘录、开放猜想
- 🌿 演进 (Budding): 结构渐明、初步代码推导、积极修正
- 🌳 常青 (Evergreen): 逻辑闭环、持久经得起检验的基石
- 🔬 实验待证 (Speculative): 结合沙盒与仿真探针的理论探索
训练脚本开始运行,屏幕上不断出现 loss。它从 8 降到 5,再降到 3。看到这条曲线,我们很容易觉得:模型正在学会语言。
但先别急着下结论。如果模型看到了本该预测的答案,或者代码把目标错移了一个位置,损失一样可能下降。损失下降只说明模型正在更好地完成代码实际定义的任务。这个任务是否正确,需要我们先检查。
上一篇解释了文本怎样变成 token。本篇接着追踪这串整数:模型看见什么,要预测什么,以及错了以后怎样计分。
这里讨论最基本的自回归语言建模:每个位置根据此前的内容预测下一个 token。暂不加入标签平滑、类别权重和其他辅助损失。
把一句话展开成几个预测问题
我们自定义一条教学序列:
[BOS] 今天 下雨 [EOS][BOS] 表示开始,[EOS] 表示结束;中间两个片段假设各占一个 token。这是为了看清位置关系,不代表所有模型都使用这些标记或这种切分。
它对应三个问题:
| 当前已经看见 | 正确的下一个 token |
|---|---|
[BOS] | 今天 |
[BOS] 今天 | 下雨 |
[BOS] 今天 下雨 | [EOS] |
第二个问题有多种合理续写。“今天晴天”也说得通,但这条训练记录实际出现的是“下雨”,所以本次用“下雨”作为目标。其他文本可以提供其他续写的训练机会。
语言模型并不是给“这句话是否符合事实”打标签。它学习的是训练数据中的条件分布:在某种上下文后,哪些 token 更可能出现。因此,文本中的错误、偏见或重复,也可能成为容易学习的规律。
为什么整段一起输入,却不算提前看答案
训练时可以把整条序列放进一个张量,同时计算多个位置的输出。关键在于每个位置能读取哪些信息。
在通常的因果注意力约束下,位置 可以读取自己和更早的位置,不能读取未来。Transformer 原论文也说明了用遮罩阻止解码位置访问后续位置的设计。Attention Is All You Need,第 3.1 节
于是 今天 所在位置虽然与 下雨 一起出现在输入张量里,它的计算仍不能使用后者。
用矩阵表示这种可见关系:
| 计算哪个位置 | BOS | 今天 | 下雨 | EOS |
|---|---|---|---|---|
| BOS | 可见 | 不可见 | 不可见 | 不可见 |
| 今天 | 可见 | 可见 | 不可见 | 不可见 |
| 下雨 | 可见 | 可见 | 可见 | 不可见 |
| EOS | 可见 | 可见 | 可见 | 可见 |
所以可以同时计算前三个预测题,而不是训练时也逐字生成。最后一个 EOS 位置在这个独立例子中没有后续目标,可以不计损失。
这还有一个常被混淆的地方:训练通常提供真实前缀,而不是把模型刚才猜错的 token 接回去。计算“下雨”这个目标时,上文是数据里的“今天”。模型即使在第一个位置最想输出别的词,也不会自动改写这条训练输入。
生成时情况不同:下一步通常接着模型自己生成的内容计算。它前面犯的错会进入后面的上下文。因此,真实前缀下的预测损失与连续生成的表现有关,却不是同一项测量。
标签只应该错位一次
如果序列的编号是:
[BOS, 今天, 下雨, EOS] = [0, 1, 2, 3]最直观的手写训练方式是:
输入 x: [0, 1, 2]标签 y: [1, 2, 3]每个输入位置的输出与对应标签比较。注意,标签是类别编号,不是要把整数做减法。把“下雨”的编号从 2 换成 200,只要所有映射一致,不会让它离“今天”更远。
一些封装会在模型内部完成错位。例如 Transformers 4.57.1 的 GPT-2 文档允许传入与 input_ids 相同的 labels,因为内部负责把预测位置与下一个标签对齐。这里引用的是这个固定版本的接口约定,不能据此推断所有模型和训练器都相同。GPT-2 标签接口
两种约定都可以。真正危险的是混着用:
| 情况 | 位置“今天”的输出实际上被要求预测什么 |
|---|---|
| 正确错位一次 | 下雨 |
| 完全没有错位,又与同位置标签比较 | 今天 |
| 外部先错位,内部又错位 | 可能变成 EOS,取决于切片实现 |
没有错位时,当前位置已经看见自己的输入,任务可能退化为复制。错位两次则可能跳过一个 token。它们不一定立即报错,甚至都可能产生下降的曲线。
最有效的检查不是盯着框架名称,而是打印一条样本实际参与计分的配对:这一行输出能看见的最后一个 token 是什么,标签又是什么? 连续检查三个位置,通常比先跑几千步更有用。
模型怎样给候选 token 分配概率
模型在每个位置输出一组分数,通常叫 logits。词表有多少个条目,就有多少个候选分数。这些分数可以为负,也不要求加起来等于一。
softmax 把它们转成概率。对当前上下文,候选 的概率为:
是候选分数, 是词表大小。分数本身没有绝对刻度:给所有分数加同一个常数,概率不会变。
假设只有三个候选“下雨”“晴天”“结束”,它们的分数分别为 、、。指数化后是 4、2、2,除以总和 8,就得到 0.5、0.25、0.25。
这只是一个可手算的局部示例。真实模型的候选数往往多得多,但计分逻辑相同。
交叉熵为什么惩罚“很有把握地答错”
这条样本的目标是“下雨”,所以取模型分给它的概率,再计算负对数:
这里使用自然对数。正确目标的概率越小,损失越大:
| 分给正确目标的概率 | 损失,约值 |
|---|---|
| 0.8 | 0.2231 |
| 0.5 | 0.6931 |
| 0.1 | 2.3026 |
| 0.01 | 4.6052 |
因此,损失不只判断“排名第一的候选对不对”。两个模型都把“下雨”排第一,一个给 0.8,另一个给 0.5,它们的损失仍不同。
这个目标也不会直接要求每条样本的概率都等于一。当多个样本具有相同上下文却有不同后续时,模型需要在这些观察之间分配概率。容量、数据和优化过程共同决定最终能接近什么分布。
在无标签平滑的单一类别目标下,softmax 交叉熵对分数的导数可以写成:
是正确目标编号。前面的三个候选分别得到 。若只把这些分数当作可直接调整的变量,沿负梯度走,会提高正确候选分数、压低其他候选分数。
真实网络更新的是共享参数,不是为每条样本保存一套独立分数。因此,一次更新也会影响其他上下文;这正是泛化和相互干扰都可能发生的原因。
实现时,PyTorch 的 CrossEntropyLoss 接收未归一化的 logits,并在内部实现相应的计算。不要先把它们变成概率,再当作 logits 传进去,否则定义的就不是上面的目标。其类别编号、忽略位置和平均方式都应按具体接口检查。PyTorch CrossEntropyLoss
从一个位置的损失,到一个 batch 的损失
一句话有多个目标,一个 batch 又有多句话。我们需要说明哪些位置参与训练,以及怎样求平均。
设位置 的目标损失是 ,参与计分时 ,否则为零。在没有额外权重时,可以写成:
分母是有效目标数,不是输入张量的全部格子数。padding 等位置不应仅仅因为占了格子就稀释平均值。如果整批没有有效目标,分母为零,需要数据检查或明确跳过,不能把它算成“完美的零损失”。
这里还要区分两种平均:
- 按 token 平均:所有有效位置合在一起除以总数。
- 按样本平均:先求每个样本的平均,再给样本相同权重。
假设 A 有两个有效位置,平均损失为 1;B 有八个,平均损失为 3。按 token 平均是 ;按样本平均是 。
两者没有算术上的对错,但定义了不同的权重。更长的样本在前一种目标里贡献更多位置。训练日志若只显示一个数字,而不说明怎样平均,就很难比较实验。
同样道理,多个验证 batch 长度不同,不能简单平均它们的平均损失。应累加有效目标的损失总和,再除以有效目标总数。梯度累积也会遇到类似问题,后面讨论 batch 时再展开。
loss mask 不负责挡住答案
这里先记住一个区别,后续 SFT 会反复用到:
attention mask 决定信息能从哪里传过来,loss mask 决定哪些预测位置要计分。
把某个标签设为忽略值,只是不直接计算那个目标的损失,不意味着模型看不见相应的输入。比如做问答 SFT 时,可以让问题参与上下文,但只计算回答的损失。
相反,如果未来答案仍然能通过注意力被读取,单靠忽略某些标签不能消除这种泄漏。检查因果约束和检查计分范围,是两件都要做的事。
常见接口用 -100 代表忽略标签,但它不是词表里的普通 token,也不应该当作输入编号送入嵌入层。是否使用这个值,仍要以实际损失函数的约定为准。PyTorch 忽略标签说明
困惑度可以怎样理解
在同一套 token 定义和计分范围下,平均负对数损失取指数,就得到常用的困惑度:
如果模型在每个位置都给正确目标 0.5 的概率,那么 ,困惑度就是 2。若每个位置都是 0.25,则为 4。
可以把它理解为正确目标概率的几何平均数的倒数,而不是“每次恰好剩两个候选”。不同位置的分布可以差异很大,相同困惑度也不要求候选集合大小相同。
更重要的是,PPL 的单位跟着 token 走。不同分词器、不同语料、不同截断和上下文策略,会改变预测题目本身。不能把一个中文模型的 per-token PPL 与另一个采用不同切分的模型直接排成能力榜。
同一个模型验证损失下降,也还需要检查分项。假设验证集大部分是模板句,模型把模板预测得更准,就能拉低整体损失;稀少但重要的技术问答可能没有改善。
用一个小检查确认代码真的在学下一个 token
在正式训练前,可以先做下面的对齐实验。它是验证设计,不是本文宣称已经训练出了一个模型。
准备两三条极短序列,并固定分词结果,避免同时排查分词器变化。逐位置保存能看见的前缀、目标 ID、目标概率、是否计分和该位置损失。
先验证三件事:
- 人工配对吻合。 比如前缀到“今天”为止,目标确实是“下雨”。
- 手算吻合。 使用自造 logits,与独立的负对数计算比较,检查数值与平均方式。
- 信息边界吻合。 在评估模式下只改未来 token,检查更早位置的输出是否保持一致;比较时关闭随机性,并允许合理的数值误差。
第三项验证的是因果计算边界。通过它仍不等于整套数据流程正确,例如训练集和验证集可能重复,因此它不能替代第一卷的数据检查。
接下来才做极小样本过拟合:让模型反复看到这几条记录,观察能否明显降低损失。若不能,检查参数是否真的更新、学习率是否合理、梯度是否被意外切断。若能,也只能证明这套配置能拟合小样本,不代表有泛化能力。
正式比较时,再保留未见文本,固定分词、上下文和计分范围,同时报告训练损失、验证损失以及分任务生成表现。这样,曲线变化才有可解释的含义。
两个检查理解的问题
第一,一条输入是 [BOS, 今天, 下雨, EOS]。若模型内部已经错位,外部还把标签改为 [今天, 下雨, EOS, 忽略],前两个输出分别会被要求预测什么?请根据内部切片约定列出来,不只回答“会错位两次”。
第二,验证 batch A 有十个有效目标,平均损失为 1;batch B 有九十个,平均损失为 3。总体 token 平均损失是多少?为什么直接取两个日志数字的平均值会改变权重?
弄清预测题目以后,下一步才是拆开模型内部的计算:输入编号如何变成向量,向量怎样读取上下文,又如何得到词表上的分数。
文章概念网络
从章节进入正文概念。实线表示所属层级,虚线连接跨章节重复出现的概念,不代表因果关系。
71 / 71 个节点 · 正文标记术语与关键词概念
语言模型究竟在预测什么:标签错位、交叉熵与训练损失
回到原文 ↗最后更新于 2026-10-20
引用本文
@article{2026-10-20-llm-training-pretrain-02-objective,
author = {枫语},
title = {语言模型究竟在预测什么:标签错位、交叉熵与训练损失},
journal = {枫语},
year = {2026},
month = {oct},
url = {https://meiluosi.github.io/posts/2026-10-20-llm-training-pretrain-02-objective/}
}