Pytorch实现Smooth L1 Loss(Huber)

1. 基础介绍

简单版

SSD网络中的SmoothL1LossLayer层借鉴于Fast R-CNN,用于计算smooth L1损失,其中的光滑L1函数如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WrbiHsnt-1598066176607)(C:\Users\Administrator\Desktop\smoothL1\1.gif)]

其导函数为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pt8SBIyY-1598066176619)(C:\Users\Administrator\Desktop\smoothL1\2.gif)]

之所以称为光滑L1函数,是因为此函数处处可导,而原L1函数在x=0处是不可导的。

smooth L1损失为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8mAoj9Ka-1598066176624)(C:\Users\Administrator\Desktop\smoothL1\3.gif)]

其中: y i = [ y i 1 , y i 2 , . . . . , y i k ] T y_i = [y_{i1},y_{i2},....,y_{ik}]^T yi=[yi1,yi2,....,yik]T为标签向量; y ^ i \hat{y}_i y^i为预测向量。

带sigma的版本

SmoothL1Loss为欧式均方误差的修改版,为分段函数,对离散点不敏感,具体的公式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UlGwRN7s-1598066176634)(C:\Users\Administrator\Desktop\smoothL1\6.png)]

实现代码如下:

def smooth_l1_loss(input, target, sigma, reduce=True, normalizer=1.0):
    beta = 1. / (sigma ** 2)
    diff = torch.abs(input - target)
    cond = diff < beta
    loss = torch.where(cond, 0.5 * diff ** 2 / beta, diff - 0.5 * beta)
    if reduce:
        return torch.sum(loss) / normalizer
    return torch.sum(loss, dim=1) / normalizer

易于理解版

import numpy as np
import torch
 
import torch.nn.functional as F
 
a=torch.Tensor([1,5,3,0.5,0.9])
b=torch.Tensor([4,1,0,0.4,0.2])
 
loss1=F.smooth_l1_loss(a,b)
 
loss_part1=torch.abs(a-b)
loss_part2=loss_part1**2
loss_part2=loss_part2*0.50
 
print()
loss2=torch.where(loss_part1>=1,loss_part1-0.5,loss_part2)
loss2=torch.mean(loss2)
print(loss1)
print(loss2)

2. pytorch中的计算原理及使用问题

Huber损失函数,也就是通常所说SmoothL1损失:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lfYm1GfA-1598086704464)(C:\Users\ADMINI~1\AppData\Local\Temp\1595646858981.png)]
SmoothL1对于异常点的敏感性不如MSE,而且,在某些情况下防止了梯度爆炸。在Pytorch中实现的SmoothL1损失是torch.nn.SmoothL1Loss,xxx 和yyy 可以是任何包含nnn个元素的Tensor,默认求均值。这个损失函数很好理解,就是output和target对应元素计算损失,默认求平均值,然而在实际应用时会出现一些问题。

Pytorch中,假设一个样本图片为640x480(WxH)大小,二维size就是(480,640)(pytorch中格式为HxW),而经过模型输出的是Tensor类型的,size为(1,480,640),此外,在神经网络中,由于batch size的存在,所以每次计算损失是针对一个batch的,假设batch size=4,则输出为(4,1,480,640)。然而每个batch的标签,size为(4,480,640)。

将这样的输出和标签使用SmoothL1Loss进行损失计算时不会报错,因此想当然地认为在函数内部这些元素是一一对应的,然而在实验过程中发现损失不收敛,经过多方探索,最后在阅读自己的代码时发现了这个损失函数计算过程中针对size不同的广播机制,也就是说当某一维度不匹配时,会进行广播以匹配相同的维度,再进行对应元素的损失计算。

举个例子:(为了方便计算,生成的是整数)

import torch


crit = torch.nn.SmoothL1Loss()
x = torch.randint(5, (2, 1, 2, 2)).float()
y = torch.randint(5, (2, 2, 2)).float()
print(x)
print(y)
print(crit(x, y))
"""
tensor([[[[2., 3.],
          [3., 4.]]],
        [[[0., 1.],
          [2., 0.]]]])

tensor([[[3., 4.],
         [0., 3.]],
        [[2., 4.],
         [4., 0.]]])
tensor(1.4375)
"""

对于上述的xxx和yyy,按照理想中的一一对应关系手动计算结果应该是:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t5WmrguM-1598086704470)(C:\Users\ADMINI~1\AppData\Local\Temp\1595647113638.png)]

这是为什么呢?我又进行了下一步计算——计算损失的sum而不是mean,只需将损失函数的参数修改一下即可:

crit = torch.nn.SmoothL1Loss(reduction='sum')
print(crit(x, y))
"""
tensor(23.)
"""

很容易计算得到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HwV7lwt2-1598086704474)(C:\Users\ADMINI~1\AppData\Local\Temp\1595647169708.png)]

也就是说损失函数中计算了16次,然而按照一一对应的理解应该是8个元素计算2次,经过思考和手动计算后发现:由于两个tensor在第二个维度不匹配,也就是xxx的两个(1,1,2,2)广播扩展为两个(1,2,2,2)与yyy的(2,2,2)进行计算,两个8次计算,所以一共就是16次。也就是:

1、
tensor([[[2., 3.],
          [3., 4.]]],
        [[2., 3.],
          [3., 4.]]])
tensor([[[3., 4.],
         [0., 3.]],
        [[2., 4.],
         [4., 0.]]])
计算一次SmoothL1损失(8个元素)
2、
tensor([[[0., 1.],
          [2., 0.]]],
        [[0., 1.],
          [2., 0.]]])
tensor([[[3., 4.],
         [0., 3.]],
        [[2., 4.],
         [4., 0.]]])
再计算一次SmoothL1损失(8个元素)
一共16

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-exfBJDEW-1598086704478)(C:\Users\ADMINI~1\AppData\Local\Temp\1595647380564.png)]

所以在使用这类损失函数(不报错,进行广播匹配size)时,应该对输出做resize(Pytorch中对tensor使用view操作),再计算损失:

x = x.view(-1, y.size()[1:][0], y.size()[1:][1]) #即x=x.view(-1, 2, 2)
print(x.size())
crit = torch.nn.SmoothL1Loss()
print(crit(x, y))
crit = torch.nn.SmoothL1Loss(reduction='sum')
print(crit(x, y))
"""
torch.Size([2, 2, 2])
tensor(1.1875)
tensor(9.5000)
"""

计算结果与预期一致!

参考

https://blog.csdn.net/WYXHAHAHA123/article/details/104078459
https://blog.csdn.net/weixin_43915709/article/details/89430843

  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是天才很好

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值