FCN 的簡單實現
本文作者Sherlock,本文首發於知乎專欄【深度煉丹】,AI 研習社獲其授權轉載。
學習了沐神的 gluon 課程,覺得裡面有關於 fcn 的課程(http://t.cn/RQI7iD7) 特別有用,於是總結一下,同時使用 pytorch 重新實現,不僅實現 gluon 教程中的部分,同時實現論文中更精細的形式。
介紹
語義分割是一種像素級別的處理圖像方式,對比於目標檢測其更加精確,能夠自動從圖像中劃分出對象區域並識別對象區域中的類別,比如下面這個效果
上面是輸入的圖片,下面是希望得到的效果,也就是希望能夠對區域進行像素級別的劃分
在 2015 年 CVPR 的一篇論文 Fully Convolutional Networks for Semantic Segmentation(https://arxiv.org/abs/1411.4038)這篇文章提出了全卷積的概念,第一次將端到端的卷積網路推廣到了語義分割的任務當中,隨後出現了很多基於 FCN 實現的網路結構,比如 U-Net 等。
數據集
首先我們需要下載數據集,這裡我們使用 PASCAL VOC 數據集(http://t.cn/RQI7TCf),其是一個正在進行的目標檢測, 目標識別, 語義分割的挑戰,我們可以進行數據集的下載(http://t.cn/RQI7nhr)。
下載完成數據集之後進行解壓,我們可以在 ImageSets/Segmentation/train.txt 和 ImageSets/Segmentation/val.txt 中找到我們的訓練集和驗證集的數據,圖片存放在 /JPEGImages 中,後綴是 .jpg,而 label 存放在 /SegmentationClass 中,後綴是 .png
我們可以可視化一下
首先輸出圖片的大小,左邊就是真實的圖片,右邊就是分割之後的結果
然後我們定義一個函數進行圖片的讀入,根據 train.txt 和 val.txt 中的文件名進行圖片讀入,我們不需要這一步就讀入圖片,只需要知道圖片的路徑,之後根據圖片名稱生成 batch 的時候再讀入圖片,並做一些數據預處理。
數據預處理
可能你已經注意到了前面展示的兩張圖片的大小是不一樣的,如果我們要使用一個 batch 進行計算,我們需要圖片的大小保持一致,在前面使用卷積網路進行圖片分類的任務中,我們通過 resize 的辦法對圖片進行了縮放,使得他們的大小相同,但是這裡會遇到一個問題,對於輸入圖片我們當然可以 resize 成任意我們想要的大小,但是 label 也是一張圖片,且是在 pixel 級別上的標註,所以我們沒有辦法對 label 進行有效的 resize 似的其也能達到像素級別的匹配,所以為了使得輸入的圖片大小相同,我們就使用 crop 的方式來解決這個問題,也就是從一張圖片中 crop 出固定大小的區域,然後在 label 上也做同樣方式的 crop。
使用 crop 可以使用 pytorch 中自帶的 transforms,不過要稍微改一下,不僅輸出 crop 出來的區域,同時還要輸出對應的坐標便於我們在 label 上做相同的 crop
下面我們可以驗證一下隨機 crop
上面就是我們做兩次隨機 crop 的結果,可以看到圖像和 label 能夠完美的對應起來
接著我們根據數據知道裡面有 21 中類別,同時給出每種類別對應的 RGB 值
接著可以建立一個索引,也就是將一個類別的 RGB 值對應到一個整數上,通過這種一一對應的關係,能夠將 label 圖片變成一個矩陣,矩陣和原圖片一樣大,但是只有一個通道數,也就是 (h, w) 這種大小,裡面的每個數值代表著像素的類別
定義完成之後,我們可以驗證一下
可以看到上面的像素點由 0 和 1 構成,0 表示背景,1 表示 飛機這個類別
接著我們可以定義數據預處理方式,之前我們讀取的數據只有文件名,現在我們開始做預處理,非常簡單,首先隨機 crop 出固定大小的區域,然後使用 ImageNet 的均值和方差做標準化。
```
fcn 模型
fcn 模型非常簡單,裡面全部是由卷積構成的,所以被稱為全卷積網路,同時由於全卷積的特殊形式,因此可以接受任意大小的輸入,網路的示意圖如下
對於任何一張輸入圖片,由於卷積層和池化層的不斷作用,得到的輸出形狀會越來越小,但是通道數會越來越大,比如 ResNet18,會將輸入的長寬減小 32 倍,由 3x244x244 變成 512x7x7,也就是上圖的第一部分,得到的特徵圖會特別小,最後通過一個轉置卷積得到一個和輸入形狀一樣的結果,這裡我們引入了轉置卷積的概念,下面我們講一講什麼是轉置卷積
轉置卷積
我們首先可以看看下面的動畫
第一張就是我們常說的卷積的效果,而轉置卷積就是下面這個操作,相當於卷積的逆過程,將卷積的輸入和輸出反過來,卷積的正向傳播相當於圖片左乘一個矩陣 c,反向傳播相當於左乘 $c^T$,而轉置卷積的正向過程相當於左乘 $c^T$,反向過程相當於左乘 $(c^T)^T = c$,詳細的推導可以看看論文(https://arxiv.org/pdf/1603.07285.pdf)。
而轉置卷積的計算公式也非常簡單,對於卷積
轉置卷積就是將輸入和輸出反過來,即
如果我們希望輸出變成輸入的兩倍,那麼 stride 取 2,kernel 和 padding 可以對應著取,比如 kernel 取 4,那麼 padding 就取 1
在 pytorch 中轉置卷積可以使用 torch.nn.ConvTranspose2d() 來實現,下面我們舉個例子
可以看到輸出變成了輸入的 2 倍
模型結構
最簡單的 fcn 前面是一個去掉全連接層的預訓練網路,然後將去掉的全連接變為 1x1 的卷積,輸出和類別數目相同的通道數,比如 voc 數據集是 21 分類,那麼輸出的通道數就是 21,然後最後接一個轉置卷積將結果變成輸入的形狀大小,最後在每個 pixel 上做一個分類問題,使用交叉熵作為損失函數就可以了。
當然這樣的模型是特別粗糙的,因為最後一步直接將圖片擴大了 32 倍,所以論文中有一個改進,就是將網路中間的輸入聯合起來進行轉置卷積,這樣能夠依賴更多的信息,所以可以得到更好的結果,可以看看下面的圖示
fcn-32s 就是直接將最後的結果通過轉置卷積擴大 32 倍進行輸出,而 fcn-16x 就是聯合前面一次的結果進行 16 倍的輸出,fcn-8x 就是聯合前面兩次的結果進行 8 倍的輸出,我們用上圖中 fcn-8x 舉例,就是先將最後的結果通過轉置卷積擴大 2 倍,然後和 pool4 的結果相加,然後在通過轉置卷積擴大 2 倍,然後和 pool3 的結果相加,最後通過轉置卷積擴大 8 倍得到和輸入形狀一樣大的結果。
bilinear kernel
通常我們訓練的時候可以隨機初始化權重,但是在 fcn 的網路中,使用隨機初始化的權重將會需要大量的時間進行訓練,所以我們卷積層可以使用在 imagenet 上預訓練的權重,那麼轉置卷積我們使用什麼樣的初始權重呢?這裡就要用到 bilinear kernel。
我們可以看看下面的例子
可以看到通過雙線性的 kernel 進行轉置卷積,圖片的大小擴大了一倍,但是圖片看上去仍然非常的清楚,所以這種方式的上採樣具有很好的效果
下面我們使用 resnet 34 代替論文中的 vgg 實現 fcn
這裡我們去掉最後的 avgpool 和 fc 層,使用 list(pretrained_net.children())[:-2] 就能夠取到倒數第三層
下面我們開始定義我們的網路結構,就像上面顯示的一樣,我們會取出最後的三個結果進行合併
接著我們定義一些語義分割常用的指標,比如 overal accuracy,mean IU 等等,下面這個是參考 wkentaro 的 pytorch-fcn(http://t.cn/RQIZIH8) 得到的
訓練
可以看到,我們的模型在訓練集上的 mean IU 達到了 0.64 左右,驗證集上的 mean IU 達到了 0.53 左右,下面我們可視化一下最後的結果
可以看到,通過訓練,模型已經有了基本效果,但是離論文中的效果有差距,有可能是訓練的問題,更多的原因應該是沒有使用 caffe model 中的 vgg 模型,這裡特別提醒一下,如果要使用 caffe model,那麼圖像預處理的時候要是 BRG 的格式,同時大小是 0 ~ 255,減去均值。
另外代碼中使用的 mxtorch 可以在我的 github 主頁看到:http://t.cn/RQIZlDB。
※用基於 TensorFlow 的強化學習在 Doom 中訓練 Agent
TAG:AI研習社 |