Semantic Segmentation --Improve Semantic Segmentation by Global Convolutional Network(GCN)论文解读

Large Kernel Matters —— GCN

论文全称:Large Kernel Matters——Improve Semantic Segmentation by Global Convolutional Network

作者栏里面有熟悉的Face++的Jian Sun~

原文地址:Large Kernel Matters —— GCN

实现代码:

Abstract

在现有的模型架构设计中有这样一个趋势:
堆叠小卷积核比大卷积核更有效。(主要说的是VGG的3×3 和GoogleNet中的1×1 )。但考虑到Semantic Segmentation需要逐像素分割预测,要同时完成分割和预测(classification and localization tasks simultaneously)。
考虑到这一问题,提出了Global Convolutional Network(GCN),同时给出了一个基于残差(residual,即Resnet的主要贡献)的boundary refinement(BR)模块用于细化物体边界,论文在PASCAL VOC 2012(82.2%)和Cityscapes(76.9% )上达到了state-of-the-art.

Introduction

论文上来先分析Semantic Segmentation。

我们要做的任务分成两部分:classification&localization.

论文指出这两部分在要义上是相互矛盾的(these two tasks are naturally contradictory),从而论文提出了一个改进架构Global Convolutional Network(GCN)

部分 任务 要义 GCN的设计
classification 每个像素有对应的语义信息,即分类要正确 要求模型对各种变换有不变形(例如翻转和旋转) 网络应该采用较大的核,使得feature map与像素分类层之间有密切的连接,从而增强处理不同变换的能力
localization 每个像素分类标签与对应的种类对齐 要求对变换敏感,可以精确分割像素 模型应该是完全卷积(没有FC或全局池化),保持定位信息

大致的示意图如下:

这里写图片描述

  • A. Classification : 多次提取高层次的feature map,然后做分类.
    例如:AlexNet,VGGNet,GoogleNet,ResNet等,整体呈现的是“圆锥形网络”(Cone-shaped),这样的feature map的空间上是粗糙的(高语义的),分类任务可通过后续接FC层或全局池化层,这样有模型能接收不同类型的输入,保持较强的鲁棒性。

  • B. Segmentation : 输入和输出大小相同(feature map尺度没变),全卷积层最后每个通道对应一个分类结果.
    这需要有较大的feature map保持空间信息。所有大多数模型例如:FCN,DeepLab,Deconv-Net采用桶状(Barrel-shaped)结构,使用Deconvolution, Unpooling 和Dilated-Convolution等技术反向生成高分辨率feature map.

  • C. GCN : 全卷积设计,最后分类使用了多通道的信息(和ShuffleNet有那么点神似).
    考虑到B中分类器和feature map不是全局连接,难以处理输入上的变换。例如下图,分类器与输入对象的红点对齐,如果接收野不够大(valid receptive filed,VRF),则很难分别出来.如果使用更大的feature map,则效果更差.
    这里写图片描述
    而GCN就是在使用全卷积的结构上尽可能的使用大的卷积核,从而达到分类和分割平衡。

总结一下Paper的Contributions:

  • 针对“classification” and “localization” 提出了GCN架构
  • 引入边界细化模块(Boundary Refinement block)进一步提高物体边界的划分性能
  • 在PASCAL VOC 2012(82.2%) and Cityscapes(76.9%)取得state-of-the-art.

以FCN为基础,从三个方面尝试提高semantic segmentation 性能:

  • Context Embedding : 上下文语义嵌入。这里列举了一些工作:ParseNet使用全局池化分支增加额外上下文信息;Dilated-Net使用空洞卷积增加上下文信息;Deeplab-V2使用Atrous Spatial Pyramid Pooling模块增加上下文信息.
  • Resolution Enlarging : FCN是使用deconvolution来提高层次的feature map的分辨率;在Deconv-Net和SegNet上采用unpooling来学习上采样;Deeplab和Dilated-Net提出了一个特殊的dilated convolution来扩大feature map的大小,从而达到更大的分辨图.
  • Boundary Alignment : 边界对齐的任务是优化分割对象的边界预测。许多方法是使用CRF(条件随机场)。后续的Deeplab提出的denseCRF等等一大堆的CRF改进版。

这里点出本文的看法:Semantic Segmentation是在大的feature map上的分类任务.(semantic segmentation is a classification task on large feature map)

Architecture

在GCN架构中,将核大小增加到feature map的空间大小,从而可以获取全局信息(Global Convolution)。这里没有直接使用大卷积核,而是采用GoogleNet的思想(可以参考我以前写的GoogleNet笔记Inception-V2),将大卷积核拆分为卷积组合,即GCN模块(下图F2.B),这里在卷积层后不使用非线性,保持计算复杂度为O(2k)

模型的整体架构如下(下图F2.A):
这里写图片描述

模型使用ResNet作为特征提取层(后面实验有介绍),使用FCN的结构作为分割架构。不同尺度的特征是从不同大小的feature map上提取的,对于不同层级的feature map使用GCN提取全局信息,同时高层次的feature map通过上采样来补充语义信息,最终融合得到预测图。这里提出了一个残差结构(residual structure)的BR模块(上图F2.C)学习边界信息。

Experiment

论文在PASCAL VOC 2012和Cityscapes上做了评估。

项目 属性
特征提取层 预训练的Resnet152权重
优化器 标准的SGD
batch size 1
权重衰减 momentum 0.99 and weight decay 0.0005
数据增强 去均值,水平翻转
度量标准 标准平均IoU值
实现工具 Caffe

实验方案

将输入放缩到512×512 ,这样最终的feature map大小为16×16 ,论文比较了四种结构,示意图如下:

这里写图片描述

  • B:单纯的1×1 卷积,提供一个baseline
  • A:GCN结构,使用参数k 控制核大小,论文使用了不同k (3到15,使用奇数保持对齐),论证了论文思想。在k=15 是接收野大概为16×16 ,达到了“全局卷积”,在实验结果上来看,性能随着内核大小k 增加而增加,从而论证了GCN的设计思想。
    这里写图片描述
  • C:考虑到模型性能提升可能是因为参数增加,这里设计一个k×k 的卷积核做对比,从结果来看,GCN结构比大卷积核效果好,可以看到随着k增加,大卷积核效果反倒下降了,这其中的原因可能是因为过拟合,在训练过程中,因为参数多,网络难以收敛,实际原因还有待研究
    这里写图片描述
  • D:对于大卷积核,可以使用小卷积核堆叠达到类似的接收野,为了等效比较,在堆叠小卷积时没用使用非线性(在VGG那些网络中使用非线性了,但是计算量也上去了),结果是GCN好一点。
    这里写图片描述
    同时还比较了堆叠小卷积核数量的影响,随着卷积核数目减少性能也减少了,结果还是GCN好一点:
    这里写图片描述

  • 其他实验:为了进一步分析GCN是如何影响分割结果的,即论证GCN是通过引入密集连接来提高分类结果,通过BR来细化边缘分割,论文将评分分为两部分:

    • boundary region:像素点接近分割边缘的部分,取distance<7
    • internal region: 除了boundary region剩余的像素

    实验结果如下(k=15 ):
    这里写图片描述
    可以看到GCN有效的提高了Internal的评分,BR提高了Boundary的评分.

预训练模型

考虑到大卷积核的优点,很自然的把GCN的思想应用到ResNet152上,论文提出了一个ResNet-GCN结构,如下图:

这里写图片描述

为了尽可能同条件比较,将Resnet-GCN精心选择k 达到和原Resnet相似的计算量和参数量,先在ImageNet2015上预训练,再在PASCAL VOC 2012微调,考虑到Resnet152计算成本太高,这里选择Resnet50来比较,模型结构如下:
这里写图片描述

实验结果如下:

这里写图片描述

可以看到,在ImageNet上Resnet-GCN的结果稍差,但是在semantic segmentation上表现好了很多,可以有信心的得出GCN有助于提高分割性能

PASCAL VOC 2012

在PASCAL VOC 2012数据集上,

先使用MS COCO数据集预训练,训练分为三个阶段:

  • Stage-1: 将COCO,BSD,标准的PASCAL VOC 2012混合成一个109,892数据集训练
  • Stage-2: 使用BSD,PASCAL VOC 2012预训练调整模型
  • Stage-3: 只使用PASCAL VOC 2012

输入图片在Stage-1会填充到640×640 ,在Stage2-3会填充到512×512 ,实验结果如下:

这里写图片描述

GCN+BR效果较好,同时后添加CRF会进一步提升.

提交到PASCAL VOC 2012获得了state-of-the-art:

这里写图片描述

Cityscapes

Cityscapes的图片大小固定为1024×2048 ,这对于模型太大了,故先随机将图片裁剪成800×800 来训练,同时将GCN的k 从15提升到25,最后的特征图大小为25×25 。训练一共分为2个阶段:

  • Stage-1 : 混合标注粗糙和精细的图片,一共22973张,一起训练
  • Stage-2 : 只对训练集上图片做微调

结果如下:
这里写图片描述

在测试阶段,将图片切分为4个1024×1024 ,融合预测分数.提交得到了state-of-the-art成绩:

这里写图片描述

Conclusion

给出分析结果:大核有助于提高分类结果,全卷积可以保持分割信息,使用BR可以细化分割结果。后面的实验给出了验证,实验效果还可以。
但是把,提交的结果中分成了多个训练阶段,其中各种的训练技巧不好复现啊。

模型代码分析

github上覆现的模型代码: Pytorch

GCN模块的实现:

这里写图片描述

# many are borrowed from https://github.com/ycszen/pytorch-ss/blob/master/gcn.py
class _GlobalConvModule(nn.Module):
    def __init__(self, in_dim, out_dim, kernel_size):
        super(_GlobalConvModule, self).__init__()
        pad0 = (kernel_size[0] - 1) / 2
        pad1 = (kernel_size[1] - 1) / 2
        # kernel size had better be odd number so as to avoid alignment error
        super(_GlobalConvModule, self).__init__()
        self.conv_l1 = nn.Conv2d(in_dim, out_dim, kernel_size=(kernel_size[0], 1),
                                 padding=(pad0, 0)) # 左kx1卷积
        self.conv_l2 = nn.Conv2d(out_dim, out_dim, kernel_size=(1, kernel_size[1]),
                                 padding=(0, pad1)) # 左1xk卷积
        self.conv_r1 = nn.Conv2d(in_dim, out_dim, kernel_size=(1, kernel_size[1]),
                                 padding=(0, pad1)) # 右1xk卷积
        self.conv_r2 = nn.Conv2d(out_dim, out_dim, kernel_size=(kernel_size[0], 1),
                                 padding=(pad0, 0)) # 右kx1卷积

    def forward(self, x):
        x_l = self.conv_l1(x)
        x_l = self.conv_l2(x_l)
        x_r = self.conv_r1(x)
        x_r = self.conv_r2(x_r)
        x = x_l + x_r   # sum操作
        return x

BF模块实现:

这里写图片描述

class _BoundaryRefineModule(nn.Module):
    def __init__(self, dim):
        super(_BoundaryRefineModule, self).__init__()
        self.relu = nn.ReLU(inplace=True)
        self.conv1 = nn.Conv2d(dim, dim, kernel_size=3, padding=1)  # 分支3x3卷积
        self.conv2 = nn.Conv2d(dim, dim, kernel_size=3, padding=1)  # 分支3x3卷积

    def forward(self, x):
        residual = self.conv1(x)
        residual = self.relu(residual)  # Conv + ReLU
        residual = self.conv2(residual) # Conv
        out = x + residual  # sum操作
        return out

基于Resnet152的整体架构

这里写图片描述


class GCN(nn.Module):
    def __init__(self, num_classes, input_size, pretrained=True):
        super(GCN, self).__init__()
        self.input_size = input_size
        resnet = models.resnet152()
        if pretrained:
            resnet.load_state_dict(torch.load(res152_path))
        self.layer0 = nn.Sequential(resnet.conv1, resnet.bn1, resnet.relu)
        self.layer1 = nn.Sequential(resnet.maxpool, resnet.layer1)
        self.layer2 = resnet.layer2
        self.layer3 = resnet.layer3
        self.layer4 = resnet.layer4

        # 所有GCN模块
        self.gcm1 = _GlobalConvModule(2048, num_classes, (7, 7))
        self.gcm2 = _GlobalConvModule(1024, num_classes, (7, 7))
        self.gcm3 = _GlobalConvModule(512, num_classes, (7, 7))
        self.gcm4 = _GlobalConvModule(256, num_classes, (7, 7))

        # 所有BR模块
        self.brm1 = _BoundaryRefineModule(num_classes)
        self.brm2 = _BoundaryRefineModule(num_classes)
        self.brm3 = _BoundaryRefineModule(num_classes)
        self.brm4 = _BoundaryRefineModule(num_classes)
        self.brm5 = _BoundaryRefineModule(num_classes)
        self.brm6 = _BoundaryRefineModule(num_classes)
        self.brm7 = _BoundaryRefineModule(num_classes)
        self.brm8 = _BoundaryRefineModule(num_classes)
        self.brm9 = _BoundaryRefineModule(num_classes)

        initialize_weights(self.gcm1, self.gcm2, self.gcm3, self.gcm4, self.brm1, self.brm2, self.brm3,
                           self.brm4, self.brm5, self.brm6, self.brm7, self.brm8, self.brm9)

    def forward(self, x):
        # if x: 512
        fm0 = self.layer0(x)  # 256
        fm1 = self.layer1(fm0)  # 128
        fm2 = self.layer2(fm1)  # 64
        fm3 = self.layer3(fm2)  # 32
        fm4 = self.layer4(fm3)  # 16

        gcfm1 = self.brm1(self.gcm1(fm4))  # 16
        gcfm2 = self.brm2(self.gcm2(fm3))  # 32
        gcfm3 = self.brm3(self.gcm3(fm2))  # 64
        gcfm4 = self.brm4(self.gcm4(fm1))  # 128

        # 上采样融合输出
        fs1 = self.brm5(F.upsample_bilinear(gcfm1, fm3.size()[2:]) + gcfm2)  # 32
        fs2 = self.brm6(F.upsample_bilinear(fs1, fm2.size()[2:]) + gcfm3)  # 64
        fs3 = self.brm7(F.upsample_bilinear(fs2, fm1.size()[2:]) + gcfm4)  # 128
        fs4 = self.brm8(F.upsample_bilinear(fs3, fm0.size()[2:]))  # 256
        out = self.brm9(F.upsample_bilinear(fs4, self.input_size))  # 512

        return out
发布了77 篇原创文章 · 获赞 572 · 访问量 67万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章