现在对问题的定义应该比价清楚了,接下来是nll_loss怎么算的,用公式不太好写,这里就用文字描述了:真实类别的预测概率的平均值乘负一。两条数据的真实标签分别是第0类和第3类,相应的预测概率分别为0.2和0.7,平均值为0.45,再乘负一,得0.45,与程序输出情况一致。其中求平均值是因为程序默认reduction='mean'
可以看出来nll_loss只能求每条数据只属于一个类别的情况(我目前理解是这样的),不能出现一条数据既属于第0类,又属于第1类。
同样适用上面的数据,我们计算cross_entropy
label3 = torch.tensor([
[1, 0, 0, 0],
[0, 0, 0, 1]
], dtype = torch.float32)
pred3 = torch.tensor([
[0.2, 0.7, 0.8, 0.1],
[0.1, 0.3, 0.5, 0.7]
loss = F.cross_entropy(pred3, label3)
print(loss) # 输出 tensor(1.3965)
上面的label3代表数据是每个类别的真实概率是多少,跟上面的两个表格一样。label3 也可以用indices(也就是指明属于哪个类别),即 label3 = torch.tensor([0,3]),两者是等价的。
下面探讨如何用log_softmax和nll_loss组合出cross_entropy,代码如下:
label2 = torch.tensor([0, 3])
pred2 = torch.tensor([
[0.2, 0.7, 0.8, 0.1],
[0.1, 0.3, 0.5, 0.7]
pred2 = F.log_softmax(pred2, dim = 1) # dim = 1是横着四个元素和为1, dim = 0是竖着两个元素和为1
loss = F.nll_loss(pred2, label2)
print(loss) # 输出 tensor(1.3965)
这里比第一次的代码多了一句 pred2 = F.log_softmax(pred2, dim = 1)。log_softmax的意思是先softmax,再log(实际是ln,以e为底的log)。log用来保证最终结果为正(softmax压缩到区间[0,1])
为了更深刻的理解,我们接下来手算一下。
原始数据1
softmax后
0.1860
0.3067
0.3390
0.1683
log后(ln)
-
1
.
6
8
2
-
1
.
1
8
1
8
-
1
.
0
8
1
7
-
1
.
7
8
2
0
按照nll_loss的计算方法:真实类别的预测概率的平均值乘负一:-1 * (-
1
.
6
8
2
+ -
1
.
1
1
1
0
) / 2 = 1.3965,与程序输出结果一致。
如果cross_entropy时,每条数据可以同时属于多个类别,又该如何计算呢?如下面的代码,第一条数据同时属于0,1类别,第二条数据同时属于2,3类别。
label4 = torch.tensor([
[1, 1, 0, 0],
[0, 0, 1, 1]
], dtype = torch.float32)
pred4 = torch.tensor([
[0.2, 0.7, 0.8, 0.1],
[0.1, 0.3, 0.5, 0.7]
loss = F.cross_entropy(pred4, label4)
print(loss) # 输出 tensor(2.6430)
这里我们先明确cross_entropy的计算方法,下面是公式。其中pi是真实概率,qi是预测概率。
Pytorch的cross_entropy自动对输入(input),也就是上面的pred4进行log_softmax,按照上面的计算,pred4经过处理变成
[[-1.682, -1.1818, -1.0817, -1.7820],
[-1.7109, -1.5109, -1.3111, -1.1110]]
按照公式H(p, q) = -(1 * -1.682 + 1 * -1.1818 + 1 * -1.3111 + 1 * -1.1110) = 5.2859,默认要求平均,因为是两条数据所以除以2等于2.64295,与程序输出2.6430基本一致。
用nll_loss+log_softmax的方法计算代码如下,感觉有些麻烦。
pred5 = F.log_softmax(pred4, dim = 1)
loss = F.nll_loss(pred5[0].unsqueeze(0), torch.tensor([0])) + F.nll_loss(pred5[0].unsqueeze(0), torch.tensor([1])) + F.nll_loss(pred5[1].unsqueeze(0), torch.tensor([2])) + F.nll_loss(pred5[1].unsqueeze(0), torch.tensor([3]))
print(loss/2) # 输出 tensor(2.6430)
其中的unsqueeze(0)表示增加一个维度,也就是加一层方括号。