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

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

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原理公式为:

image.png

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

需要说明几点

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

image.png

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:

image.png

image.png

  • 4、继续前向传播直至得到输出;
  • 5、计算损失和梯度:

image.png

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将模型分为非线性部分和线性部分、,为模型输出。

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

image.png

最后,经过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的正向过程定义为:

image.png

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

image.png

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

image.png

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

image.png

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

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

相关文章
|
1月前
|
人工智能
【Mixup】探索数据增强技术:深入了解Mixup操作
【Mixup】探索数据增强技术:深入了解Mixup操作
265 0
|
人工智能 数据可视化 数据处理
快速在 PaddleLabel 标注的花朵分类数据集上展示如何应用 PaddleX 训练 MobileNetV3_ssld 网络
快速在 PaddleLabel 标注的花朵分类数据集上展示如何应用 PaddleX 训练 MobileNetV3_ssld 网络
686 0
快速在 PaddleLabel 标注的花朵分类数据集上展示如何应用 PaddleX 训练 MobileNetV3_ssld 网络
|
数据处理 计算机视觉 Python
【目标检测】指定划分COCO数据集训练(车类,行人类,狗类...)
【目标检测】指定划分COCO数据集训练(车类,行人类,狗类...)
2670 0
|
30天前
|
计算机视觉
【YOLOv8改进】Inner-IoU: 基于辅助边框的IoU损失(论文笔记+引入代码)
YOLO目标检测专栏探讨了IoU损失的局限性,并提出创新改进。分析发现,不同尺度的辅助边框对高IoU和低IoU样本的回归有不同影响。因此,提出了Inner-IoU Loss,利用尺度因子ratio控制辅助边框大小以优化损失计算。实验验证了该方法能提升检测效果,增强泛化能力。创新点包括根据样本特性选择辅助边框尺度和Inner-IoU Loss的设计。更多详情见YOLO目标检测创新改进与实战案例专栏。
|
1月前
|
机器学习/深度学习 缓存 测试技术
Nice Trick | 不想标注数据了!有伪标签何必呢,Mixup+Mosaic让DINO方法再继续涨点
Nice Trick | 不想标注数据了!有伪标签何必呢,Mixup+Mosaic让DINO方法再继续涨点
118 0
|
1月前
|
存储 数据可视化 计算机视觉
基于YOLOv8的自定义数据姿势估计
基于YOLOv8的自定义数据姿势估计
|
算法 数据挖掘
简单涨点 | Flow-Mixup: 对含有损坏标签的多标签医学图像进行分类(优于Mixup和Maniflod Mixup)(二)
简单涨点 | Flow-Mixup: 对含有损坏标签的多标签医学图像进行分类(优于Mixup和Maniflod Mixup)(二)
112 1
|
机器学习/深度学习 计算机视觉
【让模型更加谦虚】Adaptive Label Smoothing方法让模型结果更加鲁棒
【让模型更加谦虚】Adaptive Label Smoothing方法让模型结果更加鲁棒
162 0
【让模型更加谦虚】Adaptive Label Smoothing方法让模型结果更加鲁棒
|
机器学习/深度学习 自动驾驶 大数据
3D检测涨点Trick | 2D检测居然可以教BEV进行3D目标检测
3D检测涨点Trick | 2D检测居然可以教BEV进行3D目标检测
468 0
|
机器学习/深度学习 人工智能 自然语言处理
首个目标检测扩散模型,比Faster R-CNN、DETR好,从随机框中直接检测
首个目标检测扩散模型,比Faster R-CNN、DETR好,从随机框中直接检测
146 0