【语义分割】类别不平衡损失函数合集
在语义分割领域,我们会常常遇到类别不平衡的问题。比如要分割的目标(前景)可能只占图像的一小部分,因此负样本的比重很大,导致网络倾向于将所有样本判断为负样本。本文介绍了在数据不平衡时常用的一些损失函数。
类别不平衡会出现什么问题呢?假设我们需要训练一个分类器来对黄豆和绿豆分类,用100颗豆子训练分类器,其中99颗黄豆、1颗绿豆,那么分类器会倾向于把所有豆子都分类为黄豆,因为这么做就可以达到99%的准确率。但是我们不希望分类器这么做,所以需要一些方法来提升分类器的性能。
目录
一、Weighted Cross Entropy Loss
二、Focal Loss
三、Dice Loss
总结
参考:
四、Lovasz Loss
源码分析(多分类)
五、OHEM(Online Hard Example Mining)
六、Semantic Encoding Loss
七、Pixel Contrast Cross Entropy Loss
网络结构
对比损失
采样策略
声明
一、Weighted Cross Entropy Loss
交叉熵损失函数的实现可以参考【深度学习损失函数numpy实现并与torch对比】,当语义分割数据不平衡时,可以计算各个类别在数据集中所占的比例,然后将比率取倒数作为权重。
二、Focal Loss
语义分割多分类Focal Losss代码:PaddleSeg Focal Loss
何凯明大神的RetinaNet中提出了Focal Loss来解决类别不平衡的问题,下式为focal loss的公式,α为类别的权重,γ为大于0的值,在2分类的情况下:
首先给出公式如下,则:
再给出公式如下,则:
论文给出的focal loss公式:
将和带入上式,有:
将t=0和t=1分别带入,得到下式(y=p,)(下式来源):
对于多分类的情况,可根据BinaryCrossEntropy推广到多分类交叉熵的方法推广。
focal loss是如何起作用的呢?
首先对其求导,为了计算方便,简化上式:去掉常数α,设置γ为2,用ln代替log,得到下式:
对其求导:
可以看出,接近1时,focal loss的梯度趋于0,靠近0,focal loss的梯度越来越大。那么预测和真实值非常接近的时候,梯度极小,网络参数几乎不变,当预测值和真实值差距较大时,梯度变大,网络参数开始调整。
三、Dice Loss
dice loss 来自文章V-Net: Fully Convolutional Neural Networks for Volumetric Medical Image Segmentation,旨在应对语义分割中正负样本强烈不平衡的场景。
对于二分类问题,TP\FP\FN\TN定义如下:
对于语义分割任务,可看下图,蓝色和绿色为预测区域(FP+TP),橙色为真实类别区域,那么dice coefficient的定义为:
可以看出dice coefficient是可以体现出预测区域和真实区域的重叠程度,它的取值范围是[0, 1],当dice coefficient为1时,说明预测区域和真实区域完全重叠,是理想状态;当dice coefficient为0时,说明预测结果一点作用没有。
dice coefficient在数据不平衡时能够给出均衡的评价。
给定优化指标本身与代理损失函数之间的选择,最优选择就是指标本身。既然dice coefficient越大越好,且数据不平衡不会影响到它,那么可以把dice作为优化目标。神经网络训练时的目标就是使损失函数最小,但是这里的dice coefficient是越大越好,所以对他进行一点小修改得到dice loss:
为了防止分子分母出现0,再在分子分母加上一个很小的数,得到:
上面的函数是离散的,不能作为神经网络的优化目标,把网络输出的概率值带进去,使它连续(p为网络输出的概率值,t为one-hot标签图,p和t维度相同):
Dice Loss梯度分析:
设p为网络预测结果(概率值),t为目标值(标签),则dice loss为:
当t=0时,如下式,若p值很小,那么梯度会很大,从而使得训练不稳定:
当t=1时,如下式:
总结
dice loss 对正负样本严重不平衡的场景有着不错的性能。但是loss不稳定(小目标的dice coefficient容易变化剧烈),可能存在梯度饱和的现象。
参考:
dice-loss
四、Lovasz Loss
论文:The Lovász-Softmax loss: A tractable surrogate for the optimization of the intersection-over-union measure in neural networks
github:官方实现
语义分割的任务效果常常用iou(intersection over union)来评价,那么能不能直接使用iou来作为损失函数呢?
先看iou的公式:
假设把iou作为损失函数,那么它的形式为(论文中的公式4):
函数不连续,不能直接作为损失函数(dice loss为什么连续,因为计算的时候是用的预测的概率值,这里为什么不行?计算iou的时候已经离散化了)。
iou不连续,没法直接作为损失函数,我们就需要一种方法来解决这个问题,下面先回顾一下高数知识。
看到这里,肯定有同学想说“这里讲这个东西干嘛呢?”。让我们回到原来的话题,iou loss不连续怎么办?看一眼论文中的公式8,这个求和公式和上面的求和公式是不是有那么一点点相似。
还是看不懂?没关系,看下面,上图红框部分也就容易理解了()
上式和上上式近似一下:
看到了这里,大概能理解公式是怎么回事了,但是和又是怎么一回事呢?是一个向量,保存所有预测值和标签值的差的绝对值。表示按照从大到小的顺序排列。
现在只差最后一步, 将排序后,红框中怎么计算?
下面是作者的源码,将标签按照 排序后,按照 顺序逐像素剔除计算iou,使得iou从大到小排列,则jaccard从小到大排列,保证计算出的梯度大于0。(iou loss取值区间为[0, 1])
def lovasz_grad(gt_sorted):"""Computes gradient of the Lovasz extension w.r.t sorted errorsSee Alg. 1 in paper"""p = len(gt_sorted)gts = gt_sorted.sum()intersection = gts - gt_sorted.float().cumsum(0)union = gts + (1 - gt_sorted).float().cumsum(0)jaccard = 1. - intersection / unionif p > 1: # cover 1-pixel casejaccard[1:p] = jaccard[1:p] - jaccard[0:-1]return jaccard
源码分析(多分类)
假设网络输出的概率图[N, C, H, W],对应的标签为[N, H, W],先将其维度变换为[N * H * W, C]和[N * H * W],代码如下:
def flatten_probas(probas, labels, ignore=None):"""Flattens predictions in the batch"""if probas.dim() == 3:# assumes output of a sigmoid layerB, 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) # B * H * W, C = P, Clabels = labels.view(-1)if ignore is None:return probas, labelsvalid = (labels != ignore)vprobas = probas[valid.nonzero().squeeze()]vlabels = labels[valid]return vprobas, vlabels
对每个类别,计算预测概率和标签的差的绝对值,从大到小排序,并计算对应的梯度,将差值和梯度点积运算,得到损失。
def lovasz_softmax_flat(probas, labels, classes='present'):"""Multi-class Lovasz-Softmax lossprobas: [P, C] Variable, class probabilities at each prediction (between 0 and 1)labels: [P] Tensor, ground truth labels (between 0 and C - 1)classes: 'all' for all, 'present' for classes present in labels, or a list of classes to average."""if probas.numel() == 0:# only void pixels, the gradients should be 0return probas * 0.C = probas.size(1)losses = []class_to_sum = list(range(C)) if classes in ['all', 'present'] else classesfor c in class_to_sum:fg = (labels == c).float() # foreground for class cif (classes is 'present' and fg.sum() == 0):continueif 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.datafg_sorted = fg[perm]losses.append(torch.dot(errors_sorted, Variable(lovasz_grad(fg_sorted))))return mean(losses)
五、OHEM(Online Hard Example Mining)
论文(这篇是目标检测的论文,没有求证是否是第一篇提出该方法的论文):Training Region-based Object Detectors with Online Hard Example Mining
代码参考:ohem_cross_entropy_loss
在线困难样本挖掘的方法就是从数据中挑选出难分类的样本进行训练(预测概率和真实值差距大的样本就是难分类样本),通过对难分类样本进行针对性的训练,可以有效提高模型性能,该方法在数据不平衡的情况下非常有效。
对于一组训练数据,根据预测概率和真实值的差,设立阈值并挑选出难分类的样本,仅在挑选出的样本上计算损失,过程较为简单,直接上代码(代码来自PaddleSeg):
class OhemCrossEntropyLoss(nn.Layer):"""Implements the ohem cross entropy loss function.Args:thresh (float, optional): The threshold of ohem. Default: 0.7.min_kept (int, optional): The min number to keep in loss computation. Default: 10000.ignore_index (int64, optional): Specifies a target value that is ignoredand does not contribute to the input gradient. Default ``255``."""def __init__(self, thresh=0.7, min_kept=10000, ignore_index=255):super(OhemCrossEntropyLoss, self).__init__()self.thresh = thresh # 概率阈值,真是类别预测概率比阈值低的被认为是难样本self.min_kept = min_kept # 最少用于计算损失的像素点数量self.ignore_index = ignore_index # 忽略计算损失的标签self.EPS = 1e-5 # 防止数值计算出错def forward(self, logit, label):"""Forward computation.Args:logit (Tensor): Logit tensor, the data type is float32, float64. Shape is(N, C), where C is number of classes, and if shape is more than 2D, thisis (N, C, D1, D2,..., Dk), k >= 1.label (Tensor): Label tensor, the data type is int64. Shape is (N), where eachvalue is 0 <= label[i] <= C-1, and if shape is more than 2D, this is(N, D1, D2,..., Dk), k >= 1."""if len(label.shape) != len(logit.shape):label = paddle.unsqueeze(label, 1)# get the label after ohemn, c, h, w = logit.shapelabel = label.reshape((-1, ))valid_mask = (label != self.ignore_index).astype('int64')num_valid = valid_mask.sum()label = label * valid_maskprob = F.softmax(logit, axis=1) # 计算预测的概率prob = prob.transpose((1, 0, 2, 3)).reshape((c, -1))if self.min_kept < num_valid and num_valid > 0:# let the value which ignored greater than 1prob = prob + (1 - valid_mask)# get the prob of relevant labellabel_onehot = F.one_hot(label, c)label_onehot = label_onehot.transpose((1, 0))prob = prob * label_onehot # 真实类别对应的预测概率prob = paddle.sum(prob, axis=0)threshold = self.threshif self.min_kept > 0:index = prob.argsort()threshold_index = index[min(len(index), self.min_kept) - 1]threshold_index = int(threshold_index.numpy()[0])if prob[threshold_index] > self.thresh:threshold = prob[threshold_index]kept_mask = (prob < threshold).astype('int64') # 根据阈值选择参与计算的像素点label = label * kept_maskvalid_mask = valid_mask * kept_mask# make the invalid region as ignorelabel = label + (1 - valid_mask) * self.ignore_indexlabel = label.reshape((n, 1, h, w))valid_mask = valid_mask.reshape((n, 1, h, w)).astype('float32')loss = F.softmax_with_cross_entropy(logit, label, ignore_index=self.ignore_index, axis=1)loss = loss * valid_maskavg_loss = paddle.mean(loss) / (paddle.mean(valid_mask) + self.EPS)label.stop_gradient = Truevalid_mask.stop_gradient = Truereturn avg_loss
六、Semantic Encoding Loss
论文:Context Encoding for Semantic Segmentation
自己复现的地址:ENCNet_paddle
Semantic Encoding Loss是ENCNet中使用的辅助损失函数,普通的交叉熵损失函数无法考虑全局信息,可能导致小目标无法被正确识别,Semantic Encoding Loss平等地考虑不同大小的目标。Semantic Encoding Loss较为简单,它的输入维度是[batch_size, num_classes],target维度和输入维度相同,对图片中包含的所有类别,target中对应的该类别的标签都为1。
下面给出自己使用paddlepaddle实现的代码:
class SECrossEntropyLoss(nn.Layer):"""The Semantic Encoding Loss implementation based on PaddlePaddle."""def __init__(self, *args, **kwargs):super(SECrossEntropyLoss, self).__init__()def forward(self, logit, label):# logit维度为[N, C, 1, 1]或[N, C],label维度为[N, C]if logit.ndim == 4:logit = logit.squeeze(2).squeeze(3)assert logit.ndim == 2, "The shape of logit should be [N, C, 1, 1] or [N, C], but the logit dim is {}.".format(logit.ndim)batch_size, num_classes = paddle.shape(logit)se_label = paddle.zeros([batch_size, num_classes])for i in range(batch_size):hist = paddle.histogram(label[i],bins=num_classes,min=0,max=num_classes - 1)hist = hist.astype('float32') / hist.sum().astype('float32')se_label[i] = (hist > 0).astype('float32')loss = F.binary_cross_entropy_with_logits(logit, se_label)return loss
七、Pixel Contrast Cross Entropy Loss
论文:Exploring Cross-Image Pixel Contrast for Semantic Segmentation
自己使用paddlepaddle复现的地址(仅实现BatchSample):contrast_seg_paddle
Pixel Contrast Cross Entropy Loss并不是设计应对数据不平衡问题,但是它的样本采样策略在一定程度上可以应对数据不平衡问题,可作为辅助损失函数使用。
对于语义分割任务,当考虑上下文信息时一般是指的图片的上下文信息,但是本文作者提出利用“全局”(数据集所有图片)上下文信息来提升语义分割效果。核心思想在于:对于数据集中所有的同类像素,它的embedding应该是相似的,对于不同类别的像素,它的embedding应该是不同的。于是作者提出Pixel Contrast Cross Entropy Loss,目标是使同类像素的embedding尽可能靠近,不同类别像素的embedding尽可能远离。
如下图,对不同图片中的同类像素,通过对比学习的方法使同类像素的embedding靠近,不同类别像素的embedding远离,来提升语义分割的效果。
网络结构
对于一个任意的语义分割网络,额外引入一个project,project输出像素对应的embedding,将embedding送入Pixel Contrast Cross Entropy Loss优化,提高语义分割的效果。(相当于引入了一个辅助损失函数)
对比损失
得到了embedding,需要设计一个损失函数,该损失函数实现的功能为:使相同类别的embedding尽可能靠近,不同类别的embedding尽可能远离。怎么通过衡量2个embedding的距离呢?通过点积运算,2个向量点积值越大,表示越相似,越小表示越不相似。
损失函数如下式,表示embedding向量,表示与同类别的embedding向量,表示点积运算,和相似性越低,损失函数越接近0,相似度越高,损失越大。
需要注意的是:embedding不是采集自一张图片,而是采集自不同图片
采样策略
首先给出困难样本的定义:接近于-1,则为正困难样本(理想状态接近1),若接近1,则 为负困难样本(理想状态为1)。
文中提出3种采样策略来选择训练样本:
1、 Hardest Example Sampling
从正困难样本和负困难样本中各自挑选最难的K个样本参与训练。
2、 Semi-Hard Example Sampling
对于embedding向量,选择与其最近的10%个负样本(负困难样本)和最远的10%个正样本(正困难样本)构成集合,每次训练从集合中挑选K个样本参与训练。
3、Segmentation-Aware Hard Anchor Sampling
预测结果(使seg头的输出,不是project的输出)正确的像素对应的embedding作为易分类样本,预测结果错误的embedding作为难分类样本,每次训练从难分类样本和易分类样本中各随机挑选K个样本参与训练。
挑选样本的时候,为什么要一半难样本一半易样本?
如果只挑选困难样本进行分类,那么网络训练出来的分类器可能如下图:
但是如果考虑到易样本呢?就可能变成这样了:
所以挑选样本的时候,从困难样本和容易的样本中各挑选一半。
声明
本篇文章禁止转载。
【语义分割】类别不平衡损失函数合集相关推荐
- 【损失函数合集】超详细的语义分割中的Loss大盘点
前言 前两天介绍了一下Contrastive Loss,Triplet Loss以及Center Loss.今天正好是周六,时间充分一点我就来大概盘点一下语义分割的常见Loss,希望能为大家训练语义分 ...
- dice系数 交叉熵_语义分割中的损失函数
1 交叉熵 信息量:当一个事件发生的概率为 ,那么该事件对应的概率的信息量是 . 信息量的熵:信息量的期望,假设 事件 共有n种可能,发生 的概率为 ,那么该事件的熵 为: 相对熵,又称KL散度,如果 ...
- 将特定像素点在图像上连接起来_(NeurIPS 2019) Gated CRF Loss-一种用于弱监督图像语义分割的新型损失函数...
本文已经被NeurIPS 2019(2019 Conference and Workshop on Neural Information Processing Systems)接收,论文为弱监督图像语 ...
- NeurIPS 2019 | 用于弱监督图像语义分割的新型损失函数
作者丨赵磊 学校丨北京林业大学硕士生 研究方向丨语义分割 本文已经被 NeurIPS 2019 (2019 Conference and Workshop on Neural Information ...
- 自定义语义分割数据集(划分训练集与验证集)、并且将一个文件夹下的所有图片的名字存到txt文件
目录 1.划分训练集.验证集与测试集 2.文件名称保存为txt 3.文件移动 4. 将数据集保存为.pkl格式以及读取.pkl格式文件 我们可以借助Pytorch从文件夹中读取数据集,十分方便,但是P ...
- 基于IOU的损失函数合集, IoU, GIoU, DIoU,CIoU, EIoU
目标检测任务的损失函数一般由 Classificition Loss(分类损失函数)和Bounding Box Regeression Loss(回归损失函数)两部分构成. Bounding ...
- python遥感影像地物分类_基于轻量化语义分割网络的遥感图像地物分类方法与流程...
本发明属于图像处理 技术领域: ,特别涉及一种地物分类方法,可用于土地利用分析.环境保护以及城市规划. 背景技术: :遥感图像地物分类,旨在取代繁琐的人工作业,利用地物分类方法,得到输入遥感图像的地物 ...
- 制作自定义语义分割数据集
自定义语义分割数据集(划分训练集与验证集).并且将一个文件夹下的所有图片的名字存到txt文件 我们可以借助Pytorch从文件夹中读取数据集,十分方便,但是Pytorch中没有提供数据集划分的操作,需 ...
- 精度高、模型小、速度快!梯形DenseNets结构实现语义分割新高度!
点击我爱计算机视觉标星,更快获取CVML新技术 今天上午arXiv出现一篇非常值得参考的语义分割文章<Efficient Ladder-style DenseNets for Semantic ...
最新文章
- 【实验】广域网点到点协议PPP PAP CHAP的双向验证、单项认证
- python绘图教程_pyplot绘图教程
- Send mail in ECC
- POJ - 1050 To the Max(最大连续子段和,线性dp)
- 【运维】从实战掌握自动化运维工具Ansible
- 软考-系统分析师-论文写作-备考总结笔记
- rs 华为hcip 课件下载_华为路由与交换hcip最新题库
- Java开发微信公众号后台
- 一个dsp最小系统至少要有_DSP原理及应用(2812)试卷_附答案卷B2(2015城南)
- 深圳计算机免考申请在哪,深圳自考申请免考要什么条件
- 点击或长按复制打开微信H5落地页如何制作?
- IT运维的365天--007PC端微信图片不能正常接收发送
- NTU-RGBD骨架数据分析
- 【东周列国志】读后感
- 强烈推荐 :最用心的运营数据指标解读
- 培训机构靠谱吗?|猿代码科技
- 谷歌浏览器设置缓存方法
- 第7次版本迭代的微信答题小程序完美上线
- 有人这样评价Ruby,你赞同他的观点吗?
- 什么是路由器?路由器有什么用?
热门文章
- 基于微信教室预约小程序系统设计与实现 开题报告
- 安装post man
- 【观察】大型企业的数字化转型之旅 浪潮云ERP是真正的“引路人”
- 猫咪藏在哪个房间python_猫咪总喜欢把自己藏在不可思议的地方,这是为什么呢?...
- Maven deploy配置方法
- 小程序之旅(8) wx.qy.login 坑
- jmeter压测学习10-linux上执行遇到的问题 There is insufficient memory for the Java Runtime Environment to continu
- Android ImgaView背景图片不失真处理
- 哈工大2019秋数据结构期末试题
- Eight-point algorithm