從單一卷積核到一疊濾波器
在第 1 篇中,我們讓單一卷積核滑過影像,看著它在找到自己被調校來偵測的圖樣(例如一道垂直邊緣)時亮起來。那一個卷積核產生一張特徵圖:一格格的分數,回答「我的圖樣在這裡出現得有多強?」。而真正的卷積層,做的事其實沒有更神秘——它只是同時跑很多個這樣的卷積核而已。
想像一層裡有 16 個不同的卷積核。每一個都各自獨立地滑過同一份輸入,各自做出自己的特徵圖。如果每張圖都是 H × W,把這 16 張疊起來,就得到一個形狀為 H × W × 16 的 3D 輸出。第三個維度——疊起來的圖張數——就是我們所說的輸出特徵通道。要記住的規則:輸出通道數,等於這一層裡卷積核的個數。
一張輸入影像送進數個卷積核,每個各自產生一張特徵圖,這些圖疊成一個 3D 輸出方塊,其深度等於卷積核的個數。
因為每個卷積核有自己的一組權重,每個就學會偵測不同的圖樣:一個可能對垂直邊緣有反應,另一個對水平邊緣,第三個對小小的亮點,第四個對某種特定的顏色轉變。把輸出體積想成一疊卡片——每張卡片是一張特徵圖,而整疊卡片就是這一層對影像的完整報告:「邊緣出現在這些地方、亮點出現在那些地方……」。
通道:輸入深度與輸出深度
在每一個空間位置上,卷積核把它的 K × K × C_in 方塊蓋在輸入上,把每個權重乘上它底下的輸入值——橫跨全部 C_in 個通道——再把這一整堆加總成單一個數字。所以即使方塊是 3D 的,它仍然把深度壓扁,每個位置只吐出一個值。因此不管輸入有多深,一個卷積核仍然只產生一張特徵圖。打個比方:每個卷積核就像一個小委員會,讀完每一個顏色層之後,投出單一一票。
一個 3 通道輸入,一個 3x3x3 的卷積核方塊在某個位置同時蓋住三個通道;相乘後加總,產生單一個輸出值。
把它具體化。取一個形狀為 32 × 32 × 3 的輸入(一張小的 RGB 影像)。單一個 3 × 3 × 3 的卷積核,以「valid」卷積(不填補、步幅 1)跑過去,產生一張 30 × 30 × 1 的特徵圖——卷積核裡的那個 3 和輸入裡的那個 3 對上了並被加總掉,這就是為什麼輸出深度是 1,而不是 3。現在把十六個這樣的卷積核疊進同一個卷積層:你會得到十六張 30 × 30 的圖,也就是一個 30 × 30 × 16 的輸出。關鍵在於,這 16 個輸出特徵通道中的每一個,都看過了全部 3 個輸入通道——沒有任何一個輸出通道是只看過紅色的。
一個卷積層中可學習權重的個數。
把公式由左讀到右。K × K 是單一卷積核的空間覆蓋範圍(3×3 就是 9 個權重)。乘上 C_in,是因為卷積核要橫跨每一個輸入通道(所以一個 3×3 卷積核作用在 3 通道輸入上,其實是 3×3×3 = 27 個權重)。乘上 C_out,是因為這一層裡有那麼多個獨立的卷積核。最後加上 C_out,是因為每個卷積核都配一個偏置項。代入 K=3、C_in=3、C_out=16:params = 3×3×3×16 + 16 = 432 + 16 = 448。重點是,這個數目完全不依賴影像的高或寬——這和第 1 篇的全連接層形成強烈對比,後者的參數量會隨像素數一起膨脹。同樣這 448 個權重,可以處理 32×32 的影像,也可以處理 4000×3000 的影像。
步幅:跨大步來縮小特徵圖
目前為止,我們的卷積核都是一次踮一個像素地前進。步幅就是卷積核在兩次計算之間跨出的步伐有多大。步幅 1 的意思是「移動一個像素、計算、再重複」——它造訪每一個位置。步幅 2 的意思是「每次移動兩個像素」,跳過每隔一個的位置。
用步幅 1 時,輸出(幾乎)和輸入一樣大——又密又細。用步幅 2 時,你沿著每個軸只在大約一半的位置上計算卷積核,所以每個空間維度大致砍半,特徵圖出來的面積大約只剩四分之一。因為卷積核的權重仍然是學來的,所以一個帶步幅的卷積層就是一個便宜、可學習的降採樣器:它一邊縮小圖、一邊抽取特徵,一次完成。
一個卷積核沿著一列滑動,步幅 1 時落在連續的位置上,步幅 2 時隔一個落一個,產生較短的輸出。
一個小例子。取一列寬度為 7 的輸入,以及一個寬度為 3 的卷積核(不填補)。步幅 1 時,卷積核的左緣可以停在第 0、1、2、3、4 行——五個落點,所以輸出寬 5。步幅 2 時,它只落在第 0、2、4 行——三個落點,所以輸出寬 3。同樣的卷積核、同樣的輸入;把步幅加倍,就把輸出幾乎砍半。
填補:保護邊界
注意到「valid」卷積讓我們寬 32 的輸入變成寬 30,寬 7 的那一列變成寬 5。把很多這樣的層疊起來,圖就一直縮——在一個很深的網路裡,你會直接把圖用光。還有第二個、比較隱微的問題:要把卷積核的中心對準角落的像素,它就會懸在邊緣外,所以角落和邊界的像素被造訪的次數,遠少於中央的像素。若不處理,邊界附近的資訊就會悄悄消失。
解法是填補:在做卷積之前,我們先用一圈額外的像素把輸入框起來——通常是零,所以叫零填補。有了一圈一像素厚的零框,卷積核現在就能把中心直接對準原本的邊緣像素了,因為外側現在有「虛擬」像素讓它去重疊。這些零對加權總和毫無貢獻,所以不會憑空造出假訊號——它們只是給卷積核一個立足之地。
一個輸入格被一圈一像素厚的零框包圍,一個卷積核以原本的邊緣像素為中心,它的一角現在重疊到一個填補用的零上。
兩種填補模式各有標準名稱。Valid(有效)表示完全不填補:卷積核只停在能完整放下的地方,所以特徵圖會縮小。Same(相同)表示「剛好填補足夠多,讓輸出維持和輸入相同的空間尺寸」(在步幅 1 時)。對一個大小為 K 的卷積核,達成這件事所需的每側填補量是 P = (K − 1) / 2:3×3 需要 P=1,5×5 需要 P=2,7×7 需要 P=3。注意這只在 K 為奇數時才會是整數——這正是為什麼卷積核大小 3、5、7 是日常標準:它們有明確定義的中心,以及乾淨、對稱的填補。
輸出尺寸公式
上面所有的東西——卷積核大小、步幅、填補——全都匯聚到一條能預測精確輸出尺寸的公式裡。這是整篇指南中最有用的一條式子;把它學到能在紙上直接用的程度。
沿某一軸的輸出尺寸。同樣的公式也獨立地適用於高度(把 W 換成 H)。
這裡 O 是輸出寬度,W 是輸入寬度,K 是卷積核大小,P 是加在每一側的填補量,S 是步幅。逐塊來讀。我們減去 K,是因為卷積核需要 K 個像素的空間才放得下,它的第一個落點才算數。我們加上 2P,是因為填補在左邊加了 P 個可用像素,也在右邊加了 P 個,拓寬了卷積核能遊走的範圍。我們除以 S,是因為我們只落在每隔 S 個的位置上,所以剩下的落點數量按 S 縮小。我們加 1,是因為最一開始的那個落點總是算數——光做除法只數了落點之間的間隔,沒數到落點本身。而我們取下取整 ⌊·⌋,是因為一個會懸出邊界的最後一步根本不會踏出去,所以任何不滿一步的零頭都被捨去。同樣這條公式也管著高度:只要把 W 換成 H。
def conv_out(W, K, P, S):
# Output size along one axis of a convolution.
# W = input size, K = kernel size, P = padding per side, S = stride
return (W - K + 2 * P) // S + 1 # // is floor division
# Three contrasting 3x3 layers on a 32-wide input:
conv_out(32, 3, 0, 1) # valid -> 30
conv_out(32, 3, 1, 1) # same -> 32
conv_out(32, 3, 1, 2) # stride-2, padded -> 16拿我們寬 32 的輸入配 3×3 卷積核,逐一走一遍。Valid(P=0、S=1):(32 − 3 + 0)/1 + 1 = 29 + 1 = 30——圖縮小了 K−1 = 2,正如我們先前看到的。Same(P=1、S=1):(32 − 3 + 2)/1 + 1 = 31 + 1 = 32——填補剛好抵銷了縮小,所以尺寸被保住(這就是 P = (K−1)/2 在發揮作用)。步幅 2 加填補(P=1、S=2):(32 − 3 + 2)/2 + 1 = 31/2 + 1 = 15.5 → 下取整成 15 → +1 = 16——這裡下取整真的咬下去了,把那半步丟掉,於是寬 32 的輸入俐落地砍半成 16。把同樣的算術用在高度上,會把一個 32×32 的輸入分別變成 30×30、32×32 和 16×16。
權重共享為何讓這一切划算
把這一切連回第 1 篇對全連接層太大的擔憂。一個全連接層讓每個輸出對每一個輸入都擁有自己私有的權重,所以它的參數量會隨影像尺寸爆炸。權重共享就是卷積給出的答案:同一個小卷積核在每個位置被重複使用,所以我們只為這個卷積核付一次代價,而不是每個像素付一次。
用數字說話。一個把 64 個輸入特徵通道映射到 64 個輸出通道的 3×3 卷積層,有 3×3×64×64 ≈ 36,864 個權重(再加 64 個偏置)——大約 37k——而且它能作用在任何尺寸的影像上。對比一個全連接層:就算只是把一張不算大的 32×32×64 特徵圖攤平(那是 65,536 個數字),再接到區區 64 個輸出,也需要 65,536 × 64 ≈ 420 萬個權重——而且影像一變大,這個數字還會跟著漲。卷積精瘦了一百倍以上,而且不依賴尺寸。
權重共享,再配上局部連接(每個輸出只看一小塊 K×K 的鄰域),換來兩樣東西。第一,可訓練性:一個只有數千而非數百萬參數的層,遠遠更好最佳化,也遠遠更不渴求資料。第二,一個內建的先驗——一個被烤進結構裡的假設:有用的視覺特徵是局部的(邊緣和紋理住在小鄰域裡),而且是與位置無關的(一道邊緣不管出現在哪裡都還是邊緣)。到處重複使用同一個卷積核,正好把這個信念編碼進去,而這對影像來說通常成立,所以它能改善對新圖片的泛化能力。