ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

详解PyTorch中的交叉熵!

2021-04-29 21:01:45  阅读:219  来源: 互联网

标签:torch tensor 交叉 0.2 PyTorch 详解 log target


最近在做交叉熵的魔改,所以需要好好了解下交叉熵,遂有此文。

关于交叉熵的定义请自行百度,相信点进来的你对其基本概念不陌生。

推荐原文链接(排版精美):https://mp.weixin.qq.com/s/94qCM6Kim0UaqAr6HaGpiQ

本文将结合PyTorch,介绍离散形式的交叉熵在二分类以及多分类中的应用。注意,本文出现的二分类交叉熵和多分类交叉熵,本质上都是一个东西,二分类交叉熵可以看作是多分类交叉熵的一个特例,只不过在PyTorch中对应方法的实现方式不同(不同之处将在正文详细讲解)。

好了,废话少叙,正文开始~

## 二分类交叉熵
$$L=-\frac1N \sum_{i=1}^{N}[y_ilog(p_i)+(1-y_i)log(1-p_i)]$$
其中,$N$是总样本数,$y_i$是第$i$个样本的所属类别,$p_i$是第$i$个样本的预测值,一般来说,它是一个概率值。

上栗子:

|   .   |     $y_i$ |   $p_i$   |
| :--------: | :--------:| :------: |
| 第1个样本       |   1 |  0.8  |
| 第1个样本     |   0 |  0.2  |
| 第1个样本  |   0 |  0.4  |

按照上面的公式,交叉熵计算如下:
$$L=\frac13[(1*log0.8+(1-1)*log(1-0.8))+(0*log0.2+(1-0)*log(1-0.2))+(0*log0.4+(1-0)*log(1-0.4))]=0.319$$
![Alt text](./1.png)

其实,在PyTorch中已经内置了`BCELoss`,它的主要用途是计算二分类问题的交叉熵,我们可以调用该方法,并将结果与上面手动计算的结果做个比较:
![Alt text](./2.png)

嗯,结果是一致的。

需要注意的是,输入`BCELoss`中的预测值应该是个概率$p_i$。

上面的栗子直接给出了预测的$p_i$,这是符合要求的。但在更一般的二分类问题中,网络的输出取值是整个实数域(可正可负可为0)。

为了由这种输出值得到对应的$p_i$,你可以在网络的输出层之后新加一个`Sigmoid`层,这样便可以将输出值的取值规范到0和1之间,这就是交叉熵公式中的$p_i$。

当然,你也可以不更改网络输出,而是在将输出值送入交叉熵公式进行性计算之前,手动用`Simgmoid`函数做一个映射。

在PyTorch中,甚至提供了`BCEWithLogitsLoss`方法,它可以直接将输入的值规范到0和1 之间,相当于将`Sigmoid`和`BCELoss`集成在了一个方法中。

还是举个栗子来具体进行说明:假设pred是shape为[4,2]的tensor,其中4代表样本个数,2代表该样本分别属于两个类别的概率(前提是规范到了0和1之间,否则就是两个实数域上的值,记住,现在我们讨论的是二分类);target是shape为[4]的tensor,4即样本数。
```python
pred=torch.randn(4,2)#预测值
target=torch.rand(4).random_(0,2)#真实类别标签
```

在使用任何一种方法之前,都需要先对target做独热编码,否则target和pred维度不匹配:
```python
#将target进行独热编码
onehot_target=torch.eye(2)[target.long(), :]
```
在做编码前,target看起来长这样:
```
tensor([0., 1., 1., 1.])
```
编码后,target变成了这样:
```
tensor([[1., 0.],
        [0., 1.],
        [0., 1.],
        [0., 1.]])
```
现在,target的shape也是[4,2]了,和pred的shape一样,所以下面可以开始计算交叉熵了。

- 使用`Sigmoid`和`BCELoss`计算交叉熵
    
    先使用`nn.Sigmoid`做一下映射:
    ![Alt text](./3.png)
    可以看到,映射后的取值已经被规范到了0和1之间。
    
    然后使用`BCELoss`进行计算:
    ![Alt text](./4.png)
    


- 只使用`BCELossWithLogits`计算交叉熵
    ![Alt text](./5.png)


两种方法的计算结果完全一致。不过官方建议使用`BCELossWithLogits`,理由是能够提升数值计算稳定性。

以后,当你使用PyTorch内置的二分类交叉熵损失函数时,只要保证输入的预测值和真实标签的维度一致(N,...),且输入的预测值是一个概率即可。满足这两点,一般就能避免常见的错误了。
![Alt text](./7.png)
(BCELoss的使用)


关于二分类交叉熵的介绍就到这里,接下来介绍多分类交叉熵。

## 多分类交叉熵
$$L=-{\frac1N} {\sum_{i=1}^{N}}  {\sum_{c=1}^{K}} y_{ic}log(p_{ic})$$
其中,N代表样本数,K代表类别数,$p_{ic}$代表第i个样本属于类别c的概率,$\sum_{c=1}^{K}p_{ic}=1,i=1,2,...,N$,$y_{ic}\in \{0,1\}$,可以看作一个one-hot编码(若第i个样本属于类别c,则对应位置的$y_ic$取1,否则取0)。

这个公式乍看上去有点复杂,其实不难。不妨取第$i$个样本,计算这个样本的交叉熵,公式如下:
$$L_i=\sum_{c=1}^{K}y_{ic}log(p_{ic})$$

假设N=2, K=3,即总共3个样本,3个类别,样本的数据如下

|.      |    $y_{i1}$|   $y_{i2}$   |$y_{i3}$|$p_{i1}$|$p_{i2}$|$p_{i3}$|
| :--------: | :--------:| :------: |:------:|
| 第1个样本   |   0|  1  |0|0.2|0.3|0.5
| 第2个样本   |   1|  0 |0|0.3|0.2|0.5
| 第3个样本   |   0|  0  |1|0.4|0.4|0.2

$$L_1=0*log(0.2)+1*log(0.3)+0*log(0.5)=-1.2039$$
$$L_2=1*log(0.3)+0*log(0.2)+0*log(0.5)=-1.2039$$
$$L_3=0*log(0.4)+0*log(0.4)+0*log(0.2)=-1.6094$$

看吧,最终的交叉熵只不过是做了N这样的计算,然后平均一下,加个负号:
$$L=-\frac13(L_1+L_2+L_3)=1.3391$$

你可能已经发现,这里的$p_{ic},c=1,2,3$之和为1。没错,这是网络的输出做了softmax后得到的结果。在上一部分**关于二分类的问题中,输入交叉熵公式的网络预测值必须经过`Sigmoid`进行映射,而在这里的多分类问题中,需要将`Sigmoid`替换成`Softmax`,这是两者的一个重要区别!**

现在让我们用代码来实现上面的计算过程:
```python
#预测值,假设已做softmax
pred=torch.tensor([[0.2,0.3,0.5],[0.3,0.2,0.5],[0.4,0.4,0.2]])
#真实类别标签
target=torch.tensor([1,0,2])
# 对真实类别标签做 独热编码
one_hot = F.one_hot(target).float()
"""
one_hot:
tensor([[0., 1., 0.],
        [1., 0., 0.],
        [0., 0., 1.]])
"""
#对预测值取log
log=torch.log(pred)
#计算最终的结果
res=-torch.sum(one_hot*log)/target.shape[0]
print(res)# tensor(1.3391)
```
这和我们之前手动计算的结果是一样的。代码很简单,只需注意代码中的`one_hot*log`是逐元素做乘法。

以上是其内部实现原理。在实际使用时,为了方便,PyTorch已经封装好了以上过程,你只需要调用一下相应的方法或函数即可。

在PyTorch中,有一个叫做`nll_loss`的函数,可以帮助我们更快的实现上述计算,此时无需对target进行独热编码,于是代码可简化如下:
```python
import torch.nn.functional as F
#预测值,已做softmax
pred=torch.tensor([[0.2,0.3,0.5],[0.3,0.2,0.5],[0.4,0.4,0.2]])
#真实类别标签,此时无需再做one_hot,因为nll_loss会自动做
target=torch.tensor([1,0,2])
#对预测值取log
log=torch.log(pred)
#计算最终的结果
res=F.nll_loss(log, target)
print(res)# tensor(1.3391)
```

等等,还没完。在PyTorch中,最常用于多分类问题的,是`CrossEntropyLoss`.

它可以看作是`softmax`+`log`+`nll_loss`的集成。

上面的栗子中的预测值是已经做完softmax之后的,为了说明`CrossEntropyLoss`的原理,我们换一个预测值没有做过softmax的新栗子,这种栗子也是我们通常会遇到的情况:
```python
#4个样本,3分类
pred=torch.rand(4,3)
#真实类别标签
target=torch.tensor([0,1,0,2])
```
先按照`softmax`+`log`+`nll_loss`的步骤走一遍:
```python
logsoftmax=F.log_softmax(pred)
"""
logsoftmax:

tensor([[-0.8766, -1.4375, -1.0605],
        [-1.0188, -0.9754, -1.3397],
        [-0.8926, -1.0962, -1.3615],
        [-1.0364, -0.8817, -1.4645]])
"""
res=F.nll_loss(logsoftmax,target)
pritnt(res)#tensor(1.0523)
```

直接使用`CrossEntropyLoss`:
```python
res=F.cross_entropy(pred, target)
print(res)#tensor(1.0523)
```
结果是一样的。


![Alt text](./8.png)
(CrossEntropyLoss的使用)


参考:
- [1] https://zhuanlan.zhihu.com/p/35709485
- [2] https://zhuanlan.zhihu.com/p/159477597
 

标签:torch,tensor,交叉,0.2,PyTorch,详解,log,target
来源: https://blog.csdn.net/qq_38669138/article/details/116276900

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有