# 交叉熵损失：模型怎么知道自己猜错了，又怎么把错变成参数更新

从「猜中没有」到「给正确答案分配了多少概率」，手算 loss、读懂困惑度，并看清梯度 p−y 怎样把上亿参数推出乱猜

> 序列建模基础 · Transformer 架构 · 约 7 分钟 · 10 月 26 日

## 本篇要点

1. 交叉熵损失衡量的不是「猜中没有」，而是模型给正确答案分配了多少概率：$L=-\log p_{\text{correct}}$。
2. 选 $-\log p$ 而不是 $1-p$，是因为它对「自信地错」惩罚陡增（$p=0.01$ 时损失 4.605，$p=0.5$ 时 0.693），并且取对数后多个位置的罚分可以直接相加。
3. 四词例子里 $p=[0.534,0.197,0.197,0.072]$，正确答案「跑」时损失 2.631；即使正确答案是概率最高的「猫」，损失仍有 0.627，因为训练要求的是高信心而非勉强猜中。
4. 交叉熵即「用模型的分布去编码真实出现的词，平均要多少比特」；取指数得到困惑度，损失 2.631 约等于 3.80 比特、困惑度 13.9。
5. 随机初始化时损失约等于词表大小的对数，GPS-2 的 $\ln 50257\approx 10.82$，所以训练第一步 loss 落在 10.8 附近是代码正确的最快自检；GPT-2 最小模型在 WikiText-2 上困惑度 29.41，即损失 3.38、每词约 4.9 比特。
6. 目标不需要人工标注：目标和输入是同一串 token，整体右移一格，一条 1000 token 的文档一次前向就提供 1000 道题，罚分再取平均。
7. 损失对每个分数的梯度是 $p_k-y_k$，所以每步更新把正确答案的分数抬起、其余词按各自当前概率的大小压低。
8. 下一词预测之所以够用，是因为压低「每词多少比特」必须利用语法、事实、体裁等一切规律；但损失只和语料里的单一答案比，有下界，会掩盖长尾，且降低损失并不等于模型变得听话。

---

上一篇结束时，模型手里已经拿到一张概率表：五万多个候选词，每个词一个概率，加起来等于 1。但一个刚初始化、参数全是随机数的模型，吐出来的这张表基本是乱猜。要让它从乱猜变成会写文章，得先有一个能衡量「这次猜得多离谱」的数字，而且这个数字要能告诉每个参数该往哪边挪。

## 打勾打叉为什么不行

最自然的想法是判对错：正确答案是「跑」，你猜的是「猫」，扣分。

这个标准在语言模型上失效，原因有两个。一是太稀疏：词表五万个词，随机猜猜中的概率约五万分之一，训练早期几乎每个位置都错，所有错误看起来一样糟，模型得不到方向感。二是不看信心：给出 $p(\text{跑})=0.49$ 和 $p(\text{跑})=0.001$，在「猜对了吗」这个标准下没有区别，可这两次预测的质量差得远。

所以要衡量的不是「猜中没有」，而是**你把多少概率放在了正确答案上**。

## 只盯住正确答案那一格

规则可以写得极简：这一次预测的罚分只看模型给正确词分配的概率 $p$，

$$L=-\log p_{\text{correct}}$$

$p$ 越大罚得越少，越小罚得越重，而且重得很快。代入几个数：$p=0.9$ 时 $L=0.105$；$p=0.5$ 时 $0.693$；$p=0.1$ 时 $2.303$；$p=0.01$ 时 $4.605$。概率越接近 0，罚分涨得越陡，趋向无穷——模型把 99% 的信心押在错误答案上，比它犹犹豫豫地错更应该被纠正。这个性质用 $1-p$ 得不到：$p=0.01$ 得 0.99，$p=0.001$ 得 0.999，几乎看不出差别。

取对数还有一个附带好处：两个独立的预测，概率相乘，取负对数就变成相加。一整句话的总罚分因此可以直接把各位置的罚分加起来——这是后面一条句子能同时算几百道题的前提。

## 回到那四个词，手算一次

接着上一篇的例子：词表只有四个词（猫、狗、鱼、跑），某个位置算出的概率是

$$p=[0.534,\ 0.197,\ 0.197,\ 0.072]$$

如果真实出现的下一个词就是「跑」，$L=-\ln 0.072=2.631$。如果正确答案是「猫」，$L=-\ln 0.534=0.627$。同一张概率表，答案不同，罚分差四倍。

有个容易误会的地方值得停一下：**即使猜中了，罚分也不是零。**「猫」是概率最高的词，可它只给了 53% 的信心，仍被罚 0.627。这是对的——训练不希望模型停在「大概吧」。

## 正式的名字，和两个自检数字

把真实答案写成一个只在一格取 1 的向量 $y$，上面这件事的一般写法是

$$H(y,p)=-\sum_{k=1}^{V} y_k \log p_k$$

因为 $y$ 只有一格是 1，求和里其它项全被 0 乘掉，结果还是 $-\log p_{\text{correct}}$。这个量叫**交叉熵（cross-entropy）**。PyTorch 的 `CrossEntropyLoss` 直接吃未归一化的 logits，内部做完 log_softmax 再取正确答案那一格的负对数 [1]；实际训练代码常写成一行，把 batch 和序列长度一起拉平：

```python
loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1))
```

这是 nanoGPT 里原样的一行 [3]。$L$ 的实在读法是「平均每猜一个词要花多少信息量」，换成比特就是除以 $\ln 2$：2.631 nat 等于 3.80 比特。取指数得到**困惑度** $\mathrm{PPL}=e^{L}$，上例是 $e^{2.631}\approx 13.9$，读作「模型的犹豫程度相当于在 13.9 个词里瞎猜」。

这些换算给出两把尺子。**开局应该多差？** 随机初始化时模型对五万个词几乎一视同仁，$L\approx \ln V=\ln 50257\approx 10.82$。训练脚本第一步 loss 落在 10.8 附近才算对；显示 2.3 或 60，那是算错了，不是学得快。**训好了有多好？** GPT-2 论文里最小的模型（117M）在 WikiText-2 上困惑度 29.41 [2]，即 $L=\ln 29.41\approx 3.38$、每词 4.88 比特：五万多个候选，平均只花不到 5 比特就定位到下一个，相当于把范围压到 30 个词左右。从 10.82 降到 3.38，就是整个预训练在做的事。

## 一条句子怎么变成几百道题

现在说清「和什么比」。答案不是人工标签，而是语料里本来就有的东西：输入是一段长度为 $T$ 的文本，目标是把同一段文本**右移一位**——第 1 个位置预测第 2 个 token，第 2 个预测第 3 个，依此类推 [4]。

于是「下一个词是什么」这一个问题，在一条 1000 token 的文档里变成 1000 道题，任何原始文本都能用，不需要标注。这 1000 道题在一次前向里全部算完（因果掩码保证第 $i$ 个位置看不到第 $i+1$ 个 token），每道题各得一个 $-\log p$，再取平均：

$$L=\frac{1}{B\cdot T}\sum_{i=1}^{B\cdot T}-\log p_{i,\ y_i}$$

取平均而不是求和，是为了让 loss 的尺度不随序列长度和 batch 大小漂移。`CrossEntropyLoss` 默认的 `reduction='mean'` 就是这个意思 [1]。

## 这个数字怎么真的挪动参数

$L$ 要能改参数，靠的是它对每个分数的梯度有个异常干净的形式：

$$\frac{\partial L}{\partial z_k}=p_k-\mathbb{1}[k=\text{正确}]$$

用上例代入，正确是「跑」，梯度就是 $[0.534,\ 0.197,\ 0.197,\ -0.928]$。梯度下降是减梯度，所以这一步之后：猫的分数降 $0.534\eta$，狗和鱼各降 $0.197\eta$，跑的分数升 $0.928\eta$（$\eta$ 是学习率）。

读法很直白：**把正确答案的分数往上抬，把所有其他词的分数往下压，压的力度正好等于它当前的概率。** 已经只有 0.001 的词几乎不用管，那个抢走 53% 概率的错误答案才要被按住。这里的「所有其他词」不是巧合——softmax 的分母让抬高一格就挤压其余所有格，竞争关系直接写进了损失里。这也是 softmax 和交叉熵总成对出现的原因：二者的组合给出这个 $p-y$，换掉任何一个，求导都会麻烦得多。

## 这么笨的目标，凭什么够用

$L$ 的物理意义是「平均每词多少比特」。要把它压低，模型必须真的利用文本里的规律：语法让某些词必然跟随另一些词；事实让「法国的首都是」后面几乎必然是「巴黎」；体裁让一段代码的下一行大概率是缩进和括号；论证让「因此」前面通常有个已成立的前提。**任何能减少预测误差的内部结构都会降低 loss，因而都会被保留下来。** 在这个意义上，loss 是一本总账，为学好语言付出的一切表征都在为它服务。

模型也可以靠死记硬背降 loss：见过整段，下次直接背出下一句。但语料有几十 GB、参数只有上亿时，背并不划算。GPT-2 论文的观察支持这一点：只在 WebText 上做下一词预测，模型零样本就能在问答、翻译、阅读理解这些从未被标注的任务上做出像样的成绩，而且模型越大越好 [2]。

## 几个容易推过头的地方

**它只和一个答案比。** 语料里每个位置只有一个真实的下一位，模型提出另一种同样合理的续写仍会被罚。学的是「这份语料里的下一位」，不是「所有说得通的下一位」。

**loss 低不等于事实对。** 语料里写错的，模型学的就是错的；靠背训练文本也能把 loss 压得很低，但那是记忆不是理解。看到异常低的 loss，先怀疑数据泄漏。

**loss 有下界，不会归零。** 有些位置本来就无法预测（下一句谁先开口、某个编号是多少），这部分不确定性是文本自带的。后期下降变慢不是方法失效。

**压低 loss 不等于变听话。** 网络文本里「提问」和「像助手一样回答」的比例不受控，模型学到的下一位更可能是继续写网文，而不是回答你。所以预训练之后还需要指令微调和对齐——那是后面的话题。

## 走完这一步

链路闭合了：随机初始化的模型对一段文本的每个位置给出概率表；真实的下一词由同一段文本右移一位得到；每个位置算一个 $-\log p_{\text{correct}}$，全 batch 平均成一个数；这个数对每个分数的梯度是 $p-y$，把正确答案往上抬、其余往下压；重复几十亿次之后，loss 从 10.8 掉到 3 附近。

下一步要问的是更底层的一件事：这个数怎么一路传回前面几十层注意力里的每一个参数上？那是反向传播的话题。

## 思维导图

```mermaid
flowchart LR
  A["模型给出词表上的概率表"] --> B["问题：怎么衡量这次猜得多离谱"]
  B --> C["判对错不行：太稀疏、不看信心"]
  C --> D["只看正确答案那格：L = -log p"]
  D --> E["四词手算：正确答案概率 0.072 时 L=2.631"]
  D --> F["正式说法：交叉熵，每词多少比特"]
  F --> G["困惑度 = e^L：13.9 个词里瞎猜"]
  G --> H["自检：随机初始化 L≈ln V≈10.82"]
  H --> I["GPT-2 最小版：L≈3.38，每词约 4.9 比特"]
  D --> J["目标从哪来：同一段文本右移一位，几百道题取平均"]
  J --> K["梯度 = p - y：答案上抬，其余按概率压低"]
  K --> L["上亿参数被推离乱猜"]
  L --> M["为什么够用：压低比特数必须利用语法、事实、推理规律"]
  M --> N["边界：只比单一答案、有下界、不等于听话"]
```

## 术语表

- 交叉熵损失：拿模型的概率分布去和真实答案比，只取正确答案那一格的负对数，越小说明模型给正确答案的信心越足。
- 负对数似然：$-\log p_{\text{correct}}$ 的正式叫法，取对数让多个位置的罚分从相乘变成相加。
- one-hot 目标：把「正确答案是第几个词」写成一个只有一格为 1 的向量，使交叉熵的求和自动只剩一项。
- 困惑度：损失的指数 $e^{L}$，读作「模型犹豫的程度相当于在多少个候选里瞎猜」。
- 错位一位的目标构造：输入和目标取自同一段文本，目标整体右移一格，于是每个位置都成为一道预测下一词的题。
- 梯度 $p-y$：交叉熵配 softmax 后对每个分数求导的结果，含义是正确答案往上抬、其余词按当前概率大小往下压。

## 来源

1. [PyTorch docs: CrossEntropyLoss — 接收未归一化 logits，等价于 log_softmax 加 NLLLoss，默认取平均](https://docs.pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html)
2. [Language Models are Unsupervised Multitask Learners（GPT-2 论文）— 下一词预测训练与零样本迁移，表 3 给出 WikiText-2 困惑度](https://cdn.openai.com/better-language-models/language-models.pdf)
3. [nanoGPT model.py — GPT 前向中 loss 的实际写法](https://github.com/karpathy/nanoGPT/blob/master/model.py)
4. [openai-community/gpt2 模型卡 — 目标序列为输入右移一个 token](https://huggingface.co/openai-community/gpt2)

---

原文：https://pangzhengboyin.com/articles/cross-entropy-loss-next-token-training-cee2a592

> **庞征博引** · 想学的，慢慢都会
>
> 庞征博引是把想学的东西写成连载的 AI 学习工具：每篇 5–10 分钟，边读边问，接下来学什么跟着你走。这篇就是这样写出来的。
>
> 开始你自己的连载 → https://pangzhengboyin.com
