每日最新頭條.有趣資訊

重新發現語義分割,一文簡述全卷積網絡

選自Medium

作者:Farhan Ahmad

機器之心編譯

參與:李詩萌、路

全卷積網絡自 2012 年出現以來,在影像分類和影像檢測領域取得了巨大成功。本文利用筆電電腦構建了一個小型全卷積網絡,詳細介紹了全卷積網絡的思路、過程等等,值得一看

語義分割是一種學習如何識別影像中對象範圍的機器學習技術。語義分割賦予機器學習系統與人類相似的理解影像內容的能力。它促使機器學習算法定位對象的精準邊界,無論是街景影像中的汽車和行人,還是醫療影像中的心髒、肝髒和腎髒。

左圖:分割後的 CT 掃描影像(圖源:Semantic Scholar)。右圖:分割後的街景影像(圖源:Cityscapes Dataset)

在語義分割方面有一些優秀的文章,這篇文章也許是最全面的:資源 | 從全連接層到大型卷積核:深度學習的語義分割全指南

這篇文章的主要內容是如何針對 MNIST 數字建立一個小而快速的語義分割網絡。

背景

語義分割網絡有很多類,本文的重點在於全卷積網絡(FCN)。Berkely 的論文(https://people.eecs.berkeley.edu/~jonlong/long_shelhamer_fcn.pdf)第一次提出 FCN。FCN 是通過擴展普通的卷積網絡(CNN)建立的,因此 FCN 有更多參數而且訓練時間更長。本文所述的工作源於構建一個非常小的 FCN,該 FCN 可以在幾分鐘內在普通的筆電電腦上訓練得到。這個想法的實現首先需要建立一個在每張影像中都包含多個 MNIST 數字的數據集。用於生成此派生數據集的代碼在這裡:https://github.com/farhanhubble/udacity-connect/blob/master/segmented-generator.ipynb。為避免混淆,我們將該數據集命名為 M2NIST(多數字 MNIST)。

M2NIST

M2NIST 中的每一張圖都是灰度圖(單通道),大小為 64*84 像素,最多包含 MNIST 數據集中的 3 個數字。如下所示:

M2NIST 中的多數字影像

M2NIST 數據集的標簽是分割掩碼。分割掩碼是一個二進製影像(像素值為 0 或 1),其高度和寬度都和多數字影像一致,但是有 10 個通道,從 0~9 的每一個數字都有一個通道。掩碼中的第 k 個通道的像素都設定為 1,這與輸入的多數字影像中數字 k 的位置是一致的。如果數字 k 沒有出現在多數字影像中,就將掩碼中的第 k 個通道的所有像素設定為 0。另一方面,如果多數字影像包含多個第 k 個數字的實例,則第 k 個通道就將所有像素設定為 1,這是為了與多數字影像中的任一實例一致。例如,上述多數字影像的掩碼如下圖所示:

上述多數字影像的掩碼。只有數字 2、3 和 9 的通道的一些像素為 1。

為了讓事情變得簡單,M2NIST 數據集結合了 MNIST 中的數字,而且並未做任何諸如旋轉或縮放這樣的變化。M2NIST 可以保證數字不會發生重疊。

FCN 背後的思路

FCN 背後的思路非常簡單。與 CNN 類似,FCN 也級聯了卷積層和池化層。卷積層和最大池化層降低了輸入影像的太空維度,還結合局部模式生成更多抽象「特徵」。這種級聯就是所謂的將原始輸入編碼為更抽象的編碼特徵的編碼器。

在 CNN 中,編碼器後緊跟著一些全連接層,這些全連接層可以將編碼器產生的局部特徵混合到全局預測結果中,而全局預測可以告訴我們感興趣的對象是否存在。

CNN = 編碼器 + 分類器

典型的 CNN 結構。圖源:https://www.mathworks.com/solutions/deep-learning/convolutional-neural-network.html

在 FCN 中,我們想要預測掩碼。如果在一張輸入影像中有 n 類對象的話,那麽掩碼就有 n 個通道。掩碼中第 k 個通道的 r 行 c 列的像素預測輸入圖中坐標為 (r,c) 的像素屬於類別 k 的概率。這也被稱為像素級密集預測。因為每個像素屬於不同類別的概率和應該為 1,所以從通道 1 到 n 在 (r,c) 的值相加的和應該為 1。

包含數字 2、3 和 9 的 M2NIST 影像帶有通道 ID 的掩碼。通道 0 到 9 在 (r,c) 處的值相加的和等於 1。

讓我們來了解一下 FCN 是如何完成像素級密集預測的。首先,FCN 使用轉置卷積從編碼器階段逐漸擴展輸出特徵。轉置卷積可以將特徵重新分配至來源的像素位置。為了更好地理解轉置卷積,請參閱下文:https://towardsdatascience.com/up-sampling-with-transposed-convolution-9ae4f2df52d0

要強調的重要一點是轉置卷積不會撤銷卷積操作。轉置卷積使用和卷積結合多個值一樣的方式重新分配一些卷積操作的輸出,但方向相反。

轉置卷積將一個值從它來源的位置重新分配到(多個)位置。圖源:https://towardsdatascience.com/up-sampling-with-transposed-convolution-9ae4f2df52d0

使用多重轉置卷積重複擴展或進行所謂的上采樣,直到特徵的高度和寬度與輸入影像一致。這樣的操作提供了每個像素位置的特徵,並構成了 FCN 的解碼器階段。

FAN = 編碼器 + 解碼器

典型 FCN 架構。第一個階段是編碼器階段,與 CNN 相似,編碼器階段減少了輸入的高度(H)和寬度(W),並增加了通道(C)的厚度或數量。第二個階段是解碼器階段,該階段使用了轉置卷積(反卷積)對來自編碼器的特徵進行上采樣,直至其尺寸與輸入影像一致。上圖展現了每一層後的輸出 H 和 W。輸出的通道厚度(數量)並未展示出來,但可以量化表示。圖源:https://www.doc.ic.ac.uk/~jce317/semantic-segmentation.html

解碼器的輸出是形狀為 H*W*C 的體(volume),其中 H 和 W 是輸入影像的維度,C 是超參數。之後 C 通道會以像素級的方式和 n 個通道組合在一起,n 是對象類別的數量。特徵值的像素級結合會使用普通的 1*1 卷積執行。1*1 卷積常用於這種「降維」操作。

大多數情況下 C>n,所以可以將該操作稱為降維。值得一提的是,在大多數實現中,降維應用於編碼器階段的輸出而非解碼階段的輸出。這是為了減小網絡的尺寸(https://arxiv.org/pdf/1409.4842.pdf)。

無論使用解碼器對編碼器的輸出進行上采樣,然後將解碼器輸出維度降為 n 還是將編碼器的輸出維度直接降為 n 然後用解碼器對降維後的輸出進行上采樣,最終結果都是 H*W*n。然後用 Softmax 分類器以像素為部門預測每個像素所屬 n 類中任一類的概率。

舉一個具體的例子,假設編碼器的輸出是 14*14*512,如上面的 FCN 圖所示,類別數量 n 是 10。一種選擇是先使用 1*1 的卷積降低厚度。這一步操作後輸出結果變為 14*14*10,然後進行上采樣,結果變為 28*28*10、56*56*10 等等,直到得到 H*W*10 的輸出。第二個選擇是先進行上采樣,得到 28x28x512、56x56x512 等,直到 HxWx512 的輸出,再使用 1*1 的卷積降低厚度至 H*W*10。顯而易見,第二個選擇會消耗更多記憶體,因為厚度為 512 的中間輸出明顯會比第一種選擇產生的厚度為 10 的中間輸出消耗更多記憶體。

以編碼器-解碼器架構思想為基礎,我們接下來了解一下如何重新利用 CNN 的部分組件使其成為 FCN 的編碼器。

重新利用 MNIST 分類器

一般而言,FCN 是通過擴展現有 CNN 分類網絡(如 Vgg、Resnet 或 GoogleNet)來建立的。FCN 的建立不僅再利用了這些 CNN 架構,還利用了這些架構預訓練期間的權重,這顯著地減少了 FCN 的訓練時間。

原始論文(https://people.eecs.berkeley.edu/~jonlong/long)中是這樣描述如何將 CNN 轉換為 FCN 的:

通過丟棄最終的分類器層斷開每一個網絡,然後將所有的全連接層轉換為卷積層。

用於建立 FCN 的 CNN 結構很簡單:卷積層-最大池化層-卷積層-最大池化層-全連接層-全連接層。該 CNN 結構和訓練代碼在此:https://github.com/farhanhubble/udacity-connect/blob/master/mnist.ipynb。保存訓練好的網絡,這樣才可以對其進行再利用。該網絡定義如下:

為了「斷開」該網絡,我們移除了最後的分類器層 dense10。然後用 1*1 的卷積層代替了僅剩的全連接層 dense32。這是我們之前並未提及但原文中進行了的操作。在上述代碼中相當於移除了 flatten 和 dens32 層,插入了新的 1*1 卷積,並將輸出通道數設定為 32。這等同於丟棄了最後一個最大池化層 pool2 後的所有層,再添加一個 1*1 的卷積層。

用於構建 FCN 初始版本的代碼地址:https://github.com/farhanhubble/udacity-connect/blob/4408cc1e8917f37e287d09177d6e4585bfe164ff/FCN-mnist.ipynb(最後更新的代碼(https://github.com/farhanhubble/udacity-connect/)看似不同但重點一致)。

在下面的代碼片段中,通過get_tensor_by_name()提取了最後的最大池化層的輸出,然後將其饋送到輸出厚度為 32 的 1*1 卷積中。該卷積層是原始 CNN 種 dense32 層的「替代」。接下來再用 1*1 卷積將輸出厚度減少到 10。這是之前討論過的降維。

這就完成了 FCN 的編碼器階段。為了建立解碼器階段,我們需要考慮如何縮放編碼器輸出的寬度與高度以及縮放的尺度。

儘管編碼器中的卷積層和最大池化層來自於用於分類 28*28 的 MNIST 影像的 CNN,但也可以輸入任意大小的影像。輸入影像的高度和寬度對卷積層和最大池化層沒什麽影響,但對全連接層影響較大,不過因為已經斷開最後的全連接層並將所有全連接層轉換為 1*1 的卷積層,因此避免了影響。

當將 64*84*1 的 M2NIST 影像輸入到編碼器時,第一個卷積層(來自原始的 CNN)的卷積核大小 k=5,步長 s=1,輸出深度 f=8,產生的輸出大小為 60*40*8。k=2、s=2 的最大池化層將輸出大小減半為 30*40*8。k=3、s=1、f=8 的下一個卷積層產生了大小為 28*38*8 的輸出,緊跟著的下一個最大池化層再度將輸出減半為 14*19*8。

總而言之:

FCN 借用的 CNN 部分得到大小為 64*84*1 的輸入,輸出了 14*19*8 的特徵。

編碼器的下一層(dense32 的替代品)是輸出厚度 f=32 的 1*1 的卷積層。該卷積層將 14*19*8 的特徵重新結合到 14*19*32 的新特徵中。

將這些特徵的厚度減小(降維)。這用了厚度 f=10 的 1*1 卷積。所以編碼器最終輸出的特徵形狀為 14*19*10。然後通過解碼器對這些特徵進行上采樣,直到特徵變為 64*84*10。

解碼器要將 14*19*10 的特徵上采樣為 64*84*10 的特徵。

上采樣要分階段完成,以避免最終輸出(掩碼)的 ugly pattern。在(前期)實現中,把 14*19*10 的特徵上采樣到 30*40*10,然後再上采樣為 64*84*10。

用類似於卷積的轉置卷積進行上采樣,以卷積核大小 k、步長 s 和濾波器數量(厚度)f 作為參數。每一個轉置卷積的濾波器數量 f 都設定為 10,因為我們不用改變厚度。

步長取決於最終維度和初始維度的比例。對第一個轉置卷積而言,高度的比例是(30/14),寬度的比例是(40/19),二者的值都約為 2,故 s=2。在第二個轉置卷積中,該比例分別是 64/30 和 84/40,所以還是 s=2。

確定卷積核大小是一件有點棘手且相當依賴經驗的事。對第一個轉置卷積來說,k=1 將維度從 14*19*10 加倍為 28*38*10。為了得到 30*40*10,用 k=2 和 k=3 試了一下,但是都沒得到令人滿意的結果。最終,當 k=4 的時候起作用了。對第二個轉置卷積而言,卷積核大小通常為 k=6。

解碼器階段,卷積核大小(k)和步長(s)的值都經過仔細選擇。

解碼器代碼僅需調用 TensorFlow API 的兩行:

為了進行像素級的概率計算,將解碼器的輸出饋送到 Softmax 層。沿著厚度(通道)應用 Softmax。

在配備英偉達 1050 Ti GPU 的筆電電腦上用交叉熵損失函數訓練 100~400 個 epoch 後得到 FCN。一般而言,訓練 2000 個樣本僅需幾分鐘。

在這個初始設計中存在高偏差問題,該問題會在之後的迭代中解決。此外,還有一些邏輯和編程上的錯誤導致網絡采取次優行為。下圖是最佳早期設計的圖示:

早期 FCN 網絡的預測。第一列是輸入,接下來的 10 列是 10 個數字的預測掩碼。由於網絡設計的錯誤產生了白色的背景。有一些數字能清晰地分割開,另一些則模糊不清。

上述樣本的真值。第一列是輸入,剩下的 10 列是已知掩碼。

在修正了錯誤之後,該網絡能夠執行近乎完美的分割。下圖是輸出的預測值:

修改後的設計的預測結果。還是有一些模糊不清,但總體結果很好。

總結

所有的研究和實驗大概花費了兩周時間完成,並得到了可接受的結果。該問題很值得重新研究,因為小尺寸網絡可以完成成百甚至上千次的實驗,而這在之前是不可能完成的,至少在沒有大量算力的情況下是不可能完成的。

完整代碼地址:https://github.com/farhanhubble/udacity-connect/

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

?------------------------------------------------

獲得更多的PTT最新消息
按讚加入粉絲團