背景与核心结论
在 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 和按标签取值的计算逻辑,代码如下:- import torch
- input = torch.randn(3, 3)
- soft_input = torch.nn.Softmax(dim=0)
- print(soft_input(input))
- # tensor([[0.7284, 0.7364, 0.3343],
- # [0.1565, 0.0365, 0.0408],
- # [0.1150, 0.2270, 0.6250]])
- print(torch.log(soft_input(input)))
- # tensor([[-0.3168, -0.3059, -1.0958],
- # [-1.8546, -3.3093, -3.1995],
- # [-2.1625, -1.4827, -0.4701]])
- # 标签为[0,1,2]时,第一行取第0个、第二行取第1个、第三行取第2个
- print((0.3168 + 3.3093 + 0.4701) / 3)
- # 1.3654000000000002
- loss = torch.nn.NLLLoss()
- target = torch.tensor([0, 1, 2])
- print(loss(input, target))
- # 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 并反向传播:- m = nn.Sigmoid()
- loss = nn.BCELoss()
- input = torch.randn(3, requires_grad=True)
- target = torch.empty(3).random_(2)
- output = loss(m(input), target)
- output.backward()
复制代码 原文强烈建议使用 BCELoss 时在模型最后一层加 Sigmoid 层。BCELoss 的参数签名为:- 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。其参数签名为:- 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 调用输入不一致的说明。 |