• Loss损失函数


         本博客记录一下遇到的各种损失,如想了解各种损失及其代码,也可查看mmdet项目的loss部分

    交叉熵

           适用于多分类任务,交叉熵属于分类损失中常见的一种损失,-ylogP取平均,概率P为1时,损失为0。在bert的mlm预训练任务中使用了ignore_index入参,可仅根据部分位置(15%mask处)计算损失。在实际计算时,标签转为one-hot,y=0的位置-ylogP为0,不参与损失计算,可参考如下链接中的红色示例交叉熵损失和二元交叉熵损失_飞机火车巴雷特的博客-CSDN博客_交叉熵损失

    计算过程为:softmax——log——nllloss

    softmax输出在0-1之间

    log输出在负无穷到0之间,softmax和log可合并为F.log_softmax操作

    nllloss取log输出中相应位置的值,求平均后取负,输出在0到正无穷之间

    有多种实现方法,下例中所有loss=tensor(1.3077)

    1. import torch
    2. import torch.nn as nn
    3. import torch.nn.functional as F
    4. # input = torch.randn(3,3)
    5. # print(input)
    6. input = torch.tensor([[0.2,0.3,0.4],
    7. [-0.02,-0.13,0.2],
    8. [0.5,-0.3,0.73]])
    9. target = torch.tensor([0,2,1])
    10. target1 = torch.FloatTensor([[1,0,0],
    11. [0,0,1],
    12. [0,1,0]])
    13. ##############################################################
    14. """
    15. softmax+只用正确类位置的概率
    16. nn.Softmax-torch.log-nn.NLLLoss
    17. =F.log_softmax-nn.NLLLoss
    18. =nn.CrossEntropyLoss
    19. =F.cross_entropy
    20. """
    21. # 1、nllloss softmax-log-nllloss
    22. softmax=nn.Softmax(dim=1) # 横向softmax
    23. nllloss=nn.NLLLoss()
    24. softmax_log = torch.log(softmax(input))
    25. softmax_log2=F.log_softmax(input, dim=1)
    26. """
    27. print(softmax_log)
    28. tensor([[-1.2019, -1.1019, -1.0019],
    29. [-1.1448, -1.2548, -0.9248],
    30. [-0.9962, -1.7962, -0.7662]])
    31. """
    32. loss1 = nllloss(softmax_log,target)
    33. print(loss1)
    34. loss2 = nllloss(softmax_log2,target)
    35. print(loss2)
    36. # 2、nllloss=取对应值求平均取负
    37. loss3 = -(softmax_log[0][target[0]]+
    38. softmax_log[1][target[1]]+
    39. softmax_log[2][target[2]])/3
    40. print(loss3)
    41. # 3、直接调用CrossEntropyLoss、cross_entropy
    42. crossEntropyLoss=nn.CrossEntropyLoss()
    43. loss4=crossEntropyLoss(input, target)
    44. print(loss4)
    45. loss5=F.cross_entropy(input, target)
    46. print(loss5)

    二元交叉熵/二分类交叉熵

            适用于二分类、多标签分类任务。交叉熵在softmax后只取正确位置的值计算loss。二元交叉熵认为类件独立,在sigmoid后取所有位置的值计算loss,-(ylogP+(1-y)log(1-P))取平均,当y为0或1时,相加的两项中必有一项为0。

    1. """
    2. sigmoid+使用所有类别的概率
    3. F.sigmoid- F.binary_cross_entropy
    4. =F.binary_cross_entropy_with_logits
    5. =nn.BCEWithLogitsLoss
    6. =nn.BCELoss
    7. """
    8. softmax=nn.Softmax(dim=1)
    9. print('softmax=',softmax(input))
    10. sigmoid = F.sigmoid(input)
    11. print('sigmoid=',sigmoid)
    12. # softmax= tensor([[0.3006, 0.3322, 0.3672],
    13. # [0.3183, 0.2851, 0.3966],
    14. # [0.3693, 0.1659, 0.4648]])
    15. # sigmoid= tensor([[0.5498, 0.5744, 0.5987],
    16. # [0.4950, 0.4675, 0.5498],
    17. # [0.6225, 0.4256, 0.6748]])
    18. loss6 = F.binary_cross_entropy(sigmoid, target1)
    19. print(loss6)
    20. loss7 = F.binary_cross_entropy_with_logits(input, target1)
    21. print(loss7)
    22. loss = nn.BCELoss(reduction='mean')
    23. loss8 =loss(sigmoid, target1)
    24. print(loss8)
    25. loss =nn.BCEWithLogitsLoss()
    26. loss9 =loss(input, target1)
    27. print(loss9)
    28. def binary_cross_entropyloss(prob, target, weight=None):
    29. weight=torch.ones((3,3)) # 正样本权重
    30. loss = -weight * (target * torch.log(prob) + (1 - target) * (torch.log(1 - prob)))
    31. # print('torch.numel(target)=',torch.numel(target)) # 元素个数9
    32. loss = torch.sum(loss) / torch.numel(target) # 求平均
    33. return loss
    34. loss10=binary_cross_entropyloss(sigmoid, target1)
    35. print(loss10)

    平衡交叉熵Balanced Cross-Entropy——设置超参数

            常用在语义分割任务中,处理正负样本不均衡问题,平衡正负样本(在二进制交叉熵中其实也可以设置一个参数设置正样本权重)。

             blanced和focal可理解为一种思想,用在各种损失的优化上。如在-(ylogP+(1-y)log(1-P))基础上增加一个beta系数,修改为 -(BylogP+(1-B)(1-y)log(1-P))

    pytorch中实现Balanced Cross-Entropy_fpan98的博客-CSDN博客

    下例中将1-y/w*h作为beta,实际中也可自己设置一个超参数

    1. input = torch.tensor([[0.2,0.3,0.4],
    2. [-0.02,-0.13,0.2],
    3. [0.5,-0.3,0.73]])
    4. target2 = torch.FloatTensor([[1,0,0],
    5. [1,1,1],
    6. [0,1,1]])
    7. def balanced_loss(input, target):
    8. input = input.view(input.shape[0], -1)
    9. target = target.view(target.shape[0], -1)
    10. loss = 0.0
    11. for i in range(input.shape[0]):
    12. # 本例中beta分别为tensor(0.6667)、tensor(0.)、tensor(0.3333)
    13. beta = 1-torch.sum(target[i])/target.shape[1] # 样本中非1的概率
    14. print('beta=',beta)
    15. x = torch.max(torch.log(input[i]), torch.tensor([-100.0]))
    16. y = torch.max(torch.log(1-input[i]), torch.tensor([-100.0]))
    17. l = -(beta*target[i] * x + (1-beta)*(1 - target[i]) * y)
    18. print('l=',l)
    19. loss += torch.sum(l)
    20. return loss
    21. loss11=balanced_loss(sigmoid, target2)

    平衡交叉熵Balanced Cross-Entropy——设置正负样本比例进行在线难样本挖掘

            本例中将参与损失计算的负样本限制为正样本数量的3倍,选取较难的负样本

    另一篇博文中也提到了在线难样本挖掘及其代码基于【基于(基于pytorch的resnet)的fpn】的psenet_北落师门XY的博客-CSDN博客

    1. class BalanceCrossEntropyLoss(nn.Module):
    2. '''
    3. Balanced cross entropy loss.
    4. Shape:
    5. - Input: :math:`(N, 1, H, W)`
    6. - GT: :math:`(N, 1, H, W)`, same shape as the input
    7. - Mask: :math:`(N, H, W)`, same spatial shape as the input
    8. - Output: scalar.
    9. Examples::
    10. >>> m = nn.Sigmoid()
    11. >>> loss = nn.BCELoss()
    12. >>> input = torch.randn(3, requires_grad=True)
    13. >>> target = torch.empty(3).random_(2)
    14. >>> output = loss(m(input), target)
    15. >>> output.backward()
    16. '''
    17. def __init__(self, negative_ratio=3.0, eps=1e-6):
    18. super(BalanceCrossEntropyLoss, self).__init__()
    19. self.negative_ratio = negative_ratio
    20. self.eps = eps
    21. def forward(self,
    22. pred: torch.Tensor,
    23. gt: torch.Tensor,
    24. mask: torch.Tensor,
    25. return_origin=False):
    26. '''
    27. Args:
    28. pred: shape :math:`(N, 1, H, W)`, the prediction of network
    29. gt: shape :math:`(N, 1, H, W)`, the target
    30. mask: shape :math:`(N, H, W)`, the mask indicates positive regions
    31. '''
    32. positive = (gt * mask).byte()
    33. negative = ((1 - gt) * mask).byte()
    34. positive_count = int(positive.float().sum())
    35. negative_count = min(int(negative.float().sum()),
    36. int(positive_count * self.negative_ratio))
    37. loss = nn.functional.binary_cross_entropy(
    38. pred, gt, reduction='none')[:, 0, :, :]
    39. positive_loss = loss * positive.float()
    40. negative_loss = loss * negative.float()
    41. negative_loss, _ = torch.topk(negative_loss.view(-1), negative_count)
    42. balance_loss = (positive_loss.sum() + negative_loss.sum()) /\
    43. (positive_count + negative_count + self.eps)
    44. if return_origin:
    45. return balance_loss, loss
    46. return balance_loss

    Focal Loss

          处理难易样本问题,根据模型输出概率P调节比重,多了一个超参数gamma

    -(B((1-P)**gamma)ylogP+(1-B)(P**gamma)(1-y)log(1-P))

    1. class BCEFocalLosswithLogits(nn.Module):
    2. def __init__(self, gamma=0.2, alpha=0.6, reduction='mean'):
    3. super(BCEFocalLosswithLogits, self).__init__()
    4. self.gamma = gamma
    5. self.alpha = alpha
    6. self.reduction = reduction
    7. def forward(self, logits, target):
    8. # logits: [N, H, W], target: [N, H, W]
    9. logits = F.sigmoid(logits)
    10. alpha = self.alpha
    11. gamma = self.gamma
    12. loss = - alpha * (1 - logits) ** gamma * target * torch.log(logits) - \
    13. (1 - alpha) * logits ** gamma * (1 - target) * torch.log(1 - logits)
    14. if self.reduction == 'mean':
    15. loss = loss.mean()
    16. elif self.reduction == 'sum':
    17. loss = loss.sum()
    18. return loss
    19. L=BCEFocalLosswithLogits()
    20. loss12= L(sigmoid, target2)

    基于样本次数的加权

            统计不同类别数量,得到该类别出现的概率p,根据1/log(a+p)得到各类别的权重,其中a为超参数用于平滑

    处理样本不均衡问题的方法,样本权重的处理方法及代码_洲洲_starry的博客-CSDN博客_样本权重设置

    DICE LOSS

         适用于正负样本不均衡情况,常用于语义分割这种前后景像素数差异大的情况计算方式为

    1-2*abs(y*p)/(abs(y)+abs(p))     在实际计算中,y肯定大于等于0,p通过sigmoid后也大于等于0,代码中没有出现abs的函数

        也可以参考pse那篇博文中将在线难样本挖掘(控制正负样本比例1:3)和doce loss结合的代码

    1. def dice_loss(input, target):
    2. input = input.contiguous().view(input.size()[0], -1)
    3. target = target.contiguous().view(target.size()[0], -1).float()
    4. a = torch.sum(input * target, 1)
    5. b = torch.sum(input * input, 1) + 0.001
    6. c = torch.sum(target * target, 1) + 0.001
    7. d = (2 * a) / (b + c)
    8. return 1-d

     L1 loss

            回归损失,不考虑方向,计算为方式为 abs(y-p)的平均值

    1. from torch.nn import L1Loss
    2. loss = L1Loss()
    3. loss13 = loss(input, target1)
    4. print(loss13)
    5. tmp = abs(input- target1)
    6. loss14 = torch.sum(tmp)/torch.numel(tmp)
    7. print(loss14)

    loss出现nan

          表现为训练一开始损失是正常减小的,后来零星出现Nan,到后来就都是Nan了。这其实就是出现了梯度爆炸现象。在各类模型、各类损失中都有可能出现这个现象。如下述博主遇到的情况:

    用yolov3 VOC训练自己的数据时出现的问题及解决方法_小蜗zdh的博客-CSDN博客

    ta采取的是修改学习率调整策略从而调小学习率

    pytorch MultiheadAttention 出现NaN_qq_37650969的博客-CSDN博客_attention nan

    ta遇到了在attention中整行被mask的情况

    原因包含:

    1)loss计算中除以nan

    2)loss计算中log0,如二进制交叉熵计算loss = -(y*ln(p)+(1-y)*ln(1-p))

    3)softmax的输入全是负无穷,输出就是Nan 

    解决方法一般有:

    1)交叉熵输入p加上一个很小的数限制一边范围

    crossentropy(out+1e-8, target)

    2)双边限制范围

     q_logits = torch.clamp(q_logits, min=1e-7, max=1 - 1e-7)   # 添加了截断区间

     q_log_prob = torch.nn.functional.log_softmax(q_logits, dim=1)

    3)调小学习率,减小震荡

    4)评估损失函数是否适合任务

    5)评估是否存在标注问题

    Q:为什么公式中没有取均值,但代码中经常需要取均值

    A:实际是一个batch的样本取了平均损失,而不是对单个样本计算平均,reduction可设置为

    'none', 'mean', 'sum'

    Q:各类别样本不平衡有哪些处理方法

    A:重采样、基于样本数量加权、balanced 设置正负样本超参数、balanced 控制正负样本比例、dice loss

    推荐阅读:

    pytorch学习经验(五)手动实现交叉熵损失及Focal Loss - 简书

  • 相关阅读:
    FastAPI 学习之路(十三)Cookie 参数,Header参数
    【Linux】进程控制,进程替换
    详解uniapp的生命周期 ~ 解惑 实用 ~
    Docker Swarm实现容器的复制均衡及动态管理:详细过程版
    linux 自带压力测试工具ab
    Java排序算法(三):插入排序
    【技能树笔记】网络篇——练习题解析(九)
    Activiti7工作流引擎:节点动态跳转
    Unity2023引入可选的基于64位浮点位置的大世界坐标系
    李永乐六套卷复盘
  • 原文地址:https://blog.csdn.net/weixin_41819299/article/details/122586657