本文是《算法工程师的数学:读公式不卡壳的最小集》系列的第 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 评测系列反复强调的一件事——它的数学根源就是”温度改变了分布”。

八、自测

  1. 一个只有三个 token 的语言模型,某位置真实 token 的概率是 0.25,这个位置的 loss(nat)是多少?换成 bit 呢?
  2. 写出 SFT 的 loss:prompt 有 \(P\) 个 token、回答有 \(R\) 个 token,哪些位置进入求和?除以多少?
  3. 词表 50257 的模型第一步 loss 是 3.1,最可能的原因是什么?
  4. logits \((5, 3, 3)\):softmax 后三个概率是多少?温度 2 时呢?
  5. 为什么 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 会按你的通知设置发邮件,不用守在这里。

×