Smooth L1 Loss(Huber):pytorch中的计算原理及使用问题

Huber损失函数,也就是通常所说SmoothL1损失:
S m o o t h L 1 ( x , y ) = { 0.5 ( x i − y i ) 2 if  ∣ x i − y i ∣ < 1   ∣ x i − y i ∣ − 0.5 o t h e r w i s e Smooth_{L1}(x,y) = \begin{cases} 0.5(x_i-y_i)^2 & \text{if $|x_i-y_i|<1$ } \\[2ex] |x_i-y_i|-0.5 & \text{$otherwise$} \end{cases} SmoothL1(x,y)=0.5(xiyi)2xiyi0.5if ∣xiyi<otherwise
SmoothL1对于异常点的敏感性不如MSE,而且,在某些情况下防止了梯度爆炸。在Pytorch中实现的SmoothL1损失是torch.nn.SmoothL1Loss, x x x y y y 可以是任何包含 n n n个元素的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)
"""

对于上述的 x x x y y y,按照理想中的一一对应关系手动计算结果应该是:
( 0.5 + 0.5 + 2.5 + 0.5 ) + ( 1.5 + 2.5 + 1.5 + 0 ) 8 = 9.5 8 = 1.1875 \frac{(0.5+0.5+2.5+0.5)+(1.5+2.5+1.5+0)}{8}=\frac{9.5}8=1.1875 8(0.5+0.5+2.5+0.5)+(1.5+2.5+1.5+0)=89.5=1.1875
这是为什么呢?我又进行了下一步计算——计算损失的sum而不是mean,只需将损失函数的参数修改一下即可:

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

很容易计算得到: s u m m e a n = 23 1.4375 = 16 \frac{sum}{mean}=\frac{23}{1.4375}=16 meansum=1.437523=16
也就是说损失函数中计算了16次,然而按照一一对应的理解应该是8个元素计算8次,经过思考和手动计算后发现:由于两个tensor在第二个维度不匹配,也就是 x x x的两个(1,1,2,2)广播扩展为两个(1,2,2,2)与 y y y的(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


[ ( 0.5 + 0.5 + 2.5 + 0.5 ) + ( 0 + 0.5 + 0.5 + 3.5 ) ] + [ ( 2.5 + 2.5 + 1.5 + 2.5 ) + ( 1.5 + 2.5 + 1.5 + 0 ) ] 16 \frac{[(0.5+0.5+2.5+0.5)+(0+0.5+0.5+3.5)]+[(2.5+2.5+1.5+2.5)+(1.5+2.5+1.5+0)]}{16} 16[(0.5+0.5+2.5+0.5)+(0+0.5+0.5+3.5)]+[(2.5+2.5+1.5+2.5)+(1.5+2.5+1.5+0)]
= 23 16 = 1.4375 =\frac{23}{16}=1.4375 =1623=1.4375
所以在使用这类损失函数(不报错,进行广播匹配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)
"""

计算结果与预期一致!

你可能感兴趣的:(Pytorch,损失函数)