从概率论到信息论:由浅入深理解 KL 散度和交叉熵

在机器学习里,我们经常会遇到两个非常重要的概念:

  • 交叉熵(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 散度就不再只是两个公式,而是一套从概率到信息、再到模型训练目标的统一语言。

发表评论