简单涨点 | Flow-Mixup: 对含有损坏标签的多标签医学图像进行分类(优于Mixup和Maniflod Mixup)

共 35117字,需浏览 71分钟

 ·

2021-02-20 13:54

点击上方【AI人工智能初学者】,选择【星标】公众号

期待您我的相遇与进步

本文提出一种用于多标签医学图像分类的新正则化方法:FlowMixup,涨点明显!性能优于Mixup、ERM等方法。
作者单位:浙江大学, 圣母大学

1 、简介

在临床实践中,医学图像解释通常涉及多标签分类,因为患者的犯病的地方往往会出现多种症状或并发症。最近,在医学图像解释上基于深度学习的框架已达到专家级的性能,这其中部分功劳可以归功于大量准确的注释或者说标注。但是,手动注释大量医学图像是不切实际的,而自动注释快速但不精确(可能引入损坏的标签)。

在这项工作中,作者提出了一种新的正则化方法,称为Flow-Mixup,用于带有损坏标签的多标签医学图像分类。Flow-Mixup指导模型捕获每种异常的鲁棒特征,从而帮助有效处理损坏的标签,并可以应用自动注释。

具体地说,Flow-Mixup通过向模型的隐藏状态添加约束来解耦提取的特征。而且,与其他已知的正则化方法相比,Flow-Mixup更稳定,更有效,理论和经验分析所示。对两个心电图数据集和包含损坏标签的胸部X射线数据集进行的实验验证了Flow-Mixup是有效的,并且对损坏的标签不敏感。

这项工作的3个主要贡献:

  • 1、提出了一种用于多标签医学图像分类的正则化方法Flow-Mixup,并证明了Flow-Mixup对已损坏标签的鲁棒性;
  • 2、对MixupManifold-Mixup以及Flow-Mixup进行了比较,表明使用MixupManifold-Mixup会产生“correlation conflicts”现象和“distribution shift”现象。
  • 3、在几个带有损坏标签的多标签医学图像分类数据集上进行了实验,验证了Flow-Mixup优于已知的正则化方法。

2 相关正则化方法

2.1 Mixup

Mixup介绍
Mixup是一种运用在计算机视觉中的对图像进行混类增强的算法,它可以将不同类之间的图像进行混合,从而扩充训练数据集。

Mixup原理
假设 是一个 样本, 是该 样本对应的标签; 是另一个 样本, 是该 样本对应的标签, 是由参数为 的贝塔分布计算出来的混合系数,由此可以得到Mixup原理公式为:

其中 指的是贝塔分布, 是混合后的 样本, 是混合后的 样本对应的标签。

需要说明几点

1.在论文作者多组实验中,无论如何设置 的值,期望 始终近似为0.5。这可能是由于权重 在每个batch样本都会随机产生,在整个训练过程中会有N个batch,权重在N个batch中期望近似为0.5。所以作者认为产生 样本混合权重 的Beta分布参数 时,算法效果相对较好。Beta分布如下图:

2. 没有太多的限制,当 =1时,就是两张图片样本混合;当 >1时,便是2个 图片样本两两对应混合。此外, 可以是同一批样本,也可以是不同批样本。一般在代码实现过程中,两个 图片是同一批样本,唯一不同的是, 是原始 图片样本,而 是对 维度进行shuffle后得到的。

Mixup代码实现

mixup代码实现部分如下:

import numpy as np
import torch
import torch.nn as nn
from loss.focal import FocalLoss
LOSS=FocalLoss()

def criterion(batch_x, batch_y, alpha=1.0, use_cuda=True):
    '''
    batch_x:批样本数,shape=[batch_size,channels,width,height]
    batch_y:批样本标签,shape=[batch_size]
    alpha:生成lam的beta分布参数,一般取0.5效果较好
    use_cuda:是否使用cuda
    
    returns:
     mixed inputs, pairs of targets, and lam
    '''

    
    if alpha > 0:
     #alpha=0.5使得lam有较大概率取0或1附近
        lam = np.random.beta(alpha, alpha)
    else:
        lam = 1
    batch_size = batch_x.size()[0]
    if use_cuda:
        index = torch.randperm(batch_size).cuda()
    else:
        index = torch.randperm(batch_size) #生成打乱的batch_size索引
 
 #获得混合的mixed_batchx数据,可以是同类(同张图片)混合,也可以是异类(不同图片)混合
 mixed_batchx = lam * batch_x + (1 - lam) * batch_x[index, :]
 
 """
 Example:
 假设batch_x.shape=[2,3,112,112],batch_size=2时,
 如果index=[0,1]的话,则可看成mixed_batchx=lam*[[0,1],3,112,112]+(1-lam)*[[0,1],3,112,112]=[[0,1],3,112,112],即为同类混合
 如果index=[1,0]的话,则可看成mixed_batchx=lam*[[0,1],3,112,112]+(1-lam)*[[1,0],3,112,112]=[batch_size,3,112,112],即为异类混合
 """

    batch_ya, batch_yb = batch_y, batch_y[index]
    return mixed_batchx, batch_ya, batch_yb, lam


def mixup_criterion(criterion, inputs, batch_ya, batch_yb, lam):
    return lam * criterion(inputs, batch_ya) + (1 - lam) * criterion(inputs, batch_yb)
##########################################################################

#####################修改位置3:train.py文件修改代码如下######################
if torch.cuda.is_available() and DEVICE.type=="cuda":  #add
 inputs, targets = inputs.cuda(), targets.cuda()   
else:
 inputs = inputs.to(DEVICE)
 targets = targets.to(DEVICE).long()                   
 
if cfg['USE_MIXUP']:
 inputs, targets_a, targets_b, lam = mixup.mixup_data(
  inputs,targets,cfg["MIXUP_ALPHA"], torch.cuda.is_available())  
 
 #映射为Variable
 inputs, targets_a, targets_b = map(Variable, (inputs,targets_a,targets_b))
 #抽取特征,BACKBONE为粗特征抽取网络
 features = BACKBONE(inputs)   
 #抽取特征,HEAD为精细的特征抽取网络
 outputs = mixup.mixup_criterion(HEAD, features, targets_a, targets_b, lam)
 loss = mixup.mixup_criterion(LOSS, outputs, targets_a, targets_b, lam)             
else:
 features = BACKBONE(inputs)  
 outputs = HEAD(features, targets)                     
 loss = FocalLoss(outputs, labels)

2.2 Manifold Mixup

基于Mixup,后续涌现出一些很棒的工作。Manifold mixup通过对Hidden States进行插值,取得了优于Mixup的效果。

Maniflod Mixup的具体流程如下:

  • 1、随机选网络的某一层第k层(包括输入层);
  • 2、传2个Batch的数据给网络,前向传播到第k层,得到隐藏表征hidden reprense ;
  • 3、使用Mixup:
  • 4、继续前向传播直至得到输出;
  • 5、计算损失和梯度:
def mixup_process(out, target_reweighted, lam):
    indices = np.random.permutation(out.size(0))
    out = out*lam + out[indices]*(1-lam)
    target_shuffled_onehot = target_reweighted[indices]
    target_reweighted = target_reweighted * lam + target_shuffled_onehot * (1 - lam)
    
    #t1 = target.data.cpu().numpy()
    #t2 = target[indices].data.cpu().numpy()
    #print (np.sum(t1==t2))
    return out, target_reweighted

class PreActResNet(nn.Module):
    def __init__(self, block, num_blocks, initial_channels, num_classes,  per_img_std= False, stride=1):
        super(PreActResNet, self).__init__()
        self.in_planes = initial_channels
        self.num_classes = num_classes
        self.per_img_std = per_img_std
        #import pdb; pdb.set_trace()
        self.conv1 = nn.Conv2d(3, initial_channels, kernel_size=3, stride=stride, padding=1, bias=False)
        self.layer1 = self._make_layer(block, initial_channels, num_blocks[0], stride=1)
        self.layer2 = self._make_layer(block, initial_channels*2, num_blocks[1], stride=2)
        self.layer3 = self._make_layer(block, initial_channels*4, num_blocks[2], stride=2)
        self.layer4 = self._make_layer(block, initial_channels*8, num_blocks[3], stride=2)
        self.linear = nn.Linear(initial_channels*8*block.expansion, num_classes)

    def _make_layer(self, block, planes, num_blocks, stride):
        strides = [stride] + [1]*(num_blocks-1)
        layers = []
        for stride in strides:
            layers.append(block(self.in_planes, planes, stride))
            self.in_planes = planes * block.expansion
        return nn.Sequential(*layers)

    def compute_h1(self,x):
        out = x
        out = self.conv1(out)
        out = self.layer1(out)
        return out

    def compute_h2(self,x):
        out = x
        out = self.conv1(out)
        out = self.layer1(out)
        out = self.layer2(out)
        return out

    def forward(self, x, target= None, mixup=False, mixup_hidden=False, mixup_alpha=None):
        #import pdb; pdb.set_trace()
        if self.per_img_std:
            x = per_image_standardization(x)
        
        if mixup_hidden:
            layer_mix = random.randint(0,2)
        elif mixup:
            layer_mix = 0
        else:
            layer_mix = None   
        
        out = x
        
        if mixup_alpha is not None:
            lam = get_lambda(mixup_alpha)
            lam = torch.from_numpy(np.array([lam]).astype('float32')).cuda()
            lam = Variable(lam)
        
        if target is not None :
            target_reweighted = to_one_hot(target,self.num_classes)
        
        if layer_mix == 0:
                out, target_reweighted = mixup_process(out, target_reweighted, lam=lam)

        out = self.conv1(out)
        out = self.layer1(out)

        if layer_mix == 1:
            out, target_reweighted = mixup_process(out, target_reweighted, lam=lam)

        out = self.layer2(out)

        if layer_mix == 2:
            out, target_reweighted = mixup_process(out, target_reweighted, lam=lam)

        
        out = self.layer3(out)
        if  layer_mix == 3:
            out, target_reweighted = mixup_process(out, target_reweighted, lam=lam)

        out = self.layer4(out)
        out = F.avg_pool2d(out, 4)
        out = out.view(out.size(0), -1)
        out = self.linear(out)
        
        if target is not None:
            return out, target_reweighted
        else
            return out

然而,由于“correlation conflicts”现象和“distribution shift”现象,这两种方法由于混淆忽略了异常之间的特征相关性,因此都不太适合多标签分类,而Manifold Mixup在训练中往往是不稳定的。因此本文提出了一种用于多标签医学图像分类的Flow-Mixup方法,避免了Mixup和Manifold Mixup的缺点。

3 Flow-Mixup

3.1 Flow-Mixup概述

在本文中提出了一种新的正则化方法Flow-Mixup,用于多标签医学图像的分类。考虑到深度学习分类器 ,其中 是一个非线性函数, 是一个线性函数。采用Flow-Mixup的正向训练过程有以下几个步骤:

首先,在训练前选择隐藏状态s将模型分为非线性部分和线性部分 为模型输出。

其次,将数据(例如图像)转发到选定的隐藏状态,并将一个新的混合模块应用到隐藏状态的特征(混合模块如下图所示)。

最后,经过Mixing module的处理后,该特征继续进行前向传播,直到输出。在Mixing module中,Flow-Mixup将模型的前端部分限制为学习非线性函数,其余部分为线性函数。

在处理多标签医学图像时,非线性函数提取异常特征时,模型的线性函数会将异常特征投影到标签空间。当非线性部分的输出被输入到线性部分时,非线性部分的约束被保持,而线性部分要求其输入位于线性可分空间。不同于Mixup,特殊的Mixing module引入了额外的Flow维度,从而允许在一个模型中同时使用几个Mixing module。

3.2 Mixing Module

在深度学习模型中,图像的张量一般有4个维度:batch维度、channel维度、width和height维度。本文提出的Flow-Mixup引入了一个新的维数,称为Flow维数。如图1左图所示,假设原特征z在经过Mixing Module处理前的Flow维数为1,则Mixing Module的输出 的Flow维数为2。Flow大小通过特征连接操作增加。在特征馈送到Mixing Module后,第1步是复制这些特征。然后,对特征拷贝进行Mixing处理,然后沿着Flow维串接成原始特征。Mixing Module的正向过程定义为:

其中,Mixing操作 将一个特征副本 变换为2个小副本 ,并对其采用标准Mixing,如图1右侧所示:

是通过对 进行随机索引Shuffle得到的。p从beta分布pB(α,α)中随机抽样, 是控制Mixing度的超参数。 表示流向级联,导致Flow size增大。根据式(2),将特征z转换为具有双流量大小的 。由于在前向传播过程中Flow大小增加了1倍,Mixing Module在后向传播过程中应将梯度减半,以保持梯度的大小。混合模的反向传播定义为:

其中 表示原始特征的梯度, 表示混合特征的梯度(如图1所示)。这样,Mixing Module可以同时应用于几种隐藏状态,同时保留原始特征,如图2(b)所示。

注意,正则化方法不能完全限制后续层为线性函数,因此应用几个Mixing Module有助于加强线性约束。在实现中,如果隐藏状态是最后一种状态(图2(b))或只有一种状态(图2(a))应用Mixing Module,则可选择计算原始特征向输出层的前向传播。如果原始特性没有前向传播,Mixing Module则退化为普通的Mixup操作:

在正向传播计算 和在反向传播

4. 方法对比

4.1 与Mixup对比

正如在以前的工作中所讨论的,异常的特征可以是相关的,可能不是线性可分的。换句话说,异常的内在相关性可能与Mixup的线性约束相冲突。因此,用Mixup正则化训练多标签图像分类器可能会导致性能下降。

如图3所示,在将数据流形映射到满足Mixup线性约束的低维空间后,由于2类的边界不能处理这2类的数据,就会发生“Correlation conflicts”现象。与此相反,在Flow-Mixup中,异常的相关特征可以首先被非线性函数解耦为异常特有的特征,这些特征存在于线性可分空间中。

4.2 与Maniflod Mixup对比

Maniflod Mixup允许在训练过程中应用Mixup操作几个隐藏的状态。然而,这种Mixup操作不能同时进行。Maniflod Mixup在每次训练迭代中随机选择其中一种隐藏状态进行Mixing操作,因此存在以下2个缺点:

  • 1、每次迭代的参数更新都会影响最终的参数。因此,很难确切地知道数据混合应用到一个隐藏状态的程度,由于Mixing操作是用概率来融合的。因此,也很难确定Mixing操作的超参数。

  • 2、由于训练条件到隐藏状态(是否使用混合操作)是多变的,因此训练过程是不稳定的,存在“Distribution shift”现象。

Distribution shift”是指客观特征分布发生变化。理想情况下,在隐藏状态上使用Mixing操作将限制特征存在于线性可分空间。然而,Maniflod Mixup不断将约束改变为隐藏状态,导致训练过程不稳定,性能下降。

为了观察模型训练中出现的“Distribution shift”现象,作者比较CIFAR-10训练集上的特征分布,如图所示:

在CIFAR-10的训练集上使用Mixing和不Mixing训练PreAct-ResNet-32模型。然后收集每个残差块的输出和模型输出。为了避免对分类结果的影响,对每个块输出和模型输出的收集特征使用k-means聚类算法(划分为k=10个类)。然后计算 的平均值(类似于方差分析中的 )来观察特征分布。 ,其中SSI为簇内平方和,SST为总平方和。 表示来自群间方差的总方差的百分比。 越高,簇的边界越清晰。定义如下:

其中C为簇个数,N为图像个数, 为属于第C个簇的图像个数。 是第j幅图像在第 隐藏状态下的特征。 表示一个数据在第i个隐藏状态下的特征尺寸,即 ,其中D、H、W分别为通道、高和宽。 分别表示第 个隐藏状态下的数据平均特征和第 个簇在第 个隐藏状态下的数据平均特征。

从图4可以看出,使用Mixup学习到的特征的 明显高于没有进行任何Mixing操作的特征。因此,在使用Manifold Mixup时,由于Mixing和不Mixing的客观特征分布有很大的不同,因此会产生“Distribution shift”现象。

5 实验

作者在ChestX-ray14数据集和阿里巴巴天池云大赛的2个心电图记录数据集上进行实验。

实验结论:

在CXR分类中最好表现、ECG分类的最佳表现。可以看出,Flow-Mixup在处理不同程度的标签损坏方面优于其他正则化方法。同时与其他正则化方法相比,Flow-Mixup方法的性能验证了Flow-Mixup方法的性能。

6 参考

[1].https://blog.csdn.net/sinat_36618660/article/details/101633504

[2].Manifold Mixup: Better Representations by Interpolating Hidden States

[3].Flow-Mixup: Classifying Multi-labeled Medical Images with Corrupted Labels

原文获取方式,扫描下方二维码

回复【Flow-Mixup】即可获取论文与源码

声明:转载请说明出处

扫描下方二维码关注【AI人工智能初学者】公众号,获取更多实践项目源码和论文解读,非常期待你我的相遇,让我们以梦为马,砥砺前行!!!

点“在看”给我一朵小黄花呗

浏览 61
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报