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

訓練深層 CNN,並把解析度建回來

用殘差塊與批次正規化訓練數十層的深層網路,再把特徵上採樣回原始解析度。

為什麼更深不一定更好

到目前為止你已經能組出一個能用的 CNN:堆幾層 卷積層、適時做池化、攤平、然後分類。下一步很誘人:就是「更多」——二十層、五十層、一百層。直覺看起來無懈可擊:層數越多代表容量越大,所以網路至少應該擬合得一樣好。但在 2010 年代的大半時間裡,深度學習就卡在這裡。天真地堆上幾十層並不會給你更好的網路,反而會給你一個更難訓練的網路——有時候根本訓不起來。

第一個元兇是梯度。訓練靠的是反向傳播:把誤差訊號從損失函數一路往回送過每一層,途中乘上每一層的局部導數。當你把幾十個這樣的乘法串在一起,乘積往往會漂向兩種災難之一。如果每層的因子大多小於一,訊號就會以幾何速度縮小,傳到最前面的層時幾乎變成零——這就是梯度消失。如果它們大多大於一,訊號就會爆掉——這就是梯度爆炸。無論哪一種,最靠近輸入的層都收不到能用的回饋,於是它們永遠學不會那些上層全都仰賴的低階特徵(邊緣、紋理)。

第二個元兇更隱晦,也是真正震撼整個領域的發現:退化(degradation)。研究者發現一個 56 層的「樸素」網路,其訓練誤差竟然比 18 層的還要——不只是測試誤差更差,而是在它正在訓練的那批資料上誤差就更差。這是關鍵的判別線索。一個過擬合的模型會把訓練集背起來,所以訓練誤差極小;但這裡更深的模型連把訓練用的 特徵圖 擬合好都做不到。多出來的層並不是強到壓過資料,而是擋住了最佳化的去路。

所以本軌道的最後一篇,其實是在講兩個穩定器,是它們聯手讓「深度」終於開始有回報:殘差連接,它給梯度一條清楚的回家路、並讓多加的層預設無害;以及批次正規化,它讓在網路裡流動的數值保持乖巧。一旦能訓練深層網路,我們就回過頭學怎麼把解析度建回來——最後再把現代 CNN 當成一個整體收尾。數學會在後面幾節登場;在這裡你只要先記住一幅圖像:深度,首先是個訓練問題,其餘都在其次。

殘差塊:學習差異

這就是打破深度障礙的解法,而它簡單到幾乎有點難為情。與其要求一疊層從輸入算出一個全新的輸出,不如只要求它算出對輸入的改變量,然後把這個改變量加回去。這個塊的輸出,就是它的輸入加上那些層決定要調整的部分。這些層學的是殘差——進來的東西和該出去的東西之間的差異——而不是從零開始學整件事。

想像編輯文章。從一張白紙重寫一段話很難;但在既有草稿上批改——「這個子句收緊一點、那個字刪掉」——就容易多了,而且如果草稿已經完美,你只要寫「不用改」。殘差塊運作方式完全一樣:它預設「什麼都不做」的行為很便宜,它只把力氣花在真正有幫助的修正上。

一個殘差塊:輸入 x 流過一小疊 conv/BN/ReLU 層 F(x),同時一條平行的捷徑把 x 原封不動地帶過來;兩者在最後的激活前相加。

圖示輸入 x 分成兩條路徑:一條主路徑經過兩個標示為 F(x) 的卷積層,一條彎曲的捷徑箭頭標示為恆等,兩者在一個加號處會合後送入 ReLU。

y = \mathcal{F}(x, \{W_i\}) + x

這樣讀:這個塊的輸出 y 等於把變換 𝓕 作用在輸入 x 上,再把原本的 x 直接加回去。各符號:x 是進入這個塊的特徵圖;𝓕(x, {W_i})殘差函數——一小疊 conv → 批次正規化 → ReLU → conv 的層,其可學習權重集合記為 {W_i};而 + x恆等捷徑(或稱跳接),一條把輸入原封不動往前送的線。關鍵在於:𝓕 再也不必重現 x——它只要產出修正量就好。如果一個塊最好的選擇是什麼都別做,那些層只要把 𝓕 → 0,y = x 就免費掉出來。這就是為什麼多疊幾個殘差塊永遠不會更糟:一疊恆等映射隨時都能當作退路。

第二個好處正是拯救梯度的關鍵。把這個塊對它的輸入微分,那個加法會乾淨俐落地拆成兩項。

\frac{\partial y}{\partial x} = 1 + \frac{\partial \mathcal{F}}{\partial x}

這個塊的局部導數是 1(來自 +x 捷徑)加上 ∂𝓕/∂x(來自那些層)。那個常數 1 就是決勝點。在樸素網路裡,如果某層的導數很小——比如 0.01——那麼串 50 層就會把梯度乘上 0.01⁵⁰,這跟零沒兩樣:訊號死了。有了殘差,每個塊貢獻的因子接近 1 + 0.01 = 1.01;跨 50 個塊就是 1.01⁵⁰ ≈ 1.6——梯度傳到第一層時幾乎毫髮無傷。這條捷徑就像一條梯度高速公路:反向傳播永遠能沿著 +x 那些線一路直下,完全繞過那些會掐住訊號的層。梯度消失與退化兩個問題同時迎刃而解。

有一個實務上的小皺褶。+x 這個加法要求 x 和 𝓕(x) 形狀相同,但殘差階段常常會改變通道數與空間尺寸(例如通道加倍、解析度減半)。當捷徑的形狀對不上時,你就用一個 1×1 卷積——就是第 4 篇那個便宜的通道混合器——來重塑它,通常配上相符的步幅,好讓被跳過的 特徵圖 在相加前對齊 𝓕(x)。這只是在捷徑上加一個小轉接頭,並沒有改變核心想法。

def residual_block(x, conv1, bn1, conv2, bn2, shortcut=None):
    # main path F(x): conv -> BN -> ReLU -> conv -> BN
    out = relu(bn1(conv1(x)))
    out = bn2(conv2(out))

    # identity shortcut; reshape with a 1x1 conv only if shapes differ
    identity = x if shortcut is None else shortcut(x)  # shortcut = 1x1 conv

    out = out + identity        # the +x that builds the gradient highway
    return relu(out)            # activation applied AFTER the addition
一個基本的殘差塊。注意加法發生在最後的 ReLU 之前,而捷徑除非形狀需要轉接,否則什麼都不做。

批次正規化:讓激活值穩定

殘差連接給了梯度一條回家的路,但訓練的痛還有第二個來源:當資料往前流,網路內部激活值的尺度會漂移。訓練初期權重是隨機的,所以某一層的輸出可能聚在 0.1 附近,下一層卻聚在 50 附近;每一層都在試著學習,但它自己輸入的統計量卻一直在腳下變動。批次正規化直接對症下藥:在每一層把激活值重新標準化,讓它們維持在一個穩定、可預測的範圍。

對 CNN 來說最關鍵的細節是:哪些東西被放在一起做正規化。批次正規化是逐通道運作的:對於某個 卷積層 的每一個 特徵通道,它把該通道在整個小批次裡(而且對卷積層而言,還包含每一個空間位置)的所有激活值蒐集起來,對這一池做正規化。一個學會偵測(比如說)垂直邊緣的通道,會有它自己的平均值與變異數,跟旁邊那個偵測色塊的通道互不相干。所以一個有 64 個通道的層,會學到 64 套各自獨立的正規化。

批次正規化用跨小批次匯集的統計量把每個通道標準化,再套上可學習的縮放 γ 與平移 β,讓網路想的話可以把尺度調回來。

一批特徵圖的圖示,其中一個通道在所有樣本中被標示出來;箭頭顯示計算該通道的平均值與變異數、將其正規化、再乘上 gamma 並加上 beta。

\hat{x} = \frac{x - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}}, \qquad y = \gamma\,\hat{x} + \beta

把這條公式一個符號一個符號拆開看。x 是正在被正規化的那個通道裡的單一激活值。μ_B 是該通道激活值在目前小批次 B 上的平均值,σ_B² 是它們的變異數——兩者都針對每個批次重新量測。減去 μ_B 把數值重新置中到零;除以標準差 √(σ_B²) 把它們重新縮放到大約單位散布。ε 是一個極小的常數(像是 10⁻⁵),加在根號裡純粹是為了當某個通道剛好是常數時,你也永遠不會除以零。結果 就是標準化後的激活值:平均 0、變異數 ≈ 1。

接著是巧妙的後半段:y = γ x̂ + β。這裡的 γ(縮放)與 β(平移)是可學習參數,每個通道一對,跟任何權重一樣由反向傳播訓練。為什麼要把剛剛做的事撤銷掉?因為硬把每個激活值壓成平均 0、變異數 1,對某個特定通道而言可能是錯的——也許 ReLU 在輸入往正向平移時效果最好,或某個特徵在散布更寬時才真正有用。γ 與 β 讓網路自己選它要的尺度與中心;在極端情況下,令 γ = √(σ_B²) 且 β = μ_B 就能精確還原出原本的激活值。正規化於是成了有幫助的預設值,而不是一件緊身衣,因此沒有任何表達能力被犧牲。

走一遍具體數字。假設某個通道在一個大小為四的小批次裡的激活值是 [2, 4, 6, 8]。那麼 μ_B = 5、σ_B² = ((−3)² + (−1)² + 1² + 3²)/4 = 5,所以標準差 ≈ 2.24。激活值 x = 8 變成 x̂ = (8 − 5)/2.24 ≈ 1.34。如果這個通道學到的參數是 γ = 1、β = 0,輸出就是 1.34;如果網路決定 γ = 2、β = 0.5,它就會改為輸出 2(1.34) + 0.5 ≈ 3.18。同樣的標準化步驟,最後的尺度由網路自己選。

為什麼這麼有幫助?藉由把每一層的輸入分布釘在一個穩定的形狀上,批次正規化讓每一層不必再追著一個移動的目標跑——它的前面那些層可以重新洗牌自己的權重,而不會把它腳下的地基抽掉。具體上你會得到更快、更穩定的訓練、能容忍高得多的學習率(大步幅不再把激活值炸到無限大),還附帶一個輕微的正則化效果:因為 μ_B 與 σ_B² 是從隨機的小批次算出來的,每個樣本在每個 epoch 都被略為不同的統計量正規化,這種溫和的雜訊會抑制過擬合。

轉置卷積:可學習的上採樣

到目前為止一切都只往一個方向流:卷積與池化把一張大影像縮成一小疊深的特徵圖——對回答「這張圖裡有什麼?」很在行。但很多任務需要的恰好相反。語意分割必須標記每一個像素,所以它的輸出必須是完整的影像解析度。影像生成必須從一個小小的編碼合成出一張大圖。對這些任務,我們需要往回建——把一張粗略、低解析度的特徵圖變成更精細、更高解析度的。問題是:要怎麼上採樣,才能讓網路自己,而不是用像最近鄰複製那種固定規則?

答案就是轉置卷積——也叫做分數步幅卷積,或被寬鬆地(且有點誤導地)稱為「反卷積」。最好把它理解成把卷積的資料流倒著跑。一個正常的步幅卷積拿輸入的一塊區域、把它塌縮成一個輸出數字;轉置版本做的是鏡像——它拿一個輸入數字、把它攤散到輸出上一整塊核大小的區域。每個輸入值乘上整個核,這個被縮放過的核被「蓋章」到輸出網格上,而當相鄰輸入蓋出來的章彼此重疊時,這些貢獻就加總起來。

「轉置」這個名字是字面上的意思。回想一下:卷積可以寫成把攤平的輸入乘上一個巨大、稀疏的權重矩陣。轉置卷積就是乘上同一個矩陣的轉置——同樣的權重、角色對調——而這恰好就是把小的輸出形狀變回大的輸入形狀的那個運算。重要的是,核的權重是由反向傳播出來的,所以網路會自己發現填補細節的最佳方式,而不是用固定的內插去模糊。

重新檢視步幅與填補。在前向卷積裡,步幅讓視窗在輸入上移動;在轉置卷積裡,步幅改為把輸入在輸出網格上彼此撐開,而填補則是從輸出邊界修掉——同樣的旋鈕,倒著轉。

網格圖示,顯示一個小輸入被擴展到較大的輸出網格上,步幅把各輸入的貢獻撐開、核在每處蓋章,重疊區域被加總。

O = (W - 1)\cdot S - 2P + K

這就是第 2 篇的輸出尺寸公式倒著跑。回想前向卷積把特徵圖縮成 O_前 = ⌊(W − K + 2P)/S⌋ + 1。轉置版本把它反轉:O 是(較大的)輸出尺寸,W 是輸入尺寸,K 是核尺寸,P填補S步幅——但每個旋鈕的工作都翻轉了。步幅 S 不再滑動視窗;它在輸出網格上把輸入值彼此撐開,在它們之間插入 S−1 個間隙,而這正是放大特徵圖的關鍵(S = 2 大致讓解析度加倍)。填補 P 現在是被減去的:它不再加一圈邊框,而是從結果每一邊修掉 P 個像素。所以前向過程除以 S 來縮小的地方,轉置過程改成乘以 S 來放大。

我們來驗證它確實放大了特徵圖。拿一個小小的 2×2 輸入(W = 2),核 K = 3、步幅 S = 2、填補 P = 0:O = (2 − 1)·2 − 0 + 3 = 2 + 3 = 5,所以 2×2 的特徵圖變成 5×5。反方向也檢查一下:對一個 5×5 輸入做前向卷積,K = 3、S = 2、P = 0,得到 ⌊(5 − 3)/2⌋ + 1 = 1 + 1 = 2——回到 2×2。如承諾的,這兩個運算互為鏡像。(若 S = 1、P = 0、K = 3,同樣的 2×2 放大得溫和些,成 (2−1)·1 + 3 = 4,也就是 4×4。)

# Transposed conv as 'spread and sum' — the reverse of a strided conv.
# input X is HxW, kernel K is kxk, stride s. Output starts at zeros.
for i in range(H):
    for j in range(W):
        # stamp the whole kernel, scaled by this one input value,
        # at the output location set by the stride
        r, c = i * s, j * s
        output[r:r+k, c:c+k] += X[i, j] * K   # '+=' makes overlaps SUM
# (a final crop of P pixels per side applies the -2P term)
每個輸入值在輸出上畫出一份被縮放過的核;步幅決定這些章落點相隔多遠,重疊處則累加。

資料擴增與正則化

殘差塊與批次正規化讓我們能訓練深層 CNN。但訓練準確率不是目的——我們要的是網路能泛化到它從沒見過的影像。這裡單一最有效的工具,也是最接地氣的:資料擴增。在每張影像送進網路之前,你對它套一個隨機、且保持標籤不變的變換——水平翻轉、隨機裁切、小角度旋轉或縮放、色彩抖動(微調亮度/對比/色相),或更狠的招數如 cutout(抹掉一塊隨機區域)與 mixup(把兩張影像連同它們的標籤混合)。把貓翻轉後牠還是貓,所以標籤依然有效、但像素是全新的;網路等於看到了一個大得多、也更多樣的訓練集。

更深的洞見在於為什麼這有效,而且它把整個軌道串了起來。擴增是你用來注入你希望模型擁有的不變性的手段。如果你希望物件平移、翻轉或改變亮度時預測都不變,你就給網路看每個樣本的許多平移、翻轉、重新打光的版本,並每一次都要求相同的標籤。模型別無選擇,只能學會去忽略那些干擾因素的特徵。你字面上就是在「以身作則」教導穩健性。

一張訓練影像變成許多張:翻轉、裁切、旋轉、色彩抖動與 cutout 產生各種不同的視角,而它們全都保有相同的標籤。

一個網格,中央是一張貓的原始照片,周圍是擴增後的版本——翻轉、裁切、旋轉、調亮度、以及一張被抹掉方形區塊的——每一張都標著同樣的「貓」標籤。

這也是對第 3 篇誠實的後續。回想那裡的告誡:池化只給 CNN 微弱、近似的平移不變性,而嚴格說來卷積是 平移等變 的(平移輸入,特徵圖就跟著平移),而非完全不變。光靠架構並不保證在中央被認出的臉,到角落也會被認出,或一個傾斜的數字會被讀成一樣。擴增就是我們用來補足這個差距的方式:網路無法免費保證的不變性,我們就用樣本把它操練進去。兩者互補——架構提供有用的先驗,擴增提供那個先驗搆不到的穩健性。

另外兩個正則化器補齊了這套工具箱,它們從權重端而非資料端對付過擬合。權重衰減(weight decay)加上一個與權重大小成比例的小懲罰,把網路推向更簡單、量級更小、因而泛化更好的解。Dropout 在每個訓練步驟隨機把一部分激活值歸零,於是沒有任何單一單元能變得不可或缺,網路必須學出冗餘、穩健的表示;它在現代卷積堆疊內部用得較少(批次正規化本身已有正則化效果),但在最後的全連接層仍很常見。當訓練準確率很高、但驗證準確率落後時就該動用它們——這正是第 1 節說的過擬合特徵。

現代 CNN,以及視覺的下一步

退一步,整個軌道就拼成一幅完整的圖。第 1 篇讓你看到卷積不過是一個小視窗在影像上滑動、算加權和。第 2 篇把它變成一個真正的層——輸入與輸出通道、步幅、填補——並給了我們輸出尺寸公式。第 3 篇加上池化、不斷長大的感受野,以及那條經典的「縮小空間、增加通道、最後接分類器」的管線。第 4 篇讓卷積變得有效率(1×1 混合器、深度可分離、空洞卷積)。而本篇加上了讓這一切變又可逆的三樣東西:殘差塊、批次正規化、轉置卷積上採樣,外加讓它泛化的擴增。每一塊都接著下一塊。

一個現代骨幹網路的完整流程:stem 先把輸入降採樣,數個殘差階段抽取越來越抽象的特徵,全域平均池化把空間塌縮,最後一個線性層產生類別分數。

由左到右的管線:輸入影像、一個 stem 卷積、四個各自把解析度減半並把通道加倍的殘差階段,接著全域平均池化成單一向量,最後一個線性分類器輸出類別分數。

這裡把一個現代骨幹網路從頭讀到尾。一個 stem——一個步幅 卷積層(ConvNeXt 風格的設計會用較大、近似分塊的 stem)——快速把原始影像降採樣並把它提升到許多通道。接著是數個殘差階段;在一個階段內,各塊維持解析度不變,階段之間則用一個步幅塊把空間尺寸減半、同時把通道加倍,於是特徵越來越抽象、感受野也越來越寬。最後一個階段之後,全域平均池化把每個通道的特徵圖平均成單一數字,把空間維度塌縮成每通道一個值的向量。最後一個 線性 層把這個向量映射成類別分數。Stem → 殘差階段 → GAP → 線性:這個範本,加上每個塊內部的批次正規化,就描述了你會遇到的絕大多數 CNN 分類器。

值得誠實談談是什麼讓 CNN 成為 CNN——它的歸納偏置(inductive biases),也就是烙進架構裡的假設。局部性:卷積只看一小塊鄰域,賭的是相近的像素最相關。權重共享:同一個核在每個位置被重複使用,所以在某一處學到的特徵偵測器到處都能用,大幅削減參數。平移等變:平移輸入,特徵圖就同步平移。這些先驗不是免費午餐——它們是強假設——但對影像而言它們是對的假設,這正是為什麼 CNN 能用這麼少的資料學到這麼多。

誠實的對比對象是 Vision Transformer(ViT),它把影像切成小塊(patch),並從第一層起就讓全域自注意力把任意一塊與任意另一塊關聯起來。ViT 丟掉了 CNN 內建的局部性與平移先驗,換來彈性——它能直接建模長距離關係——但有代價:資料少時它往往不如 CNN,因為它必須從資料中學那些 CNN 免費假設好的結構。所以資料有限、或算力有限時,CNN 的先驗依然出色;資料量龐大時,ViT 會追上、且常常超越。不令人意外地,界線已經模糊——混合架構遍地都是,像 ConvNeXt 這樣的現代卷積網路借用了 Transformer 的訓練配方,而 Transformer 也借用了卷積 stem。沒有單一贏家,只有對應你資料預算的正確工具。

接下來往哪走。這個軌道教的是分類——一張影像一個標籤。從這裡開始,物件偵測會找出並框住多個物件,而語意/實例分割則標記每一個像素(用你剛學的上採樣爬回完整解析度)。另有一個專門的 Vision Transformer 軌道接續注意力這條線。它們全都站在你現在已經掌握的同一個基礎之上。

  1. Stem:影像最初是怎麼被降採樣並提升到通道的(步幅?分塊大小?)——這決定了起始的解析度與寬度。
  2. 階段與塊:有幾個階段,每個塊是不是殘差塊?追蹤通道在哪裡加倍、解析度在哪裡減半。
  3. 正規化與激活:批次正規化(或其變體)與哪種非線性,以及它們是在加法之前還是之後。
  4. 卷積口味:普通、1×1、深度可分離,還是空洞?這告訴你成本/感受野的取捨(第 4 篇)。
  5. 感受野:到了最後一個階段,單一輸出單元的 感受野 是否覆蓋了足夠多的影像,足以勝任這個任務?
  6. 輸出頭:分類用 GAP → 線性,密集預測則用轉置卷積上採樣——這揭示了網路被打造來輸出什麼。