语义分割实现地表建筑物识别4 评价函数与损失函数

语义分割实现地表建筑物识别4 评价函数与损失函数

一、学习目标

  1. 理解各类语义分割的评价函数及原理
  2. 对比各类损失函数原理,并进行具体实践

二 评价函数与损失函数

评价函数及原理

损失函数描述的是预测值与真实值之间的误差。配合优化算法,我们可以获得最优模型参数。

小结

语义分割问题可以看作对每一个图像像素的的分类问题。而对于分类问题,我们的损失函数可以从常规的混淆矩阵、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)=2TP/(TP)=2TP/(FP+2TP+FN)
为了方便实现最小化的损失函数Dice Loss: L = 1 − D i c e ( T , P ) L = 1 - Dice(T, P) L=1Dice(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(ylog(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)=ylog(y)+(1y)log(1y)
其中

  • 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/Nsum(αy(1p)γlogp+(1α)(1y)pγlog(1p))

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=cy=c/y=cy=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 相比于交叉熵函数具有更好的效果。

  1. 针对类别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=…
  2. 将Jaccard loss改写为关于M的子模集合函数 Jaccard loss:M∈{0,1}→|Mc|/|{y=c}∪M|
  3. 子模的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值