整体说明
- 交叉熵损失(Cross-Entropy Loss, CE Loss)和负对数似然损失(Negative Log-Likelihood Loss, NLL Loss)的核心区别在于 是否包含 Softmax 激活 以及 适用场景的精细化
- PyTorch 框架中的交叉熵损失 = Softmax(或 Sigmoid)+ 负对数似然损失
- 使用原则
- 分类任务直接用 CE Loss(输入 logits),简单且数值稳定
- 若已手动添加 Softmax/LogSoftmax 层,或需要自定义概率输出,再用 NLL Loss
- NLL Loss 是“对概率的负对数”,CE Loss 是“对分布差异的衡量”,但在分类任务中(预测分布由 Softmax 生成)二者结果等价
分类任务场景符号定义
- 模型原始输出(logits):\(z = [z_1, z_2, …, z_C]\)(\(C\) 为类别数)
- 真实标签:\(y\)(one-hot 编码为 \(y_{one-hot} = [0, 0, …, 1, …, 0]\),或类别索引 \(y \in {0,1,…,C-1}\))
- Softmax 激活:将 logits 映射为概率分布 \(p_i = \text{Softmax}(z)i = \frac{e^{z_i}}{\sum{j=1}^C e^{z_j}}\)(满足 \(\sum p_i = 1\))
- 对数概率:\(\log(p_i)\)(概率越大,对数概率越接近 0;概率越小,对数概率越负)
负对数似然损失(Negative Log-Likelihood Loss, NLL Loss)
- 负对数似然损失(NLL Loss)是 对 模型输出的概率分布 \(p\) 取对数后,取负,再根据真实标签\(y\)选择对应类别的项
- 若标签为 one-hot 编码:
$$ L_{\text{NLL}} = -\sum_{i=1}^C y_i \cdot \log(p_i)$$- 此时仅真实类别 \(y_k=1\),所以可进一步简化为
$$ L_{\text{NLL}} = -\log(p_k) $$- 这也是 负对数似然损失(NLL Loss)名字的由来
- 此时仅真实类别 \(y_k=1\),所以可进一步简化为
- 若标签为类别索引(本质是一个 index,one-hot 转换成向量以后与 one-hot 编码一致):直接取真实类别 \(y\) 对应的概率 \(p_y\) 的对数再取负
$$ L_{\text{NLL}} = -\log(p_y)$$
- 若标签为 one-hot 编码:
- 特别说明: NLL Loss 本身不包含 Softmax 步骤
- 输入给 NLL Los 的必须是 已经经过 Softmax 激活的概率分布 \(p\) 或对数概率 \(\log(p)\))
交叉熵损失(Cross-Entropy Loss, CE Loss)
- 交叉熵损失(CE Loss) 用于衡量 模型预测概率分布 \(p\) 与 真实标签分布 \(q\) (one-hot编码)之间的差异,公式与 NLL Loss形式完全一致 ,但隐含了“先将 logits 转为概率”的逻辑
- 数学公式:
$$ L_{\text{CE}} = -\sum_{i=1}^C q_i \cdot \log(p_i)$$- 其中: \(q\) 是真实分布
- one-hot 时简化为
$$ L_{\text{CE}} = -\log(p_y) $$
- 关键区别:在深度学习框架中(如 PyTorch、TensorFlow),CE Loss 的输入是 原始 logits ,而非概率
- 框架会内部先对 logits 做 Softmax,再计算负对数似然
- 数学公式:
CE Loss 和 NLL Loss 对比
- 本质上,交叉熵(CE)的数学定义是
$$ H(q,p) = -\sum q_i \log(p_i)$$ - 而 负对数似然(NLL)是
$$ -\sum q_i \log(p_i)$$- 仅当 \(p\) 是模型预测的概率分布时,因此当 \(p\) 是 Softmax 输出时,CE Loss = NLL Loss ,但二者的 计算链路 不同:
$$ \text{CE Loss(logits)} = \text{NLL Loss(Softmax(logits))}$$
- 仅当 \(p\) 是模型预测的概率分布时,因此当 \(p\) 是 Softmax 输出时,CE Loss = NLL Loss ,但二者的 计算链路 不同:
附录:为什么框架中的 CE Loss 更常用?
- 在 PyTorch 中:
nn.NLLLoss():输入必须是 对数概率 (通常需先过nn.LogSoftmax(dim=1))nn.CrossEntropyLoss():输入是 logits,内部等价于nn.LogSoftmax(dim=1) + nn.NLLLoss()
- 选择 CE Loss 的核心原因是数值稳定性 :
- 直接计算 \(\log(\text{Softmax}(z_i))\) 时,若 \(z_i\) 很大(如 1000),\(e^{z_i}\) 会溢出(变成无穷大);而框架会用数学变换优化:
$$ \log\left(\frac{e^{z_i}}{\sum_j e^{z_j}}\right) = z_i - \log\left(\sum_j e^{z_j}\right) $$ - 通过减去 \(\log(\sum_j e^{z_j})\)(即 LogSumExp),避免了指数爆炸,数值更稳定
- 直接计算 \(\log(\text{Softmax}(z_i))\) 时,若 \(z_i\) 很大(如 1000),\(e^{z_i}\) 会溢出(变成无穷大);而框架会用数学变换优化:
- 补充:一种常用的等价推导(可以优化计算的方法)是
$$
\begin{align}
\text{log_sofmax}(x) &= \log \frac{e^{x_{i}}}{\sum_{j=1}x_{j}} \\
&= \log e^{x_i} - \log \sum_{j=1}x_{j} \\
&= x_i - \log \sum_{j=1}x_{j}
\end{align}
$$- 上面的式子中,只需要计算一次 \(\log \sum_{j=1}x_{j}\) 即可(且不同维度可重用该值), 其他的都是加减法运算
使用代码示例
用 CrossEntropyLoss(直接输入 logits)
1
2
3
4
5
6
7
8
9
10
11import torch
import torch.nn as nn
# 模型输出logits(C=3类,batch_size=2)
logits = torch.tensor([[2.0, 1.0, 0.1], [1.0, 3.0, 0.5]])
# 真实标签(类别索引)
labels = torch.tensor([0, 1])
# 交叉熵损失(内部含Softmax)
ce_loss = nn.CrossEntropyLoss()
print(ce_loss(logits, labels)) # 输出:0.4170(数值稳定)用 NLLLoss(需先过LogSoftmax)
1
2
3
4
5
6
7# 先将logits转为对数概率
log_softmax = nn.LogSoftmax(dim=1)
log_probs = log_softmax(logits) # 等价于框架内部优化后的计算
# 负对数似然损失(输入是对数概率)
nll_loss = nn.NLLLoss()
print(nll_loss(log_probs, labels)) # 输出:0.4170(与CE Loss结果一致)
附录:错误示例(NLLLoss 直接输入 logits)
- 常常犯的错:
1
2# 直接用logits输入NLLLoss(会报错或结果错误)
print(nll_loss(logits, labels)) # 错误:logits不是对数概率,数值会异常
附录:BCE Loss
二分类任务时,CE Loss 常被称为 二元交叉熵(BCE Loss) ,与 NLL Loss 的关系同样成立:
nn.BCEWithLogitsLoss():输入是 logits(单输出,如 \(s\)),内部先做 Sigmoid(\(\sigma(s) = 1/(1+e^{-s})\)),再计算BCE Loss:
$$ L = -[y\log(\sigma(s)) + (1-y)\log(1-\sigma(s))]$$- 若手动用 Sigmoid + NLLLoss:需将标签转为 float(如 \(y \in {0.0, 1.0}\)),且输出为单通道概率,此时
$$ L = -[y\log(p) + (1-y)\log(1-p)]$$- 与 BCEWithLogitsLoss 结果一致
BCELoss 还可以直接使用
1
torch.nn.BCELoss()
- 具体操作就是实现了书上定义的二分类交叉熵定义
- 普通样本计算公式:
$$ loss(o,t)=-\frac{1}{n}\sum_i(t_i\log(o_i)+(1-t_i)\log(1-o_i)) $$ - 带有权重的单个样本计算公式:
$$ loss(o,t)=-\frac{1}{n}\sum_i w_i (t_i \log(o_i)+(1-t_i)\log(1-o_i)) $$
BCELoss vs CrossEntropyLoss 比较
BCELoss对应的网络只有一个输出值CrossEntropyLoss对应的网络有两个输出值- 可以证明, 二分类时
BCELoss与CrossEntropyLoss等价- 证明时,将每个
CrossEntropyLoss的计算公式中的softmax函数分子分母同时除以shift(\(\text{shift} = max (x_i)\)), 即可得到为下面的定义,进一步可得到BCELoss的计算公式
$$f_i(x) = \frac{e^{(x_i - \text{shift})}} { \sum^j e^{(x_j - \text{shift})}} $$
- 证明时,将每个
附录:相关损失函数 MultiLabelMarginLoss
使用
1
torch.nn.MultiLabelMarginLoss()
用于多标签分类的损失函数
总结
- 一般来说直接使用
CrossEntropyLoss即可- 二分类时还可以使用
nn.BCELoss - 二分类时使用
nn.BCELoss的话,输入的input和target维度都为(n, 1)的维度 - 二分类时使用
CrossEntropyLoss则输入的input为 (n x 2) 的维度
- 二分类时还可以使用
- 如果使用
NLLLoss则一定记得在输出层最后加一层log_softmax层 - 注意,
log指的是以e为底的对数函数,而不是以10为底的- Mac自带的计算器中
log是以10为底的,ln才是以e为底的
- Mac自带的计算器中