語義分割演算法之DeepLabV3+論文理解及源碼解析

  • 2019 年 12 月 4 日
  • 筆記

前言

之前講了DeepLabV1,V2,V3三個演算法,DeepLab系列語義分割還剩下最後一個DeepLabV3+,以後有沒有++,+++現在還不清楚,我們先來解讀一下這篇論文並分析一下源碼吧。論文地址:https://arxiv.org/pdf/1802.02611.pdf

背景

語義分割主要面臨兩個問題,第一是物體的多尺度問題,第二是DCNN的多次下取樣會造成特徵圖解析度變小,導致預測精度降低,邊界資訊丟失。DeepLab V3設計的ASPP模組較好的解決了第一個問題,而這裡要介紹的DeepLabv3+則主要是為了解決第2個問題的。我們知道從DeepLabV1系列引入空洞卷積開始,我們就一直在解決第2個問題呀,為什麼現在還有問題呢?我們考慮一下前面的程式碼解析推文的DeepLab系列網路的程式碼實現,地址如下:https://mp.weixin.qq.com/s/0dS0Isj2oCo_CF7p4riSCA 。對於DeepLabV3,如果Backbone為ResNet101,Stride=16將造成後面9層的特徵圖變大,後面9層的計算量變為原來的4倍大。而如果採用Stride=8,則後面78層的計算量都會變得很大。這就造成了DeepLabV3如果應用在大解析度影像時非常耗時。所以為了改善這個缺點,DeepLabV3+來了。

演算法原理

DeepLabV3+主要有兩個創新點。

編解碼器

為了解決上面提到的DeepLabV3在解析度影像的耗時過多的問題,DeepLabV3+在DeepLabV3的基礎上加入了編碼器。具體操作見論文中的下圖:

其中,(a)代表SPP結構,其中的8x是直接雙線性插值操作,不用參與訓練。(b)是編解碼器,融合了高層和低層資訊。(c)是DeepLabv3+採取的結構。

我們來看一下DeepLabV3+的完整網路結構來更好的理解這點:

對於編碼器部分,實際上就是DeepLabV3網路。首先選一個低層級的feature用1 * 1的卷積進行通道壓縮(原本為256通道,或者512通道),目的是減少低層級的比重。論文認為編碼器得到的feature具有更豐富的資訊,所以編碼器的feature應該有更高的比重。這樣做有利於訓練。對於解碼器部分,直接將編碼器的輸出上取樣4倍,使其解析度和低層級的feature一致。舉個例子,如果採用resnet conv2 輸出的feature,則這裡要上取樣。將兩種feature連接後,再進行一次的卷積(細化作用),然後再次上取樣就得到了像素級的預測。

實驗結果表明,這種結構在Stride=16時有很高的精度同時速度又很快。stride=8相對來說只獲得了一點點精度的提升,但增加了很多的計算量。

更改主幹網路

論文受到近期MSRA組在Xception上改進工作可變形卷積(Deformable-ConvNets)啟發,Deformable-ConvNets對Xception做了改進,能夠進一步提升模型學習能力,新的結構如下:

最終,論文使用了如下的改進:

  • 更深的Xception結構,不同的地方在於不修改entry flow network的結構,為了快速計算和有效的使用記憶體
  • 所有的max pooling結構被stride=2的深度可分離卷積代替
  • 每個3×3的depthwise convolution都跟BN和Relu

最後將改進後的Xception作為encodet主幹網路,替換原本DeepLabv3的ResNet101。

實驗

論文使用modified aligned Xception改進後的ResNet-101,在ImageNet-1K上做預訓練,通過擴張卷積做密集的特徵提取。採用DeepLabv3的訓練方式(poly學習策略,crop)。注意在decoder模組同樣包含BN層。

使用1*1卷積少來自低級feature的通道數

上面提到過,為了評估在低級特徵使用1*1卷積降維到固定維度的性能,做了如下對比實驗:

實驗中取了尺度為的輸出,降維後的通道數在32和48之間最佳,最終選擇了48。

使用3*3卷積逐步獲取分割結果

編解碼特徵圖融合後經過了卷積,論文探索了這個卷積的不同結構對結果的影響:

最終,選擇了使用兩組卷積。這個表格的最後一項代表實驗了如果使用和同時預測,上取樣2倍後與結合,再上取樣2倍的結果對比,這並沒有提升顯著的提升性能,考慮到計算資源的限制,論文最終取樣簡單的decoder方案,即我們看到的DeepLabV+的網路結構圖。

Backbone為ResNet101

在這裡插入圖片描述

Backbone為Xception

這裡可以看到使用深度分離卷積可以顯著降低計算消耗。

與其他先進模型在VOC12的測試集上對比:

在目標邊界上的提升,使用trimap實驗模型在分割邊界的準確度。計算邊界周圍擴展頻帶(稱為trimap)內的mIoU。結果如下:

與雙線性上取樣相比,加decoder的有明顯的提升。trimap越小效果越明顯。Fig5右圖可視化了結果。

結論

論文提出的DeepLabv3+是encoder-decoder架構,其中encoder架構採用Deeplabv3,decoder採用一個簡單卻有效的模組用於恢複目標邊界細節。並可使用空洞卷積在指定計算資源下控制feature的解析度。論文探索了Xception和深度分離卷積在模型上的使用,進一步提高模型的速度和性能。模型在VOC2012上獲得了SOAT。Google出品,必出精品,這網路真的牛。

程式碼實現

結合一下網路結構圖還有我上次的程式碼解析點這裡 看,就很容易了。

  from __future__ import absolute_import, print_function    from collections import OrderedDict    import torch  import torch.nn as nn  import torch.nn.functional as F    from .deeplabv3 import _ASPP  from .resnet import _ConvBnReLU, _ResLayer, _Stem      class DeepLabV3Plus(nn.Module):      """      DeepLab v3+: Dilated ResNet with multi-grid + improved ASPP + decoder      """        def __init__(self, n_classes, n_blocks, atrous_rates, multi_grids, output_stride):          super(DeepLabV3Plus, self).__init__()            # Stride and dilation          if output_stride == 8:              s = [1, 2, 1, 1]              d = [1, 1, 2, 4]          elif output_stride == 16:              s = [1, 2, 2, 1]              d = [1, 1, 1, 2]            # Encoder          ch = [64 * 2 ** p for p in range(6)]          self.layer1 = _Stem(ch[0])          self.layer2 = _ResLayer(n_blocks[0], ch[0], ch[2], s[0], d[0])          self.layer3 = _ResLayer(n_blocks[1], ch[2], ch[3], s[1], d[1])          self.layer4 = _ResLayer(n_blocks[2], ch[3], ch[4], s[2], d[2])          self.layer5 = _ResLayer(n_blocks[3], ch[4], ch[5], s[3], d[3], multi_grids)          self.aspp = _ASPP(ch[5], 256, atrous_rates)          concat_ch = 256 * (len(atrous_rates) + 2)          self.add_module("fc1", _ConvBnReLU(concat_ch, 256, 1, 1, 0, 1))            # Decoder          self.reduce = _ConvBnReLU(256, 48, 1, 1, 0, 1)          self.fc2 = nn.Sequential(              OrderedDict(                  [                      ("conv1", _ConvBnReLU(304, 256, 3, 1, 1, 1)),                      ("conv2", _ConvBnReLU(256, 256, 3, 1, 1, 1)),                      ("conv3", nn.Conv2d(256, n_classes, kernel_size=1)),                  ]              )          )        def forward(self, x):          h = self.layer1(x)          h = self.layer2(h)          h_ = self.reduce(h)          h = self.layer3(h)          h = self.layer4(h)          h = self.layer5(h)          h = self.aspp(h)          h = self.fc1(h)          h = F.interpolate(h, size=h_.shape[2:], mode="bilinear", align_corners=False)          h = torch.cat((h, h_), dim=1)          h = self.fc2(h)          h = F.interpolate(h, size=x.shape[2:], mode="bilinear", align_corners=False)          return h      if __name__ == "__main__":      model = DeepLabV3Plus(          n_classes=21,          n_blocks=[3, 4, 23, 3],          atrous_rates=[6, 12, 18],          multi_grids=[1, 2, 4],          output_stride=16,      )      model.eval()      image = torch.randn(1, 3, 513, 513)        print(model)      print("input:", image.shape)  print("output:", model(image).shape)  

參考文章

https://blog.csdn.net/u011974639/article/details/79518175