文章目录
- 前言
- 1 dice loss
- 2 BCE-Dice Loss
- 3 Jaccard/Intersection over Union (IoU) Loss
- 4 Focal Loss
- 5 Active-Contour-Loss
- 6 Boundary Loss for Remote Sensing Imagery Semantic Segmentation
前言
这篇博文为一些常见的损失函数提供了参考,你可以很轻松的导入到代码中。
损失函数定义了神经网络模型如何根据每回合的残差计算总体误差,这反过来又影响它们在进行反向传播时调整系数的方式,因此损失函数的选择直接影响模型的性能。
对于分割和其他分类任务,默认选择的损失函数是二进制交叉熵(BCE)。当一个特定的度量,例如dice系数或IoU,被用来判断模型性能时,竞争对手有时会试验从这些度量派生出的损失函数——通常是形式1 - f(x),其中f(x)是有问题的度量。这些函数不能简单地用NumPy编写,因为它们是在GPU中实现的,因此需要来自相应模型库的后端函数,其中也包含一个用于反向传播算法的梯度。这并没有想象中复杂。
在多类分割中,通常使用损失函数来计算每个类的平均损失,而不是从整个预测张量中计算损失。这篇博文将作为基本代码的模板参考,但是为了多类平均而修改它应该是很简单的。例如,如果压扁张量包含连续的类,你可以将它们分成四个等长的类,计算它们各自的损失并求平均值。
希望这篇博文对你有所帮助,欢迎你的任何修改建议。
1 dice loss
骰子系数或Sørensen-Dice系数,是一种常见的标准二进制分类任务,如像素分割,也可以修改作为损失函数:
#PyTorch
class DiceLoss(nn.Module):
def __init__(self, weight=None, size_average=True):
super(DiceLoss, self).__init__()
def forward(self, inputs, targets, smooth=1):
#comment out if your model contains a sigmoid or equivalent activation layer
inputs = F.sigmoid(inputs)
#flatten label and prediction tensors
inputs = inputs.view(-1)
targets = targets.view(-1)
intersection = (inputs * targets).sum()
dice = (2.*intersection + smooth)/(inputs.sum() + targets.sum() + smooth)
return 1 - dice
2 BCE-Dice Loss
这种损失结合了骰子损失和标准的二进制交叉熵(BCE)损失,这通常是默认的分割模型。将这两种方法结合在一起可以在一定程度上减少损失,同时受益于BCE的稳定性。任何学过逻辑回归的人都熟悉多类BCE的方程:
#PyTorch
class DiceBCELoss(nn.Module):
def __init__(self, weight=None, size_average=True):
super(DiceBCELoss, self).__init__()
def forward(self, inputs, targets, smooth=1):
#comment out if your model contains a sigmoid or equivalent activation layer
inputs = F.sigmoid(inputs)
#flatten label and prediction tensors
inputs = inputs.view(-1)
targets = targets.view(-1)
intersection = (inputs * targets).sum()
dice_loss = 1 - (2.*intersection + smooth)/(inputs.sum() + targets.sum() + smooth)
BCE = F.binary_cross_entropy(inputs, targets, reduction='mean')
Dice_BCE = BCE + dice_loss
return Dice_BCE
3 Jaccard/Intersection over Union (IoU) Loss
IoU指标,或Jaccard指标,类似于骰子指标,计算为两个集合之间的正实例重叠与它们相互组合值之间的比率:
与骰子度量一样,它也是评价像素分割模型性能的常用方法。
#PyTorch
class IoULoss(nn.Module):
def __init__(self, weight=None, size_average=True):
super(IoULoss, self).__init__()
def forward(self, inputs, targets, smooth=1):
#comment out if your model contains a sigmoid or equivalent activation layer
inputs = F.sigmoid(inputs)
#flatten label and prediction tensors
inputs = inputs.view(-1)
targets = targets.view(-1)
#intersection is equivalent to True Positive count
#union is the mutually inclusive area of all labels & predictions
intersection = (inputs * targets).sum()
total = (inputs + targets).sum()
union = total - intersection
IoU = (intersection + smooth)/(union + smooth)
return 1 - IoU
4 Focal Loss
Facebook人工智能研究的Lin等人在2017年引入了focal loss,作为对抗极端不平衡数据集的一种手段,在这些数据集中阳性样例相对较少。他们的论文“Focal Loss for Dense Object Detection”可以在这里找到: arxiv.org/abs/1708.02…
#PyTorch
ALPHA = 0.8
GAMMA = 2
class FocalLoss(nn.Module):
def __init__(self, weight=None, size_average=True):
super(FocalLoss, self).__init__()
def forward(self, inputs, targets, alpha=ALPHA, gamma=GAMMA, smooth=1):
#comment out if your model contains a sigmoid or equivalent activation layer
inputs = F.sigmoid(inputs)
#flatten label and prediction tensors
inputs = inputs.view(-1)
targets = targets.view(-1)
#first compute binary cross-entropy
BCE = F.binary_cross_entropy(inputs, targets, reduction='mean')
BCE_EXP = torch.exp(-BCE)
focal_loss = alpha * (1-BCE_EXP)**gamma * BCE
return focal_loss
5 Active-Contour-Loss
import torch
def active_contour_loss(y_true, y_pred, weight=10):
'''
y_true, y_pred: tensor of shape (B, C, H, W), where y_true[:,:,region_in_contour] == 1, y_true[:,:,region_out_contour] == 0.
weight: scalar, length term weight.
'''
# length term
delta_r = y_pred[:,:,1:,:] - y_pred[:,:,:-1,:] # horizontal gradient (B, C, H-1, W)
delta_c = y_pred[:,:,:,1:] - y_pred[:,:,:,:-1] # vertical gradient (B, C, H, W-1)
delta_r = delta_r[:,:,1:,:-2]**2 # (B, C, H-2, W-2)
delta_c = delta_c[:,:,:-2,1:]**2 # (B, C, H-2, W-2)
delta_pred = torch.abs(delta_r + delta_c)
epsilon = 1e-8 # where is a parameter to avoid square root is zero in practice.
lenth = torch.mean(torch.sqrt(delta_pred + epsilon)) # eq.(11) in the paper, mean is used instead of sum.
# region term
c_in = torch.ones_like(y_pred)
c_out = torch.zeros_like(y_pred)
region_in = torch.mean( y_pred * (y_true - C_in )**2 ) # equ.(12) in the paper, mean is used instead of sum.
region_out = torch.mean( (1-y_pred) * (y_true - C_out)**2 )
region = region_in + region_out
loss = weight*lenth + region
return loss
6 Boundary Loss for Remote Sensing Imagery Semantic Segmentation
摘要:
由于地理空间数据日益重要,包括语义分割在内的分析成为当今计算机视觉中日益流行的任务。卷积神经网络是一种强大的视觉模型,可以产生层次结构的特征,从业者广泛使用它们来处理遥感数据。在进行遥感图像分割时,往往存在一类具有精确定义边界的多个实例,准确提取这些边界至关重要。分段边界划分的精度明显地影响了整个分割区域的质量。然而,广泛使用的分割损失函数,如BCE、IoU损失或骰子损失,并不能充分惩罚边界的错位。在本文中,我们提出了一种新的损失函数,即边界检测的度量会计精度的可微替代物。我们可以使用任何神经网络的损失函数来进行二值分割。我们在一个合成数据集上对UNet进行了各种修改,并使用了真实世界的数据(ISPRS波茨坦,INRIAAIL)。
class BoundaryLoss(nn.Module):
"""Boundary Loss proposed in:
Alexey Bokhovkin et al., Boundary Loss for Remote Sensing Imagery Semantic Segmentation
https://arxiv.org/abs/1905.07852
"""
def __init__(self, theta0=3, theta=5):
super().__init__()
self.theta0 = theta0
self.theta = theta
def crop(self, w, h, target):
nt, ht, wt = target.size()
offset_w, offset_h = (wt - w) // 2, (ht - h) // 2
if offset_w > 0 and offset_h > 0:
target = target[:, offset_h:-offset_h, offset_w:-offset_w]
return target
def to_one_hot(self, target, size):
n, c, h, w = size
ymask = torch.FloatTensor(size).zero_()
new_target = torch.LongTensor(n, 1, h, w)
if target.is_cuda:
ymask = ymask.cuda(target.get_device())
new_target = new_target.cuda(target.get_device())
new_target[:, 0, :, :] = torch.clamp(target.detach(), 0, c - 1)
ymask.scatter_(1, new_target, 1.0)
return torch.autograd.Variable(ymask)
def forward(self, pred, gt):
"""
Input:
- pred: the output from model (before softmax)
shape (N, C, H, W)
- gt: ground truth map
shape (N, H, w)
Return:
- boundary loss, averaged over mini-bathc
"""
gt = torch.squeeze(gt)
n, c, h, w = pred.shape
log_p = F.log_softmax(pred, dim=1)
# softmax so that predicted map can be distributed in [0, 1]
pred = torch.softmax(pred, dim=1)
# one-hot vector of ground truth
gt = self.crop(w, h, gt)
one_hot_gt = self.to_one_hot(gt, log_p.size())
# boundary map
gt_b = F.max_pool2d(
1 - one_hot_gt, kernel_size=self.theta0, stride=1, padding=(self.theta0 - 1) // 2)
gt_b -= 1 - one_hot_gt
pred_b = F.max_pool2d(
1 - pred, kernel_size=self.theta0, stride=1, padding=(self.theta0 - 1) // 2)
pred_b -= 1 - pred
# extended boundary map
gt_b_ext = F.max_pool2d(
gt_b, kernel_size=self.theta, stride=1, padding=(self.theta - 1) // 2)
pred_b_ext = F.max_pool2d(
pred_b, kernel_size=self.theta, stride=1, padding=(self.theta - 1) // 2)
# reshape
gt_b = gt_b.view(n, c, -1)
pred_b = pred_b.view(n, c, -1)
gt_b_ext = gt_b_ext.view(n, c, -1)
pred_b_ext = pred_b_ext.view(n, c, -1)
# Precision, Recall
P = torch.sum(pred_b * gt_b_ext, dim=2) / (torch.sum(pred_b, dim=2) + 1e-7)
R = torch.sum(pred_b_ext * gt_b, dim=2) / (torch.sum(gt_b, dim=2) + 1e-7)
# Boundary F1 Score
BF1 = 2 * P * R / (P + R + 1e-7)
# summing BF1 Score for each class and average over mini-batch
loss = torch.mean(1 - BF1)
return loss