在分类以及语义分割任务中,CrossEntropy是十分常用的一个损失函数,pytorch也对其进行了实现用于直接使用。
但本人在阅读其源码时,发现nn.CrossEropyLoss并不是直接按照交叉熵公式: L o s s ( p , y ) = − ∑ i = 1 n y i l o g ( p i ) Loss(p, y)=-\sum_{i=1}^{n}y_ilog(p_i) Loss(p,y)=−∑i=1nyilog(pi)计算而得,实际上其是融合了多个计算而成:softmax计算+log计算+nll_loss计算。其具体原理,推荐一篇讲解得十分详细的pytorch语义分割中CrossEntropyLoss()损失函数的理解与分析,博主本人也是看了这篇博客才算真正理解了pytorch里面的交叉熵实现。
不过今天我不是要讲交叉熵,而是想讲一下其中的NLLLoss。这篇博客中也有举例解释NLLLoss的情况,但其用的例子是二维的,我们实际使用过程中大多数都是处理4D的tensor,因此,可能不够直观,接下来我就贴出我自己对NLLLoss理解后的实现代码,大家阅读一下就能更加清楚明白了。
N, C, H, W = 2, 2, 3, 3
loss = nn.NLLLoss()
data = torch.randn(N, C, H, W)
target = torch.empty(N, H, W, dtype=torch.long).random_(0, C)
output = loss(data, target)
print(data)
print(target)
print(output)
loss = 0.0
for batch in range(N):
for i in range(H):
for j in range(W):
index = target[batch][i][j] # 获取target,实际也就是该计算loss的索引值
item = data[batch][index][i][j] # 根据target得到的索引值,取出对应值,并添加负号
loss -= item
print(loss / N / H / W)
代码输出
data: tensor([[[[ 0.4086, -1.0978, 1.2770],
[ 0.2216, 1.0113, 1.0838],
[ 0.4149, 2.6782, 0.1263]],
[[-0.2087, 1.2173, 0.6427],
[-1.1500, 0.0756, 0.4218],
[ 0.9435, -1.5560, -1.0130]]],
[[[ 1.4858, -0.2244, 2.0683],
[ 0.4505, -0.3687, -0.8775],
[-0.7105, 0.0538, 0.9036]],
[[ 0.6779, -0.4425, 1.3001],
[ 0.4512, -1.3058, -0.4361],
[ 0.5120, -0.0333, 0.2222]]]])
target: tensor([[[0, 0, 0],
[1, 0, 0],
[1, 0, 1]],
[[0, 1, 1],
[0, 1, 1],
[1, 1, 0]]])
nll_loss by pytorch: tensor(-0.3653)
nll_loss by hand: tensor(-0.3653)
实际上NLLLoss就是遍历target的每个空间位置 ( i , j ) , 0 < = i < H , 0 < = j < W (i,j),0<=i<H,0<=j<W (i,j),0<=i<H,0<=j<W,从而得到每个位置的正确的类别,即索引值,然后根据索引值取出输入对应位置 ( i , j ) (i,j) (i,j)的值,然后取负值,即可作为loss。