CNN各模块介绍(二)

七.全连接层

八.BN 层

1.BN的由来

BN是由Google于2015年提出,论文是《Batch Normalization_ Accelerating Deep Network Training by Reducing Internal Covariate Shift》,这是一个深度神经网络训练的技巧,主要是让数据的分布变得一致,从而使得训练深层网络模型更加容易和稳定。所以目前BN已经成为几乎所有卷积神经网络的标配技巧了。

2.BN的作用,优点

通俗地讲,刚开始的数据都是同一个分布的,模型学习过程中,模型的参数已经适合于一种分布,突然又要适应另一种分布,这就会让模型的参数发生很大的调整,从而影响到收敛速度和精度,这就是Internal covariate shift。而BN的作用就是将这些输入值或卷积网络的张量进行类似标准化的操作,将其放缩到合适的范围,从而加快训练速度;另一方面使得每一层可以尽量面对同一特征分布的输入值,减少了变化带来的不确定性。

(1)BN使得网络中每层输入数据的分布相对稳定,加速模型学习速度

BN通过规范化与线性变换使得每一层网络的输入数据的均值与方差都在一定范围内,使得后一层网络不必不断去适应底层网络中输入的变化,从而实现了网络中层与层之间的解耦,允许每一层进行独立学习,有利于提高整个神经网络的学习速度。

(2)BN使得模型对网络中的参数不那么敏感,简化调参过程,使得网络学习更加稳定

在神经网络中,我们经常会谨慎地采用一些权重初始化方法(例如Xavier)或者合适的学习率来保证网络稳定训练。

(3)BN允许网络使用饱和性激活函数(例如sigmoid,tanh等),缓解梯度消失问题

在不使用BN层的时候,由于网络的深度与复杂性,很容易使得底层网络变化累积到上层网络中,导致模型的训练很容易进入到激活函数的梯度饱和区;通过normalize操作可以让激活函数的输入数据落在梯度非饱和区,缓解梯度消失的问题;另外通过自适应学习 γ与 β又让数据保留更多的原始信息。

(4)BN具有一定的正则化效果

在Batch Normalization中,由于我们使用mini-batch的均值与方差作为对整体训练样本均值与方差的估计,尽管每一个batch中的数据都是从总体样本中抽样得到,但不同mini-batch的均值与方差会有所不同,这就为网络的学习过程中增加了随机噪音,与Dropout通过关闭神经元给网络训练带来噪音类似,在一定程度上对模型起到了正则化的效果。
另外,原作者通过也证明了网络加入BN后,可以丢弃Dropout,模型也同样具有很好的泛化效果。

3.BN的操作阶段

【BN添加在激活层之前】

4.BN可以防止梯度消失吗

BN可以防止学习过程中梯度消失,这一点论文中有阐述,作者说可以如果使用sigmod激活函数的时候,如果不用BN的话,会让反向传播的过程中梯度消失(当输出值较大或较小时,sigmod函数就会进入饱和区域,导致其导数几乎为零),但是可以通过使用Relu激活函数来解决,那就意味着BN主要还是让数据分布变为一致。

5.为什么归一化后还要放缩和平移

(1)减均值除方差得到的分布是正态分布,我们能否认为正态分布就是最好或最能体现我们训练样本的特征分布呢?
答: 非也,如果激活函数在方差为1的数据上,没有表现最好的效果,比如Sigmoid激活函数。这个函数在-1~1之间的梯度变化不大。假如某一层学习到特征数据本身就分布在S型激活函数的两侧,把它归一化处理、标准差也限制在了1,把数据变换成分布于s函数的中间部分,就没有达到非线性变换的目的,换言之,减均值除方差操作后可能会削弱网络的性能。
因此,必须进行一些转换才能将分布从0移开。使用缩放因子γ和移位因子β来执行此操作。随着训练的进行,这些γ和β也通过反向传播学习以提高准确性。这就要求为每一层学习2个额外的参数来提高训练速度。这个最终转换因此完成了批归一算法的定义。缩放和移位是算法比较关键,因为它提供了更多的灵活性。假设如果我们决定不使用BatchNorm,我们可以设置γ=σ和β= mean,从而返回原始值。
PS:γ和β也是待学习的参数,在网络学习的过程中会被更新

6.BN LN IN GN SN

Batch Normalization:
1.BN的计算就是把每个通道的NHW单独拿出来归一化处理,而保留通道 C 的维度。
2.针对每个channel我们都有一组γ,β,所以可学习的参数为2*C
3.受batchsize 影响,当batch size越小,BN的表现效果也越不好,因为计算过程中所得到的均值和方差不能代表全局

Layer Normalizaiton:
1.LN的计算就是把每个CHW单独拿出来归一化处理,不受batchsize 的影响,受C影响
2.常用在RNN网络,但如果输入的特征区别很大,那么就不建议使用它做归一化处理
Instance Normalization
1.IN的计算就是把每个HW单独拿出来归一化处理,不受通道和batchsize 的影响
2.常用在风格化迁移,但如果特征图可以用到通道之间的相关性,那么就不建议使用它做归一化处理

Group Normalizatio
1.GN的计算就是把先把通道C分成G组,然后把每个gHW单独拿出来归一化处理,最后把G组归一化之后的数据合并成CHW
2.GN介于LN和IN之间,当然可以说LN和IN就是GN的特列,比如G的大小为1或者为C

Switchable Normalization
1.将 BN、LN、IN 结合,赋予权重,让网络自己去学习归一化层应该使用什么方法
2.集万千宠爱于一身,但训练复杂

九.卷积神经网络优缺点

优点
• 局部感知的共享卷积核(共享参数),对高维数据的处理没有压力
• 无需选择特征属性,只要训练好权重,即可得到特征值
• 深层次的网络抽取图像信息比较丰富,表达效果好

缺点
• 需要调参,需要大量样本,训练迭代次数比较多,最好使用GPU训练
• 物理含义不明确,从每层输出中很难看出含义来

十.Loss介绍

导读:对于图像分割,通过模型的优化提高准确率一直是大家推进的重点,目标损失函数作为算法求解重要的一部分,在帮助模型快速收敛方面发挥着重要的作用。本文全面的盘点了图像分割中常用的4类损失函数,文内还附有代码,推荐大家收藏~

这是一篇关于图像分割损失函数的总结,具体包括:
Binary Cross Entropy
Weighted Cross Entropy
Balanced Cross Entropy
Dice Loss
Focal loss
Tversky loss
Focal Tversky loss
log-cosh dice loss (本文提出的新损失函数)
论文地址:
https://arxiv.org/pdf/2006.14822.pdf
代码地址:
https://github.com/shruti-jadon/Semantic-Segmentation-Loss-Functions
项目推荐:
https://github.com/JunMa11/SegL
图像分割一直是一个活跃的研究领域,因为它有可能修复医疗领域的漏洞,并帮助大众。在过去的5年里,各种论文提出了不同的目标损失函数,用于不同的情况下,如偏差数据,稀疏分割等。在本文中,总结了大多数广泛用于图像分割的损失函数,并列出了它们可以帮助模型更快速、更好的收敛模型的情况。此外,本文还介绍了一种新的log-cosh dice损失函数,并将其在NBFS skull-stripping数据集上与广泛使用的损失函数进行了性能比较。某些损失函数在所有数据集上都表现良好,在未知分布数据集上可以作为一个很好的选择。
1 简介
深度学习彻底改变了从软件到制造业的各个行业。深度学习在医学界的应用也十分广泛,例如使用U-Net进行肿瘤分割、使用SegNet进行癌症检测等。在这些应用中,图像分割是至关重要的,分割后的图像除了告诉我们存在某种疾病外,还展示了它到底存在于何处,这为实现自动检测CT扫描中的病变等功能提供基础保障。
图像分割可以定义为像素级别的分类任务。图像由各种像素组成,这些像素组合在一起定义了图像中的不同元素,因此将这些像素分类为一类元素的方法称为语义图像分割。在设计基于复杂图像分割的深度学习架构时,通常会遇到了一个至关重要的选择,即选择哪个损失/目标函数,因为它们会激发算法的学习过程。损失函数的选择对于任何架构学习正确的目标都是至关重要的,因此自2012年以来,各种研究人员开始设计针对特定领域的损失函数,以为其数据集获得更好的结果。

在本文中,总结了15种基于图像分割的损失函数。被证明可以在不同领域提供最新技术成果。
这些损失函数可大致分为4类:
基于分布的损失函数,( Distribution-based)
基于区域的损失函数,(Region-based)
基于边界的损失函数,(Boundary-based)
基于复合的损失函数(Compounded_based)。

本文还讨论了确定哪种目标/损失函数在场景中可能有用的条件。除此之外,还提出了一种新的log-cosh dice损失函数用于图像语义分割。为了展示其效率,还比较了NBFS头骨剥离数据集上所有损失函数的性能。
2 Distribution-based loss
2-1. Binary Cross-Entropy二进制交叉熵损失函数
交叉熵定义为对给定随机变量或事件集的两个概率分布之间的差异的度量。它被广泛用于分类任务,并且由于分割是像素级分类,因此效果很好。在多分类任务中,经常采用 softmax 激活函数+交叉熵损失函数,因为交叉熵描述了两个概率分布的差异,然而神经网络输出的是向量,并不是概率分布的形式。所以需要 softmax激活函数将一个向量进行“归一化”成概率分布的形式,再采用交叉熵损失函数计算 loss。
交叉熵损失函数的具体表达为:

交叉熵损失函数可以用在大多数语义分割场景中,但它有一个明显的缺点:当图像分割任务只需要分割前景和背景两种情况。当前景像素的数量远远小于背景像素的数量时,y=0的数量远大于y=1的数量,损失函数y=0的成分就会占据主导,使得模型严重偏向背景,导致效果不好。
#二值交叉熵,这里输入要经过sigmoid处理

import torch  
import torch.nn as nn  
import torch.nn.functional as F  
nn.BCELoss\(F.sigmoid\(input\), target\)

#多分类交叉熵, 用这个 loss 前面不需要加Softmax层nn.CrossEntropyLoss(input, target)
2-2. Weighted Binary Cross-Entropy加权交叉熵损失函数

[公式]
加权交叉嫡损失函数只是在交叉嫡Loss的基础上为每一个类别添加了一个权重参数为正样本加权。设置B>1 , 减少假阴性; 设置 B<1, 减少假阳性。这样相比于原始的交叉嫡Loss, 在样本数量不均衡的情况下可以获得更好的效果。

class WeightedCrossEntropyLoss\(torch.nn.CrossEntropyLoss\):  
   """  
   Network has to have NO NONLINEARITY\!  
   """  
   def \_\_init\_\_\(self, weight=None\):  
       super\(WeightedCrossEntropyLoss, self\).\_\_init\_\_\(\)  
       self.weight = weight  
  
   def forward\(self, inp, target\):  
       target = target.long\(\)  
       num\_classes = inp.size\(\)\[1\]  
  
       i0 = 1  
       i1 = 2  
  
       while i1 \< len\(inp.shape\): # this is ugly but torch only allows to transpose two axes at once  
           inp = inp.transpose\(i0, i1\)  
           i0 += 1  
           i1 += 1  
  
       inp = inp.contiguous\(\)  
       inp = inp.view\(-1, num\_classes\)  
  
       target = target.view\(-1,\)  
       wce\_loss = torch.nn.CrossEntropyLoss\(weight=self.weight\)  
  
       return wce\_loss\(inp, target\)

2-3. Balanced Cross-Entropy平衡交叉熵损失函数

与加权交叉熵损失函数类似,但平衡交叉熵损失函数对负样本也进行加权
2-4. Focal Loss

Focal loss是在目标检测领域提出来的。其目的是关注难例(也就是给难分类的样本较大的权重)。对于正样本,使预测概率大的样本(简单样本)得到的loss变小,而预测概率小的样本(难例)loss变得大,从而加强对难例的关注度。但引入了额外参数,增加了调参难度。

class FocalLoss(nn.Module):  
   """  
   copy from: https://github.com/Hsuxu/Loss_ToolBox-PyTorch/blob/master/FocalLoss/FocalLoss.py  
   This is a implementation of Focal Loss with smooth label cross entropy supported which is proposed in  
   'Focal Loss for Dense Object Detection. (https://arxiv.org/abs/1708.02002)'  
       Focal_Loss= -1*alpha*(1-pt)*log(pt)  
   :param num_class:  
   :param alpha: (tensor) 3D or 4D the scalar factor for this criterion  
   :param gamma: (float,double) gamma > 0 reduces the relative loss for well-classified examples (p>0.5) putting more  
                   focus on hard misclassified example  
   :param smooth: (float,double) smooth value when cross entropy  
   :param balance_index: (int) balance class index, should be specific when alpha is float  
   :param size_average: (bool, optional) By default, the losses are averaged over each loss element in the batch.  
   """  
  
   def __init__(self, apply_nonlin=None, alpha=None, gamma=2, balance_index=0, smooth=1e-5, size_average=True):  
       super(FocalLoss, self).__init__()  
       self.apply_nonlin = apply_nonlin  
       self.alpha = alpha  
       self.gamma = gamma  
       self.balance_index = balance_index  
       self.smooth = smooth  
       self.size_average = size_average  
  
       if self.smooth is not None:  
           if self.smooth < 0 or self.smooth > 1.0:  
               raise ValueError('smooth value should be in [0,1]')  
  
   def forward(self, logit, target):  
       if self.apply_nonlin is not None:  
           logit = self.apply_nonlin(logit)  
       num_class = logit.shape[1]  
  
       if logit.dim() > 2:  
           # N,C,d1,d2 -> N,C,m (m=d1*d2*...)  
           logit = logit.view(logit.size(0), logit.size(1), -1)  
           logit = logit.permute(0, 2, 1).contiguous()  
           logit = logit.view(-1, logit.size(-1))  
       target = torch.squeeze(target, 1)  
       target = target.view(-1, 1)  
       # print(logit.shape, target.shape)  
       #   
       alpha = self.alpha  
  
       if alpha is None:  
           alpha = torch.ones(num_class, 1)  
       elif isinstance(alpha, (list, np.ndarray)):  
           assert len(alpha) == num_class  
           alpha = torch.FloatTensor(alpha).view(num_class, 1)  
           alpha = alpha / alpha.sum()  
       elif isinstance(alpha, float):  
           alpha = torch.ones(num_class, 1)  
           alpha = alpha * (1 - self.alpha)  
           alpha[self.balance_index] = self.alpha  
  
       else:  
           raise TypeError('Not support alpha type')  
         
       if alpha.device != logit.device:  
           alpha = alpha.to(logit.device)  
  
       idx = target.cpu().long()  
  
       one_hot_key = torch.FloatTensor(target.size(0), num_class).zero_()  
       one_hot_key = one_hot_key.scatter_(1, idx, 1)  
       if one_hot_key.device != logit.device:  
           one_hot_key = one_hot_key.to(logit.device)  
  
       if self.smooth:  
           one_hot_key = torch.clamp(  
               one_hot_key, self.smooth/(num_class-1), 1.0 - self.smooth)  
       pt = (one_hot_key * logit).sum(1) + self.smooth  
       logpt = pt.log()  
  
       gamma = self.gamma  
  
       alpha = alpha[idx]  
       alpha = torch.squeeze(alpha)  
       loss = -1 * alpha * torch.pow((1 - pt), gamma) * logpt  
  
       if self.size_average:  
           loss = loss.mean()  
       else:  
           loss = loss.sum()  
       return loss

2-5. Distance map derived loss penalty term距离图得出的损失惩罚项
可以将距离图定义为ground truth与预测图之间的距离(欧几里得距离、绝对距离等)。合并映射的方法有2种,一种是创建神经网络架构,在该算法中有一个用于分割的重建head,或者将其引入损失函数。遵循相同的理论,可以从GT mask得出的距离图,并创建了一个基于惩罚的自定义损失函数。使用这种方法,可以很容易地将网络引导到难以分割的边界区域。损失函数定义为:

class DisPenalizedCE(torch.nn.Module):  
   """  
   Only for binary 3D segmentation  
   Network has to have NO NONLINEARITY!  
   """  
  
   def forward(self, inp, target):  
       # print(inp.shape, target.shape) # (batch, 2, xyz), (batch, 2, xyz)  
       # compute distance map of ground truth  
       with torch.no_grad():  
           dist = compute_edts_forPenalizedLoss(target.cpu().numpy()>0.5) + 1.0  
         
       dist = torch.from_numpy(dist)  
       if dist.device != inp.device:  
           dist = dist.to(inp.device).type(torch.float32)  
       dist = dist.view(-1,)  
  
       target = target.long()  
       num_classes = inp.size()[1]  
  
       i0 = 1  
       i1 = 2  
  
       while i1 < len(inp.shape): # this is ugly but torch only allows to transpose two axes at once  
           inp = inp.transpose(i0, i1)  
           i0 += 1  
           i1 += 1  
  
       inp = inp.contiguous()  
       inp = inp.view(-1, num_classes)  
       log_sm = torch.nn.LogSoftmax(dim=1)  
       inp_logs = log_sm(inp)  
  
       target = target.view(-1,)  
       # loss = nll_loss(inp_logs, target)  
       loss = -inp_logs[range(target.shape[0]), target]  
       # print(loss.type(), dist.type())  
       weighted_loss = loss*dist  
  
       return loss.mean()

3 Region-based loss
3-1. Dice Loss
Dice系数是计算机视觉界广泛使用的度量标准,用于计算两个图像之间的相似度。在2016年的时候,它也被改编为损失函数,称为Dice损失。
Dice系数:是用来度量集合相似度的度量函数,通常用于计算两个样本之间的像素之间的相似度,公式如下:

[公式]
分子中之所以有一个系数2是因为分母中有重复计x和y的原因,取值范围是[0.1]。而针对分割任务来说x表示的就是Ground Truth分割图像,而y代表的就是预测的分割图像。
Dice Loss:

[公式]
此处,在分子和分母中添加1以确保函数在诸如 的极端情况下的确定性。Dice Loss使用与样本极度不均衡的情况,如果一般情况下使用Dice Loss会回反向传播有不利的影响,使得训练不稳定。

def get_tp_fp_fn(net_output, gt, axes=None, mask=None, square=False):  
   """  
   net_output must be (b, c, x, y(, z)))  
   gt must be a label map (shape (b, 1, x, y(, z)) OR shape (b, x, y(, z))) or one hot encoding (b, c, x, y(, z))  
   if mask is provided it must have shape (b, 1, x, y(, z)))  
   :param net_output:  
   :param gt:  
   :param axes:  
   :param mask: mask must be 1 for valid pixels and 0 for invalid pixels  
   :param square: if True then fp, tp and fn will be squared before summation  
   :return:  
   """  
   if axes is None:  
       axes = tuple(range(2, len(net_output.size())))  
  
   shp_x = net_output.shape  
   shp_y = gt.shape  
  
   with torch.no_grad():  
       if len(shp_x) != len(shp_y):  
           gt = gt.view((shp_y[0], 1, *shp_y[1:]))  
  
       if all([i == j for i, j in zip(net_output.shape, gt.shape)]):  
           # if this is the case then gt is probably already a one hot encoding  
           y_onehot = gt  
       else:  
           gt = gt.long()  
           y_onehot = torch.zeros(shp_x)  
           if net_output.device.type == "cuda":  
               y_onehot = y_onehot.cuda(net_output.device.index)  
           y_onehot.scatter_(1, gt, 1)  
  
   tp = net_output * y_onehot  
   fp = net_output * (1 - y_onehot)  
   fn = (1 - net_output) * y_onehot  
  
   if mask is not None:  
       tp = torch.stack(tuple(x_i * mask[:, 0] for x_i in torch.unbind(tp, dim=1)), dim=1)  
       fp = torch.stack(tuple(x_i * mask[:, 0] for x_i in torch.unbind(fp, dim=1)), dim=1)  
       fn = torch.stack(tuple(x_i * mask[:, 0] for x_i in torch.unbind(fn, dim=1)), dim=1)  
  
   if square:  
       tp = tp ** 2  
       fp = fp ** 2  
       fn = fn ** 2  
  
   tp = sum_tensor(tp, axes, keepdim=False)  
   fp = sum_tensor(fp, axes, keepdim=False)  
   fn = sum_tensor(fn, axes, keepdim=False)  
  
   return tp, fp, fn  
  
  
class SoftDiceLoss(nn.Module):  
   def __init__(self, apply_nonlin=None, batch_dice=False, do_bg=True, smooth=1.,  
                square=False):  
       """  
       paper: https://arxiv.org/pdf/1606.04797.pdf  
       """  
       super(SoftDiceLoss, self).__init__()  
  
       self.square = square  
       self.do_bg = do_bg  
       self.batch_dice = batch_dice  
       self.apply_nonlin = apply_nonlin  
       self.smooth = smooth  
  
   def forward(self, x, y, loss_mask=None):  
       shp_x = x.shape  
  
       if self.batch_dice:  
           axes = [0] + list(range(2, len(shp_x)))  
       else:  
           axes = list(range(2, len(shp_x)))  
  
       if self.apply_nonlin is not None:  
           x = self.apply_nonlin(x)  
  
       tp, fp, fn = get_tp_fp_fn(x, y, axes, loss_mask, self.square)  
  
       dc = (2 * tp + self.smooth) / (2 * tp + fp + fn + self.smooth)  
  
       if not self.do_bg:  
           if self.batch_dice:  
               dc = dc[1:]  
           else:  
               dc = dc[:, 1:]  
       dc = dc.mean()  
  
       return -dc

医学图像分割模型常用Loss
图像分割模型调优技巧和损失函数

5.交叉熵损失函数 CrossEntropyLoss

Pytorch中CrossEntropyLoss()函数的主要是将softmax-log-NLLLoss合并到一块得到的结果。

1.NLLLOSS
nllloss对两个向量的操作为,继续将predict中的向量,在label中对应的index取出,并取负号输出。label中为1,则取2,3,1中的第1位3,label第二位为2,则取出3,7,9的第2位9,将两数取平均后加负号后输出。
这时就可以看到最开始的nllloss初始化的时候,如果参数reduction取’mean’,就是上述结果。如果reduction取’sum’,那么各行取出对应的结果,就是取sum后输出,如下所示:

nllloss = nn.NLLLoss( reduction='sum')
predict = torch.Tensor([[2, 3, 1],
                        [3, 7, 9]])
label = torch.tensor([1, 2])
nllloss(predict, label)
#output: tensor(-12)

2.CrossEntropyLoss
torch.nn.CrossEntropyLoss相当于softmax + log + nllloss。

4. BCE Loss BCEWithLogitsLoss

BCE Loss 和BCEWithLogitsLoss
BCEWithLogitsLoss 就是把sigmoid-BCELOSS合成一步
1.BCE LOSS 二分类交叉熵损失
在图片多标签分类时,如果3张图片分3类,会输出一个3*3的矩阵。

先用Sigmoid给这些值都搞到0~1之间:

假设Target是:

下面我们用BCELoss来验证一下Loss是不是0.7194!

emmm应该是我上面每次都保留4位小数,算到最后误差越来越大差了0.0001。不过也很厉害啦哈哈哈哈哈!

2.BCEWithLogitsLoss

3、clDice Loss

ClDice 介绍
准确分割管状、网络状结构,如血管、神经元或道路,与许多研究领域有关。对于此类结构,拓扑结构是其最重要的特征,例如,保持连通性:对于血管网络,缺少连接的血管会完全改变血流动力学。
我们引入了一种新的相似性度量clDice,它是根据分割模板与其(形态学)骨架的交集计算的。关键的是,我们从理论上证明了clDice保证了二进制2D和3D分割的拓扑正确性。在此基础上,我们提出了一种计算效率高、可微的软clDice作为训练任意神经分割网络的损失函数。我们在四个公共数据集(2D和3D)上测试了用于分割的软clDice损失。在软clDice上进行训练可以获得更准确的连通性信息、更高的图形相似度和更好的体积分数。
一、概述
先上论文链接:clDice - a Novel Topology-Preserving Loss Function for Tubular Structure Segmentation.

对文章结构做一个整体的梳理:

Purpose:提高管状结构分割结果的拓扑连通性。
Methods:提出了clDice作为拓扑相似性测度,并提出soft-clDice作为损失函数,其原理为计算分割结果与骨架的相交占比。为了将soft-clDice用以模型训练,使用soft skeleton的方法得到软骨架,再计算clDice,这样就可以实现Loss函数的可微。
Results:实验在视网膜数据集、道路数据集、神经元数据集等数据集上进行了验证,soft-clDice具有良好的效果。
Conclusion:提出的soft-clDice对于血管、道路、组织等连续管状结构的分割具有良好的效果,提高了分割结果的拓扑连通性。
二、任务背景
管状和曲线结构的分割是许多领域的一个基本问题,例如临床和生物应用(从显微镜、光声或放射学图像分割血管和神经元)、遥感应用(从卫星图像分割道路网络)和工业质量控制等等。在上述领域中,拓扑上精确的分割是保证下游任务无差错的必要条件,如计算血流动力学、路线规划、阿尔茨海默病预测或中风建模。

为了评估管状结构中的分割效果,传统的基于体积的性能指标(如Dice)是次优的。在像网络拓扑提取这样的任务中,保证拓扑结构的完整性具有更大的价值。

为此,作者提出了两个问题:
1、在保证网络拓扑结构保持不变的情况下,对于管状结构和曲线结构的分割算法,什么是一个很好的像素衡量标准?
2、是否能用这个标准作为神经网络的损失函数?

三、方法介绍
1、整体框架
作者提出的方法如下图所示:
(1)首先对原图使用分割网络得到粗略分割概率图;
(2)再分别对实际结果(Actual Mask)和预测结果(Predicted Mask)提取软骨架;
(3)最后根据Soft-clDice的计算公式计算soft-clDice,与soft-Dice结合得到损失函数,进行模型训练。

2、clDice计算过程
其中clDice的计算过程如下:

当FP增多时,T p r e c TprecTprec对应下降;当FN增多时,T s e n s TsensTsens对应下降。由此可见,T p r e c TprecTprec对应了拓扑学上的Precision,而T s e n s TsensTsens则对应了拓扑学上的Sensitivity。

最后定义c l D i c e clDiceclDice为T p r e c TprecTprec和T s e n s TsensTsens的F1-score:

3、soft-clDice和模型训练
为了将clDice用以模型训练的Loss函数,文章提出了soft-clDice和soft-skeletonize。

文章使用最小和最大过滤器用作形态膨胀和侵蚀的灰度替代,提出了“软骨架化”(soft-skeletonize)。soft-skeletonize中涉及的迭代过程如下图所示。计算中涉及的超参数k表示迭代次数,并且必须大于或等于观察到的最大半径。

soft-skeletonize算法 soft-skeletonize 具体步骤和代码和soft-clDice的计算过程如下图所示,总体来说先腐蚀(minpool)再膨胀(maxpool),再用原骨架减去该结果,如此不断迭代k次得到软化骨架,然后再根据公式计算soft-clDice。

最后将soft-Dice和soft-clDice的加权和作为Loss函数:

四、实验结果

文章分别使用5个公共数据集来验证clDice和soft-clDice作为度量和目标函数的效果。在2D中,使用了DRIVE视网膜数据集、马萨诸塞州道路数据集和CREMI神经元数据集进行评估。在3D中,使用具有附加高斯噪声项的合成血管数据集和脑血管多通道容积扫描的Vessap数据集进行了评估。实验的可视化结果如下图所示:

五、总结
文章介绍了一种用于管状结构分割的拓扑保持相似性度量clDice,并在损失函数中使用clDice的可微版本soft-clDice来训练2D和3D神经网络。文章使用clDice以及其他基于体积、拓扑和图形的度量来测试分割质量,实验发现在soft-clDice上进行训练可以获得更精确的连通性信息、更好的图相似性、更好的Euler特征以及更好的Dice和准确性。除此之外,clDice可以应用到其他基于深度学习的分割任务中,如生物医学成像中的神经元分割、工业质量控制中的裂纹检测或遥感分割等任务。

六、代码

import torch
import torch.nn as nn
import torch.nn.functional as F


def soft_erode(img):
    torch.cuda.synchronize(img.device)
    if len(img.shape) == 4:
        p1 = -F.max_pool2d(-img, (3, 1), (1, 1), (1, 0))
        p2 = -F.max_pool2d(-img, (1, 3), (1, 1), (0, 1))
        torch.cuda.synchronize(img.device)
        ret = torch.min(p1, p2)
        del p1, p2
        return ret
    elif len(img.shape) == 5:
        p1 = -F.max_pool3d(-img, (3, 1, 1), (1, 1, 1), (1, 0, 0))
        p2 = -F.max_pool3d(-img, (1, 3, 1), (1, 1, 1), (0, 1, 0))
        p3 = -F.max_pool3d(-img, (1, 1, 3), (1, 1, 1), (0, 0, 1))
        torch.cuda.synchronize(img.device)
        ret = torch.min(torch.min(p1, p2), p3)
        del p1, p2, p3
        return ret


def soft_dilate(img):
    torch.cuda.synchronize(img.device)
    if len(img.shape) == 4:
        return F.max_pool2d(img, (3, 3), (1, 1), (1, 1))
    elif len(img.shape) == 5:
        return F.max_pool3d(img, (3, 3, 3), (1, 1, 1), (1, 1, 1))


def soft_open(img):
    return soft_dilate(soft_erode(img))


def soft_skel(img, iter_):
    img1 = soft_open(img)
    skel = F.relu(img - img1)
    for j in range(iter_):
        img = soft_erode(img)
        img1 = soft_open(img)
        delta = F.relu(img - img1)
        skel = skel + F.relu(delta - skel * delta)
        del img1, delta
    return skel


def soft_cldice(y_true, y_pred, iter=2, smooth=1.0):
    skel_pred = soft_skel(y_pred, iter)
    tprec = (torch.sum(torch.mul(skel_pred, y_true)) + smooth) / (torch.sum(skel_pred) + smooth)
    skel_true = soft_skel(y_true, iter)
    tsens = (torch.sum(torch.mul(skel_true, y_pred)) + smooth) / (torch.sum(skel_true) + smooth)
    cl_dice = 2.0 * (tprec * tsens) / (tprec + tsens)
    return cl_dice
    
def dice(self,y_true, y_pred):
    smooth = 0.0001  # 防止0除
    y_pred = torch.sigmoid(y_pred)
    intersection =torch.sum(torch.mul(y_pred , y_true) )
    return (2. * intersection + smooth) / (torch.sum(y_pred) + torch.sum(y_true)+ smooth)

2、Dice Loss

dice Loss 介绍

Dice Loss 最先是在VNet 这篇文章中被提出,后来被广泛的应用在了医学影像分割之中。
1、Dice系数与Dice Loss
Dice系数是一种集合相似度度量函数,通常用于计算两个样本的相似度,取值范围在[0,1]:

其中 |X∩Y| 是X和Y之间的交集,|X|和|Y|分表表示X和Y的元素的个数,其中,分子的系数为2,是因为分母存在重复计算X和Y之间的共同元素的原因。
Dice Loss:

Laplace smoothing:
Laplace smoothing 是一个可选改动,即将分子分母全部加 1:

带来的好处:
(1)避免当|X|和|Y|都为0时,分子被0除的问题
(2)减少过拟合
2、Dice 系数计算
首先将 |X∩Y| 近似为预测图pred和label GT 之间的点乘,并将点乘的元素的结果相加:
(1)预测分割图与 GT 分割图的点乘:

(2)逐元素相乘的结果元素的相加和:

对于二分类问题,GT分割图是只有0,1两个值的,因此 |X∩Y| 可以有效的将在 Pred 分割图中未在 GT 分割图中激活的所有像素清零. 对于激活的像素,主要是惩罚低置信度的预测,较高值会得到更好的 Dice 系数.
(3)计算|X|和|Y|,这里可以采用直接元素相加,也可以采用元素平方求和的方法:

python 代码实现
def dice(self,y_true, y_pred):
    smooth = 0.0001  # 防止0除
    y_pred = torch.sigmoid(y_pred)
    intersection =torch.sum(torch.mul(y_pred , y_true) )
    return (2. * intersection + smooth) / (torch.sum(y_pred) + torch.sum(y_true)+ smooth)

3、Dice Loss VS CE
语义分割中一般用交叉熵来做损失函数,而评价的时候却使用IOU来作为评价指标,(GIOU这篇文章中说道:给定优化指标本身与代理损失函数之间的选择,最优选择就是指标本身。)为什么不直接拿类似IOU的损失函数来进行优化呢?
(1)首先采用交叉熵损失函数,而非 dice-coefficient 和类似 IoU 度量的损失函数,一个令人信服的愿意是其梯度形式更优:
交叉熵损失函数中交叉熵值关于 logits 的梯度计算形式类似于p−t,其中,p是 softmax 输出;t为 target;而关于 dice-coefficient 的可微形式,loss 值为 2pt/(p2+t2) 或 2pt/(p+t),其关于p的梯度形式是比较复杂的:2t2/(p+t)2 或 2t*(t2−p2)/(p2+t2)^2. 极端场景下,当p和t的值都非常小时,计算得到的梯度值可能会非常大. 通常情况下,可能导致训练更加不稳定.
(2) 直接采用 dice-coefficient 或者 IoU 作为损失函数的原因,是因为分割的真实目标就是最大化 dice-coefficient 和 IoU 度量. 而交叉熵仅是一种代理形式,利用其在 BP 中易于最大化优化的特点.
Dice Loss 存在的问题:
(1)训练误差曲线非常混乱,很难看出关于收敛的信息。尽管可以检查在验证集上的误差来避开此问题。

4、Log-Cosh Dice Loss
Dice系数是一种用于评估分割输出的度量标准。它也已修改为损失函数,因为它可以实现分割目标的数学表示。但是由于其非凸性,它多次都无法获得最佳结果。Lovsz-softmax损失旨在通过添加使用Lovsz扩展的平滑来解决非凸损失函数的问题。同时,Log-Cosh方法已广泛用于基于回归的问题中,以平滑曲线。

将Cosh(x)函数和Log(x)函数合并,可以得到Log-Cosh Dice Loss:

def dice(self,y_true, y_pred):
smooth = 0.0001 # 防止0除
y_pred = torch.sigmoid(y_pred)
intersection =torch.sum(torch.mul(y_pred , y_true) )
return (2. * intersection + smooth) / (torch.sum(y_pred) + torch.sum(y_true)+ smooth)

dice_Loss =1.0 - self.dice(y_true, y_pred)
LogCoshDiceLoss=torch.log((torch.exp(dice_Loss) + torch.exp(-dice_Loss)) / 2.0)

1、Focal Loss摘要

简介
Focal Loss目标是解决样本类别不平衡以及样本分类难度不平衡等问题,如目标检测中大量简单的background,很少量较难的foreground样本。
Focal Loss通过修改交叉熵函数,通过增加类别权重α 和 样本难度权重调因子(modulating factor)(1−pt)γ,来减缓上述问题,提升模型精确。
该损失函数降低了大量简单负样本在训练中所占的权重,也可理解为一种困难样本挖掘。
###1 技术背景
我们知道object detection的算法主要可以分为两大类:two-stage detector和one-stage detector。前者是指类似Faster RCNN,RFCN这样需要region proposal的检测算法,这类算法可以达到很高的准确率,但是速度较慢。虽然可以通过减少proposal的数量或降低输入图像的分辨率等方式达到提速,但是速度并没有质的提升。后者是指类似YOLO,SSD这样不需要region proposal,直接回归的检测算法,这类算法速度很快,但是准确率不如前者。作者提出focal loss的出发点也是希望one-stage detector可以达到two-stage detector的准确率,同时不影响原有的速度。

2 拟解决问题

作者认为one-stage detector的准确率不如two-stage detector的原因是:样本不均衡问题,其中包括两个方面:
1.解决样本的类别不平衡问题
2.解决简单/困难样本不平衡问题
大量loss小的简单样本相加,可以淹没稀有类.
如在object detection领域,一张图像可能生成成千上万的candidate locations,但是其中只有很少一部分是包含object的(1:1000)。
这就带来了类别不均衡。那么类别不均衡会带来什么后果呢?引用原文讲的两个后果:
(1) training is inefficient as most locations are easy negatives that contribute no useful learning signal;
(2) en masse, the easy negatives can overwhelm training and lead to degenerate models.
负样本数量太大,占总的loss的大部分,而且多是容易分类的,因此使得模型的优化方向并不是我们所希望的那样。

3.解决方案

为了解决(1)解决样本的类别不平衡问题和(2)解决简单/困难样本不平衡问题,
作者提出一种新的损失函数:focal loss。这个损失函数是在标准交叉熵损失基础上改进得到,

其中,−log(pt) 为初始交叉熵损失函数,α 为类别间(0-1二分类)的权重参数,(1−pt)γ 为简单/困难样本调节因子(modulating factor),而γ 则聚焦参数(focusing parameter)。

3_1、形成过程:
(1)初始二分类的交叉熵(Cross Emtropy, CE)函数:

在上面的y∈{±1} 为指定的ground-truth类别,p∈[0,1] 是模型对带有 y=1 标签类别的概率估计。为了方便,我们将pt定义为:

和重写的CE(p,y):

3_2、平衡交叉熵(Balanced Cross Entropy):
一个普遍解决类别不平衡的方法是增加权重参数α∈[0,1],当$ y=1 类的权重为\alpha$ ,y=−1 类的权重为1−α 。在实验中,α 被设成逆类别频率(inverse class frequence),αt定义与pt一样:

因此,α−balanced 的CE损失函数为:

3_3、聚焦损失(Focal Loss):
尽管α能平衡positive/negative的重要性,但是无法区分简单easy/困难hard样本。为此,对于简单的样本增加一个小的权重(down-weighted),让损失函数聚焦在困难样本的训练。
因此,在交叉熵损失函数增加调节因子(1−pt)γ ,和可调节聚参数γ≥0。,所以损失函数变成:

当pt→0时,同时调节因子也 (1−pt)γ→0 ,因此简单样本的权重越小。直观地讲,调节因子减少了简单示例的loss贡献,并扩展了样本接收低loss的范围。 例如,在γ= 2的情况下,与CE相比,分类为pt = 0.9的示例的损失将降低100倍,而对于pt≈0.968的示例,其损失将降低1000倍。 这反过来增加了纠正错误分类示例的重要性(对于pt≤0.5和γ= 2,其损失最多缩小4倍)。

3_4、最终的损失函数Focal Loss形式:

根据论文作者实验,α=0.25 和 γ=2 效果最好

4、解决方案__

4_1.首先回顾二分类交叉上损失:
是经过激活函数的输出,所以在0-1之间。可见普通的交叉熵对于正样本而言,输出概率越大损失越小。对于负样本而言,输出概率越小则损失越小。此时的损失函数在大量简单样本的迭代过程中比较缓慢且可能无法优化至最优。那么Focal loss是怎么改进的呢?

4_2.增加gamma
首先在原有的基础上加了一个因子,其中gamma>0使得减少易分类样本的损失。使得更关注于困难的、错分的样本。
例如gamma为2,对于正类样本而言,预测结果为0.95肯定是简单样本,所以(1-0.95)的gamma次方就会很小,这时损失函数值就变得更小。而预测概率为0.3的样本其损失相对很大。对于负类样本而言同样,预测0.1的结果应当远比预测0.7的样本损失值要小得多。对于预测概率为0.5时,损失只减少了0.25倍,所以更加关注于这种难以区分的样本。这样减少了简单样本的影响,大量预测概率很小的样本叠加起来后的效应才可能比较有效。
4_3.加入平衡因子alpha
加入平衡因子alpha,用来平衡正负样本本身的比例不均:文中alpha取0.25,即正样本要比负样本占比小,这是因为负例易分。

只添加alpha虽然可以平衡正负样本的重要性,但是无法解决简单与困难样本的问题。
gamma调节简单样本权重降低的速率,当gamma为0时即为交叉熵损失函数,当gamma增加时,调整因子的影响也在增加。实验发现gamma为2是最优。

4_4. 总结
作者认为one-stage和two-stage的表现差异主要原因是大量前景背景类别不平衡导致。作者设计了一个简单密集型网络RetinaNet来训练在保证速度的同时达到了精度最优。在双阶段算法中,在候选框阶段,通过得分和nms筛选过滤掉了大量的负样本,然后在分类回归阶段又固定了正负样本比例,或者通过OHEM在线困难挖掘使得前景和背景相对平衡。而one-stage阶段需要产生约100k的候选位置,虽然有类似的采样,但是训练仍然被大量负样本所主导。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

德卡先生的信箱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值