在机器学习里,我们经常会遇到两个非常重要的概念:
- 交叉熵(Cross Entropy)
- KL 散度(Kullback-Leibler Divergence)
它们经常出现在分类任务、知识蒸馏、VAE、语言模型训练、强化学习策略约束等场景里。
很多时候我们会直接使用它们:
torch.nn.CrossEntropyLoss() torch.nn.KLDivLoss()
但如果只停留在公式层面,很容易产生几个疑问:
- 为什么公式里会有
log? - 交叉熵和 KL 散度到底有什么关系?
- 为什么最小化交叉熵等价于最小化 KL 散度?
- 正向 KL 和反向 KL 为什么行为不一样?
- 为什么反向 KL 会有 mode-seeking 的特点?
这篇文章会从概率论的直觉讲起,再过渡到信息论,最后回到机器学习里的实际使用。
1. 从概率分布开始
假设我们有一个随机变量 (X),它可能取若干个值。
比如抛硬币:
[ X \in {H, T} ]
真实分布 (P) 是:
[ P(H)=0.5,\quad P(T)=0.5 ]
这里 (P) 表示真实世界中事件发生的概率。
但在机器学习中,我们通常不知道真实分布 (P),于是会训练一个模型分布 (Q) 去近似它。
例如模型认为:
[ Q(H)=0.9,\quad Q(T)=0.1 ]
此时就有一个很自然的问题:
真实分布是 (P),但模型认为是 (Q),这两个分布到底差了多少?
交叉熵和 KL 散度,就是用来回答这个问题的重要工具。
不过在进入它们之前,我们先要理解一个更底层的概念:信息量。
2. 信息量:为什么是 (-\log p)?
信息论里,一个事件 (x) 的信息量定义为:
[ I(x)=-\log P(x) ]
这个公式一开始看起来有点突然:为什么要用 log?为什么还要加负号?
我们先从直觉出发。
一个事件越罕见,它发生时带来的信息量应该越大。
比如:
- “太阳明天升起”概率很高,所以信息量很低;
- “连续抛 10 次硬币全是正面”概率很低,所以信息量很高。
因此信息量应该满足:
[ P(x) 越小,I(x) 越大 ]
并且:
[ P(x)=1 时,I(x)=0 ]
因为必然发生的事情不会带来新的信息。
但最关键的要求是第三条:
两个独立事件同时发生时,总信息量应该等于各自信息量之和。
假设事件 (A) 和事件 (B) 独立,那么:
[ P(A,B)=P(A)P(B) ]
我们希望信息量满足:
[ I(A,B)=I(A)+I(B) ]
也就是:
[ I(P(A)P(B))=I(P(A))+I(P(B)) ]
什么函数可以把乘法变成加法?
答案就是 log。
因为:
[ \log(ab)=\log a+\log b ]
所以信息量自然定义成:
[ I(x)=-\log P(x) ]
之所以加负号,是因为概率 (P(x)) 位于 (0) 到 (1) 之间,(\log P(x)) 是负数。加负号后,信息量就是非负的。
3. 用 bit 理解信息量
如果 log 以 2 为底,信息量单位就是 bit。
例如:
[ P(x)=\frac{1}{2} ]
则:
[ I(x)=-\log_2\frac{1}{2}=1 ]
表示这个事件带来 1 bit 信息。
如果:
[ P(x)=\frac{1}{4} ]
则:
[ I(x)=-\log_2\frac{1}{4}=2 ]
如果:
[ P(x)=\frac{1}{8} ]
则:
[ I(x)=-\log_2\frac{1}{8}=3 ]
这背后有一个非常直观的解释:
从 8 个等可能选项中确定一个答案,大约需要 3 次二分判断。
比如:
第一次:答案在前 4 个里吗? 第二次:答案在前 2 个里吗? 第三次:答案是第 1 个吗?
所以:
[ -\log_2\frac{1}{8}=3 ]
表示“确定这个事件大约需要 3 bit 信息”。
4. 熵:一个分布的平均信息量
单个事件的信息量是:
[ I(x)=-\log P(x) ]
如果我们想知道整个分布 (P) 的平均信息量,就需要对所有事件求期望:
[ H(P)=\mathbb{E}_{x\sim P}[-\log P(x)] ]
展开后就是:
[ H(P)=-\sum_x P(x)\log P(x) ]
这就是熵(Entropy)。
熵可以理解为:
当数据真实服从分布 (P) 时,平均需要多少信息量来描述一个样本。
也可以理解为:
分布自身的不确定性。
例如公平硬币:
[ P(H)=0.5,\quad P(T)=0.5 ]
熵为:
[ H(P)=-0.5\log_2 0.5-0.5\log_2 0.5=1 ]
也就是平均每次抛硬币有 1 bit 信息。
如果硬币非常偏:
[ P(H)=0.99,\quad P(T)=0.01 ]
那熵会更低,因为结果更容易预测,不确定性更小。
5. 交叉熵:用 Q 去编码 P 的平均代价
现在我们回到机器学习中的问题。
真实分布是 (P),模型分布是 (Q)。
如果我们知道真实分布 (P),那么最优编码长度是:
[ -\log P(x) ]
但如果我们不知道 (P),而是用模型分布 (Q) 来编码,那么编码长度变成:
[ -\log Q(x) ]
真实样本仍然来自 (P),所以要对 (P) 求期望:
[ H(P,Q)=\mathbb{E}_{x\sim P}[-\log Q(x)] ]
展开为:
[ H(P,Q)=-\sum_x P(x)\log Q(x) ]
这就是交叉熵(Cross Entropy)。
它的直觉是:
真实分布是 (P),但我们用模型分布 (Q) 去编码或预测时,平均需要付出多少信息代价。
注意对比一下熵和交叉熵:
[ H(P)=-\sum_x P(x)\log P(x) ]
[ H(P,Q)=-\sum_x P(x)\log Q(x) ]
可以这样理解:
熵 H(P):真实分布 P,用 P 自己编码自己 交叉熵 H(P,Q):真实分布 P,但用 Q 来编码
6. 交叉熵的硬币例子
真实硬币是公平的:
[ P(H)=0.5,\quad P(T)=0.5 ]
模型认为:
[ Q(H)=0.9,\quad Q(T)=0.1 ]
那么交叉熵是:
[ H(P,Q)=-0.5\log_2 0.9-0.5\log_2 0.1 ]
计算后大约是:
[ H(P,Q)\approx 1.737\text{ bits} ]
而真实分布自己的熵是:
[ H(P)=1\text{ bit} ]
这说明:
如果用错误的模型分布 (Q) 去编码真实分布 (P),平均每次需要 1.737 bits,而最优情况下只需要 1 bit。
多出来的部分就是 KL 散度。
7. KL 散度:用 Q 编码 P 时额外多付出的代价
KL 散度定义为:
[ D_{KL}(P|Q)=\sum_x P(x)\log\frac{P(x)}{Q(x)} ]
它也可以写成期望形式:
[ D_{KL}(P|Q)=\mathbb{E}_{x\sim P}\left[\log\frac{P(x)}{Q(x)}\right] ]
从信息论角度看,KL 散度的含义非常清晰:
真实分布是 (P),但我们用模型分布 (Q) 来编码或预测时,平均比使用 (P) 自己多付出多少信息代价。
因为用 (Q) 编码的长度是:
[ -\log Q(x) ]
用 (P) 编码的最优长度是:
[ -\log P(x) ]
两者差值是:
[ -\log Q(x)-(-\log P(x)) ]
整理得到:
[ \log\frac{P(x)}{Q(x)} ]
再对真实分布 (P) 求平均:
[ D_{KL}(P|Q)=\mathbb{E}_{x\sim P}\left[\log\frac{P(x)}{Q(x)}\right] ]
这就是 KL 散度公式的来源。
8. KL 散度与交叉熵的关系
KL 散度可以展开:
[ D_{KL}(P|Q)=\sum_x P(x)\log\frac{P(x)}{Q(x)} ]
等价于:
[ D_{KL}(P|Q)
\sum_x P(x)\log P(x)
\sum_x P(x)\log Q(x) ]
而:
[ H(P)=-\sum_x P(x)\log P(x) ]
[ H(P,Q)=-\sum_x P(x)\log Q(x) ]
所以:
[ D_{KL}(P|Q)=H(P,Q)-H(P) ]
也就是:
[ H(P,Q)=H(P)+D_{KL}(P|Q) ]
这个公式非常重要:
交叉熵 = 真实分布自身的不确定性 + 模型分布不匹配带来的额外代价。
在机器学习训练中,真实数据分布 (P) 通常是固定的,所以 (H(P)) 对模型参数来说是常数。
因此:
[ \min H(P,Q) ]
等价于:
[ \min D_{KL}(P|Q) ]
这就是为什么分类任务中最小化交叉熵,本质上是在让模型分布 (Q) 接近真实分布 (P)。
9. 为什么 KL 散度不是距离?
虽然我们经常说 KL 散度衡量两个分布的“差异”,但它不是严格意义上的距离。
原因是:
[ D_{KL}(P|Q)\neq D_{KL}(Q|P) ]
也就是说,KL 散度不满足对称性。
从含义上看:
[ D_{KL}(P|Q) ]
表示:
真实分布是 (P),但用 (Q) 去编码 (P) 时,多付出多少信息代价。
而:
[ D_{KL}(Q|P) ]
表示:
真实分布换成了 (Q),但用 (P) 去编码 (Q) 时,多付出多少信息代价。
这两个问题本来就不一样。
所以 KL 散度更像是:
用某个分布去解释另一个分布时的额外代价
而不是通常意义上的几何距离。
10. 正向 KL:D_KL(P || Q)
通常我们把:
[ D_{KL}(P|Q) ]
称为正向 KL。
它的公式是:
[ D_{KL}(P|Q)=\sum_x P(x)\log\frac{P(x)}{Q(x)} ]
注意外面乘的是 (P(x))。
这意味着:
正向 KL 是在真实分布 (P) 会出现的地方统计损失。
它最害怕的是:
[ P(x)>0,\quad Q(x)\approx 0 ]
也就是说:
真实世界里可能出现的东西,模型不能给很低概率。
如果真实分布在某个区域有概率,而模型分布几乎不给概率,那么这一项:
[ P(x)\log\frac{P(x)}{Q(x)} ]
会变得非常大。
所以正向 KL 的性格是:
真实有的,模型都应该尽量覆盖。
因此正向 KL 常被称为 mode-covering,也就是倾向于覆盖真实分布中的多个模式。
11. 反向 KL:D_KL(Q || P)
反向 KL 是:
[ D_{KL}(Q|P)=\sum_x Q(x)\log\frac{Q(x)}{P(x)} ]
注意这里外面乘的是 (Q(x))。
这意味着:
反向 KL 是在模型分布 (Q) 自己会生成的地方统计损失。
它最害怕的是:
[ Q(x)>0,\quad P(x)\approx 0 ]
也就是说:
模型自己生成出来的东西,不能落到真实分布几乎不认可的地方。
如果模型在某个真实分布概率极低的地方放了概率,那么:
[ Q(x)\log\frac{Q(x)}{P(x)} ]
会变得非常大。
极端情况下,如果:
[ P(x)=0,\quad Q(x)>0 ]
则:
[ D_{KL}(Q|P)=+\infty ]
所以反向 KL 的性格是:
宁可少覆盖,也不要乱生成。
或者:
模型自己爱去的地方,真实世界必须认可。
这也是为什么反向 KL 常被称为 mode-seeking。
12. 用图像直觉理解正向 KL 和反向 KL
假设真实分布 (P) 有两个峰:
真实分布 P:
/\ /\
/ \ / \
____/ \__________/ \____
左峰 右峰
现在模型 (Q) 能力有限,只能表达一个简单分布。
正向 KL 的倾向
如果优化:
[ D_{KL}(P|Q) ]
模型会尽量覆盖真实分布的所有高概率区域。
因为左峰和右峰在 (P) 中都有概率,如果 (Q) 漏掉其中一个,正向 KL 会惩罚它。
所以正向 KL 的倾向是:
宁可覆盖宽一点,也不要漏掉真实模式。
它可能会把 (Q) 放得比较宽,试图同时覆盖两个峰。
反向 KL 的倾向
如果优化:
[ D_{KL}(Q|P) ]
模型会更关注自己生成的样本是否落在真实分布的高概率区域。
如果 (Q) 放在两个峰中间,那么中间区域在 (P) 中概率很低,就会被反向 KL 惩罚。
所以反向 KL 往往会选择其中一个峰,然后集中在那里。
它的倾向是:
宁可只选择一个安全区域,也不要把概率放到真实分布低概率的地方。
这就是 mode-seeking。
13. 用水果例子理解反向 KL
假设真实世界中常见水果是:
苹果:50% 香蕉:50% 石头:0%
真实分布 (P) 是:
[ P(\text{苹果})=0.5,\quad P(\text{香蕉})=0.5,\quad P(\text{石头})=0 ]
模型 (Q) 是一个推荐系统。
如果模型只推荐苹果:
[ Q(\text{苹果})=1.0,\quad Q(\text{香蕉})=0,\quad Q(\text{石头})=0 ]
从反向 KL 的角度看,它不一定特别糟糕。因为模型推荐的苹果确实是真实分布认可的东西。
但如果模型是:
[ Q(\text{苹果})=0.8,\quad Q(\text{香蕉})=0,\quad Q(\text{石头})=0.2 ]
由于真实分布里:
[ P(\text{石头})=0 ]
那么反向 KL 会非常大,甚至变成无穷大。
这说明反向 KL 最关心的是:
模型自己输出的东西,真实分布认不认。
它不太在意你是否覆盖了所有真实模式,但非常在意你有没有生成真实分布不支持的东西。
14. 交叉熵在分类任务中的形式
在分类任务中,真实标签通常是 one-hot 分布。
假设有三类:
猫 / 狗 / 鸟
真实类别是“猫”,则真实分布是:
[ P=[1,0,0] ]
模型预测分布是:
[ Q=[0.7,0.2,0.1] ]
交叉熵为:
[ H(P,Q)=-\sum_i P_i\log Q_i ]
因为只有真实类别那一项的 (P_i=1),所以:
[ H(P,Q)=-\log 0.7 ]
如果模型预测:
[ Q=[0.99,0.005,0.005] ]
则损失是:
[ -\log 0.99 ]
非常小。
如果模型预测:
[ Q=[0.01,0.89,0.10] ]
则损失是:
[ -\log 0.01 ]
非常大。
所以 one-hot 分类交叉熵可以简单理解为:
模型给真实类别的概率越高,损失越小;给真实类别的概率越低,损失越大。
15. 交叉熵与最大似然估计
交叉熵还有一个统计学解释:
最小化交叉熵等价于最大化训练数据的似然。
假设训练集为:
[ (x_1,y_1),(x_2,y_2),\dots,(x_N,y_N) ]
模型对真实标签的预测概率是:
[ Q(y_i|x_i) ]
那么整个训练集的似然为:
[ \prod_{i=1}^{N}Q(y_i|x_i) ]
最大化这个乘积,就是希望模型认为真实标签出现的概率尽可能大。
为了方便优化,我们通常取 log:
[ \sum_{i=1}^{N}\log Q(y_i|x_i) ]
最大化 log-likelihood 等价于最小化 negative log-likelihood:
[ -\sum_{i=1}^{N}\log Q(y_i|x_i) ]
这正是分类交叉熵在 one-hot 标签下的形式。
所以交叉熵也可以理解为:
负对数似然损失(Negative Log-Likelihood)
16. 二分类交叉熵
二分类中,标签为:
[ y\in{0,1} ]
模型预测正类概率为:
[ p ]
二分类交叉熵为:
[ L=-\left[y\log p+(1-y)\log(1-p)\right] ]
当 (y=1) 时:
[ L=-\log p ]
也就是希望模型给正类更高概率。
当 (y=0) 时:
[ L=-\log(1-p) ]
也就是希望模型给负类更高概率。
在 PyTorch 中常见的是:
torch.nn.BCELoss torch.nn.BCEWithLogitsLoss
更推荐使用:
torch.nn.BCEWithLogitsLoss
因为它把 sigmoid 和 BCE 合在一起实现,数值更稳定。
17. 多分类交叉熵
多分类中,模型通常输出 logits:
[ z=[z_1,z_2,\dots,z_C] ]
然后经过 softmax 得到概率:
[ Q_i=\frac{e^{z_i}}{\sum_j e^{z_j}} ]
如果真实类别是 (k),则交叉熵为:
[ L=-\log Q_k ]
也就是:
[ L=-\log\frac{e^{z_k}}{\sum_j e^{z_j}} ]
展开为:
[ L=-z_k+\log\sum_j e^{z_j} ]
在 PyTorch 里:
loss_fn = torch.nn.CrossEntropyLoss() logits = model(x) # shape: [N, C] target = torch.tensor([0]) # 类别索引,不是 one-hot loss = loss_fn(logits, target)
需要注意:
torch.nn.CrossEntropyLoss的输入应该是 logits,不要提前手动 softmax。
错误示例:
probs = torch.softmax(logits, dim=1) loss = loss_fn(probs, target) # 不推荐
因为 CrossEntropyLoss 内部已经包含了 LogSoftmax + NLLLoss。
18. 软标签、标签平滑与知识蒸馏
前面讲的是 one-hot 标签:
[ P=[1,0,0] ]
但有些场景中,标签本身是软分布:
[ P=[0.7,0.2,0.1] ]
这时交叉熵不能只看真实类别一项,而是完整计算:
[ H(P,Q)=-\sum_i P_i\log Q_i ]
软标签常见于:
- 知识蒸馏
- 标签平滑
- 多标注者存在分歧
- 大模型偏好分布建模
在知识蒸馏中,teacher 输出一个分布 (P),student 输出一个分布 (Q)。
我们希望 student 学到的不只是“哪个类别最大”,还包括类别之间的相似关系。
例如:
猫 和 狗 可能比 猫 和 汽车 更接近
这种信息在 one-hot 标签里是没有的,但在 teacher 的软分布里可能存在。
由于:
[ H(P,Q)=H(P)+D_{KL}(P|Q) ]
当 teacher 分布 (P) 固定时,最小化交叉熵和最小化:
[ D_{KL}(P|Q) ]
在优化方向上是一致的。
19. KL 散度在 VAE 中的作用
在 VAE 中,我们经常看到 KL 项:
[ D_{KL}(q(z|x)|p(z)) ]
其中:
- (q(z|x)):编码器根据输入 (x) 得到的隐变量分布
- (p(z)):先验分布,通常是标准正态分布
VAE 的目标通常包含两部分:
[ \mathcal{L}
\text{重建损失} + D_{KL}(q(z|x)|p(z)) ]
重建损失希望隐变量 (z) 保留足够信息,以便重建原始输入。
KL 项则希望:
编码器产生的隐变量分布不要太任性,而是尽量接近标准正态先验。
这样训练完成后,我们就可以从标准正态分布中采样 (z),再通过 decoder 生成新样本。
所以 VAE 中的 KL 项承担了一个“规整隐空间”的作用。
20. 常见误区总结
误区 1:KL 散度是距离
不准确。
KL 散度不满足对称性:
[ D_{KL}(P|Q)\neq D_{KL}(Q|P) ]
所以它不是严格的距离。
更准确的说法是:
KL 散度衡量的是用一个分布去解释另一个分布时的额外信息代价。
误区 2:交叉熵和 KL 散度完全一样
也不准确。
它们关系密切,但不是完全一样。
[ H(P,Q)=H(P)+D_{KL}(P|Q) ]
交叉熵包含两部分:
- 真实分布自身的不确定性 (H(P))
- 模型分布和真实分布不匹配带来的额外代价 (D_{KL}(P|Q))
只是当 (P) 固定时,(H(P)) 是常数,所以优化交叉熵等价于优化 KL 散度。
误区 3:反向 KL 只是把 P 和 Q 换一下,没有本质区别
区别非常大。
正向 KL:
[ D_{KL}(P|Q) ]
关注:
真实分布中出现的东西,模型有没有覆盖?
反向 KL:
[ D_{KL}(Q|P) ]
关注:
模型生成出来的东西,真实分布认不认?
所以:
正向 KL:mode-covering,倾向于覆盖所有真实模式 反向 KL:mode-seeking,倾向于选择一个高概率安全区域
误区 4:CrossEntropyLoss 输入的是概率
在 PyTorch 中,多分类的 CrossEntropyLoss 输入应该是 logits,而不是 softmax 后的概率。
正确:
loss = torch.nn.CrossEntropyLoss()(logits, target)
不推荐:
loss = torch.nn.CrossEntropyLoss()(torch.softmax(logits, dim=1), target)
21. 最后总结
可以用下面这条线串起来:
概率分布 ↓ 事件信息量:I(x) = -log P(x) ↓ 熵:H(P) = E_P[-log P(x)] ↓ 交叉熵:H(P,Q) = E_P[-log Q(x)] ↓ KL 散度:D_KL(P||Q) = H(P,Q) - H(P)
其中:
[ H(P) ]
表示真实分布 (P) 自己的不确定性。
[ H(P,Q) ]
表示真实分布是 (P),但使用 (Q) 编码或预测时的平均代价。
[ D_{KL}(P|Q) ]
表示真实分布是 (P),但使用 (Q) 编码或预测时,平均比使用 (P) 自己多付出的信息代价。
一句话理解:
交叉熵看的是“用模型分布 (Q) 解释真实分布 (P) 的总代价”;KL 散度看的是“相比最优的 (P),使用 (Q) 额外多付出的代价”。
如果放到分类任务里:
最小化交叉熵,就是让模型给真实标签分配更高概率;从分布角度看,就是让模型分布 (Q) 尽量接近真实分布 (P)。
如果放到生成模型或变分推断里:
正向 KL 更强调覆盖真实分布,反向 KL 更强调生成结果落在真实高概率区域。
理解了这条线,交叉熵和 KL 散度就不再只是两个公式,而是一套从概率到信息、再到模型训练目标的统一语言。