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(xi−yi)2∣xi−yi∣−0.5if ∣xi−yi∣<1 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)
"""
计算结果与预期一致!