酷播亮新聞
最棒的知識補給站

語義分割網絡DeepLab-v3的架構設計思想和TensorFlow實現

摘要:選自Medium作者:ThallesSilva機器之心編譯參與:NurhachuNull、劉曉坤深度卷積神經網絡在各類計算機視覺應用中取得了顯著的成功,語義分割也不例外。 這篇文章介紹了語義分割的
Te

選自Medium

作者:Thalles Silva

機器之心編譯

參與:Nurhachu Null、劉曉坤

深度卷積神經網絡在各類計算機視覺應用中取得了顯著的成功,語義分割也不例外。 這篇文章介紹了語義分割的
  TensorFlow 實現,並討論了一篇和通用目標的語義分割最相關的論文——DeepLab-v3。 DeepLab-v3
是由谷歌開發的語義分割網絡,近日,谷歌還開源了該系列的最新版本——DeepLab-v3+。

GitHub 地址:https://github.com/sthalles/deeplab_v3

語義分割

常規的圖像分類深度卷積神經網絡擁有相似的結構。 這些模型以圖像作為輸入,並輸出一個代表圖像類別的數值。

通常,分類深度卷積神經網絡有 4 種主要運算。 卷積、激活函數、池化以及全連接層。 傳遞一張圖片,通過一系列這些運算會輸出一個包含每個類別標籤的概率的特徵向量。 請注意,在這種設定下,我們是對圖片的整體進行分類。 也就是說,為一張圖像分配一個標籤。

用於圖像識別的標準深度學習模型。

與圖像分類不同的是,在語義分割中我們將對圖像中的每一個像素作出分類。 所以,對每個像素而言,模型需要將它歸類為預定義的類別之一。 換言之,語義分割是在像素級別理解圖像。

請記住,語義分割不會區分目標實例。 因此,如果我們有同一個類的兩個目標,它們最終將具有相同的類別標籤。 實例分割是區分同一類實例的問題。

語義分割與實例分割的區別。 (中) 雖然它們是相同的目標,但它們被分類為不同的目標(實例分割)。 (右) 相同的目標,相同的類別(語義分割)。

然而,常規的深度卷積神經網絡
  (如 AlexNet 和 VGG )
並不適用於密集預測的任務。 首先,這些模型包含許多用於減小輸入特徵的空間維度的層。 結果,這些層最終產生缺乏清晰細節的高度抽象的特徵向量。 第二,全連接層在計算過程中具有固定的輸入規模和鬆散的空間信息。

作為一個例子,試想通過一系列的捲積來傳遞圖像,而不是使用池化和全連接層。 我們將每次卷積都設置成步長為 1,padding 為「SAME」。 通過這種處理,每一次卷積都保留了輸入的空間維度。 我們可以堆疊很多這種卷積,並最終得到一個分割模型。

用於密集預測的全卷積神經網絡。 請注意,不存在池化層和全連接層。

這個模型可以輸出形狀為
  [W,H,C] 的概率張量,其中 W 和 H 代表的是寬度和高度,C 代表的是類別標籤的個數。 在第三個維度上應用最大化函數會得到形狀為
[W,H,1] 的張量。 然後,我們計算真實圖像和我們的預測的每個像素之間的交叉熵。 最終,我們對計算結果取平均值,並且使用反向傳播算法訓練網絡。

然而,這個方法存在一個問題。 正如前面所提到的,使用步長為 1,padding 為「SAME」,保留了輸入的維度。 但是,那樣做的後果就是模型會極其耗費內存,而且計算複雜度也是很大的。

為了緩解這個問題,分割網絡通常會有三個主要的組成部分:卷積層、降採樣層和上採樣層。

圖像語義分割模型的編碼器-解碼器結構。

在卷積神經網絡中實現降採樣的常用方式有兩個:通過改變卷積步長或者常規的池化操作。 一般而言,降採樣的目標就是減少給定特徵圖的空間維度。 因此,降採樣可以讓我們在執行更深的捲積運算時不用過多地考慮內存。 然而,這樣一來在計算的時候會損失一些特徵。

值得注意的是,這個架構的第一部分看上去類似於普通的分類深度卷積神經網絡。 不同的是,其中沒有設置全連接層。

在第一部分之後,我們就得到了形狀為 [W, H, D] 的特徵向量,其中 W,H,D 分別是特徵張量的寬度、高度和深度。 注意,這個壓縮向量的空間維度比原始輸入更加少,但是更緊緻。

頂部:VGG-16 網絡的原始形式。 要注意的是,堆疊的捲積層的頂部有三個全連接層。 底部:VGG-16 網絡中用 1×1 的捲積代替全連接層。 這種改變可以讓網絡輸出粗略的熱圖。

此時,常規的分類深度卷積神經網絡會輸出一個包含每個類別概率的密集(非空間)向量。 取而代之,我們將這個壓縮向量輸入到一系列上採樣層中。 這些上採樣層的作用就是重建與輸入維度相同的輸出向量。

通常,上採樣都是基於步長置換卷積(strided transpose convolution)完成的。 這些函數從深而窄的層變成淺而寬的層。 這裡,我們使用置換卷積將特徵向量的維度增加到期望的結果。

在大多數論文中,分割網絡的這兩個部分被稱作編碼器和解碼器。 簡而言之,第一部分將信息「編碼」為壓縮向量來代表輸入。 第二部分(解碼器)的作用是將這個信號重建為期望的輸出。

有很多基於編碼器—解碼器結構的神經網絡實現。 FCNs、SegNet,以及 UNet 是最流行的幾個。

模型架構

與大多數編碼器—解碼器架構設計不同的是,Deeplab 提供了一種與眾不同的語義分割方法。 Deeplab 提出了一種用於控制信號抽取和學習多尺度語境特徵的架構。

Deeplab
  把在 ImagNet 上預訓練得到的 ​​ResNet 作為它的主要特徵提取網絡。 但是,它為多尺度的特徵學習添加了一個新的殘差塊。 最後一個
ResNet 塊使用了空洞卷積(atrous
convolution),而不是常規的捲積。 此外,這個殘差塊內的每個卷積都使用了不同的擴張率來捕捉多尺度的語境信息。

另外,這個殘差塊的頂部使用了空洞空間金字塔池化 (ASPP,Atrous Spatial Pyramid Pooling)。 ASPP 使用了不同擴張率的捲積來對任意尺度的區域進行分類。

為了理解 Deeplab 的架構,我們需要著重註意這三個部分。 (i)ResNet 架構,(ii) 空洞卷積,(iii) 空洞空間金字塔池化(ASPP)。 接下來我們將逐一介紹這幾個部分。

ResNets

ResNet 是一個非常流行的深度卷積神經網絡架構,它贏得了 ILSVRC 2015 分類任務挑戰賽的冠軍。 它的主要貢獻之一就是提供了簡化深度學習模型訓練的框架。

在 ResNet 的原始形式中,它含有 4 個計算模塊。 每個模塊包含不同數量​​的殘差單元。 這些單元以特別的形式執行一系列的捲積運算。 同樣,每個模塊都夾雜了最大池化操作來減少空間維度。

原始論文提出了兩種殘差單元:基線塊和瓶頸塊。

基線塊包含兩個 3×3 的捲積,卷積中使用了 BN(批歸一化)和 ReLU 激活函數。

殘差模塊。 左:基線塊;右:瓶頸塊。

第二個是瓶頸塊,它包括三個堆疊的部分,用一系列的
  1×1、3×3 和 1×1 的捲積代替了之前的設計。 兩個 1×1 的捲積操作被用來減少和恢復維度。 這使得中間的 3×3
的捲積可以在一個密度相對較低的特徵向量上進行操作。 此外,每個卷積之後、每個非線性 ReLU 之前都應用了 BN。

為了有助於澄清這個問題,我們將這一組操作定義為一個輸入為 x 的函數 F——F(x)。


  F(x) 中的非線性變換之後,這個單元將 F(x) 的結果和原始輸入 x 相結合。 這種結合是通過對兩個函數求和得到的。 原始輸入 x
和非線性函數 F(x) 合併帶來了一些優勢。 它使得前面的層可以訪問後面層的梯度信號。 換句話說,跳過 F(x)
上的操作允許前面的層訪問更強的梯度信號。 這種類型的連接已經被證明有助於更深網絡的訓練。

當我們增加模型容量時,非瓶頸單元也表明有助於準確率的提高。 然而,瓶頸殘差單元具有一些實際優勢。 首先,它們在幾乎相同數量的參數下可執行更多的計算。 第二,它們與非瓶頸單元的計算複雜度相似。

在實際中,瓶頸單元更適合於訓練更深的模型,因為它們需要的訓練時間和計算資源更少。

在我們的實現中,我們將使用完全預激活殘差單元(full pre-activation Residual Unit),與標準瓶頸單元的唯一區別在於 BN 和 ReLU 激活函數的放置順序。 對於完全預激活,BN 和 ReLU(按此順序)出現在卷積之前。

不同的 ResNet 構建模塊塊體系架構。 最左邊:原始 ResNet 模塊;最右邊:改進的完全預激活版本。

正如《Identity Mappings in Deep Residual Networks》中所展示的一樣。 完全預激活單元要優於其他的變體。

注意,這些設計之間的唯一區別是卷積堆棧中 BN 和 ReLU 的順序。

空洞卷積

空洞卷積(或者擴張卷積)是具有一個因子的常規卷積,這個因子使得我們能夠擴展濾波器的視野。

以 3×3 卷積濾波器為例。 當擴張因子等於 1 時,它的行為類似於標準卷積。 但是,如果將擴張因子設置為 2,則它具有擴大卷積核的效果。

理論上,它是這樣工作的:首先,根據擴張率對卷積濾波器進行擴張。 然後,它用零填充空白空間,創建稀疏的類似濾波器。 最後,使用擴張的濾波器進行常規卷積。

不同擴張率的空洞卷積

因此,大小為 3×3、擴張率為 2 的捲積將使其能夠覆蓋 5×5 的區域。 然而,因為它的作用就像一個稀疏的過濾器,只有原始的 3 x3 單元將執行計算並生成結果。

以類似的方式,擴張因子為 3 的常規 3×3 的捲積能夠得到對應的 7×7 區域的信號。

這種效果允許我們控制計算特徵響應的分辨率。 此外,空洞卷積在不增加參數數量或計算量的情況下增加了更大範圍的語境信息。

Deeplab 還表明,必鬚根據特徵圖的大小來調整擴張率。 他們研究了在小特徵圖上使用大擴張率的結果。

給小特徵圖設置更大的擴張率的副作用。 對於 14×14 的輸入圖像,使用擴張率為 15 的 3×3 卷積,其結果和常規的 1×1 卷積類似。

當擴張率非常接近特徵圖的尺寸時,一個常規的 3×3 的空洞濾波器的效果與標準的 1×1 卷積是一樣的。

換句話說,空洞卷積的效率依賴於對擴張率的選擇。 由於這一原因,理解神經網絡中的輸出步長(output stride)的概念是很重要的。

輸出步長反映輸入圖像大小與輸出特徵圖大小的比率,它定義了輸入向量在通過網絡時經受的信號抽象程度。

輸出步長為 16,圖像大小為 224x224x3 時,輸出特徵向量比輸入圖像的維度小 16 倍,變成了 14×14。

此外,Deeplab 還討論了不同輸出步長對分割模型的影響。 Deeplab 認為過強的信號抽像不利於密集預測任務。 總之,具有較小輸出步長 (較弱信號抽象) 的模型傾向於輸出更精細的分割結果。 然而,使用較小的輸出步長訓練模型需要更多的訓練時間。

Deeplab 還展示了兩種輸出步長(8 和 16)設置下的結果。 和預期的一樣,步長等於 8 能夠產生稍微好一些的結果。 在這裡,出於實際原因,我們選擇了 16 為輸出步長。

此外,由於空洞卷積塊沒有實現降採樣,所以 ASPP 也運行在相同的特徵響應大小上。 因此,它允許使用相對較大的擴張率從多尺度的語境中學習特徵。

新型空洞殘差塊包含三個殘差單元。 三個單元都總共擁有三個 3×3 的捲積塊。 在多重網格(multigrid)方法的啟發下,Deeplab 為每個卷積設置了不同的擴張率。 總之,多重網格為三個卷積中的每個卷積定義了不同的擴張率。

在實際中:

對於 block 4,當輸出步長是 16,多重網格為(1,2,4)的時候,這三個卷積的擴張率分別是(2,4,8)。

空洞空間金字塔池化

空洞空間金字塔池化(ASPP)的思想是提供具有多尺度信息的模型。 為了做到這一點,ASPP
  添加了一系列具有不同擴張率的空洞卷積。 這些擴張率是被設計用來捕捉大範圍語境的。 此外,為了增加全局的語境信息,ASPP
還通過全局平均池化(GAP)結合了圖像級別的特徵。

這個版本的 ASPP 包含 4 個並行的操作。 它們分別是一個 1×1 的捲積以及三個 3×3 的捲積(擴張率分別是(6,12,18))。 正如我們前面所提及的,現在,特徵圖的標稱步長(nominal stride)是 16.

在原始實現的基礎上,我們使用 513 x513 的裁剪尺寸進行訓練和測試。 因此,使用 16 的輸出步長意味著 ASPP 接收大小為 32 x32 的特徵向量。

此外,為了添加更多全局語境信息,ASPP 結合了圖像級別的特徵。 首先,它將 GAP 應用於從最後一個空洞塊輸出的特徵上。 其次,所得特徵被輸入到具有 256 個濾波器的 1x 1 卷積中。 最後,將結果進行雙線性上採樣到正確的維度大小。

@slim.add_arg_scope

def atrous_spatial_pyramid_pooling(net, scope, depth=256):

“””

ASPP consists of (a) one 1×1 convolution and three 3×3 convolutions with rates = (6, 12, 18) when output stride = 16

(all
  with 256 filters and batch normalization), and (b) the image-level
features as described in https://arxiv.org/abs/1706.05587

:param net: tensor of shape [BATCH_SIZE, WIDTH, HEIGHT, DEPTH]

:param scope: scope name of the aspp layer

:return: network layer with aspp applyed to it.

“””

with tf.variable_scope(scope):

feature_map_size = tf.shape(net)

# apply global average pooling

image_level_features = tf.reduce_mean(net, [1, 2], name=’image_level_global_pool’, keep_dims=True)

image_level_features = slim.conv2d(image_level_features, depth, [1, 1], scope=”image_level_conv_1x1″, activation_fn=None)

image_level_features = tf.image.resize_bilinear(image_level_features, (feature_map_size[1], feature_map_size[2]))

at_pool1x1 = slim.conv2d(net, depth, [1, 1], scope=”conv_1x1_0″, activation_fn=None)

at_pool3x3_1 = slim.conv2d(net, depth, [3, 3], scope=”conv_3x3_1″, rate=6, activation_fn=None)

at_pool3x3_2 = slim.conv2d(net, depth, [3, 3], scope=”conv_3x3_2″, rate=12, activation_fn=None)

at_pool3x3_3 = slim.conv2d(net, depth, [3, 3], scope=”conv_3x3_3″, rate=18, activation_fn=None)

net = tf.concat((image_level_features, at_pool1x1, at_pool3x3_1, at_pool3x3_2, at_pool3x3_3), axis=3,

name=”concat”)

net = slim.conv2d(net, depth, [1, 1], scope=”conv_1x1_output”, activation_fn=None)

return net

最後,各個分支的特徵都被通過連接操作結合成一個單獨的向量。 然後使用另一個 1×1(採用 BN,和 256 個濾波器)的捲積對這個輸出進行卷積。

ASPP 之後,我們將結果輸入到另一個 1×1 的捲積中去生成最終的分割邏輯。

實現細節

這個實現用 ResNet-50 作為特徵提取器,Deeplab_v3 採取了以下網絡配置:

輸出步長=16

為新的空洞殘差塊(block 4)使用固定的多重網格空洞卷積率(1,2,4)

在最後一個空洞卷積殘差塊之後使用擴張率為(6,12,18)的 ASPP。

將輸出步長設置為 16 有利於可持續地快速訓練。 與另一個輸出步長 8 相比,輸出步長為 16 使得空洞殘差塊處理的特徵圖比步長為 8 時處理的特徵圖小四倍。

將多重網格擴張率應用於空洞殘差塊內部的 3 個卷積。

最終,ASPP 中的三個並行的捲積得到了不同的擴張率——(6,12,18)。

在計算交叉熵損失函數之前,我們將分割邏輯調整為輸入圖像的大小。 正如論文中所指出的,為了保持分辨率細節,調整分割邏輯的大小比調整真實標籤的大小更好。

基於原始的訓練過程,我們使用一個 0.5 到 2 之間的隨機因子對每個圖像做了擴展。 此外,我們還對縮放後的圖像做了隨機的左右翻轉。

最終,我們為訓練和測試裁剪了 513 x513 大小的圖像。

def deeplab_v3(inputs, args, is_training, reuse):

# mean subtraction normalization

inputs = inputs – [_R_MEAN, _G_MEAN, _B_MEAN]

# inputs has shape [batch, 513, 513, 3]

with slim.arg_scope(resnet_utils.resnet_arg_scope(args.l2_regularizer, is_training,

args.batch_norm_decay,

args.batch_norm_epsilon)):

resnet = getattr(resnet_v2, args.resnet_model)

_, end_points = resnet(inputs,

args.number_of_classes,

is_training=is_training,

global_pool=False,

spatial_squeeze=False,

output_stride=args.output_stride,

reuse=reuse)

with tf.variable_scope(“DeepLab_v3”, reuse=reuse):

# get block 4 feature outputs

net = end_points[args.resnet_model + ‘/block4’]

net = atrous_spatial_pyramid_pooling(net, “ASPP_layer”, depth=256, reuse=reuse)

net =slim.conv2d(net, args.number_of_classes, [1, 1], activation_fn=None,

normalizer_fn=None, scope=’logits’)

size = tf.shape(inputs)[1:3]

# resize the output logits to match the labels dimensions

# net = tf.image.resize_nearest_neighbor(net, size)

net = tf.image.resize_bilinear(net, size)

return net

為了實現殘差網絡 block4 中具有多重網格的空洞卷積,我們僅僅改變了 resnet_utils.py 文件中的以下這段代碼:

with tf.variable_scope(‘unit_%d’ % (i + 1), values=[net]):

# If we have reached the target output_stride, then we need to employ

# atrous convolution with stride=1 and multiply the atrous rate by the

# current unit’s stride for use in subsequent layers.

if output_stride is not None and current_stride == output_stride:

# Only uses atrous convolutions with multi-graid rates in the last (block4) block

if block.scope == “block4”:

net = block.unit_fn(net, rate=rate * multi_grid[i], **dict(unit, stride=1))

else:

net = block.unit_fn(net, rate=rate, **dict(unit, stride=1))

rate *= unit.get(‘stride’, 1)

訓練

為了訓練網絡,我們決定使用來自於《Semantic contours from inverse detectors》的擴增版的 Pascal VOC 數據集。

訓練數據由
  8252 張圖像組成。 訓練集有 5623 張,驗證集有 2299 張。 為了使用原始的 VOC2012
驗證數據集來測試模型,我們從驗證集中刪去了 558 張圖像。 這 558 張圖片也出現在官方的 VOC 驗證集中。 此外,我還添加了來自 VOC
2012 訓練集中的 330 幅圖像,它們既沒出現在 5623 張訓練集中,也沒出現在 2299 張的驗證集中。 最後,8252 張圖像中的
10%(大約 825 張圖像)用來驗證,其餘的圖像留著訓練。

注意,這與原始論文是不一樣的:這次實現沒有在 COCO 數據集上預訓練。 此外,論文中描述到的一些訓練和評估技術也沒有用到。

結果

模型能夠在 PASCAL VOC 驗證集上得到良好的結果。

像素準確率:大約 91%

平均準確率:大約 82%

均交並比(mIoU):大約 74%

頻權交並比(FWIoU):大約 86%

以下是 PASCAL VOC 驗證集的圖像分割的結果。

結論

語義分割無疑是計算機視覺領域中最流行的領域之一。 Deeplab 提供了一個傳統編碼器-解碼器體系架構的替代方案。 它提倡在多範圍的語境中使用空洞卷積學習特徵。

原文鏈接:https://medium.freecodecamp.org/diving-into-deep-convolutional-semantic-segmentation-networks-and-deeplab-v3-4f094fa387df

本文為機器之心編譯,轉載請聯繫本號獲得授權

版權聲明

本文僅代表作者觀點,不代表百度立場。
本文係作者授權百度百家發表,未經許可,不得轉載。

如有侵權請來信告知:酷播亮新聞 » 語義分割網絡DeepLab-v3的架構設計思想和TensorFlow實現