pytorch中的损失函数总结——分类和分割相关

交互分割实战知识笔记2

深度学习中训练网络,必定要考虑的问题之一就是损失函数如何选取。近年来分割中Focal Loss等十分火热,但是很多项目使用的仍然是基础的Dice,CrossEntropy等基础损失函数,效果相差也并不惊人,可见传统的Loss当中仍然有许多值得学习的地方。

本文主要针对分割分类问题中的Loss函数进行一个handbook式的分析,对于我所了解的不同Loss进行特点说明,重点研究实际计算方式,以求直观地理解他们的含义。

实验基于的版本是当前的stable版本pytorch-1.7.1

目录

  • 计算公式细节
    • nn.L1Loss
    • nn.MSELoss
    • nn.SmoothL1Loss
    • nn.NLLLoss
    • nn.CrossEntropyLoss
    • nn.BCELoss 以及 nn.BCEWithLogitsLoss
    • nn.MultiLabelMarginLoss
    • nn.MultiLabelSoftMarginLoss
    • nn.MultiMarginLoss
    • nn.HingeEmbeddingLoss
    • nn.PoissonNLLLoss
    • nn.KLDivLoss
  • 要点总结

计算公式细节

总的loss计算公式都满足,所以下文的公式只写其中的的计算部分。

nn.L1Loss

就是MAE(mean absolute error),计算公式为

有mean和sum两种模式选,通过reduction控制。

例子

target = torch.tensor([1,1,0,1,0]).float()
output = torch.tensor([1,0,0,0,0]).float()

loss_fn = torch.nn.L1Loss(reduction='mean')
loss = loss_fn(output, target)
print(loss)

loss_fn = torch.nn.L1Loss(reduction='sum')
loss = loss_fn(output, target)
print(loss)

结果

tensor(0.4000)
tensor(2.)

nn.MSELoss

如其名,mean squared error,也就是L2正则项,计算公式为

有mean和sum两种模式选,通过reduction控制。

例子

target = torch.tensor([1,0,0,1,0]).float()
output = torch.tensor([1,2,0,0,0]).float()

loss_fn = torch.nn.MSELoss(reduction='mean')
loss = loss_fn(output, target)
print(loss)

loss_fn = torch.nn.MSELoss(reduction='sum')
loss = loss_fn(output, target)
print(loss)

结果

tensor(1.)
tensor(5.)

nn.SmoothL1Loss

对L1做了一点平滑,比起MSELoss,对于outlier更加不敏感。
l_{n}=\left\{\begin{array}{ll} 0.5\left(x_{n}-y_{n}\right)^{2} / \text {beta}, & \text { if }\left|x_{n}-y_{n}\right|<\text {beta} \\ \left|x_{n}-y_{n}\right|-0.5 * \text {beta}, & \text { otherwise } \end{array}\right.
在Fast-RCNN中使用以避免梯度爆炸。

nn.NLLLoss

negative log likelihood loss, 用于训练n类分类器,
对于不平衡数据集,可以给类别添加weight,计算公式为

预期输入形状,其中为batch大小,C为类别数;

计算每个case的target对应类别的概率的负值,然后求取平均/和,一般与一个LogSoftMax连用从而获得对数概率。

例子

target = torch.tensor([1,0,3])
output = torch.randn(3,5)
print(output)

loss_fn = torch.nn.NLLLoss()
loss = loss_fn(output, target)
print(loss)

结果

tensor([[ 0.1684, -0.2378, -0.5189,  1.5398, -1.1828],
        [-0.4370,  0.3035,  1.3718, -0.2823, -0.4714],
        [ 0.2863, -0.3008,  0.8902,  0.4902, -0.4487]])
tensor(0.0615)

结果

nn.CrossEntropyLoss

经典Loss, 计算公式为:


相当于先将输出值通过softmax映射到每个值在,和为1的空间上。
希望正确的class对应的loss越小越好,所以对求取, 把映射到上,正确项的概率占比越大,整体损失就越小。

torch里的CrossEntropyLoss(x) 等价于 NLLLoss(LogSoftmax(x))

预期输入未normalize过的score,输入形状和NLL一样,为

例子1

target = torch.tensor([1,0,3])
output = torch.randn(3,5)
print(output)

loss_fn = torch.nn.CrossEntropyLoss()
loss = loss_fn(output, target)
print(loss)

结果

tensor([[-0.6324,  0.1134,  0.0695, -1.6937, -0.3634],
        [ 1.2044,  2.0876, -1.6558, -0.4869, -0.8516],
        [-0.7290, -0.4808,  0.8488, -0.3595, -1.3598]])
tensor(1.4465)

例子2-用numpy实现的CrossEntropyLoss

target = torch.tensor([1,0,3])
output = torch.randn(3,5)
print(output)

result = np.array([0.0, 0.0, 0.0])
for ix in range(3):
    log_sum = 0.0
    for iy in range(5):
        if(iy==target[ix]): result[ix] += -output[ix, iy]
        log_sum += exp(output[ix, iy])
    result[ix] += log(log_sum)
print(result)
print(np.mean(result))

loss_fn = torch.nn.CrossEntropyLoss()
loss = loss_fn(output, target)
print(loss)

结果

tensor([[ 1.6021,  0.5762, -1.9105, -1.0844, -0.0256],
        [ 1.0483,  0.8033,  1.1037, -1.2296,  1.2662],
        [ 0.7592, -2.6041, -1.6092, -0.2643,  1.2362]])
[1.52833433 1.43165374 2.15453246]
1.704840179536648
tensor(1.7048)

nn.BCELoss 以及 nn.BCEWithLogitsLoss

Binary Cross Entropy,公式如下:

双向的交叉熵,相当于交叉熵公式的二分类简化版,可以用于分类不互斥的多分类任务。

BCELoss需要先手动对输入sigmoid,然后每一个位置如果分类是1则加否则加,最后求取平均。

BCEWithLogitsLoss则不需要sigmoid,其他都完全一样。

例子

target = torch.tensor([[1,0,1],[0,1,1]]).float()
raw_output = torch.randn(2,3)
output = torch.sigmoid(raw_output)
print(output)

result = np.zeros((2,3), dtype=np.float)
for ix in range(2):
    for iy in range(3):
        if(target[ix, iy]==1): 
            result[ix, iy] += -log(output[ix, iy])
        elif(target[ix, iy]==0): 
            result[ix, iy] += -log(1-output[ix, iy])

print(result)
print(np.mean(result))

loss_fn = torch.nn.BCELoss(reduction='none')
print(loss_fn(output, target))
loss_fn = torch.nn.BCELoss(reduction='mean')
print(loss_fn(output, target))
loss_fn = torch.nn.BCEWithLogitsLoss(reduction='mean')
print(loss_fn(raw_output, target))

结果

tensor([[0.3370, 0.2463, 0.4499],
        [0.2124, 0.3505, 0.7828]])
[[1.08756434 0.28280236 0.79866814]
 [0.23878274 1.04849163 0.24483089]]
0.6168566833989618
tensor([[1.0876, 0.2828, 0.7987],
        [0.2388, 1.0485, 0.2448]])
tensor(0.6169)
tensor(0.6169)

nn.MultiLabelMarginLoss

multi-class multi-classification hinge loss
与将问题转换为2分类的BCELoss不同,这个loss就是为了不互斥的多分类(多类别多分类)设计的,

HingeLoss的常见形式为

其中为预测,为真实值。
如果和符号一致,则越大,loss越小,到0为止
如果符号不一样,则loss必大于1,且越大,loss越大。

总的来说,这种Loss函数训练的目标是拟合一堆标签,使得输出最后根据正负号确定结果。

nn.MultiLabelSoftMarginLoss


的值域为, 计算方式类似于BCE,就是把填到了BCE的当中。文档里说适用于多分类(互斥)的问题当中,这个式子是基于最大熵计算的。
BCELoss公式如下

nn.MultiMarginLoss

公式如下:

和MultiLabelMarginLoss公式非常像,仔细一看发现就是相同函数的不同接口,只是nn.MultiMarginLoss不支持多标签多分类,所以输入的y_true应当为这种,直接给出多分类的类别,格式为和。

nn.HingeEmbeddingLoss

公式如下:

同nn.MultiLabelMarginLoss的标准HingeLoss形式类似,希望拟合的标签为,其中是指定的margin,默认为1.0;实际上是。

常用于非线性的embedding或者半监督中。

nn.PoissonNLLLoss

NLL的泊松分布版本,输入形状变成了和,
公式为

被认为符合的泊松分布,没太用过这种Loss,网上也没啥相关的。

nn.KLDivLoss

KL散度,也就是相对熵,用来比较两个分布之间的信息损失,
计算公式为:

此处补充,普通的信息熵计算公式为:

交叉熵计算公式为:

很显然,KL散度直接计算两个分布间的自信息(-log项)差距在y分布上的期望,不能直接理解为距离(因为KLDiv(x,y)!=KLDiv(y,x)),可以理解为用x去拟合y所损失的信息量

要点总结

你可能感兴趣的:(pytorch中的损失函数总结——分类和分割相关)