inf问题主要由log(0)或exp(大数)引发,核心解法是跳过显式softmax+log,改用log-sum-exp技巧对logits直接计算,避免中间溢出。

直接结论:Inf 问题几乎总是由 log(0) 或 exp(大数) 引起,核心解法不是加 epsilon 到输出概率,而是跳过 softmax + log 的显式计算,改用 log-sum-exp 技巧处理 logits。
为什么手动实现 softmax + log 会出 Inf
很多人写交叉熵时先调 softmax,再取对应类别的概率,最后 log。这种两步法在 logits 值较大(如 >88)时,exp(88) 就已超出 float32 表示范围(≈1.6e38),导致 softmax 输出为 inf/0,后续 log(0) 直接变成 -inf 或 nan。
常见错误模式:
- 用
np.exp(logits) / np.sum(np.exp(logits))计算 softmax - 没做
logits - np.max(logits, axis=-1, keepdims=True)预处理 - 对 softmax 结果直接
np.log(p),未clip且未检查 p 是否为 0
PyTorch 官方 CrossEntropyLoss 的稳定实现原理
它不显式计算 softmax,而是用等价公式:-logits[k] + log(sum(exp(logits))),其中 log(sum(exp(logits))) 通过 logsumexp 实现,内部自动减去最大值保证数值安全。
你可以这样验证其稳定性:
import torch logits = torch.tensor([[100.0, 1.0, 2.0]]) # 含极大值 labels = torch.tensor([0]) loss = torch.nn.CrossEntropyLoss()(logits, labels) print(loss.item()) # 正常输出 ~0.0,不会报错
而手动实现若漏掉 max-shift,torch.exp(100.0) 就已 overflow。
自己写稳定交叉熵的三个关键动作
以 NumPy 为例,避免 Inf 的最小可靠实现需满足:
- 对
logits做中心化:logits = logits - np.max(logits, axis=-1, keepdims=True) - 用
np.log(np.sum(np.exp(logits), axis=-1))替代np.log(softmax(...)[k]) - 最终损失为:
-logits[range(N), labels] + np.log(np.sum(np.exp(logits), axis=-1))
注意:不要在 softmax 输出后加 epsilon 再 log——这治标不治本,仍可能因中间 inf 导致整个计算链失效。
二分类场景下 BCEWithLogitsLoss 为何更安全
当你用 sigmoid + BCELoss 时,sigmoid(x) 在 x > 88 时趋近 1,x log(sigmoid(x)) 或 log(1-sigmoid(x)) 极易触发 log(0)。
正确做法是用 torch.nn.BCEWithLogitsLoss(或 NumPy 中的 np.where(x >= 0, x - np.log1p(np.exp(-x)), -np.log1p(np.exp(x)))),它把 sigmoid 和 log 合并为一个数值稳定的函数,全程只操作 logits,不暴露中间概率。
这个细节常被忽略:**稳定性的根源不在“加 epsilon”,而在“避免生成接近 0 或 1 的中间概率值”。**
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











