TL;DR

Way2AI系列,确保出发去"改变世界"之前,我们已经打下了一个坚实的基础。

作者在学习机器学习之逻辑回归任务时,遇到的交叉熵计算不符合预期,才发现了PyTorch的别有洞天。

于是,本文便是结合实验交代了PyTorch中交叉熵损失的真实计算过程。

数据准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np

np.random.seed(seed=1024)

X = np.random.dirichlet(np.ones(3), size=3)
Y = np.array([0, 1, 2])

print (X)
print (Y)

# Output
# [[0.13807842 0.76510708 0.09681451]
# [0.31698662 0.26993158 0.4130818 ]
# [0.46864621 0.01320047 0.51815332]]
# [0 1 2]

正文

大部分博客给出的公式如下:

$$
H = - \sum_i{y_i log(\hat{y}_i)}
$$

其中 $\hat{y}_i$ 为预测值,$y_i$ 为真实值。

我们在低维空间复现此公式,注意到PyTorch可以采用class indices直接取下标进行计算,这里采用同样的方式模拟。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

def cross_entropy_(X, Y):
h = 0
for i in range(len(Y)):
_h = 0
for _ in X[i]:
_h += np.log(X[i][Y[i]])
h += - _h
return np.around(h / len(Y), 4)


cross_entropy_(X, Y)

# Output
# 3.947

我们看一下PyTorch的计算结果。

1
2
3
4
5
6
7
8
9
import torch
import torch.nn as nn


entroy = nn.CrossEntropyLoss()
entroy(torch.from_numpy(X), torch.from_numpy(Y))

# Output
# tensor(1.1484, dtype=torch.float64)

可以看到,结果并不相同。所以PyTorch应该是采用了另外的实现方式,而这也是大部分教程没有交代的。

$$
H(x, class) = - log{\frac{e^{x_{class}}}{\sum_i{e^{x_i}}}} = - x_{class} + log{\sum_i{e^{x_i}}}
$$

代码如下:

1
2
3
4
5
6
7
8
9
10
11
def cross_entropy(X, Y):
h = 0
for i in range(len(Y)):
_h = sum([np.exp(j) for j in X[i]])
h += (- X[i][Y[i]] + np.log(_h))
return np.around(h / len(Y), 4)

cross_entropy(X, Y)

# Output
# 1.1484

如此,可以看到PyTorch的CrossEntropyLoss的真正计算过程。

More

事实上,我们还可以发现,nn.CrossEntropyLoss() 其实是 nn.logSoftmax() 和 nn.NLLLoss() 的整合版本。
$$
logSoftmax = log{\frac{e^x}{\sum_i{e^{x_i}}}}
$$

$$
NLLLoss(x, class) = -x[class]
$$

验证代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np
import torch
import torch.nn as nn

X = np.random.dirichlet(np.ones(3), size=3)
Y = np.array([0, 1, 2])

X_ = torch.from_numpy(X)
Y_ = torch.from_numpy(Y)

entroy = nn.CrossEntropyLoss()
print(entroy(X_, Y_))

softmax = nn.LogSoftmax()
loss = nn.NLLLoss()
print(loss(softmax(X_), Y_))

# Output
# tensor(1.0033, dtype=torch.float64)
# tensor(1.0033, dtype=torch.float64)

结论

  1. nn.CrossEntropyLoss() 的计算公式如下:
    $$
    H(x, class) = - log{\frac{e^{x_{class}}}{\sum_i{e^{x_i}}}} = - x_{class} + log{\sum_i{e^{x_i}}}
    $$

  2. nn.CrossEntropyLoss() 是 nn.logSoftmax() 和 nn.NLLLoss() 的整合。