当前位置:   article > 正文

【pytorch损失函数(1)】之nn.BCELoss二进制交叉熵和 nn.BCEWithLogitsLoss

nn.bceloss

我们之前写过《【熵】熵,KL散度,交叉熵,最大熵模型》, 简而言之,交叉熵表示互信息量,表达的是预测值与真实值之间的分布关系,交叉熵越小,两者间的概率分布越相近。

交叉熵计算公式: H ( p , q ) = − ∑ k = 1 n ( p k ∗ l o g ( q k ) ) H(p, q) = - \sum_{k=1}^n (p_k * log(q_k)) H(p,q)=k=1n(pklog(qk))。其中, p k p_k pk 是预测值的期望, q k q_k qk是真实值的期望,通常都是1。

1、pytorch损失函数之nn.BCELoss()(二进制交叉熵)

基础的损失函数 BCE (Binary cross entropy)

1.1 是什么?

这种BCE损失是交叉熵损失的一种特殊情况,因为当你只有两个类时,它可以被简化为一个更简单的函数。这用于测量例如自动编码器中重建的误差。这个公式假设x和y是概率,所以它们严格地在0和1之间
在这里插入图片描述

由公式可以看出,BCELoss相比CELoss而言,似乎考虑到了互信息间的计算。

如此分析,BCELoss在处理二分类问题也就是0-1问题时,就会有一项变为0。那么公式就好像跟CELoss有了些相似。

BCELoss对于输入数据有两个要求:

要求输入的predict和target必须是同样shape的。
要求输入的predict的数值范围应该为0~1
  • 1
  • 2

我们看CELoss,其实,predict和target的shape是不同的,target 是 predict 的抽象结果。例如

entroy=nn.CrossEntropyLoss()
input=torch.Tensor([[-0.7715, -0.6205,-0.2562]])
target = torch.tensor([0])
output = entroy(input, target)
  • 1
  • 2
  • 3
  • 4

思考

参考

那么针对要求的predict和target是一致的,那么BCELoss去解决多分类问题如何构造target呢?这时候就需要用到one-hot这种数据格式了。

那,针对问题2要求的数值范围我们应该怎么控制呢?上面提到的Softmax不就是个很好的0~1映射嘛。

1.2 怎么代码实现和代码使用?

pytorch中,表示求一个二分类的交叉熵:

class torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction=‘elementwise_mean’)
  • 1

它的loss如下:
l ( x , y ) = L = { l 1 , l 2 , . . . , l n } , 其中 l n = − w n [ y n l o g y n ^ + ( 1 − y n ) l o g ( 1 − y n ^ ) ] l(x,y)=L=\{l_1,l_2,...,l_n\},其中l_n=-w_n[y_nlog\hat{y_n}+(1-y_n)log(1-\hat{y_n})] l(x,y)=L={l1,l2,...,ln},其中ln=wn[ynlogyn^+(1yn)log(1yn^)]

这里n表示批量大小。 w n w_n wn​表示权重。

当参数reduce设置为 True,且参数size_average设置为True时,表示对交叉熵求均值,当size_average设置为Flase时,表示对交叉熵求和。参数weight设置的是 w n w_n wn​,其是一个tensor, 且size与批量数一样(不设置时可能都为1)。目标值 y的范围是0-1之间。输入输出的维度都是 ( ( N , ∗ ) (N,*) N,N是批量数,*表示目标值维度。

1.3 推导过程

我们定义:一个二项分布,随机变量只有两种可能值,所以是一个二分类。定义二分类的交叉熵形式:

− y l o g y ^ − ( 1 − y ) l o g ( 1 − y ^ ) . . . . . . . . . . . . . . ( 1 ) -ylog\hat{y}-(1-y)log(1-\hat{y})..............(1) ylogy^(1y)log(1y^)..............(1)
其中 y ^ \hat{y} y^​是输出值在0-1之间.

就是将最后分类层的每个输出节点使用sigmoid激活函数激活,然后对每个输出节点和对应的标签计算交叉熵损失函数.

分析交叉熵作为损失函数的梯度情况:

我们假设,对于批量样本 ( x 1 , y 1 ) , ( x 2 , y 2 ) . . . {(x_1,y_1),(x_2,y_2)...} (x1,y1),(x2,y2)...则可以对交叉熵求和或者求均值:

∑ i − y i l o g y i ^ − ( 1 − y i ) l o g ( 1 − y i ^ ) . . . . . . . . . . . ( 2 ) \sum_{i}-y_ilog\hat{y_i}-(1-y_i)log(1-\hat{y_i})...........(2) iyilogyi^(1yi)log(1yi^)...........(2)
(这里我们将标签值y视作先验分布, y ^ \hat{y} y^​为模型分布)

若激活函数使用的是sigmoid函数,则 y ^ = σ ( z ) \hat{y}=\sigma(z) y^=σ(z),其中 z = w x + b z=wx+b z=wx+b。采用链式法则求导,则有:

1 n ∑ i − y i l o g y i ^ − ( 1 − y i ) l o g ( 1 − y i ^ ) . . . . . . . . . . ( 2 ) \frac{1}{n}\sum_{i}-y_ilog\hat{y_i}-(1-y_i)log(1-\hat{y_i})..........(2) n1iyilogyi^(1yi)log(1yi^)..........(2)

求导,可得:
∂ L ∂ w = − 1 n ∑ i ( y σ ( z ) − 1 − y 1 − σ ( z ) ) ∂ σ ∂ w = − 1 n ∑ i ( y σ ( z ) − 1 − y 1 − σ ( z ) ) σ ′ x \frac{\partial L}{\partial w}=-\frac{1}{n}\sum_i(\frac{y}{\sigma(z)}-\frac{1-y}{1-\sigma(z)})\frac{\partial \sigma}{\partial w}=-\frac{1}{n}\sum_i(\frac{y}{\sigma(z)}-\frac{1-y}{1-\sigma(z)}) {\sigma}'x wL=n1i(σ(z)y1σ(z)1y)wσ=n1i(σ(z)y1σ(z)1y)σx

由于 σ ( z ) = 1 / ( 1 + e − z ) \sigma(z)=1/(1+e^{-z}) σ(z)=1/(1+ez)

所以最终得到: ∂ L ∂ w = 1 n ∑ i x ( σ ( z ) − y ) \frac{\partial L}{\partial w}=\frac{1}{n}\sum_i x(\sigma(z)-y) wL=n1ix(σ(z)y)

而对偏置的导数也等于 ∂ L ∂ b = 1 n ∑ i ( σ ( z ) − y ) \frac{\partial L}{\partial b}=\frac{1}{n}\sum_i (\sigma(z)-y) bL=n1i(σ(z)y)可以看见使用交叉熵作为损失函数后,反向传播的梯度不在于sigmoid函数的导数有关了。这就从一定程度上避免了梯度消失。

举一个sigmoid导致的梯度消失的MSE损失的例子

二次函数为损失函数的梯度情况,梯度消失问题

二次函数 L = ( y − y ^ ) 2 2 L=\frac{(y-\hat{y})^2}{2} L=2(yy^)2

采用链式法则求导,则有:

∂ L ∂ w = ( y ^ − y ) σ ( z ) ′ x \frac{\partial L}{\partial w}=(\hat{y}-y){\sigma(z)}'x wL=(y^y)σ(z)x
∂ L ∂ b = ( y ^ − y ) σ ( z ) ′ \frac{\partial L}{\partial b}=(\hat{y}-y){\sigma(z)}' bL=(y^y)σ(z)
可以看出梯度都与sigmoid函数的梯度有关,如下图所示,sigmoid函数在两端的梯度均接近0,这导致反向传播的梯度也很小,这就这就不利于网络训练,这就是 梯度消失问题 。

在这里插入图片描述

1.3 应用场景

在机器学习或者深度学习中,分类问题是一个最常见的任务,分类问题一般又分为:二分类任务、多分类任务和多标签分类任务

  • 二分类任务:输出只有0和1两个类别;
  • 多分类任务:一般指的是输出只有一个标签,类别之间是互斥的关系;
  • 多标签分类任务:输出的结果是多标签,类别之间可能互斥也可能有依赖、包含等关系。

在面对不同的分类问题的时候,选择的loss function也不一样,二分类和多标签分类通常使用sigmoid函数而多分类则一般使用softmax函数(互斥性质)。

1.3.1 二分类

BCE可以处理二分类问题,而且通常是sigmoid+BCELoss。

This loss is a special case of cross entropy for when you have only two classes so it can be reduced to a simpler function. This is used for measuring the error of a reconstruction in, for example, an auto-encoder. This formula assume xx and yy are probabilities, so they are strictly between 0 and 1.

在面对二分类的问题时,预测值经sigmoid 后数值在0-1区间。
特征项 [ x 0 , x 1 ] [x_0, x_1] [x0,x1]。标签要么是[1, 0],要么是[0, 1]。
带入BCE 公式即可
l ( x , y ) = L = { l 1 , l 2 , . . . , l n } , 其中 l n = − w n [ y n l o g y n ^ + ( 1 − y n ) l o g ( 1 − y n ^ ) ] l(x,y)=L=\{l_1,l_2,...,l_n\},其中l_n=-w_n[y_nlog\hat{y_n}+(1-y_n)log(1-\hat{y_n})] l(x,y)=L={l1,l2,...,ln},其中ln=wn[ynlogyn^+(1yn)log(1yn^)]

我们反观CELoss,CELoss是预测值通过Softmax + log + NLLLoss计算得来的。可以认为:面对二分类问题时,CELoss是Softmax + BCELoss。

1.3.2 多分类

来看一下BCELoss是怎么解决多分类问题的。要是没法解决多分类问题,BCELoss也不会在目标检测网络里经常被使用。

首先比较一下CELoss和BCELoss在解决多分类问题上有没有差异:

# 预测值
predict = torch.Tensor([[0.5796, 0.4403, 0.9087],
                        [-1.5673, -0.3150, 1.6660]])


# 1. CELoss
# 真实值
ce_target = torch.tensor([2, 0])
ce_loss = torch.nn.CrossEntropyLoss()
print('ce_loss:', ce_loss(predict, ce_target)) 

# 2.sigmoid + BCELoss
soft_input = torch.nn.sigmoid(dim=-1)
soft_out = soft_input(predict)
# 真实值
bec_target = torch.Tensor([[0, 0, 1],
                           [1, 0, 0]])
bce_loss = torch.nn.BCELoss()
print('bce_loss:', bce_loss(soft_out, bec_target)) 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

如此BCELoss相比CELoss在解决多分类问题的优势就表现了出来。CELoss只是根据每行的分类结果去取值,而BCELoss考虑了每行全部结果。

三分类

图片来源:https://www.zhihu.com/question/358811772/answer/920451413

在这里插入图片描述

左上角就是对应的输出矩阵(batch_size x num_classes), 然后经过sigmoid激活后再与绿色标签计算交叉熵损失,计算过程如右方所示。

import torch
import numpy as np

pred = np.array([[-0.4089, -1.2471, 0.5907],
                [-0.4897, -0.8267, -0.7349],
                [0.5241, -0.1246, -0.4751]])
label = np.array([[0, 1, 1],
                  [0, 0, 1],
                  [1, 0, 1]])

pred = torch.from_numpy(pred).float()
label = torch.from_numpy(label).float()

crition1 = torch.nn
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在这里插入图片描述

输出结果一致,因此训练时使用BCEWithLogitsLoss()和MultiLabelSoftMarginLoss()都可。

多分类的具体过程

若是遇到多分类问题使用二进制交叉熵。
目标:多分类问题 => 多个二分类问题

比如我们有3个类别,那么我们通过softmax得到 y ^ = [ 0.2 , 0.5 , 0.3 ] \hat{y}=[0.2,0.5,0.3] y^=[0.2,0.5,0.3]的到的一个一个样本的分类结果,这个结果的通俗解释就是:为第一类的概率为0.2,为第二类的概率为0.5,为第三类的结果过0.3。
假设这个样本真实类别为第二类,那么我们希望模型输出的结果过应该是 y = [ 0 , 1 , 0 ] y=[0,1,0] y=[0,1,0],这个就是标签值。那么损失函数可以使用交叉熵:

L = − ∑ k 3 y k l o g ( y ^ ) L=-\sum_k^3y_klog(\hat{y}) L=k3yklog(y^)

可以看见实际上这个求和只有一项。也就是 L = − l o g ( 0.5 ) L=-log(0.5) L=log(0.5)

pytorch中提供了多分类使用的损失函数nn.CrossEntropyLoss()使用的原理,与这里类似。

作者:杨夕
链接:https://www.zhihu.com/question/358811772/answer/2677137156
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

class BCELosswithLogits(nn.Module):
      def __init__(self, pos_weight=1, reduction='mean'):
          super(BCELosswithLogits, self).__init__()
          self.pos_weight = pos_weight
          self.reduction = reduction

      def forward(self, logits, target):
          # logits: [N, *], target: [N, *]
          logits = F.sigmoid(logits)
          loss = - self.pos_weight * target * torch.log(logits) - \
                (1 - target) * torch.log(1 - logits)
          if self.reduction == 'mean':
              loss = loss.mean()
          elif self.reduction == 'sum':
              loss = loss.sum()
          return loss
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

存在问题:由于 head classes的主导以及negative instances的影响,导致 BCE Loss 函数 容易受到 类别不均衡问题 影响;

优化方向:绝大部分balancing方法都是reweight BCE从而使得稀有的instance-label对能够得到得到合理的“关注”

1.3.3 位置的回归

使用中心位置使用BCE是有理论依据的,可以认为,效果等价于square L2 norm(这个结论的出处还没找到,等找到了补充,20230506)

1.3.4 用途的一个示例

在这里插入图片描述

2、BCEWithLogitsLoss

nn.BCEWithLogitsLoss() 函数等效于 sigmoid + nn.BCELoss。

在这里插入图片描述

BCEWithLogitsLoss损失函数把 Sigmoid 层集成到了 BCELoss 类中. 该版比用一个简单的 Sigmoid 层和 BCELoss 在数值上更稳定, 因为把这两个操作合并为一个层之后, 可以利用 log-sum-exp 的 技巧来实现数值稳定.

torch.nn.BCEWithLogitsLoss(weight=None, reduction='mean', pos_weight=None)
  • 1

参数:

    weight (Tensor, optional) – 自定义的每个 batch 元素的 loss 的权重. 必须是一个长度 为 “nbatch” 的 Tensor
  • 1

参考

https://atcold.github.io/pytorch-Deep-Learning/en/week11/11-1/
https://mp.weixin.qq.com/s/AwgQcafQ2pAuU7_0gEFnmg
https://pytorch-cn.readthedocs.io/zh/latest/package_references/torch-nn/#normalization-layers-source
https://samuel92.blog.csdn.net/article/details/105900876
https://blog.csdn.net/geter_CS/article/details/84747670

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/708951
推荐阅读
相关标签
  

闽ICP备14008679号