1. 基础介绍
简单版
SSD网络中的SmoothL1LossLayer层借鉴于Fast R-CNN,用于计算smooth L1损失,其中的光滑L1函数如下:
其导函数为:
之所以称为光滑L1函数,是因为此函数处处可导,而原L1函数在x=0处是不可导的。
smooth L1损失为:
其中: 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为欧式均方误差的修改版,为分段函数,对离散点不敏感,具体的公式如下:
实现代码如下:
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损失:
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,按照理想中的一一对应关系手动计算结果应该是:
这是为什么呢?我又进行了下一步计算——计算损失的sum而不是mean,只需将损失函数的参数修改一下即可:
crit = torch.nn.SmoothL1Loss(reduction='sum')
print(crit(x, y))
"""
tensor(23.)
"""
很容易计算得到:
也就是说损失函数中计算了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次
即
所以在使用这类损失函数(不报错,进行广播匹配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