语义分割实现地表建筑物识别4 评价函数与损失函数
一、学习目标
- 理解各类语义分割的评价函数及原理
- 对比各类损失函数原理,并进行具体实践
二 评价函数与损失函数
评价函数及原理
损失函数描述的是预测值与真实值之间的误差。配合优化算法,我们可以获得最优模型参数。
小结
语义分割问题可以看作对每一个图像像素的的分类问题。而对于分类问题,我们的损失函数可以从常规的混淆矩阵、Logistics回归中学习到的交叉熵损失函数、目标检测模型常见的交并比IoU和FocalLoss以及这次新接触到的Lovász-Softmax。
内容部分
1. 文字介绍
混淆矩阵
在分类问题中,常用的评价函数包括混淆矩阵【精确率和召回率、F1 score】
我们可以将语义分割看做对每个像素的分类问题。于是我们可以将图片像素预测值与真实值进行比较,得到特定像素所属的TP、TN、FP、FN四类,即:
- TP:预测为前景且真实为前景的所有像素集合【预测正确】
- FP:预测为前景但真实为背景的所有像素集合【预测错误】
- FN:预测为背景但真实为前景的所有像素集合【预测错误】
- TN:预测为背景且真实为背景的所有像素集合【预测正确】
从TP、TN、FP、FN四类,我们可以得出 - 精确率: P r e c i s i o n = T P / ( T P + F P ) Precision = TP / (TP + FP) Precision=TP/(TP+FP)
- 召回率: R e c a l l = T P / ( T P + F N ) Recall = TP / (TP + FN) Recall=TP/(TP+FN)
Dice Loss
Dice系数是度量集合相似度的函数。
D
i
c
e
(
T
,
P
)
=
2
∣
T
∩
P
∣
/
(
∣
T
∣
∪
∣
P
∣
)
=
2
T
P
/
(
F
P
+
2
T
P
+
F
N
)
Dice(T, P) = 2|T∩P|/(|T|∪|P|) = 2TP / (FP + 2TP + FN)
Dice(T,P)=2∣T∩P∣/(∣T∣∪∣P∣)=2TP/(FP+2TP+FN)
为了方便实现最小化的损失函数Dice Loss:
L
=
1
−
D
i
c
e
(
T
,
P
)
L = 1 - Dice(T, P)
L=1−Dice(T,P),进一步增加Laplace smoothing减少过拟合
BCE(Binary Cross-Entropy Loss)
将训练目标转为:使预测概率分布y^尽可能接近真实的标签概率分布y。因此需要有更适合衡量两个概率分布差异的测量函数:交叉熵损失函数
深度学习分类任务的常见损失函数还有交叉熵损失函数。BCE是交叉熵损失函数在二分类任务中的特例应用。
l
(
y
,
y
)
=
−
s
u
m
(
y
∗
l
o
g
(
y
)
)
l(y,y^) = -sum(y*log(y^))
l(y,y)=−sum(y∗log(y))
对于二分类:
l
(
y
,
y
)
=
−
∣
y
∗
l
o
g
(
y
)
+
(
1
−
y
)
∗
l
o
g
(
1
−
y
)
∣
l(y,y^) = -|y*log(y^)+(1-y)*log(1-y^)|
l(y,y)=−∣y∗log(y)+(1−y)∗log(1−y)∣
其中
- y:是否属于第i类的真实值
- y^:属于此类的概率值的预测结果
Focal Loss
Focal Loss在目标检测领域用于解决正负样本比例失调。
l
o
s
s
=
−
1
/
N
∗
s
u
m
(
α
y
(
1
−
p
)
γ
l
o
g
p
+
(
1
−
α
)
(
1
−
y
)
p
γ
l
o
g
(
1
−
p
)
)
loss=-1/N*sum(αy(1-p)^γlogp+(1-α)(1-y)p^γlog(1-p))
loss=−1/N∗sum(αy(1−p)γlogp+(1−α)(1−y)pγlog(1−p))
Lovász-Softmax
IoU 评价指标
另一种集合相似度度量方法:在目标检测中用到过的交并比。
给定真实像素标签y和预测像素标签y^,则所属类别c的IoU如下:
J
(
y
,
y
)
=
∣
y
=
c
∩
y
=
c
∣
/
∣
y
=
c
∪
y
=
c
∣
J(y,y^) = |{y=c}∩{y^=c}| / |{y=c}∪{y^=c}|
J(y,y)=∣y=c∩y=c∣/∣y=c∪y=c∣
Jaccard loss = 1- J(y,y^)
def iou_score(output, target):
intersect = np.logical_and(target, output)
union = np.logical_or(target, output)
return np.sum(intersect) / np.sum(union)
target = np.random.randint(0, 2, (3,3))
output = np.random.randint(0, 2, (3,3))
d = iou_score(output, target)
Lovász-Softmax
1-IoU做损失函数存在的问题是:离散而不能直接求导。为此,可以采用lLovász extension 将离散的Jaccard loss变得连续,从而可以直接求导,使得其作为分割网络的loss function。Lovász-Softmax 相比于交叉熵函数具有更好的效果。
- 针对类别c,所有未被正确预测的像素集合定义为:KaTeX parse error: Got function '\html@mathml' with no arguments as superscript at position 1: \̲h̲t̲m̲l̲@̲m̲a̲t̲h̲m̲l̲{\mathrel{\not=…
- 将Jaccard loss改写为关于M的子模集合函数 Jaccard loss:M∈{0,1}→|Mc|/|{y=c}∪M|
- 子模的Lovász extension 是凸函数,Lovász extension可以求解子模最小化问题,并高效实现最小化。…
2. 代码实现
评价函数
# 1. Dice Index
import numpy as np
def dice(output, target):
# |T∩P|
intersect = (output * target).sum()
# |T|∪|P|
union = output.sum() + target.sum()
smooth = 1e-6
return (2 * intersect + smooth) / (union + smooth)
target = np.random.randint(0,2,(3,3))
output = np.random.randint(0,2,(3,3))
d = dice(output, target)
def dice_loss(output,target):
'''
Dice 指数
在一些场合还可以添加上Laplacesmoothing减少过拟合:
'''
smooth = 1e-6#避免0为除数-
intersection = (output * target).sum()
return (2.*intersection+smooth)/(output.sum()+target.sum()+smooth)
#生成随机两个矩阵测试
target=np.random.randint(0,2,(3,3))
output=np.random.randint(0,2,(3,3))
DiceLoss=dice_loss(output,target)
# 2. IoU
def iou_score(output, target):
intersect = np.logical_and(target, output)
union = np.logical_or(target, output)
return np.sum(intersect) / np.sum(union)
target = np.random.randint(0, 2, (3,3))
output = np.random.randint(0, 2, (3,3))
d = iou_score(output, target)
损失函数
# 1. BCE
import torch
import torch.nn as nn
bce = nn.BCELoss()
bce_sig = nn.BCEWithLogitsLoss()
input = torch.randn(5,1 requires_grad=True)
target = torch.empty(5,1).random_(2)
pre = nn.Sigmoid()(input)
loss_bce = bce(pre, target) # 需要先进行一次Sigmoid激活,再计算损失函数
loss_bce_sig = bce_sig(input, target)
## Focal Loss
import torch.nn as nn
import torch
import torch.nn.functional as F
class FocalLoss(nn.Module):
def __init__(self, alpha=1, gamma=2, logits=False, reduce=True):
super(FocalLoss, self).__init__()
self.alpha = alpha
self.gamma = gamma
self.logits = logits # 如果BEC带logits则损失函数在计算BECloss之前会自动计算softmax/sigmoid将其映射到[0,1]
self.reduce = reduce
def forward(self, inputs, targets):
if self.logits:
BCE_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduce=False)
else:
BCE_loss = F.binary_corss_entropy(inputs, targets,reduce=False)
pt = torch.exp(-BCE_loss)
F_loss = self.alpha * (1-pt) ** self.gamma * BCE_loss
if self.reduce:
return torch.mean(F_loss)
else:
return F_loss
FL1 = FocalLoss(logits=False)
FL2 = FocalLoss(logits=True)
inputs = torch.randn(5,1,requires_grad=True)
targets = torch.empty(5,1).random_(2)
pre = nn.Sigmoid()(inputs)
f_loss_1 = FL1(pre, targets)
f_loss_2 = FL2(inputs, targets)
# Lovász-Softmax
import torch
from torch.autograd import Variable
import torch.nn.functional as F
import numpy as np
try:
from itertools import ifilterfalse
except ImportError:
from itertools import filterfalse as ifilterfalse
# ------------------------------MULTICLASS LOSSES----------------------------------
def lovasz_softmax(probas,labels, classes='present', per_image=False, ignore=None):
if per_image:
loss = mean(lovasz_softmax_flat(*flatten_probas(prob.unsqueeze(0),lab.unsqueeze(0),ignore),calsses=classes) for prob,lab in zip(probas,labels))
else:
loss = lovasz_softmax_flat(*flatten_probas(probas,labels,ignore),classes=classes)
return loss
def lovasz_softmax_flat(probas,labels,classes='present'):
if probas.numel() == 0:
return probas * 0
C = probas.size(1)
losses = []
class_to_sum = list(range(C)) if classes in ['all','present'] else classes
for c in class_to_sum:
fg = (labels==c).float()
if (classes is 'present' and fg.sum() == 0):
continue
if C == 1:
if len(classes) > 1:
raise ValueError('Sigmoid output possible only with 1 class')
class_pred = probas[:, 0]
else:
class_pred = probas[:, c]
errors = (Variable(fg) - class_pred).abs()
errors_sorted, perm = torch.sort(errors, 0, descending=True)
perm = perm.data
fg_sorted = fg[perm]
losses.append(torch.dot(errors_sorted, Variable(lovasz_grad(fg_sorted))))
return mean(losses)
def flatten_probas(probas, labels, ignore=None):
if probas.dim() == 3:
B,H,W = probas.size()
probas = probas.view(B, 1, H, W)
B,C,H,W = probas.size()
probas = probas.permute(0,2,3,1).contiguous().view(-1,C)
labels = labels.view(-1)
if ignore is None:
return probas, labels
valid = (labels != ignore)
vprobas = probas[valid.nonzero().squeeze()]
vlabels = labels[valid]
return vprobas, vlabels
def xloss(logits, labels, ignore=None):
return F.cross_entropy(logits, Variable(labels), ignore_index=255)
# ------------------------------HELPER FUNCTIONS----------------------------------
def isnan(x):
return x != x
def mean(l, ignore_nan=False, empty=0):
l = iter(l)
if ignore_nan:
l = ifilterfalse(isnan, l)
try:
n = 1
acc = next(l)
except StopIteration:
if empty == 'raise':
raise ValueError('Empty mean')
return empty
for n,v in enumerate(l,2):
acc += v
if n == 1:
return acc
return acc / n
3. baseline中的加权损失函数
baseline中采用80%的BCE与20%的Dice Loss融合得到最终的加权损失函数。具体实现如下:
class SoftDiceLoss(nn.Module):
def __init__(self, smooth=1,dims=(-2,-1)):
super(SoftDiceLoss,self).__init__()
self.smooth = smooth
self.dims = dims
def forward(self,x,y):
tp = (x*y).sum(self.dims)
fp = (x*(1-y)).sum(self.dims)
fn = ((1-x)*y).sum(self.dims)
dc = (2*tp+self.smooth)/(2*tp+fp+fn+self.smooth)
dc = dc.mean()
return 1 - dc
bce_fn = nn.BCEWithLogitsLoss()
dice_fn = SoftDiceLoss()
def loss_fn(y_pred,y_true):
bce = bce_fn(y_pred,y_true)
dice = dice_fn(y_pred.sigmoid(),y_true)
return 0.8*bce + 0.2*dice
参考
http://zh.gluon.ai/chapter_computer-vision/bounding-box.html