Pytorch中的CrossEntropyLoss()函数

一、交叉熵公式推导

  分类问题中,交叉熵函数是比较常用也是比较基础的损失函数,原来就只是会用,但一直搞不懂他是怎么来的?为什么交叉熵能够表征真实样本标签和预测概率之间的差值?现在把这个概念好好学习一下。

  首先说起交叉熵,脑子里就会出现这个东西:

L = − [ y ∗ l o g y ^ + ( 1 − y ^ ) l o g ( 1 − y ^ ) ] \mathbf{L = -[y*log\hat{y}+(1-\hat{y})log(1-\hat{y})]} L=[ylogy^+(1y^)log(1y^)]

  随后我们脑子里可能还会出现Sigmoid()这个函数:

g ( s ) = 1 1 + e − s \mathbf{g(s)=\frac{1}{1+e^{-s}}} g(s)=1+es1

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

  既然经过sigmoid之后的数据能表示样本所属某个标签的概率,那么举个例子,我们模型预测某个样本标签为1的概率是:

y ^ = P ( y = 1 ∣ X ) \mathbf{\hat{y}=P(y=1|X)} y^=P(y=1X)

  那么,这个样本不为1的概率为:

1 − y ^ = P ( y = 0 ∣ X ) \mathbf{1-\hat{y}=P(y=0|X)} 1y^=P(y=0X)

  极大似然的角度来看,就得到下式:

p ( y ∣ x ) = y ^ y ∗ ( 1 − y ^ ) 1 − y \mathbf{p(y|x)=\hat{y}^{y}*(1-\hat{y})^{1-y}} p(yx)=y^y(1y^)1y

  上式可以理解为,给定一个样本x,我们通过模型预测出其属于样本标签为y的概率,因为y是我们给的正确结果,所以我们当然希望上式越大越好。

  下一步我们要在 P(y|x) 的外面套上一层log函数,相当于进行了一次非线性的映射。log函数是不会改变单调性的,所以我们也希望 log( P( y | x ) ) 越大越好。

l o g ( p ( y ∣ x ) ) = l o g ( y ^ y ∗ ( 1 − y ^ ) 1 − y ) = y ∗ l o g y ^ + ( 1 − y ) ∗ l o g ( 1 − y ^ ) \mathbf{log(p(y|x))=log(\hat{y}^{y}*(1-\hat{y})^{1-y})=y*log\hat{y}+(1-y)*log(1-\hat{y})} log(p(yx))=log(y^y(1y^)1y)=ylogy^+(1y)log(1y^)

  这样,就得到了我们一开始说的交叉熵的形式了,但是等一等,好像还差一个符号。

  因为一般来说我们相用上述公式做loss函数来使用,所以我们想要loss越小越好,这样符合我们的直观理解,所以我们只要 -log( P( y | x ) ) 就达到了我们的目的。

L = − [ y ∗ l o g y ^ + ( 1 − y ) ∗ l o g ( 1 − y ^ ) ] \mathbf{L=-[y*log\hat{y}+(1-y)*log(1-\hat{y})]} L=[ylogy^+(1y)log(1y^)]

  上面是二分类问题的交叉熵,如果是有多分类,就对每个标签类别下的可能概率分别求相应的负log对数然后求和就好了:

L = − ∑ i = 1 N y ( i ) ∗ l o g y ^ ( i ) L=-\sum_{i=1}^{N}y^{(i)}*log\hat{y}^{(i)} L=i=1Ny(i)logy^(i)

二、 Pytorch 中交叉熵CrossEntropyLoss()

  下面要结合pytorch中的函数 CrossEntropyLoss() 来说一说具体怎么使用。

  举个小例子,假设我们有个一样本,他经过我们的神经网络后会输出一个5维的向量,分别代表这个样本分别属于这5种标签的数值(注意此时我们的5个数求和还并不等于1,需要先经过softmax处理,下面会说),我们还会从数据集中得到该样本的正确分类结果,下面我们要把经过神经网络的5维向量和正确的分类结果放到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类:")
print(input)
print("要计算loss的类别:")
print(target)
print("要计算loss的结果:")
print(output)
输入为5类:
tensor([[ 1.2389,  0.3627,  0.1153, -0.2999, -1.8458]], requires_grad=True)
要计算loss的类别:
tensor([4])
要计算loss的结果:
tensor(3.7787, grad_fn=<NllLossBackward>)

  可以看到我们的target就是一个只有一个数的数组形式(不是向量,不是矩阵,只是一个简单的数组,而且里面就一个数),input是一个5维的向量,但这,在计算交叉熵之前,我们需要先获得下面交叉熵公式的 y ^ ( i ) \hat{y}^{(i)} y^(i)

L = − ∑ i = 1 N y ( i ) ∗ l o g y ^ ( i ) \mathbf{L=-\sum_{i=1}^{N}y^{(i)}*log\hat{y}^{(i)}} L=i=1Ny(i)logy^(i)

  此处 y ^ ( i ) \hat{y}^{(i)} y^(i)的需要我们将输入的input向量进行softmax处理,softmax我就不多说了,应该比较简单,也是一种映射,使得input变成对应属于每个标签的概率值,对每个input[i]进行如下处理:

y ^ = P ( y ^ = i ∣ x ) = e x p i n p u t [ i ] ∑ j = 0 N e x p i n p u t [ i ] \mathbf{\hat{y}=P(\hat{y}=i|x)=\frac{exp^{input[i]}}{\sum_{j=0}^{N}exp^{input[i]}}} y^=P(y^=ix)=j=0Nexpinput[i]expinput[i]

  这样我们就得到了交叉熵公式中的 y ^ ( i ) \hat{y}^{(i)} y^(i).随后我们就可以把 y ^ ( i ) \hat{y}^{(i)} y^(i)带入公式了,下面我们还缺 y ( i ) y^{(i)} y(i)了,而奇怪的是我们输入的target是一个只有一个数的数组啊,而 y ^ ( i ) \hat{y}^{(i)} y^(i)是一个5维的向量,这什么情况?

  原来CrossEntropyLoss() 会把target变成ont-hot形式,我们现在例子的样本标签是[4](从0开始计算)。那么转换成one-hot编码就是[0,0,0,0,1],所以我们的最后也会变成一个5维的向量,并且不是该样本标签的数值为0,这样我们在计算交叉熵的时候只计算给定的那一项的sorce就好了,所以我们的公式最后变成了:

L ( i n p u t , t a r g e t ) = − l o g e x p i n p u t [ t a r g e t ] ∑ j = 0 N e x p i n p u t [ i ] = − i n p u t [ t a r g e t ] + l o g ∑ j = 0 N e x p i n p u t [ i ] \mathbf{L(input,target)=-log\frac{exp^{input[target]}}{\sum_{j=0}^{N}exp^{input[i]}}=-input[target]+log\sum_{j=0}^{N}exp^{input[i]}} L(input,target)=logj=0Nexpinput[i]expinput[target]=input[target]+logj=0Nexpinput[i]

  接下来,按照上面我们的推导来自己编写一下程序:

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("自己的计算结果:")
print(res)
自己的计算结果:
tensor(3.7787, grad_fn=<AddBackward0>)

参考资料

博客:https://www.cnblogs.com/JeasonIsCoding/p/10171201.html

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值