KL散度与交叉熵的区别

黎 浩然/ 13 10 月, 2026/ 大语言模型/LARGELANGUAGEMODEL/LLM, 机器学习/MACHINELEARNING, 研究生/POSTGRADUATE, 计算机/COMPUTER/ 0 comments

交叉熵衡量用预测分布 q 描述目标分布 p 的平均代价;KL 散度衡量其中超出 p 自身熵的部分。它们满足 H(p,q)=H(p)+KL(p‖q),因此固定 p 时优化两者方向相同,但数值含义不同。

文章目录
  1. 用两个候选把差别算出来
  2. 差出来的一项就是 KL
  3. 方向换了,问题也换了
  4. Python 检查恒等式与零概率
  5. 一个标签与软分布不是同一种输入
  6. 参考资料

用两个候选把差别算出来

设 p=[0.9,0.1],q=[0.5,0.5],两个位置代表相同的两个事件。自然对数下,p 的熵约0.325083,交叉熵约0.693147,KL 约0.368064,单位都是 nat。三项满足0.693147≈0.325083+0.368064。

这些是人为构造的概率,没有对应真实模型或数据集。q 在两个事件上给出同样概率,所以每个事件的负对数概率都是 log 2。p 则更偏向第一个事件,它自身的不确定性较低。

目标自身的熵
H(p) ≈ 0.325083
预测带来的额外代价
KL(p‖q) ≈ 0.368064
两部分相加
H(p,q) ≈ 0.693147
原创示意图,数值为文中人工构造例子的计算结果,不是模型实测。

差出来的一项就是 KL

对离散的同一事件空间,H(p)=-Σᵢpᵢlog pᵢ,H(p,q)=-Σᵢpᵢlog qᵢ。两式相减后得到 Σᵢpᵢ(log pᵢ-log qᵢ),也就是 KL(p‖q)。约定 pᵢ=0 的项贡献0;若 pᵢ>0 且 qᵢ=0,KL 和交叉熵为正无穷。

固定 p 时,H(p) 不随 q 改变,所以最小化交叉熵与最小化这个方向的 KL 具有相同最优目标。若 p 也在改变,例如两个分布都受训练参数影响,就不能把 H(p) 当常数丢掉。

这里讨论的是离散概率分布。SciPy 1.15.2 的官方 entropy 文档给出相应定义;库会对未归一化输入自动归一化,而下面教学实现选择拒绝它,避免悄悄改变输入。

方向换了,问题也换了

KL(p‖q) 的期望由 p 加权,KL(q‖p) 则由 q 加权。上例反向值约0.510826,与0.368064不同。它不是可任意交换参数的普通距离。

例如一个候选在目标 p 中概率很高、预测 q 中很低,正向 KL 会按目标概率放大这份代价。交换方向后权重也改变。做教师与学生分布比较时,应明确谁提供期望权重,而不是只写“加一个 KL loss”。

比较之前还需对齐事件编号。若一边第一个位置代表词 A,另一边代表词 B,即使两组向量形状一致,计算也失去了原本意义。不同 tokenizer 的词表不能直接逐位置相减;这与BPE 分词规则的绑定关系有关。

Python 检查恒等式与零概率

import math

def validate(p, q):
    if not p or len(p) != len(q):
        raise ValueError("Expected equal nonempty distributions")
    for x in (p, q):
        if not all(math.isfinite(v) and v >= 0 for v in x):
            raise ValueError("Invalid probability")
        if not math.isclose(math.fsum(x), 1.0):
            raise ValueError("Probabilities must sum to one")

def metrics(p, q):
    validate(p, q)
    entropy = -math.fsum(a * math.log(a) for a in p if a > 0)
    if any(a > 0 and b == 0 for a, b in zip(p, q)):
        return entropy, math.inf, math.inf
    cross_entropy = -math.fsum(a * math.log(b)
                               for a, b in zip(p, q) if a > 0)
    kl = math.fsum(a * (math.log(a) - math.log(b))
                   for a, b in zip(p, q) if a > 0)
    return entropy, cross_entropy, kl

p, q = [0.9, 0.1], [0.5, 0.5]
h, ce, kl = metrics(p, q)
assert math.isclose(ce, h + kl)
assert math.isclose(metrics(p, p)[2], 0.0, abs_tol=1e-12)
assert not math.isclose(kl, metrics(q, p)[2])
assert math.isinf(metrics([1., 0.], [0., 1.])[2])
assert metrics([1., 0.], [1., 0.]) == (0., 0., 0.)
for bad in ([0.2, 0.2], [-0.1, 1.1], [math.nan, 0.]):
    try:
        metrics(bad, q)
    except ValueError:
        pass
    else:
        raise AssertionError("Invalid input accepted")
print([round(x, 6) for x in (h, ce, kl)])
print(round(metrics(q, p)[2], 6))

原创代码已运行,检查交叉熵分解、同分布 KL 为0、方向不对称、目标有质量而预测为0的无穷情况,以及非法输入。输出前三项为 [0.325083,0.693147,0.368064],反向 KL 为0.510826。

实现使用自然对数,n 为事件数,时间 O(n),未创建随 n 增长的工作列表,额外空间 O(1),不计输入本身。计算中仍有舍入误差;不应把微小数值偏差误判成数学恒等式失效。

一个标签与软分布不是同一种输入

若目标 p 是 one-hot,真实类别 y 上概率为1,其余为0,那么 H(p)=0,交叉熵与 KL 都退化为 -log qᵧ。软目标则通常有非零熵,交叉熵不必在匹配目标时变成0。

这解释了为何不同目标分布、标签平滑设置或归约方式下,loss 数值不能随手横比。先记录目标的定义、概率或 logit 输入、对数底、归约维度和零概率处理,再解释数值。此文没有做训练效果实验,也不据此声称某种方向总能得到更好的模型。

Read the English version

参考资料

本文于2026年10月13日补发,文章日期保留原计划北京时间。参考资料早于计划时段,示例为原创教学计算。

支持

如果这篇文章对你有帮助,欢迎支持本站。

微信支持二维码,点击查看大图
微信
支付宝支持二维码,点击查看大图
支付宝
Buy Me a Coffee,点击支持本站
Buy Me a Coffee

二维码可点击放大。更多支持方式见支持页面。

Share this Post

Leave a Comment

您的邮箱地址不会被公开。 必填项已用 * 标注

*
*