查看: 4811|回复: 3

PyTorch多分类交叉熵损失CrossEntropyLoss用法

[复制链接]
发表于 7 天前 | 显示全部楼层 |阅读模式
背景与核心结论
在 PyTorch 分类任务中,CrossEntropyLoss 常被误认为只是套用交叉熵公式。原文指出,torch.nn.CrossEntropyLoss 实际把三个步骤合在一起:先对输出矢量 [z1, z2, …, zK] 做 softmax,使概率和为 1;再对 softmax 结果取 log;最后结合真实标签计算损失,自动加上负号,并按默认方式做算术平均。因此模型最后一层不需要再加激活函数,用 nn.Linear 直接输出即可。

如果模型最后一层已经加了激活函数,并且也计算了 log(预测值),则可以用 torch.nn.NLLLoss() 完成上述第 3 步。原文示例中 target 使用 [0,1,2] 这类类别索引;原文虽提到 one-hot,但以代码实际传入形式为准,避免把类别索引和 one-hot 混用。

多分类 CrossEntropyLoss 的计算过程
原文用 3×3 输入演示 softmax、log 和按标签取值的计算逻辑,代码如下:
  1. import torch
  2. input = torch.randn(3, 3)
  3. soft_input = torch.nn.Softmax(dim=0)
  4. print(soft_input(input))
  5. # tensor([[0.7284, 0.7364, 0.3343],
  6. #         [0.1565, 0.0365, 0.0408],
  7. #         [0.1150, 0.2270, 0.6250]])
  8. print(torch.log(soft_input(input)))
  9. # tensor([[-0.3168, -0.3059, -1.0958],
  10. #         [-1.8546, -3.3093, -3.1995],
  11. #         [-2.1625, -1.4827, -0.4701]])
  12. # 标签为[0,1,2]时,第一行取第0个、第二行取第1个、第三行取第2个
  13. print((0.3168 + 3.3093 + 0.4701) / 3)
  14. # 1.3654000000000002
  15. loss = torch.nn.NLLLoss()
  16. target = torch.tensor([0, 1, 2])
  17. print(loss(input, target))
  18. # tensor(0.1365)
复制代码
按原文手工计算,取 log 后对应位置为 0.3168、3.3093、0.4701,去掉负号后求平均得到 1.3654。但原文最后一行 loss(input, target) 传入的是原始 input,而不是 torch.log(soft_input(input)),所以输出 0.1365 与手工值 1.3654 不一致。NLLLoss 的输入应是 log 概率,不会自动做 softmax 和 log;复现时应把 log 概率作为 NLLLoss 输入。这个差异也说明 CrossEntropyLoss 的封装步骤不能拆错。

示例中 Softmax 的 dim=0 是按列归一化,实际使用时 dim 要与类别所在维度一致。CrossEntropyLoss 默认会对样本损失做平均,原文特别提醒其计算中包含负号和算术平均。

二分类 BCELoss 的用法与参数
二分类常用 torch.nn.BCELoss()。原文示例先对输入做 Sigmoid,再计算 BCELoss 并反向传播:
  1. m = nn.Sigmoid()
  2. loss = nn.BCELoss()
  3. input = torch.randn(3, requires_grad=True)
  4. target = torch.empty(3).random_(2)
  5. output = loss(m(input), target)
  6. output.backward()
复制代码
原文强烈建议使用 BCELoss 时在模型最后一层加 Sigmoid 层。BCELoss 的参数签名为:
  1. torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='mean')
复制代码
weight 是可选的 Tensor,用于给每个 batch element 的损失做手工重缩放,若传入则大小应为 nbatch;size_average 和 reduce 已废弃,由 reduction 控制;reduction 可取 'none'、'mean'、'sum',分别表示不归约、求和后除以元素数、求和。原文说明当出现 log 0 时,该项损失按 -100 处理,这类数值边界需要特别注意。

BCELoss 的输入输出规定为:Input 为 (N, *),Target 与输入同形状;Output 为标量,若 reduction 为 'none',则输出与输入同形状。

BCEWithLogitsLoss 的数值处理
为避免 BCELoss 中 log 0 无法计算的局限,原文建议使用 torch.nn.BCEWithLogitsLoss(),它会在计算时自动给 prediction 加 Sigmoid。其参数签名为:
  1. torch.nn.BCEWithLogitsLoss(weight=None, size_average=None, reduce=None, reduction='mean', pos_weight=None)
复制代码
用法与 BCELoss 相同,额外提供 pos_weight。实际排查时,要先确认模型输出是概率还是 logits:BCELoss 需要先 Sigmoid,BCEWithLogitsLoss 接收 logits,不应再手工加 Sigmoid。

适用场景与排查要点
多分类优先使用 CrossEntropyLoss,模型最后一层不要加激活函数;若已有 log 概率,则用 NLLLoss。二分类使用 BCELoss 时要配 Sigmoid,关注 log 0;需要更稳的数值处理时用 BCEWithLogitsLoss。排查损失异常时,重点检查最后一层是否有重复激活、target 是类别索引还是 one-hot、NLLLoss 输入是否为 log 概率、reduction 设置是否符合预期,以及输入和 target 形状是否匹配。

以上内容围绕原文的代码逻辑、参数含义和计算过程进行重组,保留了 CrossEntropyLoss、NLLLoss、BCELoss、BCEWithLogitsLoss 的关键事实,并补充了原文示例中手工值与 NLLLoss 调用输入不一致的说明。
回复

使用道具 举报

发表于 7 天前 | 显示全部楼层

Re: PyTorch多分类交叉熵损失CrossEntropyLoss用法

感谢分享,这篇把 CrossEntropyLoss 的封装过程拆得很直观,尤其是 NLLLoss 那一段,确实很容易踩坑:前面手工算的是 log 概率取负求平均,后面却把原始 input 直接传给 NLLLoss,结果自然对不上。这个例子反而很有排查价值,提醒大家 NLLLoss 接收的是 log 概率,不会自动再做 softmax 和 log。 另外你提到的几个点也很实用:多分类时模型最后一层用 nn.Linear 直接输出 logits,不要再加 softmax;target 用类别索引而不是 one-hot;Softmax 的 dim 要和类别维一致;二分类里 BCELoss 配 Sigmoid、BCEWithLogitsLoss 接 logits,不要重复激活。最后那段排查清单很值得收藏,损失异常时按这些方向检查能少走不少弯路。
回复 支持 反对

使用道具 举报

发表于 7 天前 | 显示全部楼层

Re: PyTorch多分类交叉熵损失CrossEntropyLoss用法

这篇整理挺清楚,几个容易混的点都点到了:CrossEntropyLoss 内部已经包含 softmax、log 和负号平均,模型最后一层直接 Linear 输出即可;NLLLoss 的输入应该是 log 概率,不能把原始 input 直接传进去;BCELoss 要配 Sigmoid,BCEWithLogitsLoss 则接收 logits,不应再手工加 Sigmoid。尤其指出原文示例里 NLLLoss 传入原始 input 导致手工值和输出对不上,这个提醒很有价值。实际排查时先确认 target 是类别索引还是 one-hot、Softmax 的 dim 是否对应类别维、reduction 是否符合预期,基本能避开大部分坑。要是再补一个 dim 与类别维对应的最小例子,对新手会更直观。
回复 支持 反对

使用道具 举报

发表于 7 天前 | 显示全部楼层

Re: PyTorch多分类交叉熵损失CrossEntropyLoss用法

楼主整理得很清楚,尤其是把 CrossEntropyLoss 里 softmax、log 和 NLLLoss 三部分拆开讲,比单记公式有用多了。我一开始看那段示例也被 last line 的 loss(input, target) 绕进去了,看到你指出 NLLLoss 不会自动做 softmax 和 log、应该传 log 概率才反应过来,这个坑确实容易踩。dim=0 按列归一化那个提醒也很关键,实际做分类时输入一般是 (N, C),类别在 dim=1 上,照抄示例很容易出问题。 另外 BCE 那部分把 BCELoss 要配 Sigmoid、BCEWithLogitsLoss 接 logits 的分界讲明白了,平时排查 loss 异常经常就是重复加了激活或者 target 形式不对。最后那个排查清单挺实用,最后一层有没有重复激活、target 是类别索引还是 one-hot、NLLLoss 输入是不是 log 概率、reduction 设置,基本覆盖了常见问题。感谢分享,收藏一下。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-27 17:24 , Processed in 0.025732 second(s), 29 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部