本文是《算法工程师的数学:读公式不卡壳的最小集》系列的第 5 篇(共八篇)。上一篇:概率入门——语言模型是一个条件分布;下一篇:熵、交叉熵与 KL——从困惑度到 DPO。
训练日志里跳动的那个数字——loss 从 11 降到 2 点几——是什么?它从哪来?为什么是那个公式?这一篇给出完整的答案:从上一篇”语言模型是条件分布”的定义出发,用一个自然的原则(让训练数据出现的概率最大),三步推出它。这三步是后面所有 loss 的模板:SFT、奖励模型、DPO 都是同一个式子换一个概率。然后讲模型是怎么把一堆实数变成概率的(softmax),以及温度、top-k、top-p 在这个分布上做了什么。
全篇的核心问题是:
能不能从”语言模型是条件分布”出发,三行推出交叉熵 loss?训练开始时 loss 应该是多少?
一、总览
1. 本文的对象
建模假设 语言模型 p_θ(x_t | x_<t) (第四篇)
↓ 原则 最大似然:选让训练集概率最大的 θ
↓ 第一步 乘积 → 取对数 → 求和
↓ 第二步 最大化 → 取负 → 最小化
↓ 第三步 除以 token 数
训练目标 L = −(1/T) Σ_t log p_θ(x_t | x_<t) ← 训练日志里的 loss,"每 token 负对数似然",也叫交叉熵
2. 本文的章节安排
| 章 | 主题 | 内容 |
|---|---|---|
| 二 | 最大似然估计 | 原则、似然函数、为什么取对数 |
| 三 | 三步推出 loss | 从似然到每 token 负对数似然;它为什么叫交叉熵 |
| 四 | 同一个模板 | SFT 的 loss mask、奖励模型、DPO:换一个概率 |
| 五 | 一个立刻能用的数字 | 初始 loss ≈ \(\ln V\);偏离说明什么 |
| 六 | softmax | logits 变概率;性质;减最大值的数值技巧;FlashAttention 的在线 softmax |
| 七 | 温度与采样 | 温度、greedy、top-k、top-p;为什么评测依赖采样设置 |
| 八 | 自测 | 五道题 |
| 九 | 本文小结 |
二、最大似然估计
1. 原则
模型有参数 \(\theta\)(Transformer 的全部权重)。给定 \(\theta\),模型给任何一个序列 \(x\) 一个概率 \(p_\theta(x)\)(第四篇的链式法则)。训练集是 \(N\) 条序列 \(x^{(1)}, \dots, x^{(N)}\)。问:\(\theta\) 取什么值最好?
最大似然估计(Maximum Likelihood Estimation,MLE)的回答是:取让训练集出现的概率最大的那个 \(\theta\)。直觉:训练集是真实世界的样本;一个好模型应该觉得”这些样本很正常、很可能出现”,而不是”这些样本几乎不可能”。
把”训练集出现的概率”当成 \(\theta\) 的函数,叫似然函数(likelihood)。假设各条序列独立,它是每条概率的乘积:
\[\mathcal{L}(\theta) = \prod_{i=1}^{N} p_\theta\big(x^{(i)}\big)\](”似然”与”概率”是同一个数,只是看待的角度不同:固定 \(\theta\) 看 \(x\) 是概率,固定 \(x\) 看 \(\theta\) 是似然。)
2. 为什么取对数
\(N\) 个小于 1 的数相乘会小到计算机存不下(\(10^{6}\) 条序列、每条概率 \(10^{-100}\),乘积是 \(10^{-10^8}\))。取对数把乘积变成求和,数值稳定;而且对数是单调递增的,最大化 \(\log \mathcal{L}\) 与最大化 \(\mathcal{L}\) 得到同一个 \(\theta\)。所以实际总是处理对数似然。
三、三步推出 loss
1. 推导
\[\begin{aligned} \hat\theta &= \arg\max_\theta \prod_{i} p_\theta\big(x^{(i)}\big) && \text{(最大似然)} \\ &= \arg\max_\theta \sum_{i} \log p_\theta\big(x^{(i)}\big) && \text{(第一步:取对数,乘积变求和)} \\ &= \arg\max_\theta \sum_{i} \sum_{t} \log p_\theta\big(x^{(i)}_t \mid x^{(i)}_{<t}\big) && \text{(链式法则:序列的 log 概率是逐 token 之和)} \\ &= \arg\min_\theta \left[ -\sum_{i} \sum_{t} \log p_\theta\big(x^{(i)}_t \mid x^{(i)}_{<t}\big) \right] && \text{(第二步:取负,最大化变最小化)} \end{aligned}\]括号里的量除以总 token 数(第三步,让它不随数据量变化),就是训练日志里的 loss:
\[\mathcal{L}(\theta) = -\frac{1}{T_{\text{total}}} \sum_{i, t} \log p_\theta\big(x^{(i)}_t \mid x^{(i)}_{<t}\big)\]——每个 token 的负对数似然(negative log-likelihood,NLL)的平均。逐个词读它:对每个位置,模型给真实的下一个 token 分配了概率 \(p\);\(-\log p\) 在 \(p = 1\) 时是 0(完全猜对,没有惩罚),\(p \to 0\) 时趋向无穷(真实 token 被认为不可能,惩罚巨大);loss 是所有位置这个惩罚的平均。
模型给真实 token 的概率 p 0.9 0.5 0.1 0.01 0.001
−log p(nat) 0.105 0.693 2.30 4.61 6.91
2. 为什么叫交叉熵
同一个量还有另一个名字:交叉熵(cross-entropy)。第六篇会给出信息论的定义:用分布 \(q\) 编码来自 \(p\) 的数据的平均码长是 \(H(p, q) = -\sum_x p(x)\log q(x)\)。训练时”真实分布” \(p\) 是 one-hot(真实的下一个 token 概率为 1,其余为 0),\(q\) 是模型分布,于是求和只剩一项:\(H(p, q) = -\log q(x_t)\)——正是上面的负对数似然。所以”最小化交叉熵”与”最大似然”是同一件事的两个名字;PyTorch 里的 F.cross_entropy 算的就是它。
3. 单位
对数取自然对数 \(\ln\) 时单位是 nat,取 \(\log_2\) 时是 bit,1 nat \(= 1/\ln 2 \approx 1.44\) bit。训练日志里的 loss 是 nat;论文里的 “bits per byte” 是 bit 再除以字节数。loss 1.8 nat = 2.6 bit/token。
四、同一个模板
第三章那三行是后面所有 loss 的模板,区别只在”把哪个概率放进 \(-\log\)“:
| 任务 | 放进 \(-\log\) 的概率 | 说明 |
|---|---|---|
| 预训练 | \(p_\theta(x_t \mid x_{<t})\),对全部 token 求和 | 上面的推导 |
| SFT(监督微调) | 同上,但只对回答部分求和 | prompt 部分的 token 不算 loss——用一个 0/1 的 loss mask 乘上去。模型只学”怎么回答”,不学”怎么提问” |
| 奖励模型 | \(P(y_w \succ y_l) = \sigma(r(y_w) - r(y_l))\) | 把 \(p_\theta\) 换成”回答 A 比回答 B 好”的概率(第六篇 Bradley-Terry);loss 是 \(-\log\sigma(r_w - r_l)\) |
| DPO | 奖励模型的概率里 \(r\) 再换成 \(\beta\log\frac{\pi_\theta}{\pi_{\text{ref}}}\) | 第六篇推出 |
| 分类(L2) | \(p_\theta(\text{真实类别} \mid \text{输入})\) | 逻辑回归、任何分类头 |
看到一个新的 loss,先问”它把什么概率放进了 \(-\log\)“,大多数时候就读懂了。
五、一个立刻能用的数字
1. 初始 loss
训练刚开始,权重是随机的,模型对下一个 token 一无所知——它的输出分布接近均匀:词表 \(V\) 个 token 每个概率 \(1/V\)。此时每个位置的 loss 是 \(-\log(1/V) = \ln V\):
词表大小 V ln V 模型
32000 10.4 Llama-2
128256 11.8 Llama-3
151936 11.9 Qwen2.5
50257 10.8 GPT-2
所以 Llama-3 的训练曲线应该从 11.8 附近开始往下走。
2. 偏离说明什么
- 第一步 loss 远大于 \(\ln V\)(比如 20):模型一开始就非常”自信”地猜错——输出分布不是均匀的而是很尖的。原因几乎总是初始化:最后一层权重太大,logits 太大。修法是把输出层初始化得更小。
- 第一步 loss 远小于 \(\ln V\)(比如 5):模型在没学之前就”知道”答案。原因是数据泄漏(比如输入里已经包含了目标 token)或 loss 算错了(比如 mask 掉了太多、或者算了 padding)。
这是训练前最便宜的 sanity check:跑一步,看 loss 是不是 \(\ln V\) 上下。L3 系列会把它扩展成一组检查。
六、softmax
1. 从 logits 到概率
模型最后一层输出的是 \(V\) 个实数(可正可负、不归一),叫 logits,记作 \(z = (z_1, \dots, z_V)\)。第四篇说分布要非负、和为 1,logits 都不满足。softmax 把它们变成分布:
\[p_j = \frac{e^{z_j}}{\sum_{k=1}^{V} e^{z_k}}\]取指数让每一项为正,除以总和让它们加起来为 1。一个三个 token 的例子:
logits z 2.0 1.0 0.0
e^z 7.39 2.72 1.00 和 = 11.11
softmax p 0.665 0.245 0.090 和 = 1
三条性质:
- 保序:logit 大的概率大;
- 差值决定比值:\(p_j / p_k = e^{z_j - z_k}\),logits 相差 1 概率比是 \(e \approx 2.7\),相差 5 是 148 倍——所以 logits 的差比绝对值重要;
- 平移不变:所有 logits 加同一个常数 \(c\),概率不变(分子分母同乘 \(e^c\))。
2. 减最大值的数值技巧
\(e^{z_j}\) 在 \(z_j\) 大于 88 左右就超出 float32 的范围(溢出成 inf)。利用平移不变性,实现上先减去最大的 logit 再取指数:
\[p_j = \frac{e^{z_j - m}}{\sum_k e^{z_k - m}}, \qquad m = \max_k z_k\]结果完全相同,但指数里最大是 \(e^0 = 1\),永远不溢出。所有框架的 softmax 都这么做。
这个技巧在 FlashAttention 里变成了核心机制。attention 要对每一行 \(T\) 个 score 做 softmax,但 FlashAttention 为了省显存分块计算,一次只看一块 score,看不到整行的最大值。做法是在线 softmax:每块维护自己的最大值 \(m_{\text{块}}\) 和局部的分母,合并两块时用 \(e^{m_1 - m_{\text{new}}}\)、\(e^{m_2 - m_{\text{new}}}\) 把两边的结果修正到同一个基准。数学上只用了”平移不变”这一条性质。Infra 地图的 GPU kernel 系列会写它的实现;这里知道它的数学根源即可。
3. logits 的差与”自信”
回到第五章:随机初始化的模型 logits 差不多大,softmax 接近均匀,loss ≈ \(\ln V\)。训练让正确 token 的 logit 比其他的高出几个单位,概率就压到它上面。一个训好的模型在”下一个 token 很确定”的位置(比如 “United” 后面的 “States”)logits 差可以到 10 以上,概率 0.99+;在开放的位置(一句话的开头)logits 差小,概率分散。困惑度(第六篇)就是在度量这种平均的分散程度。
七、温度与采样
1. 温度
推理时通常不直接用 softmax 的分布,而是先把 logits 除以一个温度 \(\tau > 0\):
\[p_j \propto e^{z_j / \tau}\]logits z = (2, 1, 0)
τ = 1 p = (0.665, 0.245, 0.090) 模型原始分布
τ = 0.5 p = (0.867, 0.117, 0.016) 更尖:logits 差被放大两倍
τ = 2 p = (0.506, 0.307, 0.186) 更平:logits 差被压缩一半
τ → 0 p = (1, 0, 0) 只剩最大的:greedy
τ → ∞ p = (1/3, 1/3, 1/3) 均匀:随机乱说
温度不改变顺序,只改变”多确定”。\(\tau < 1\) 保守、重复;\(\tau > 1\) 多样、易出错。代码生成常用 0.0–0.2,创意写作常用 0.7–1.0。
2. 截断:top-k 与 top-p
温度之外的另一类做法是截断:先把概率很小的 token 直接去掉,再在剩下的里面按比例抽。
- top-k:只保留概率最大的 \(k\) 个(比如 50),其余置零,重新归一化;
- top-p(nucleus sampling):按概率从大到小累加,累加到 \(p\)(比如 0.9)为止,保留这些,其余置零。它比 top-k 自适应:分布尖时可能只留 1–2 个,分布平时留几十个。
greedy(每步取概率最大的)是 \(\tau \to 0\) 或 \(k = 1\) 的特例。
3. 为什么评测依赖采样设置
同一个模型,\(\tau = 0\) 与 \(\tau = 0.7\) 输出的是两个不同的分布——前者确定、后者随机。评测得分是从这个分布里抽样得到的:greedy 在数学题上通常更高(不会抽到低概率的错误步骤),但在需要多样性的任务上更差;带温度的采样每次结果不同,要多跑几次取平均并报方差(第八篇)。所以比较两个模型时必须固定采样设置,论文里 “greedy” 与 “temperature 0.6, top-p 0.95” 的分数不能直接比。这也是 L5 评测系列反复强调的一件事——它的数学根源就是”温度改变了分布”。
八、自测
- 一个只有三个 token 的语言模型,某位置真实 token 的概率是 0.25,这个位置的 loss(nat)是多少?换成 bit 呢?
- 写出 SFT 的 loss:prompt 有 \(P\) 个 token、回答有 \(R\) 个 token,哪些位置进入求和?除以多少?
- 词表 50257 的模型第一步 loss 是 3.1,最可能的原因是什么?
- logits \((5, 3, 3)\):softmax 后三个概率是多少?温度 2 时呢?
- 为什么 softmax 减去最大值不改变结果?用一句话说明 FlashAttention 分块合并时靠的是这条性质的哪一部分。
答案要点:(1)\(-\ln 0.25 = 1.39\) nat \(= 2\) bit。(2)只有回答的 \(R\) 个位置(每个位置预测回答的下一个 token);通常除以 \(R\)(有 mask 的 token 数),不是 \(P + R\)。(3)\(\ln 50257 = 10.8\),3.1 远小于它——数据泄漏或 loss 算错。(4)\(e^5 = 148.4\)、\(e^3 = 20.1\),和 188.6,\(p = (0.787, 0.107, 0.107)\);温度 2 时 logits 变 \((2.5, 1.5, 1.5)\),\(p = (0.576, 0.212, 0.212)\)。(5)分子分母同乘 \(e^{-m}\) 约掉;分块时各块用自己的最大值,合并时再统一乘一个修正因子 \(e^{m_{\text{块}} - m_{\text{new}}}\)——靠的就是”乘一个公共因子不改变结果”。
九、本文小结
- 最大似然:选让训练集出现概率最大的参数;似然是各样本概率的乘积;取对数变求和(数值稳定、单调不变)。
- 三步推出 loss:取对数 → 取负 → 除以 token 数,得到每 token 负对数似然 \(-\frac{1}{T}\sum_t \log p_\theta(x_t \mid x_{<t})\);它就是交叉熵(真实分布 one-hot 时交叉熵只剩这一项);单位 nat,1 nat = 1.44 bit。
- 同一个模板:SFT 只对回答部分求和(loss mask);奖励模型把概率换成 \(\sigma(r_w - r_l)\);DPO 再换一次;分类换成真实类别的概率。读新 loss 先问”什么概率进了 \(-\log\)“。
- 初始 loss ≈ \(\ln V\)(Llama-3 是 11.8):远大于它是初始化太大,远小于它是数据泄漏或 loss 算错——训练前最便宜的检查。
- softmax \(p_j = e^{z_j} / \sum_k e^{z_k}\):保序、差值决定比值、平移不变;实现上减最大值防溢出;FlashAttention 的在线 softmax 靠的就是平移不变。
- 温度缩放 logits 的差:\(\tau \to 0\) 是 greedy,\(\tau\) 大趋向均匀;top-k / top-p 截断小概率 token。同一个模型不同采样设置是不同的分布——评测必须固定它们。
下一篇进入信息论:熵、交叉熵、KL 三个量的定义与关系,KL 的方向为什么决定”对齐会降低多样性”,以及从 KL 约束的最优策略一路推出 DPO。
本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/from-maximum-likelihood-to-cross-entropy.html)的前提下,欢迎各种形式的转载、翻译或商业引用。
COMMENTS
评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。