JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

池化、感受野,組成一整個 CNN

加入池化、看著感受野逐層變大,並組裝出你的第一個完整影像分類 CNN。

池化:總結鄰域

上一篇你建好了一個卷積層:一疊濾波器在影像上滑動,為每個濾波器產生一張 特徵圖,圖上數值高就代表「我要找的圖樣在這裡出現了」。池化層就是常常緊接在後、安靜又簡單的搭檔。它唯一的工作,就是把每張特徵圖縮小:把圖上每一個小窗格總結成一個數字。讓一個很小的窗格(通常是 2x2)掃過整張圖,用一個摘要值取代整個窗格,再往前移。這裡完全沒有可學習的權重——池化遵循一條固定規則,例如「取最大值」或「取平均值」。正因如此,我們稱它為「無參數的降採樣器」。

一個 2x2 窗格掃過一張特徵圖,把每個窗格壓成單一輸出值,產生一張高與寬都減半的特徵圖。

左側是一格格的活化值,其中一個 2x2 窗格被標示出來;箭頭把該窗格對應到右側較小網格中的一個格子。

為什麼要做這種總結?有三個理由,而且它們互相強化。第一,縮小特徵圖可以省記憶體與計算:把高和寬各減半,32x32 的圖就變成 16x16,後面每一層要處理的數字就少了四倍。第二,它換來對微小位移的穩健性——如果讓某個格子發亮的邊緣或紋理移動了一個像素,它鄰域的摘要往往不變,網路因此不再執著於精確的像素位置。第三,它讓後面的層用同樣大小的小核就能看到原圖更大的範圍,因為每個池化後的格子已經代表了前一層的一塊 2x2 區域。第三點我們講到感受野時會講清楚。

最大池化與平均池化

經典的規則有兩種,而感受其差別最乾淨的方法,就是把兩者套在「完全相同」的窗格上跑一次。取某張特徵圖上的一塊 2x2 區域,裡面是 1、7、3、2 這四個值。最大池化保留窗格裡「最大的單一值」;平均池化保留所有值的「平均」。同一塊區域,兩種摘要——而這個選擇會改變哪一種資訊得以存活。

# Pooling is applied per-channel; this is ONE 2x2 window of ONE feature map.
window = [[1, 7],
          [3, 2]]

# Max pooling: keep the single strongest activation in the window.
max_pool = max(1, 7, 3, 2)          # -> 7

# Average pooling: take the mean of all four values.
avg_pool = (1 + 7 + 3 + 2) / 4      # -> 3.25
同一個窗格在最大池化下得到 7,在平均池化下得到 3.25。

把這兩個數字讀成對兩個不同問題的回答。最大池化的 7 回答的是「這個特徵在這塊區域裡有沒有在『任何地方』出現過?」——它抓住最強的那一筆證據,把其餘丟掉,所以即使三個鄰居都很弱,單一的強反應仍能存活。平均池化的 3.25 回答的是「這個特徵在整塊區域『平均而言』有多強?」——它保留了整體強度、也比較平滑,因為每個值不論強弱都有一票。注意最大值被那個孤單的 7 拉高,而平均值被周圍三個小數字拖低。

同一個 2x2 窗格的兩種總結方式:最大池化保留峰值(7),平均池化保留平均(3.25)。

一塊內含 1、7、3、2 的 2x2 區域,分別餵入兩個輸出:標示 7 的最大值分支與標示 3.25 的平均值分支。

實務建議:在分類骨幹網路的池化層裡,最大池化是主流,因為要辨認一個物件,你多半在意某個關鍵特徵「有沒有出現過」,而不是它的平均亮度。平均池化則出現在需要平滑、整體性摘要的地方——最有名的就是放在網路最末端的全域平均池化,正是下一節要講的。但有一點兩者都一樣:池化永遠是「逐通道」獨立進行的。一個 16x16x64 的張量用 2x2 窗格池化後會變成 8x8x64——這 64 個通道各自被分開總結,通道數完全不變。

全域平均池化:從特徵圖到向量

在 CNN 的末端,你手上是一疊特徵圖,但分類器需要的是一串「攤平的數字」,才能加權成各類別的分數。全域平均池化(GAP)就是這座現代橋樑。它是把平均池化推到極致:窗格不是 2x2,而是「整張」特徵圖。所以 GAP 把每一整張圖壓成一個數字,把 H x W x C 的張量變成一個長度為 C 的向量。每個通道一個數字,空間排列完全不留。

z_c = \frac{1}{H \cdot W} \sum_{i=1}^{H} \sum_{j=1}^{W} A_c(i,j)

拆解一下:A_c 是第 c 張特徵圖(一整片二維的活化值網格),A_c(i,j) 是該圖在第 i 列、第 j 行這個空間位置上的值。雙重加總把圖上每一個值都加起來,而 H·W 是你加總的位置數量——也就是格子總數,所以除以它就把總和變成單純的平均。結果 z_c 是一個純量:通道 c 貢獻給輸出向量的那「一個」數字。對全部 C 個通道都做一次,你就得到一個長度為 C 的向量。具體地說,如果一張 7x7 的圖有 49 個活化值、總和為 98,那麼 z_c = 98 / 49 = 2.0。空間幾何沒了;存活下來的是「這個通道的特徵,平均而言在影像各處出現得有多強?」

它為何取代了舊式的輸出頭。經典的結尾是「把整個張量攤平成一條長向量,再餵給一個很大的全連接層」。把 7x7x512 攤平會得到 25,088 個數字,把它接到哪怕只是中等大小的層,都要花上數百萬個權重——龐大的參數量很容易過擬合。GAP 沒有任何參數,因此大幅削減了參數預算,連帶削掉伴隨而來的過擬合。它也能接受任意輸入尺寸:攤平要求固定的 H 和 W(像素變多就讓向量變長、把全連接層弄壞),但 GAP 不管接到多大的網格都取平均,永遠剛好吐出 C 個數字。最後,它還可解釋——因為每個通道變成一個直接的數字,你可以把每一個特徵通道讀成最後一層「支持或反對某類別」時所衡量的一筆證據。

現代標準輸出頭:一疊 7x7x512 的特徵圖經 GAP 變成 512 維向量,再經一個線性層加上 softmax,輸出各類別機率。

一疊 512 張特徵圖以箭頭指向一條由 512 個值組成的直條,再指向一小排類別機率。

把這個標準輸出頭走一遍:假設最後一個卷積區塊輸出一個 7x7x512 的張量。GAP 把這 512 張圖(各 49 格)各自平均成一個數字,產生一個 512 維向量。一個線性層再把這 512 個數字映射到例如 10 個類別分數,softmax 則把分數變成總和為 1 的機率。這條俐落的 GAP -> 線性 -> softmax 鏈,就是整個分類器的輸出頭——也正是我們在最後一節要接到完整流程上的那個結尾。

感受野:每個神經元看到多大範圍

有個問題決定了一個網路「到底有可能辨認出什麼」:當網路深處的某個單元算出一個數字時,原始輸入影像中有多大範圍「被允許」去影響它?這塊範圍就是它的感受野。第一層的單元可能只看到 3x3 一小塊像素;第十層的單元實際上可能看到影像的大半。如果一個神經元的感受野只蓋到一根鬍鬚,它永遠不可能偵測到一整隻貓——所以「擴大感受野」正是網路從看見紋理,升級到看見物件的途徑。

核心洞見在於「速度」。堆疊小的卷積核會讓感受野「逐步」擴大——每多一層 3x3 就加上一圈薄薄的上下文。但池化與步幅會讓它「快速」擴大,因為一旦降採樣,之後每一步的每個格子就涵蓋了原圖兩倍的範圍。所以有兩個旋鈕在控制視野放大的速度:你堆了多少個小卷積(慢、細緻的成長),以及你池化或加步幅多少(快、粗放的成長)。

兩個堆疊的 3x3 卷積:單一輸出格子回溯到中間層的一塊 3x3 區域,再回溯到輸入的一塊 5x5 區域。

三層堆疊的網格;最上層的一個格子連到中層的一塊 3x3 區塊,該區塊再連到最底層輸入的一塊 5x5 區塊。

RF_l = RF_{l-1} + (K_l - 1)\,\prod_{m=1}^{l-1} S_m

解讀這條遞迴式:RF_l 是經過第 l 層後的感受野大小,RF_{l-1} 是經過上一層後的大小。K_l 是這一層的核大小,所以 (K_l − 1) 是這層比起 1x1 那種掃法多伸到的輸入格數。所有先前步幅的乘積 S_1 到 S_{l−1} 是關鍵的放大因子:每個更早的池化或步幅層(步幅 > 1)都會把這個因子撐大,這正是為什麼一個降採樣層會讓「之後」每一層的感受野長得更快。從 RF_0 = 1(單一輸入像素)出發,步幅乘積為空積、等於 1。走兩個堆疊的步幅 1、3x3 卷積:第 1 層得到 RF_1 = 1 + (3−1)·1 = 3;第 2 層得到 RF_2 = 3 + (3−1)·1 = 5。圖中的 5x5 就在這裡。再加第三個一樣的卷積,就得到 5 + 2·1 = 7——這正是著名的結果:兩個堆疊的 3x3 卷積等效於一個 5x5 核,三個等效於一個 7x7,但用的權重更少。這就是為什麼一疊便宜的 3x3 卷積取代了單一的大核。

等變、不變,以及池化帶來什麼

回想第 1 篇的平移等變性:因為卷積用了權重共享——同一個濾波器套用在每個位置——所以輸入若平移,特徵圖就跟著一樣地平移。把貓往右移十個像素,它的「耳朵偵測器」反應也往右移十個像素。這就是等變:輸出會「追蹤」輸入的位置。對一個偵測零件的層來說這是對的行為,但分類器最終想要更強的東西——不管貓在哪,它都該回答「貓」。那個更強的性質叫不變性:輸入平移時,輸出不該改變。

池化正是把網路從等變「推」向不變的那股力量。想像一個最大池化窗格,最強的活化值落在左上角的格子。把窗格裡的特徵移動一個像素,讓它改落在右上角的格子:最大值還是同一個值,所以池化後的輸出沒變。這一層因此對「特徵在窗格內到底落在哪」變得局部不敏感——這正是物件分類器想要的微小位移穩健性。疊上好幾級池化,這些小小的不變性就會累積;再用全域平均池化收尾,它對整張圖取平均,位置資訊幾乎被完全丟棄,留下每個通道一份近乎與位置無關的摘要。

全部疊起來:完整的 CNN 流程

現在把零件組成標準架構。重複的單位是一個「區塊」:[ 卷積層 -> ReLU ->(可選的再一個卷積 -> ReLU)-> 池化 ]。疊幾個這樣的區塊,越往深處走,就一邊加寬特徵通道的數量,一邊用池化縮小高與寬。等空間網格變小了,就用第 3 節的輸出頭收尾:全域平均池化 -> 線性 -> softmax。這一個模式,重複再戴上 GAP 輸出頭,就是一大家族影像分類器的骨幹。

整體的形狀是一座金字塔:空間解析度下降,語意深度上升。一開始特徵圖又大又淺(像素多、通道少,每個通道是簡單的類邊緣特徵);到了深處則又小又深(像素少、通道多,每個通道是豐富的抽象概念)。而有一個成分讓這種堆疊變得有意義——就是先前 AI 課程裡的 ReLU 非線性,它在每個卷積之後把負值歸零。少了它,疊卷積會塌縮成單一個線性運算;有了它,網路才能學到真正的非線性圖樣。你往後傳遞的每一張特徵圖,都是「卷積再 ReLU」的輸出,而非生的卷積輸出。

# A small CIFAR-style classifier. Shapes shown as (H, W, C).
x = image                      # (32, 32, 3)

# Block 1: two 3x3 convs (pad='same'), then halve H and W.
x = relu(conv3x3(x, 32))       # (32, 32, 32)
x = relu(conv3x3(x, 32))       # (32, 32, 32)
x = maxpool2x2(x)              # (16, 16, 32)

# Block 2: widen channels to 64.
x = relu(conv3x3(x, 64))       # (16, 16, 64)
x = relu(conv3x3(x, 64))       # (16, 16, 64)
x = maxpool2x2(x)              # ( 8,  8, 64)

# Block 3: widen channels to 128.
x = relu(conv3x3(x, 128))      # ( 8,  8, 128)
x = relu(conv3x3(x, 128))      # ( 8,  8, 128)
x = maxpool2x2(x)              # ( 4,  4, 128)

# Head: collapse space, then classify into 10 classes.
x      = global_average_pool(x)  # (128,)  one number per channel
logits = linear(x, 10)           # (10,)
probs  = softmax(logits)         # (10,)  class probabilities (sum to 1)
通道由 3 -> 32 -> 64 -> 128 加寬,空間由 32 -> 16 -> 8 -> 4 縮小:用程式碼呈現的金字塔。
完整流程:重複的卷積-ReLU-池化區塊構成一座金字塔(空間縮小、通道變多),最後戴上 GAP -> 線性 -> softmax 的輸出頭。

由左到右:一張輸入影像,接著是逐漸變小但變深的區塊,最後是一個向量與一排類別機率長條。

ReLU,是每個卷積之後套用的非線性:正值原樣通過、負值夾到零,讓堆疊的卷積能學到非線性圖樣。

一張圖:負輸入時平貼於零,正輸入時沿對角線 y = x 上升。

退一步看,你已經建好了一個完整的影像分類 CNN:卷積偵測特徵、ReLU 加入非線性、池化縮小特徵圖並換來位移穩健性,同時感受野悄悄變大,直到深層單元能看見整個物件,最後 GAP 輸出頭把末端特徵圖變成類別機率。還剩兩個誠實的缺口。這個網路正確但昂貴——第 4 篇《更聰明、更輕量的卷積》會在不損失準確率的前提下,把卷積變得便宜得多。而我們只描述了前向傳遞——第 5 篇《訓練深層 CNN 並往回堆得更深》會示範如何真正學到這些濾波器權重,以及如何把架構往更深處堆疊而不至於崩潰。