查看: 291|回复: 0

PyTorch交叉熵损失源码与log_softmax实现

[复制链接]
发表于 3 小时前 | 显示全部楼层 |阅读模式
PyTorch的nn.CrossEntropyLoss常被直接用于多分类损失计算,但从源码看,它并没有单独实现交叉熵,而是把log_softmax和nll_loss串起来。理解这条调用链,可以解释为什么F.cross_entropy、F.nll_loss(F.log_softmax(...))和nn.CrossEntropyLoss在相同输入上会得到相同loss,也能帮助核对输入维度、target索引和reduction参数。

一、源码调用链
CrossEntropyLoss继承_WeightedLoss,构造参数包括weight、size_average、ignore_index=-100、reduce和reduction='mean'。forward阶段把weight、ignore_index、reduction传给F.cross_entropy:
  1. class CrossEntropyLoss(_WeightedLoss):
  2.     def __init__(self, weight=None, size_average=None, ignore_index=-100,
  3.                  reduce=None, reduction='mean'):
  4.         super(CrossEntropyLoss, self).__init__(weight, size_average, reduce, reduction)
  5.         self.ignore_index = ignore_index
  6.     @weak_script_method
  7.     def forward(self, input, target):
  8.         return F.cross_entropy(input, target, weight=self.weight,
  9.                                ignore_index=self.ignore_index, reduction=self.reduction)
复制代码
F.cross_entropy内部会处理旧版size_average/reduce参数,再执行log_softmax与nll_loss的组合:
  1. def cross_entropy(input, target, weight=None, size_average=None, ignore_index=-100,
  2.                   reduce=None, reduction='mean'):
  3.     if size_average is not None or reduce is not None:
  4.         reduction = _Reduction.legacy_get_string(size_average, reduce)
  5.     return nll_loss(log_softmax(input, 1), target, weight, None, ignore_index, None, reduction)
复制代码
关键点是log_softmax(input, 1)和nll_loss(...)组合,dim=1表示在类别维度归一化。因此CrossEntropyLoss的输入为网络原始输出logits,target为类别索引。从调用链看,传入的input会再次经过log_softmax,排查时可用F.log_softmax+F.nll_loss与F.cross_entropy做等价核对。参数方面,weight用于类别权重,ignore_index默认-100,reduction默认mean,size_average和reduce是旧版兼容入口。

二、为什么需要softmax
交叉熵描述两个概率分布的差异。神经网络输出是向量,不是概率分布;softmax把向量归一化成概率分布,再计算交叉熵。CrossEntropyLoss内部采用log_softmax,即先softmax再取log,然后交给nll_loss。

三、验证三种调用方式等价
  1. import torch
  2. import torch.nn.functional as F
  3. import torch.nn as nn
  4. torch.manual_seed(2019)
  5. output = torch.randn(2, 3) # 网络输出
  6. target = torch.ones(2, dtype=torch.long).random_(3) # 真实标签
  7. print('output:\n{}'.format(output))
  8. print('target:\n{}\n'.format(target))
  9. log_softmax = F.log_softmax(output, dim=1)
  10. print('log_softmax:\n{}\n'.format(log_softmax))
  11. nll_loss = F.nll_loss(log_softmax, target)
  12. print('nll_loss:{}'.format(nll_loss))
  13. cross_entropy_loss = F.cross_entropy(output, target)
  14. print('cross_entropy_loss:{}'.format(cross_entropy_loss))
  15. criterion = nn.CrossEntropyLoss()
  16. loss = criterion(output, target)
  17. print('loss:{}'.format(loss))
复制代码
输出结果如下:
  1. output:
  2. tensor([[-0.1187, 0.2110, 0.7463],
  3.         [-0.6136, -0.1186, 1.5565]])
  4. target:
  5. tensor([2, 0])
  6. log_softmax:
  7. tensor([[-1.5614, -1.2317, -0.6964],
  8.         [-2.4335, -1.9386, -0.2635]])
  9. nll_loss:1.564985990524292
  10. cross_entropy_loss:1.564985990524292
  11. loss:1.564985990524292
复制代码
F.log_softmax+ F.nll_loss、F.cross_entropy以及nn.CrossEntropyLoss得到相同值1.564985990524292,说明它们走的是同一计算路径。

四、log_softmax的展开与复现
log_softmax先做softmax,再对结果取log。下面的代码分别用PyTorch和NumPy复现:
  1. import torch
  2. import torch.nn.functional as F
  3. import numpy as np
  4. torch.manual_seed(2019)
  5. output = torch.randn(2, 3) # 网络输出
  6. print(output, '\n')
  7. torch_softmax = F.softmax(output, dim=1)
  8. print(torch_softmax)
  9. torch_log_softmax = torch.log(torch_softmax)
  10. print(torch_log_softmax)
  11. torch_log_softmax = F.log_softmax(output, dim=1)
  12. print(torch_log_softmax)
  13. torch_log_softmax = output.log_softmax(dim=1)
  14. print(torch_log_softmax, '\n')
  15. e_j = np.exp(output.numpy())
  16. e_k = e_j.sum(axis=1).reshape(-1, 1)
  17. np_softmax = e_j / e_k
  18. print(np_softmax)
  19. np_log_softmax = np.log(np_softmax)
  20. print(np_log_softmax)
复制代码
输出结果如下:
  1. tensor([[-0.1187, 0.2110, 0.7463],
  2.         [-0.6136, -0.1186, 1.5565]])
  3. tensor([[0.2098, 0.2918, 0.4984],
  4.         [0.0877, 0.1439, 0.7684]])
  5. tensor([[-1.5614, -1.2317, -0.6964],
  6.         [-2.4335, -1.9386, -0.2635]])
  7. tensor([[-1.5614, -1.2317, -0.6964],
  8.         [-2.4335, -1.9386, -0.2635]])
  9. tensor([[-1.5614, -1.2317, -0.6964],
  10.         [-2.4335, -1.9386, -0.2635]])
  11. [[0.20983616 0.29179907 0.49836478]
  12. [0.08772492 0.14391233 0.76836276]]
  13. [[-1.5614283 -1.2316898 -0.696423 ]
  14. [-2.4335492 -1.938551 -0.2634933]]
复制代码
torch.log(F.softmax(output, dim=1))、F.log_softmax(output, dim=1)和output.log_softmax(dim=1)结果一致;NumPy按行计算exp、求和、除法再取log,也能复现相同概率分布和对数结果。

五、使用时的检查点
结合源码参数,使用CrossEntropyLoss时应关注:input形状为[N, C]并在dim=1上做log_softmax;target使用torch.long类别索引,示例中为torch.ones(2, dtype=torch.long).random_(3);ignore_index默认-100用于指定忽略目标;reduction='mean'决定默认返回均值;weight可传类别权重。排查loss不一致时,先比较F.log_softmax+F.nll_loss与F.cross_entropy,确认调用路径和dim设置,再检查target索引范围与ignore_index、reduction参数是否符合预期。
回复

使用道具 举报

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

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

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

官方核心成员

关注微信公众号

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

GMT+8, 2026-10-20 14:12 , Processed in 0.037247 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部