unet论文_[论文笔记]CE-Net与PsP-Net For Semantic Seg

写在前面

(今天我们来介绍两篇论文,以CE-Net为主,因为CE-Net用到了PsP-Net中的block,所以我们顺带一起讲一下。semantic seg是逐像素点的分类,所以某种意义上讲semantic seg也可以称为 dense seg)

《CE-Net: Context Encoder Network for 2D Medical Image Segmentation》是一篇将空洞卷积金字塔池化结合,专门用在2D医学图像分割的paper,文章发表在IEEE transcations on medical imaging(TMI2019).

论文地址:https://arxiv.org/abs/1903.02740

代码实现(Pytroch):https://github.com/Guzaiwang/CE-Net(官方实现)

数据集(开源):Kaggle上肺部、视神经、视网膜(https://drive.grand-challenge.org/DRIVE/)、细胞分割。

分析问题

Unet连续的卷积和池化操作会丢掉很多空间信息(注意哦 这里分别提到了卷积和池化操作都会丢spatial info 那么下面的解决方法就是针对这两个操作分别进行改进的)

解决方法

作者提出CE-Net,主要的contribution有:

1.对Unet进行结构上的改进

  • 提出DAC(Dense Atrous Convolution)block。 利用空洞卷积,通过增大感受野获取更多high-level的info,作者说high-level的信息有助于分割精确度的提升。
  • 提出RMP(Residual Multi-kernel pooling)block。 与其说是“提出”,不如说直接拿PsP-Net中的block来用了,就是金字塔池化,利用不同kernel-size的池化操作,preserve不同scale的空间信息。

2.对Unet内部细节进行改进

encoder所有的特征提取器均采用预训练的ResNet-34(即所有的卷积操作全部变成ResNet部分结构了)替换了U-Net中直接两次卷积操作;decoder采用了bottleneck的设计(即先1*1conv先减少参数 再3*3deconv转置卷积操作 最后1*1conv还原channel )

3.泛化性

作者实验部分将CE-Net在非常多的数据集(肺结节分割、视网膜血管分割、细胞分割)上进行训练,各项指标性结果均有有效提升。

整体网络结构

edea550a746ee3d7d115b19bd011f1ca.png

CE-Net主要由feature encoder + context extractor + feature decoder三个模块组成,比起原始的Unet,本文创新点主要在context extactor这里;

context extractor(上图中红色虚线框部分)主要由DAC + RMP组成。

那下面咱们详细看一下这两个主要的block吧~

DAC block(to capture high-level semantic feature map)

还记得我们开始提出的问题吗,这个block就是针对连续的卷积操作丢失空间信息问题的。

下面先来看空洞卷积示意图,其作用就是在参数量不变的情况下增大卷积核的感受野。以下图为例,kernel同样有9个参数,从左到右第一个卷积核的感受野是3,第二个是7,第三个是11。我的理解是感受野越大那么获取的high-level信息越多,越有利于提高分割结果的准确性;而感受野越小的话丢失的细节信息越少,对小目标识别来说更有利。

e7254938f3fdd06c783b62403a4e308f.png

受到Inception-ResNet-V2和以上空洞卷积的启发,本文中作者提出了以下DAC结构:

440b45e0bb6ad84e66f97cae696cb871.png

简单解释一下Inception,它加宽(widen)了卷积操作,是一个并行结构,用不同size的卷积核分别对同一个feature map进行卷积操作,得到的结果进行concat操作,也可以叫做Multi-scale的操作。

提到了Inception加宽了卷积操作,同样的也可以加深(deepen)卷积操作,这就是何恺明大神著名的ResNet思想啦,可以使网络结构很深同时保证梯度不爆炸不消失。

那现在作者将Inception和ResNet思想结合起来,理论上讲可以同时拥有两种结构的优点。

RMP block(to detect objects at different sizes)

这个block结构上跟PsP-net是一样的。

CE-Net的RMP block如下:

24ff1edd9f1ac4a7d525e43a9f293aed.png

PsP-Net的PPM如下:

4804cd2209a43d9b004cdc4f40e12158.png

结构上都是对同一个feature map并行不同尺寸的pooling操作,最后把结果concat起来。这样可以得到不同size的feature map,为了减少训练参数每次pooling后进行一个1*1卷积。

实验结果

作者认为传统的交叉熵损失不适合小目标(即前景/背景很小)的医学图像,所以损失函数选用Diceloss+reg(正则化项用来防止网络过拟合)

作者分别对视神经、视网膜血管、肺部、细胞进行了分割实验。

先来看指标性结果:

6f1ad901aa6d55a60d0f5ad0fdde9b93.png

8d782c3e6e07a13260a7487365785cd3.png

再看一下视觉效果:

ebfb9a53a30d989dc9f7274d3149f8b2.png

3c9620140eb3c41356b0be33a2661b5c.png

无论是从指标性结果还是视觉效果上看,本文对CE-Net确实有不错的提升,而且是对各个分割任务都有提升。

关键代码解释(看注释哦~)

class CE_Net_backbone_DAC_with_inception(nn.Module):
    def __init__(self, num_classes=1, num_channels=3):
        super(CE_Net_backbone_DAC_with_inception, self).__init__()

        filters = [64, 128, 256, 512]
        resnet = models.resnet34(pretrained=True)
        self.firstconv = resnet.conv1 # Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
        self.firstbn = resnet.bn1 
        self.firstrelu = resnet.relu
        self.firstmaxpool = resnet.maxpool
        self.encoder1 = resnet.layer1
'''
Sequential(
  (0): BasicBlock(
    (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (1): BasicBlock(
    (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (2): BasicBlock(
    (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
)
'''
        self.encoder2 = resnet.layer2
        self.encoder3 = resnet.layer3
        self.encoder4 = resnet.layer4

        self.dblock = DACblock_with_inception(512)


        self.decoder4 = DecoderBlock(512, filters[2])
        self.decoder3 = DecoderBlock(filters[2], filters[1])
        self.decoder2 = DecoderBlock(filters[1], filters[0])
        self.decoder1 = DecoderBlock(filters[0], filters[0])

        self.finaldeconv1 = nn.ConvTranspose2d(filters[0], 32, 4, 2, 1)
        self.finalrelu1 = nonlinearity
        self.finalconv2 = nn.Conv2d(32, 32, 3, padding=1)
        self.finalrelu2 = nonlinearity
        self.finalconv3 = nn.Conv2d(32, num_classes, 3, padding=1)

    def forward(self, x):
        # Encoder
        x = self.firstconv(x)
        x = self.firstbn(x)
        x = self.firstrelu(x)
        x = self.firstmaxpool(x)
        e1 = self.encoder1(x)
        e2 = self.encoder2(e1)
        e3 = self.encoder3(e2)
        e4 = self.encoder4(e3)

        # Center
        e4 = self.dblock(e4)
        # e4 = self.spp(e4)

        # Decoder
        d4 = self.decoder4(e4) + e3
        d3 = self.decoder3(d4) + e2
        d2 = self.decoder2(d3) + e1
        d1 = self.decoder1(d2)

        out = self.finaldeconv1(d1)
        out = self.finalrelu1(out)
        out = self.finalconv2(out)
        out = self.finalrelu2(out)
        out = self.finalconv3(out)

        return F.sigmoid(out)

class CE_Net_backbone_inception_blocks(nn.Module):
    def __init__(self, num_classes=1, num_channels=3):
        super(CE_Net_backbone_inception_blocks, self).__init__()

        filters = [64, 128, 256, 512]
        resnet = models.resnet34(pretrained=True)
        self.firstconv = resnet.conv1
        self.firstbn = resnet.bn1
        self.firstrelu = resnet.relu
        self.firstmaxpool = resnet.maxpool
        self.encoder1 = resnet.layer1
        self.encoder2 = resnet.layer2
        self.encoder3 = resnet.layer3
        self.encoder4 = resnet.layer4

        self.dblock = DACblock_with_inception_blocks(512)


        self.decoder4 = DecoderBlock(512, filters[2])
        self.decoder3 = DecoderBlock(filters[2], filters[1])
        self.decoder2 = DecoderBlock(filters[1], filters[0])
        self.decoder1 = DecoderBlock(filters[0], filters[0])

        self.finaldeconv1 = nn.ConvTranspose2d(filters[0], 32, 4, 2, 1)
        self.finalrelu1 = nonlinearity
        self.finalconv2 = nn.Conv2d(32, 32, 3, padding=1)
        self.finalrelu2 = nonlinearity
        self.finalconv3 = nn.Conv2d(32, num_classes, 3, padding=1)

    def forward(self, x):
        # Encoder
        x = self.firstconv(x)
        x = self.firstbn(x)
        x = self.firstrelu(x)
        x = self.firstmaxpool(x)
        e1 = self.encoder1(x)
        e2 = self.encoder2(e1)
        e3 = self.encoder3(e2)
        e4 = self.encoder4(e3)

        # Center
        e4 = self.dblock(e4)
        # e4 = self.spp(e4)

        # Decoder
        d4 = self.decoder4(e4) + e3
        d3 = self.decoder3(d4) + e2
        d2 = self.decoder2(d3) + e1
        d1 = self.decoder1(d2)

        out = self.finaldeconv1(d1)
        out = self.finalrelu1(out)
        out = self.finalconv2(out)
        out = self.finalrelu2(out)
        out = self.finalconv3(out)

        return F.sigmoid(out)

思考

1.CE-Net的有效性还有待在自己的数据集上考证(毕竟实验里对这么多医学图像分割任务都有提升未免也太厉害了)

2.这两block的位置是不是可以放在encode的其他地方。比如在Unet之后有一篇Unet++发表在MICCAI,它就引入了更多的skip connection和上采样,如下图所示。按照这个思路DAC完全可以放在U-net每个doubleconv之后。

7b1fc504c82d8fe96cf55008256e51cf.png

后记

未来一周我会在自己的数据集上进行训练测试CE-net看一下其有效性,待补充...

在家充电完成啦,回来继续好好学习,保持积极平稳的心态,稳步前进!今天突然想到诗经里的“投我以木桃,报之以琼瑶.匪报也,永以为好也!”写的好美啊。古人可以借诗抒情,今人也可以借知乎文章明志嘛~因为写这个文章会有一些花时间,但今后我会保持一周至少一次论文输出,加油!(这次的配图来自泰山/玉皇山顶~

以上,如果有理解不当的地方,欢迎大家批评指正呀!(乐交诤友, 鞠躬感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值