本文介绍 pytorch自己实现一个CrossEntropy函数

pytorch自己实现一个CrossEntropy函数

This article was original written by Jin Tian, welcome re-post, first come with https://jinfagang.github.io . but please keep this copyright info, thanks, any question could be asked via wechat: jintianiloveu

最近有社区朋友分享了一个问题, 面试某大厂Ailabs岗位时候, 对方要求在不借助任何外力的情况之下手写一个CrossEntropy. 这里不借助外力指的是, 不调用任何深度学习库, 当然可以用numpy, 不借助任何公式, 凭借记忆来写. 这还只是第一环, 据说通过率不足10%. 说明大家在仰望星空的时候也得脚踏实地呀. 今天就来传授一下如何编写一个CrossEntropy函数, 告诉大家这里面可能存在的一些坑.

理论(theory)

编写之前, 先给大家补习一下理论, 相信很多人看到交叉熵, 想到的只有这个公式:

$$ CrossEntropy = \sum{(ylog(y’) + (1-y)log(1-y’))}$$

写了一大堆, 没有保存…………………. 算了, 奇坑…

关于其实交叉熵可以通过一些简化得到最终结果. 举一个例子手算一下:

[[1, 0, 0],
[0, 1, 0]]

[[545, 54, 2],
[232, 54, 546]]

两个样本,类别分别是0和1, 对应的是网络的输出. 那么如何计算交叉熵呢? 简单来说, 计算交叉熵分为两部:

  1. 先计算softmax, 然后计算log;
  2. 计算 $yi*log(yi)$

首先大家可以踩一踩这个坑,如果计算softmax使得计算结果与pytorch一致:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import torch
import numpy as np


# label = torch.Tensor([
#     [1, 2, 3],
#     [0, 2, 1],
# ])


label = torch.Tensor([0, 2, 1]).long()


fc_out = torch.Tensor([
    [245, 13., 3.34],
    [45., 43., 37.],
    [1.22, 35.05, 1.23]
])

def one_hot(a, n):
    b = a.shape[0]
    c = np.zeros([b, n])
    for i in range(b):
        c[i][a[i]] = 1
    return np.array(c)

def softmax(a):
    return [np.exp(i)/np.sum(np.exp(i)) for i in a]


def cross_entropy_loss(out, label):
    # convert out to softmax probability
    out_list = out.numpy().tolist()
    out1 = softmax(out_list)
    print(out1)

    out2 = torch.softmax(out, 0)
    print(out2)
    # [0, 2, 1] -> [[1, 0, 0], [0, 0, 1], [0, 1, 0]]
    # onehot label and rotate
    label_onehot = one_hot(label, 3)
    loss = np.sum(out1 * label_onehot.T)
    print(loss)


loss = torch.nn.CrossEntropyLoss()
lv = loss(fc_out, label)
print(lv)
lv = cross_entropy_loss(fc_out, label)
print(lv)

我们一行代码实现的softmax, 实际上运算结果跟pytorch的softmax并不一致. 思考一下这是为什么?