KLDivLoss:负值、方向与平均分母

黎 浩然/ 20 10 月, 2026/ 大语言模型/LARGELANGUAGEMODEL/LLM, 教程/TUTORIAL, 计算机/COMPUTER/ 0 comments

KLDivLoss算出负数,先检查传入的是否是log概率,以及target是否为归一化分布;数值小了一个类别数,则检查reduction。它的参数顺序与数学记号不直观,误把概率当作log概率后,代码仍可能返回一个有限数,却已经不是你想计算的KL散度。

文章目录
  1. 先把数学方向写在调用旁边
  2. 把概率误当log概率,为什么会变负
  3. 两行两类,分母差在哪里
  4. log_target只改变表示,不改变方向
  5. 九项检查把三个错误分开
  6. 对照损失时保留分子与分母
  7. 来源与补发说明

这里回答“KLDivLoss的input和target该放什么,mean与batchmean为什么不同”,用一个二分类分布把方向、数值空间和分母逐项拆开。Read in English。

先把数学方向写在调用旁边

设P是目标分布,Q是模型分布,所需量为D(P∥Q)=ΣᵢPᵢ(log Pᵢ−log Qᵢ)。这不是对称距离:通常交换P与Q会改变结果。本文算例限定两个分布均严格为正、每行和为1,使用自然对数。

PyTorch 2.8定义要求input为log空间;默认target为概率,log_target=True时target也为log空间。因此要算D(P∥Q),对应input=log Q、target=P,而不是把数学记号从左到右直接塞进两个参数。

拿P=(0.8,0.2)、Q=(0.5,0.5),计算得到0.8log1.6+0.2log0.4,约0.192745。反方向D(Q∥P)约0.223144。这两个数都可以合法,但服务于不同目标;不能只看哪一个更小来决定参数顺序。

目标P=(0.8,0.2)
模型Q=(0.5,0.5)
input=log Q;target=P
D(P∥Q)≈0.192745
原创参数映射:接口首参不是数学记号中的第一个分布。

把概率误当log概率,为什么会变负

如果把Q的概率值0.5直接放进input,点式表达式就变成ΣPᵢ(log Pᵢ−Qᵢ)。本例约为−1.000402,已经没有log Q项,自然不再受正规KL非负性的保证。不是模型“比完美更好”,而是函数输入的表示错了。

正规分布下非负性针对沿整个概率支持求和后的KL,不针对每个类别项。上述正确算例中,第二项0.2log0.4本来就是负的,只是总和仍为正。因此看到reduction=’none’里的负元素,也不能直接判定接口出错。

还要区分量级很小的浮点负误差与明显的表示错误。只有先确认方向、归一化、支持与输入空间,讨论误差容限才有意义。这里使用的负一左右反例足以暴露概念问题,不是在建议给所有训练任务设置同一个数值阈值。

两行两类,分母差在哪里

构造一个2×2批次:第一行使用上面的P与Q,第二行两者都为(0.5,0.5)。两行逐类别项求和为S≈0.192745。按样本平均是S/2≈0.0963724;按四个元素平均是S/4≈0.0481862,刚好相差类别数2。

接口的batchmean将元素总和除以第一维大小,mean则按所有元素取平均。对明确形状[B,C],每一行就是一份分布时,前者对应每样本KL的平均。若张量为[B,T,C]且每个时间步是一份分布,batchmean仍只除B,得到的是每样本跨时间总和的平均,不会自动变成每token平均。

因此序列任务需要先决定计量单位。若存在padding,可先得到逐元素项、沿类别求和,再按有效token掩码累加,除以有效token数。没有有效token时应跳过该目标或采用已定义的空批策略;分母为0时应明确处理,而不是拿一个任意常数掩盖无有效数据的批次。这是目标函数设计,不能靠更换reduction名字自动完成。

两行逐类总和S≈0.192745
按样本:S/2≈0.0963724
按元素:S/4≈0.0481862
原创分母对照:形状[2,2]且每行一份分布,均值差一个类别数。

log_target只改变表示,不改变方向

默认表示是Pᵢ(log Pᵢ−log Qᵢ)。若target保存的是aᵢ=log Pᵢ,同一项可写成exp(aᵢ)(aᵢ−log Qᵢ)。两式相等,前提是a真的是对应概率的自然对数。设置log_target=True不会帮你交换KL方向,也不是对原始logits自动归一化。

模型输出若是logits,可使用沿正确类别轴的log_softmax得到log概率。类别轴写错,形状可能仍合法,却改变了归一化对象。目标来自另一个模型时,也要明确其分布构造方式,而不是把任意浮点数组叫作软标签。

本文严格正分布避免了零概率边界。实际应用中P=0、Q=0涉及零贡献与无穷散度等情况;不能把教学代码中的log直接用于零值,也不能默认加ε不会改变目标。需要依据实际支持和数值策略另行验证。

九项检查把三个错误分开

下面代码验证正确数值、相同分布、方向差异、log表示等价、错误空间的负结果以及两种分母,并拒绝一个未归一化输入。它使用标准库math,既没有运行PyTorch,也没有测试反向梯度。

# Positive normalized distributions; standard-library math only.
from math import log, exp, isclose

def kl(p, q):
    if len(p) != len(q) or not p:
        raise ValueError('shape')
    if min(p+q) <= 0 or not isclose(sum(p),1) or not isclose(sum(q),1):
        raise ValueError('positive normalized probabilities required')
    return sum(a*(log(a)-log(b)) for a,b in zip(p,q))

p=(0.8,0.2); q=(0.5,0.5)
d=kl(p,q)
assert isclose(d,0.19274475702175753)
assert d > 0 and isclose(kl(p,p),0)
assert not isclose(d,kl(q,p))
log_input=tuple(map(log,q))
log_target=tuple(map(log,p))
by_log=sum(exp(a)*(a-b) for a,b in zip(log_target,log_input))
assert isclose(by_log,d)
wrong=sum(a*(log(a)-b) for a,b in zip(p,q))
assert wrong < 0
B,C=2,2
summed=d+kl((0.5,0.5),(0.5,0.5))
assert isclose(summed/B, d/2)
assert isclose(summed/(B*C), d/4)
assert isclose((summed/B)/(summed/(B*C)), C)
try:
    kl((0.8,0.8),q)
except ValueError:
    pass
else:
    raise AssertionError('unnormalized target accepted')
print('9 KL arithmetic checks passed')

这个校验函数刻意只接受严格正、归一化的向量。拒绝零值是示例范围限制,不是声称KL在所有含零分布上都没有定义。把教学前提写进输入校验,能防止示例被无提示地推广到不适用的场景。

对照损失时保留分子与分母

调试记录至少应含方向D(P∥Q)、两侧表示、类别轴、逐分布求和方式与最终分母。两个实验loss相差常数时,先对齐这些定义,再讨论模型质量或学习率。改变类别数、序列长度和padding比例,都会暴露隐藏的分母差异。

此前的ignore_index分母分析也强调先确认哪些位置参与目标。KL额外需要处理分布方向与log空间,不能只沿用另一个损失函数的“mean”直觉。

如果这组数值对照帮你定位了负KL或损失缩放问题,欢迎通过文末支持区自愿支持。把方向、表示和分母分开记录,是下次迁移蒸馏目标时仍能复用的方法。

来源与补发说明

KLDivLoss · log_softmax

按原计划时段前已有的PyTorch 2.8版本定义核对,推导和图示为原创。本次补发保留10月20日19:00北京时间文章日期,实际公开时间另行核验。执行了标准库示例,没有运行PyTorch集成测试或设备性能测量。

支持

如果文中的解释或示例帮你解决了问题、节省了时间,欢迎自愿打赏支持本站。

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

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

Share this Post

Leave a Comment

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

*
*