关于pytorch中的CrossEntropyLoss()的理解

分类问题中,交叉熵函数是比较常用也是比较基础的损失函数。

基本推导过程

提到交叉熵,脑子里就会出现这个公式:
L=[ylogy^+(1y)log(1y^)]L=-[y*log\hat{y}+(1-y)*log(1-\hat{y})]
然后,脑子里还会浮现出sigmoid这个函数:
g(s)=11+esg(s)=\frac{1}{1+e^{-s}}

pytorch中的CrossEntropyLoss()函数其实就是把输出结果进行sigmoid(将数据设置到0-1之间),随后再放到传统的交叉熵函数中,就会得到结果。

我们知道sigmoidsigmoid的作用其实就是把前一层的输入映射到0-1这个区间上,可以认为上一层某个输入样本的数据越大,就代表这个样本标签属于1的概率就越大,反之, 上一层样本的输入数据越小,这个样本标签属于0的概率就越大, 而且通过sigmoidsigmoid函数的图像(见下)我们可以看出来,随着输入值的增大, 其对概率增大的作用效果是逐渐减弱的, 反之同理, 这就是非线性映射的一个好处,让模型处于对中间范围的输入数据更敏感,下面是sigmoidsigmoid图:

在这里插入图片描述
既然经过sigmoidsigmoid之后的数据能表示样本所属某个标签的概率, 那么举个例子,我们将模型预测某个样本标签为1的概率是:

y^=P(y=1x)\hat{y}=P(y=1|x)
那么, 这个样本标签不为1的概率是:
1y^=P(y=0x)1-\hat{y}=P(y=0|x)

从极大似然的角度来讲就是:
P(yx)=y^y(1y^)1yP(y|x)=\hat{y}^{y}*(1-\hat{y})^{1-y}

上式可以理解为,某一个样本x,我们通过模型预测出其属于样本标签为y的概率, 因为y是我们给的正确结果, 所以我们当然希望上式越大越好。
接下来我们在P(yx)P(y|x)的外面套上一层log函数,相当于进行了一次非线性映射。众所周知,loglog函数是不会改变单调性的,所以我们也希望log(P(yx))log(P(y|x))越大越好。
log(P(yx))=log(y^y(1y^)1y)=ylogy^+(1y)log(1y^)log(P(y|x))=log(\hat{y}^{y}*(1-\hat{y})^{1-y})=y*log\hat{y}+(1-y)*log(1-\hat{y})

这样我们就得到了一开始说的交叉熵的形式了,因为一般来说如果使用上述公式做loss函数来用,我们希望loss越小越好,这样符合我们的直观理解,所以变形成为log(P(yx))-log(P(y|x))就达到了我们的目的。
L=[ylogy^+(1y)log(1y^)]L=-[y*log\hat{y}+(1-y)*log(1-\hat{y})]

上面 是二分类问题的交叉熵,如果是有多分类,就对每个变迁类别下的可能概率分别求对应的log-log然后求和就好了:
L=i=1Ny(i)logy^(i) L=-\sum_{i=1}^{N}y^{(i)}*log\,\hat{y}^{(i)}

验证

我们结合pytorchpytorch中的小例子来说明一下CrossEntropyLoss()CrossEntropyLoss()的用法。

举个小例子,假设我们有个一样本,他经过我们的神经网络后会输出一个5维的向量,分别代表这个样本分别属于这5种标签的数值(注意此时我们的5个数求和还并不等于1,需要先经过softmax处理,下面会说),我们还会从数据集中得到该样本的正确分类结果,下面我们要把经过神经网络的5维向量和正确的分类结果放到CrossEntropyLoss()CrossEntropyLoss()中,看看会发生什么:

import torch
import torch.nn as nn
import math

loss = nn.CrossEntropyLoss()
input = torch.randn(1, 5, requires_grad= True)
target = torch.empty(1, dtype = torch.long).random_(5)
output = loss(input, target)

print('输入为5类:', input)
print('要计算loss的真实类别', target)
print('loss=', output)
#自己计算的结果
first = 0
for i in range(1):
    first -= input[i][target[i]]
second = 0
for i in range(1):
    for j in range(5):
        second += math.exp(input[i][j])
res = 0
res += first + math.log(second)
print('自己的计算结果:', res)

输出结果:

输入为5类: tensor([[ 1.0249, -0.4067,  0.3037,  0.8252,  0.6239]], requires_grad=True)
要计算loss的真实类别 tensor([1])
loss= tensor(2.5990, grad_fn=<NllLossBackward>)
自己的计算结果: tensor(2.5990, grad_fn=<AddBackward0>)

源代码中的解释:

程序中运行后产生的target=1target=1,可以知道我们的targettarget就是只有一个数的张量形式, (不是向量,不是矩阵,只是一个简单的张量形式,而且里面只有一个数), inputinput是一个5维张量,我们在计算交叉熵之前,需要先获得下面交叉熵公式的y^(i)\hat{y}^{(i)}.
L=i=1Ny(i)logy^(i) L=-\sum_{i=1}^{N}y^{(i)}*log\,\hat{y}^{(i)}

这个地方的y^(i)\hat{y}^{(i)}需要将输入的inputinput向量进行softmaxsoftmax处理(这也是一种映射,可以将inputinput变成属于每个标签的概率值, 对每个input[i]input[i]进行如下处理)
y^=P(y^=ix)=einput[i]j=0Neinput[j]\hat{y}=P(\hat{y}=i|x)=\frac{e^{input[i]}}{\sum_{j=0}^{N}e^{input[j]}}
这样我们就得到了交叉熵公式中的y^(i)\hat{y}^{(i)}
随后我们就可以将y^(i)\hat{y}^{(i)}代入公式了,下面还缺少y(i)y^{(i)},但奇怪的是我们输入的targettarget是一个只有一个数的张量,但是y^(i)\hat{y}^{(i)}是一个5维的张量,这时候应该怎么处理呢?
原来CrossEntropyLoss()CrossEntropyLoss()会把targettarget自动变成onehotone-hot形式,我们现在例子的样本标签是1(从0开始计算)。那么转换成的onehotone-hot编码就是[01000][0 \,1\, 0\, 0\, 0 ],所以我们的y(i)y^{(i)}最后也变成了一个5维的张量,这样我们在计算交叉熵的时候只计算y(i)y^{(i)}给定的那一项scorescore就好了,所以我们的公式最后变成了:
L(input,target)=logeinput[target]j=0Neinput[j]=input[target]+log(j=0Neinput[j]) L(input, target)=-log\frac{e^{input[target]}}{\sum_{j=0}^{N}e^{input[j]}}=-input[target]+log(\sum_{j=0}^{N}e^{input[j]})

这样,根据上面的公式就很简单的计算出了交叉熵损失函数。

我们再看一个具有三个待预测值,而且需要五分类的例子:

import torch
import torch.nn as nn
import numpy as np
import math
#我们这次计算有三个待预测像素的点的loss值
loss = nn.CrossEntropyLoss()
input = torch.randn(3, 5, requires_grad= True)
target = torch.empty(3, dtype = torch.long).random_(5)
output = loss(input, target)
print('input is ', input)
print('target is ', target)
print('loss=', output)
#自己跟据公式计算loss
first = []
for i in range(3):
    val = input[i][target[i]]
    first.append(val)
# print(first)
second = []
sum = 0
for i in range(3):
    for j in range(5):
        sum += math.exp(input[i][j])
    second.append(sum)
    sum = 0
# print(second)
L = []
for i in range(3):
    l = -first[i] + math.log(second[i])
    L.append(l)
# print(L)

Loss = 0
for i in range(3):
    Loss += L[i]
Loss = Loss / 3 #需要除以3
print('自己计算的loss', Loss)

结果:

input is  tensor([[-0.9011, -0.0138, -0.4324, -0.3815,  0.0678],
        [ 0.3449, -1.4938, -0.3058, -1.3013,  0.5152],
        [-0.7477,  0.1374,  0.0129,  0.2553, -0.7631]], requires_grad=True)
target is  tensor([3, 0, 4])
loss= tensor(1.6919, grad_fn=<NllLossBackward>)
自己计算的loss tensor(1.6919, grad_fn=<DivBackward0>)

这个小实验可以看出,pytorchpytorch中计算的loss是平均值.
全文完

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章