交互分割实战知识笔记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更加不敏感。
在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所损失的信息量