分类问题中,交叉熵函数是比较常用也是比较基础的损失函数。
基本推导过程
提到交叉熵,脑子里就会出现这个公式:
然后,脑子里还会浮现出sigmoid这个函数:
pytorch中的CrossEntropyLoss()函数其实就是把输出结果进行sigmoid(将数据设置到0-1之间),随后再放到传统的交叉熵函数中,就会得到结果。
我们知道的作用其实就是把前一层的输入映射到0-1这个区间上,可以认为上一层某个输入样本的数据越大,就代表这个样本标签属于1的概率就越大,反之, 上一层样本的输入数据越小,这个样本标签属于0的概率就越大, 而且通过函数的图像(见下)我们可以看出来,随着输入值的增大, 其对概率增大的作用效果是逐渐减弱的, 反之同理, 这就是非线性映射的一个好处,让模型处于对中间范围的输入数据更敏感,下面是图:
既然经过之后的数据能表示样本所属某个标签的概率, 那么举个例子,我们将模型预测某个样本标签为1的概率是:
那么, 这个样本标签不为1的概率是:
从极大似然的角度来讲就是:
上式可以理解为,某一个样本x,我们通过模型预测出其属于样本标签为y的概率, 因为y是我们给的正确结果, 所以我们当然希望上式越大越好。
接下来我们在的外面套上一层log函数,相当于进行了一次非线性映射。众所周知,函数是不会改变单调性的,所以我们也希望越大越好。
这样我们就得到了一开始说的交叉熵的形式了,因为一般来说如果使用上述公式做loss函数来用,我们希望loss越小越好,这样符合我们的直观理解,所以变形成为就达到了我们的目的。
上面 是二分类问题的交叉熵,如果是有多分类,就对每个变迁类别下的可能概率分别求对应的然后求和就好了:
验证
我们结合中的小例子来说明一下的用法。
举个小例子,假设我们有个一样本,他经过我们的神经网络后会输出一个5维的向量,分别代表这个样本分别属于这5种标签的数值(注意此时我们的5个数求和还并不等于1,需要先经过softmax处理,下面会说),我们还会从数据集中得到该样本的正确分类结果,下面我们要把经过神经网络的5维向量和正确的分类结果放到中,看看会发生什么:
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>)
源代码中的解释:
程序中运行后产生的,可以知道我们的就是只有一个数的张量形式, (不是向量,不是矩阵,只是一个简单的张量形式,而且里面只有一个数), 是一个5维张量,我们在计算交叉熵之前,需要先获得下面交叉熵公式的.
这个地方的需要将输入的向量进行处理(这也是一种映射,可以将变成属于每个标签的概率值, 对每个进行如下处理)
这样我们就得到了交叉熵公式中的。
随后我们就可以将代入公式了,下面还缺少,但奇怪的是我们输入的是一个只有一个数的张量,但是是一个5维的张量,这时候应该怎么处理呢?
原来会把自动变成形式,我们现在例子的样本标签是1(从0开始计算)。那么转换成的编码就是,所以我们的最后也变成了一个5维的张量,这样我们在计算交叉熵的时候只计算给定的那一项就好了,所以我们的公式最后变成了:
这样,根据上面的公式就很简单的计算出了交叉熵损失函数。
我们再看一个具有三个待预测值,而且需要五分类的例子:
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>)
这个小实验可以看出,中计算的loss是平均值.
全文完