Day 10 我們用 PyTorch 介紹過 MNIST 手寫數字辨識。當時用的簡單模型就是 Convoluation Neural Network (CNN),常用在抽取圖像特徵,搭配 pooling layer 減少 overfitting 的機會。等了這麼久,終於要來真正認識 CNN 的架構以及他跟圖像的結合了。
一張圖片其實挺大的。就算是 MNIST 裡的手寫數字圖,28 x 28 pixels 還只有一個 color channel,就已經有 784 個不同的特徵了。RNN 在每個 timestep 接收的文字轉成 word vector 可能不過 300 的大小,一張這麼小的圖片當作 model 的 input 就已經是文字的兩三倍負擔,遑論正常大小和 RGB 3 個 color channel 的 image input 會有多驚人!
如果想要拿這麼大的 image data 來訓練 neural network,那 fully-connected layer 肯定是招架不住,因為 hidden layer 每個 node 都要連接 input 的每個維度,parameter 的數量會非常龐大。
但仔細想一下,每個 hidden node 目標是學習到對目標任務有用的某個特徵,那有需要讓每個 hidden node 都連接到整張圖片的所有角落嗎?假設我們在做車的種類判別,那可能會需要提取車的輪廓、車的標誌、車的顏色等等特徵,但這些特徵通常都只是整張圖片的一小部分。也就是說,我們需要一個架構能夠提取局部特徵就好,來減低 network 的負擔。
CNN 就是這樣一個適合圖像的 model。讓我們來看一看 CNN 怎麼提取局部特徵,並有效降低所需要的 parameter 數量。
一個典型的 CNN 會有這些 layer:
2 - 4 可以重複很多層,中間加入 normalization layer 亦可。
在介紹重點層之前,要知道 CNN 跟一般 fully-connected netowrk 不一樣的是他的 3D input 讓中間幾層也都是 3D 的形狀,weight x height x depth
。Depth 可以想成 feature 數量,每一個 feature 都來自上一層 weight x height
的某個局部以及 depth
的全部。這在思考時需要稍加留意。
—— 左:一般 neural network。右:CNN。[2]
接下來主要介紹 convolutional layer 和 pooling layer。
每層 conv layer 都由幾個 filter 或稱 kernel 作用在 input 上形成一個 activation map 作為下一層的 feature。每個 filter 會掃過整個 input 一一提取局部特徵,且 filter 的 depth 會和 input 一樣。
我們拿 32 x 32 x 3 的 input 和 5 x 5 x 3 的 filter 為例,一個 filter 和一張 image 做 convolution 大概是這樣:
—— Convolution with 1 filter。[1]
一個 filter 其實就是一組 weights W,將這個 input 局部 的每個 node 連接到 hidden node,進行 的運算( 是 element-wise multiplication)。其實跟之前見過的 fully-connected layer 很像,只是每個 hidden node 連接到更小的 input 範圍。我們稱這個局部 為這個 hidden node 的 receptive field。
而在 input 邊緣的部分,因為比較少被重複掃過,整個 activation map 也會小一點。以這組的大小來說 output 會是 28 x 28。
不懂為什麼的話可以拿紙畫一下 7 x 7 的 image 有幾個 3 x 3 大小的小正方形,也就會觀察出 output 大小會是 , 是 input 大小,f 是 filter 大小。
這邊可以觀察到整個 filter 是重複利用的,所以一個 5 x 5 x 3 的 filter 只會有 75 個 weights!跟之前相比有效率非常多。
為了對每個局部學習不一樣的特徵,我們會有很多不同的 filter 重複一樣的步驟:
—— Convolution with multiple filters。[1]
假如有 k 個 filter,就會形成 k 個 activation map,下一層的 depth 也是 k。這一層的 parameters 不算 bias 的話只會有 75 k 個,相當節能減碳。
Filter 掃過去的過程可以有些不同設定,例如從原本一次移一格變成兩格,這個大小稱為 stride。
而在邊角的時候也不一定要 filter 在 input 上,可以選擇讓 filter 超過邊界,而在超過的 input 加 padding。Padding 時還可以選擇要都填 0、延伸邊界、或其他一些選擇。
—— Zero-padding。[1]
另外還有一些不常見的設定,有興趣可以參考 PyTorch Conv2d。可以視任務特性選擇這些設定,或是用預設大概也不會差太多。不過記得這些設定也會影響 output size 的算法!
Pooling 通常出現在 activation 之後,進入下個 convolutional layer 之前。為了減少 parameters 數量以避免 overfitting,會先用 pooling 把局部結果集合起來,決定更大局部的大致特徵。另外 pooling 是 depth-wise 的,也就是每個 depth channel 不會和其他 channel 互動,自己做自己的 pooling。
以 max pooling 來說,假設 filter size 是 2 x 2,stride 是 2,那大概會長這樣:
—— Max pooling。[1]
原本 input 每 2 x 2 個局部結果取 max 當作大區域的代表,而 output 也減少了 2 x 2 倍。注意到 pooling layer 其實沒有 parameter 需要訓練,可以把它想成單純的 function。
除了 max pooling,另外常見的還有 average pooling 取平均。那麼兩者要怎麼選擇呢?因為 max pooling 只選了最大的 activation,他會喪失比較多訊息,但如果你的任務主要就是只想保留顯著的特徵,那麼 max pooling 是個好選擇。反之如果你的主要目標需要藉由提取比較完整的全局特徵來達成,那麼 average pooling 可能就會表現比較好。
介紹完後,應該大致知道為什麼 convolutional layer 可以提取局部特徵而且能減少 network parameter,還有 pooling layer 可以藉由篩選局部最有用的特徵縮減 output 大小以避免 overfitting。
到這邊已經可以建立自己的 network 架構了。其實要建立一個還不錯的 CNN 可以很無腦,大致上就循環 CONV-RELU-POOL
接到 FC
:
INPUT - [(CONV-RELU) * N - POOL?] * M - (FC - RELU) * K - SOFTMAX
早期 CNN 的架構設計反而比不上把他建得更深來得更能讓 performance 飛快提升。之後 performance 比較難快速提升後,才有些比較有趣的架構設計,例如之前提過 ResNet 的 residual,和 GoogleLeNet 的 inception module 等等。有興趣可以看看延伸閱讀的連結。
Network 中每一層都在抽取不同 level 的 abstract feature,這個之前應該有提到。前面幾層會捉摸靠近圖像本身的 generic feature,例如邊角、明暗、線條角度等等。後面幾層慢慢抽絲剝繭,理出跟預測任務最相關的 specific feature,例如人的五官、車的輪胎等等。
Visualize 一下每一層的 output,就能證明這件事:
—— 每層學到不同 level 的 feature。[4]
而把 network 建深而不是建廣的理由,就是因為 higher-level feature 很多其實都來自相同的 lower-level feature,所以一層一層的提取讓這些 feature 可以重複使用,增加效率。相反的,一個很廣但沒幾層的 network,想必每一層都重複提取了很多 low-level 特徵,大部分時候就比較沒效率了。