交叉熵损失,softmax函数和 torch.nn.CrossEntropyLoss()中文
背景
多分类问题里(单对象单标签),一般问题的setup都是一个输入,然后对应的输出是一个vector,这个vector的长度等于总共类别的个数。输入进入到训练好的网络里,predicted class就是输出层里值最大的那个entry对应的标签。
交叉熵在多分类神经网络训练中用的最多的loss function(损失函数)。 举一个很简单的例子,我们有一个三分类问题,对于一个input \(x\),神经网络最后一层的output (\(y\))是一个\((3 \times 1)\)的向量。然后这个\(x\)对应的ground-truth(\(y^{'}\) )也是一个\((3 \times 1)\)的向量。
交叉熵
来举一个例子,让三个类别分别是类别0,1和2。这里让input \(x\)属于类别0。所以ground-truth(\(y^{'}\) ) 就等于\((1,0,0)\), 让网络的预测输出(\(y\))等于\((3,1,-3)\)。
交叉熵损失的定义如下公式所示(在上面的列子里,i是从0到2的):
Softmax
softmax的计算可以在下图找到。注意在图里,softmax的输入\((3,1,-3)\) 是神经网络最后一个fc层的输出(\(y\))。\(y\)经过softmax层之后,就变成了\(softmax(y)=(0.88,0.12,0)\)。\(y\)的每一个entry可以看作每一个class的预测得分,那么\(softmax(y)\)的每一个entry就是每一个class的预测概率。
对于上面的列子,当前\(x\)的分类loss就是\(H(y,y^{'})=-1\times \log(0.88)=0.12\) (注意,这里\(\log\)的base是\(e\))
softmax常用于多分类过程中,它将多个神经元的输出,归一化到\((0, 1)\) 区间内,因此Softmax的输出可以看成概率,从而来进行多分类。
nn.CrossEntropyLoss() in Pytorch
其实归根结底,交叉熵损失的计算只需要一个term。这个term就是在softmax输出层中找到ground-truth里正确标签对应的那个entry \(j\) ,也就是(\(\log(softmax(y_j))\))。(当然咯,在计算\(softmax(y_j)\)的时候,我们是需要y里所有的term的值的。)
因为entry \(j\)对应的是ground-truth里正确的class。只有在\(i=j\)的时候才\(y^{'}_i = 1\),其他时候都等于0。
在下面的代码里,我们把python中torch.nn.CrossEntropyLoss() 的计算结果和用公式计算出的交叉熵结果进行比较. 结果显示,torch.nn.CrossEntropyLoss()的input只需要是网络fc层的输出\(y\), 在torch.nn.CrossEntropyLoss()里它会自己把\(y\) 转化成\(softmax(y)\) 然后再进行交叉熵loss的运算.
所以当我们用PyTorch搭建分类网络的时候,不需要再在最后一个fc层后再手动添加一个softmax层。
注意,在用PyTorch做分类问题的时候,在网络搭建时(假设全连接层的output是y),在之后加一个 y = torch.nn.functional.log_softmax (y),并在训练时,用torch.nn.functional.nll_loss(y, labels)。这样达到的效果和不用log_softmax层,并用torch.nn.CrossEntropyLoss(y,labels)做损失函数是一模一样的。
import torch
import torch.nn as nn
import math
output = torch.randn(1, 5, requires_grad = True) #假设是网络的最后一层,5分类
label = torch.empty(1, dtype=torch.long).random_(5) # 0 - 4, 任意选取一个分类
print ('Network Output is: ', output)
print ('Ground Truth Label is: ', label)
score = output [0,label.item()].item() # label对应的class的logits(得分)
print ('Score for the ground truth class = ', label)
first = - score
second = 0
for i in range(5):
second += math.exp(output[0,i])
second = math.log(second)
loss = first + second
print ('-' * 20)
print ('my loss = ', loss)
loss = nn.CrossEntropyLoss()
print ('pytorch loss = ', loss(output, label))
下面这段新增代码分别用nn.functional.nll_loss()和nn.CrossEntropyLoss()对一个神经网络输出(fc_output
)和其label
进行了交叉熵损失计算
import torch
import torch.nn as nn
import torch.nn.functional as F
# raw output from the net, a 10d vector
fc_output = torch.randn(1, 5, requires_grad = True) # tensor of shape 1x10
label = torch.empty(1, dtype=torch.long).random_(5) # tensor of shape 1
# output element being softmaxed
softmax_output = F.softmax(fc_output, dim=1)
# output element being softmaxed and apply log to it
log_softmax_output = F.log_softmax(fc_output, dim=1)
print(' label = ', label)
print(' output(raw) = ', fc_output.detach())
print(' softmax(output) = ', softmax_output.detach())
print('log(softmax(output)) = ', log_softmax_output.detach()) # can use ln() to check
loss1 = F.nll_loss(log_softmax_output, label)
cross_entropy_loss = nn.CrossEntropyLoss()
loss2 = cross_entropy_loss(fc_output, label)
print()
print('loss from nn.functional.nll_loss() = ', loss1)
print('loss from nn.CrossEntropyLoss() = ', loss2)
第二段代码的运行结果为:
label = tensor([1])
output(raw) = tensor([[-0.7707, 0.1843, 0.2211, 0.4185, -0.3662]])
softmax(output) = tensor([[0.0903, 0.2346, 0.2434, 0.2965, 0.1353]])
log(softmax(output)) = tensor([[-2.4050, -1.4500, -1.4131, -1.2157, -2.0005]])
loss from nn.functional.nll_loss() = tensor(1.4500, grad_fn=<NllLossBackward>)
loss from nn.CrossEntropyLoss() = tensor(1.4500, grad_fn=<NllLossBackward>)