文章目录
PyTorch中交叉熵损失(Cross Entropy Loss)的三种计算方式详解
在深度学习分类任务中,交叉熵损失是最常用的损失函数之一。PyTorch的torch.nn.functional模块提供了便捷的API,同时我们也可以手动拆解计算过程。本文将结合代码示例,详细讲解三种等价的交叉熵损失计算方式,帮助理解其底层逻辑。
一、核心背景知识
交叉熵损失用于衡量模型输出的概率分布与真实标签分布的差异,对于分类任务(类别索引形式的标签),其计算公式为:
C
r
o
s
s
E
n
t
r
o
p
y
=
−
1
N
∑
i
=
1
N
l
o
g
(
p
y
i
)
CrossEntropy = -\frac{1}{N}\sum_{i=1}^N log(p_{y_i})
CrossEntropy=−N1i=1∑Nlog(pyi)
其中:
- N N N 是样本数量
- p y i p_{y_i} pyi 是模型对第 i i i个样本真实类别 y i y_i yi的预测概率
- 模型输出的
logits(原始得分)需先通过softmax转换为概率,再取对数计算
二、完整代码示例(带详细注释)
import torch
import torch.nn.functional as F
# ===================== 1. 构造测试数据 =====================
# 模拟模型输出的logits:未经过softmax的原始得分
# batch_size=2(2个样本),num_classes=3(3个类别:0/1/2)
logits = torch.tensor([[2.0, 1.0, 0.1], # 样本1:0类得分最高
[0.5, 3.0, 0.2]]) # 样本2:1类得分最高
# 真实标签:类别索引形式(非独热编码),这是F.cross_entropy要求的格式
targets = torch.tensor([0, 1]) # 样本1真实类别0,样本2真实类别1
print("===== 基础数据 =====")
print(f"模型输出logits(shape: {logits.shape}):")
print(logits)
print(f"\n真实标签:{targets}")
# ===================== 方式1:直接使用F.cross_entropy(推荐) =====================
# 方式1.1:reduction="none" → 输出每个样本的损失值
loss_none = F.cross_entropy(logits, targets, reduction="none")
# 方式1.2:reduction="mean" → 输出所有样本损失的平均值(默认方式)
loss_mean = F.cross_entropy(logits, targets, reduction="mean")
print("\n===== 方式1:直接调用F.cross_entropy =====")
print(f"每个样本的交叉熵损失:{loss_none}")
print(f"所有样本的平均交叉熵损失:{loss_mean}")
# ===================== 方式2:拆解为log_softmax + nll_loss =====================
# step1: log_softmax = log(softmax(logits)) → 先转概率再取对数
log_probs = F.log_softmax(logits, dim=-1) # dim=-1表示对最后一维(类别维度)计算
# step2: nll_loss(负对数似然损失)→ 取真实类别对应的负对数概率
nll_loss = -log_probs[range(len(targets)), targets] # 按索引取对应位置的值
manual_loss_none = nll_loss # 对应reduction="none"
manual_loss_mean = nll_loss.mean() # 对应reduction="mean"
print("\n===== 方式2:log_softmax + nll_loss(手动拆解) =====")
print(f"log_softmax结果:\n{log_probs}")
print(f"每个样本的交叉熵损失:{manual_loss_none}")
print(f"所有样本的平均交叉熵损失:{manual_loss_mean}")
# ===================== 方式3:完全手动实现(softmax + log + 负对数) =====================
# step1: softmax将logits转为概率分布
probs = F.softmax(logits, dim=-1)
# step2: 对概率取自然对数
log_probs_manual = torch.log(probs)
# step3: 取真实类别对应的负对数概率
manual_loss_none_v2 = -log_probs_manual[range(len(targets)), targets]
manual_loss_mean_v2 = manual_loss_none_v2.mean()
print("\n===== 方式3:完全手动实现(softmax → log → 负对数) =====")
print(f"softmax概率分布:\n{probs}")
print(f"概率取对数结果:\n{log_probs_manual}")
print(f"每个样本的交叉熵损失:{manual_loss_none_v2}")
print(f"所有样本的平均交叉熵损失:{manual_loss_mean_v2}")
# ===================== 验证三种方式等价性 =====================
print("\n===== 等价性验证 =====")
print(f"方式1与方式2结果是否一致:{torch.allclose(loss_none, manual_loss_none)}")
print(f"方式1与方式3结果是否一致:{torch.allclose(loss_none, manual_loss_none_v2)}")
三、三种计算方式详解
1. 方式1:直接调用F.cross_entropy(工程首选)
- 核心逻辑:PyTorch封装了完整的交叉熵计算流程,内部自动完成
softmax→log→负对数似然的全过程。 - 优点:简洁高效,避免手动计算的误差(如
torch.log(F.softmax())可能出现数值不稳定),是实际项目中最推荐的方式。 - 关键参数:
reduction="none":返回每个样本的损失值,适用于需要对样本损失加权/筛选的场景;reduction="mean":返回所有样本损失的平均值(默认),适用于常规批量训练;reduction="sum":返回所有样本损失的总和。
2. 方式2:log_softmax + nll_loss(理解底层逻辑)
- 核心逻辑:将交叉熵拆解为两个步骤:
F.log_softmax:先对logits做softmax得到概率,再取对数(数值稳定性优于单独调用torch.log(F.softmax()));nll_loss(Negative Log Likelihood Loss):取真实类别对应的负对数概率,即交叉熵的核心计算。
- 适用场景:需要单独获取“对数概率”的场景(如多标签分类、自定义损失函数)。
3. 方式3:完全手动实现(深入理解原理)
- 核心逻辑:完全拆解为三个独立步骤,直观展示交叉熵的数学定义:
F.softmax(logits):将logits转换为0-1之间的概率分布;torch.log(probs):对概率取自然对数;- 取真实类别对应的负对数概率,得到每个样本的交叉熵损失。
- 注意点:这种方式可能存在数值不稳定问题(如概率接近0时,
torch.log会得到极大的负数),因此不推荐在工程中使用,仅用于学习理解。
四、输出结果说明
运行上述代码后,输出结果如下(数值略有浮点误差属正常):
===== 基础数据 =====
模型输出logits(shape: torch.Size([2, 3])):
tensor([[2.0000, 1.0000, 0.1000],
[0.5000, 3.0000, 0.2000]])
真实标签:tensor([0, 1])
===== 方式1:直接调用F.cross_entropy =====
每个样本的交叉熵损失:tensor([0.4170, 0.1249])
所有样本的平均交叉熵损失:tensor(0.2710)
===== 方式2:log_softmax + nll_loss(手动拆解) =====
log_softmax结果:
tensor([[-0.4170, -1.4170, -2.3170],
[-2.5498, -0.1249, -2.8498]])
每个样本的交叉熵损失:tensor([0.4170, 0.1249])
所有样本的平均交叉熵损失:tensor(0.2710)
===== 方式3:完全手动实现(softmax → log → 负对数) =====
softmax概率分布:
tensor([[0.6590, 0.2424, 0.0986],
[0.1131, 0.8668, 0.0201]])
概率取对数结果:
tensor([[-0.4170, -1.4170, -2.3170],
[-2.5498, -0.1249, -2.8498]])
每个样本的交叉熵损失:tensor([0.4170, 0.1249])
所有样本的平均交叉熵损失:tensor(0.2710)
===== 等价性验证 =====
方式1与方式2结果是否一致:True
方式1与方式3结果是否一致:True
总结
- 工程实践首选方式1:
F.cross_entropy简洁、高效且数值稳定,满足绝大多数分类任务需求; - 学习理解选方式2/3:方式2(
log_softmax + nll_loss)兼顾可读性和稳定性,方式3完全手动拆解,适合理解交叉熵的数学本质; - 核心等价关系:
F.cross_entropy(logits, targets) = F.nll_loss(F.log_softmax(logits, dim=-1), targets),三种方式最终结果完全一致。
通过这三种方式的对比,不仅能掌握交叉熵损失的使用方法,更能深入理解其底层计算逻辑,为自定义损失函数、排查训练问题打下基础。
554




被折叠的 条评论
为什么被折叠?



