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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章