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

讓它真的訓練起來:初始化、泛化與通往 CNN 之路

讓網路能學而不停滯的大師級清單——聰明的權重初始化、過擬合陷阱,以及影像為何接下來需要卷積。

初始化為何決定生死

上一篇我們留了一個懸念:反向傳播告訴我們該如何「改變」每一個權重,卻從沒說過在「第一步之前」這些權重應該是多少。而它們不能空著沒有值——一個網路說穿了就是一大堆數字,在第零步時,每一個數字都必須先持有某個數值。這個在任何學習發生之前就做下的選擇,決定了網路是會活過來,還是永遠僵在原地。這正是權重初始化的工作,也是「能訓練的模型」與「悄悄失敗的模型」之間的分水嶺。

先從最誘人的壞主意說起:把每個權重都設成零(或同樣糟的——全設成「同一個」非零數)。這感覺中立又公平,卻是致命的。想像任一個全連接層,其中許多神經元都看著相同的輸入。如果兩個神經元的權重一模一樣,那麼對任何輸入,它們算出的輸出都會一模一樣。在反向傳播時,它們會收到一模一樣的梯度,於是更新把它們往一模一樣的方向推——然後它們就永遠保持相同。整層的行為就像只有一個神經元被複製了很多份。這就是對稱性問題:神經元永遠無法分化成不同的特徵偵測器,因為沒有任何東西能打破它們之間的平手。

所以我們改採隨機。但光是隨機還不夠——這些隨機數的尺度影響極大,這正是第二種天真的失敗。想像一個訊號往前流過一疊很深的層。如果權重稍微太大,每一層都把訊號放大,經過許多層後,激活值就會朝著巨大的數字爆炸(回傳的梯度也一樣——這是「爆炸」的情形)。如果權重稍微太小,每一層都把訊號縮小,經過許多層後就萎縮到幾乎為零——這正是我們在第 4 篇遇過的梯度消失問題:學習訊號還沒抵達前面幾層就已經淡化成零。無論哪一種,學習都會停擺。

由此我們可以用一句話講清楚好的初始化要追求的全部目標,它也將指引下一節的一切:讓激活值(往前傳)與梯度(往後傳)的典型大小,在通過許多層時大致保持恆定。不放大、不縮小——穩定就好。如果我們能在第零步就設計出這種穩定,那麼從第一個梯度步開始,訓練就有了勝算。

保持變異數的初始化:Xavier 與 He

讓我們把「讓尺度保持穩定」變成一道食譜,幾乎不用機率——只需要一個關於「把隨機數加起來」的樸素事實。一個神經元計算其輸入的加權和:它取 n_in 個輸入,每個乘上一個權重,再全部相加。關鍵的直覺是:當你把許多彼此獨立的隨機貢獻加起來時,總和的散布幅度會隨著加總的項數而增大。加的項愈多,總和晃動的範圍就愈寬。因此一個有 256 個輸入的神經元,產生的輸出範圍會遠比只有 4 個輸入的寬,即使個別權重的大小相同。

這立刻提示了修正之道:既然輸入愈多會讓總和愈大,那就按比例把權重縮小,好讓輸出維持和輸入相同的散布幅度。結果顯示,正確的縮放量大約是 1/√(n_in)——把隨機權重除以扇入數的平方根。這正是權重初始化方案的核心。最有名的兩種,來自針對兩種不同激活函數把這件事做精確。Xavier(又稱 Glorot)初始化是為對稱、S 形的激活函數(如 sigmoid 與 tanh)調校的;He 初始化則是為修正線性單元(ReLU)——我們在第 2、4 篇倚重的激活函數——調校的,而它多帶了一個轉折。

\mathrm{Var}(W) \;=\; \frac{2}{n_{\text{in}}}\qquad\Longleftrightarrow\qquad \mathrm{std}(W) \;=\; \sqrt{\frac{2}{n_{\text{in}}}}

He 初始化:以此變異數從常態分布中抽取每個權重。

請慢慢讀。Var(W) 是權重的變異數——衡量隨機權重值散布程度的量(它的平方根 std(W) 是標準差,也就是權重離零的典型距離)。n_in 是「扇入」:餵進這個神經元的輸入數量。這條公式說:把權重的散布幅度恰好選成 2/n_in。分母上的 n_in 就是我們「輸入愈多、權重愈縮」的規則——輸入愈多的神經元拿到愈小的權重,使加總後的輸出維持穩定的散布。分子上的 2 則是 ReLU 修正:ReLU 把每個負輸入都歸零,因此平均而言約有「一半」的貢獻被消音。把存活的輸入砍半,會把輸出的散布也砍半,所以我們把變異數乘以 2 來補償,回到穩定的尺度。Xavier 用的是相同形式但沒有那個 2(Var(W) = 1/n_in),因為 sigmoid/tanh 不會把一半輸入歸零。無論選哪一種,重點都是第 1 節的目標:讓激活的變異數一層又一層保持大致恆定,使訊號在深度方向上既不爆炸也不消失。

用一個數字把它落實。假設某層的神經元各有 256 個輸入,且我們把它們送進 ReLU,所以採用 He 初始化。那麼 std(W) = √(2 / 256) = √0.0078125 ≈ 0.088。於是我們從一個以零為中心、標準差約 0.088 的常態分布中抽取每個權重——刻意取小的數字,而不是天真的程式碼可能隨手用的 0 到 1 範圍。作為對照,同一層用 Xavier 會得到 √(1 / 256) = 0.0625。0.088 與 0.0625 之間那個「2 的因子」差異,正是把 ReLU 修正具象化——看似不大,但在數十層上累乘,就是健康訊號與死寂訊號之間的分別。

import numpy as np

def he_init(n_in, n_out):
    # ReLU layers: variance 2 / n_in  ->  std = sqrt(2 / n_in)
    std = np.sqrt(2.0 / n_in)
    W = np.random.randn(n_out, n_in) * std   # break symmetry + right scale
    b = np.zeros(n_out)                       # biases can start at zero
    return W, b

def xavier_init(n_in, n_out):
    # sigmoid / tanh layers: variance 1 / n_in (no factor of 2)
    std = np.sqrt(1.0 / n_in)
    W = np.random.randn(n_out, n_in) * std
    b = np.zeros(n_out)
    return W, b

W, b = he_init(256, 128)
print(W.std())   # ~0.088, matching sqrt(2/256)
五行寫出 He 與 Xavier:相同的點子(依扇入縮放),只差一個 2 的因子。

過擬合與泛化

好的初始化為我們換來一個「能」訓練的網路。但一個把訓練損失壓到零的網路,未必真的學到了任何有用的東西。真正的獎賞是泛化:在從沒見過的新影像上表現良好,而不只是它讀過的那些。經典的失敗就是過擬合,而這個比喻精準無比。想像一個學生,他不去理解教材,而是把去年模擬考的標準答案逐字背下來。在那些模擬題上他考了滿分 100。到了真正的考試、換成新題目,他就崩潰了。過擬合的網路做的是同一件事:它把訓練集背了下來,而不是學到背後的規律。

過擬合的招牌特徵:訓練損失持續下降,驗證損失卻轉折向上攀升。

隨訓練時間變化的兩條曲線。訓練損失穩定下降趨近零。驗證損失起初下降、抵達最低點後再次上升,與訓練曲線間的差距愈拉愈大。

這張圖顯示了我們如何「偵測」它,而它直接呼應第 3 篇的損失函數——也就是網路要最小化的那個單一數字。我們在兩組分開的影像上量測損失。訓練損失算在網路用來學習的資料上;驗證損失算在它從未拿來訓練、被保留起來的影像上。一開始兩者一起下降——網路學到的是處處都有用的真規律。但到了某個時刻,曲線開始分岔:訓練損失持續朝零下滑,驗證損失卻觸底後開始回升。那道愈拉愈寬的縫隙,就是過擬合的視覺指紋。此刻網路在標準答案上愈來愈好,在真正的考試上卻愈來愈「差」。

欠擬合(太死板、高偏差)與過擬合(太靈活、高變異數)是同一道權衡的兩端。

三幅擬合資料點的圖:一條沒抓到趨勢的直線(欠擬合、高偏差);一條順著趨勢的平滑曲線(良好擬合);一條穿過每個點連雜訊都吻合的扭曲曲線(過擬合、高變異數)。

這背後是偏差—變異數權衡,所有機器學習的核心張力。太簡單的模型抓不住真實規律——不管餵它什麼資料,它都犯同樣的系統性錯誤。我們稱之為欠擬合高偏差:就像用一條直線去擬合一條曲線。太靈活的模型則相反——它扭曲自己去迎合訓練資料中每一個怪癖與雜訊點,於是它的預測會因看到的恰巧是哪些例子而劇烈擺盪。那就是過擬合高變異數。藝術就在於落在中間:靈活到足以抓住真訊號,死板到足以忽略雜訊。這對在影像上運作的多層感知器尤其要命,因為——下一節會把這點殘酷地具象化——這類網路擁有「龐大」數量的權重,給了它們綽綽有餘的靈活度去背題。在影像上,MLP 幾乎是預設就會、而且很容易過擬合。

實務上的完整訓練流程

現在我們已經湊齊所有零件,可以把第 4 篇那個理想化的「前向、損失、後向、更新」迴圈,升級成一套真實世界的工作流程——一份實務工作者真正會遵循的檢查清單,也是讀完這篇後你應該能逐行讀懂的那種腳本。相較第 4 篇多出的新材料,正是我們剛學到的:把資料切成不同的集合、明智地初始化權重,以及在訓練過程中盯著過擬合。

  1. 把資料切成三個互不重疊的集合:訓練集(網路從中學習)、驗證集(訓練期間盯著它來偵測過擬合並調整選擇)、測試集(只在最後動一次,作為誠實的最終評分)。
  2. 用與激活函數相匹配的方案初始化權重——ReLU 層用 He、sigmoid/tanh 用 Xavier——讓訊號從穩定的尺度起步(第 2 節)。偏置設為零。
  3. 在週期(epoch,一個週期=完整掃過訓練集一遍)上做迴圈,並在每個週期內對小批次(mini-batch,例如一次 32 或 64 張影像的小塊)做迴圈。
  4. 對每個小批次跑那四步循環:前向傳播取得預測、計算損失、後向傳播取得梯度,再讓每個權重逆著它的梯度走一步來更新。
  5. 每個週期結束時,「同時」量測訓練損失與驗證損失。同時盯著兩者,正是你能在驗證曲線一轉向上時就抓到過擬合的方法。
  6. 調整學習率(太高會發散、太低會龜速),並在驗證損失不再進步時停止(提前停止)。到那時才報出測試集的分數。
把訓練迴圈視覺化:資料與初始化後的權重進入;前向 → 損失 → 後向 → 更新不斷旋轉,直到驗證不再進步為止。

一張循環圖:小批次進入前向傳播產生預測,對照標籤算出損失,後向傳播產生梯度,權重被更新,箭頭再繞回下一個小批次;一條側支定期在驗證集上做評估。

# A real training loop, stripped to its essentials
W = initialize_weights(layer_sizes, scheme="he")   # section 2: stable scale

best_val = float("inf")
for epoch in range(max_epochs):
    for x_batch, y_batch in train_loader:          # mini-batches
        preds = forward(W, x_batch)                # 1. forward propagation
        loss  = loss_fn(preds, y_batch)            # 2. loss (guide 3)
        grads = backward(loss)                     # 3. backprop / autodiff (guide 4)
        W     = update(W, grads, learning_rate)    # 4. step against the gradient

    train_loss = evaluate(W, train_set)
    val_loss   = evaluate(W, val_set)              # watch BOTH curves
    print(epoch, train_loss, val_loss)

    if val_loss < best_val:                        # early stopping:
        best_val, best_W = val_loss, W             #   remember the best model
    elif val_loss > best_val:                      #   validation turned upward
        break                                      #   -> stop before overfitting

test_score = evaluate(best_W, test_set)            # touched only once, at the end
每一行都對應到本學程中的一個概念——這裡再也沒有任何神秘之處。

那個迴圈裡有三行扛起了整個學程的重量。`forward(...)` 是第 1~3 篇的前向傳播——像素進、預測出。`backward(...)` 是第 4 篇的反向傳播引擎,用自動微分算出每一個梯度。而最開頭那一行 `initialize_weights(..., "he")`,正是本篇的權重初始化,給了前兩者一個有勝算的起點。關於 `update` 最後補一句:我們一直用的那個樸素的「逆著梯度走一步」是最簡單的優化器,叫 SGD。真實訓練通常用更聰明的表親——帶動量的 SGD,會在一致的方向上累積速度;以及 Adam,會為每個權重自適應地調整步長。我們只點名;它們的內部細節屬於後續的優化學程。迴圈的形狀則完全不變。

為何 MLP 難以應付真實影像

現在我們已經會建網路、把它初始化好、訓練它、並防範過擬合。那麼為什麼整個學程要以「多層感知器是處理真實影像的『錯』工具」作結?因為有兩個具體的失敗,而第一個是一面由數字砌成的高牆。回想第 1 篇:要把影像餵進全連接層,我們必須先把它攤平——把二維的像素網格拉直成一條長向量。一張中等大小、224×224 像素、含 3 個色彩通道的彩色影像,攤平後是 224 × 224 × 3 = 150,528 個輸入數字。這還只是「單一」一層的輸入。

\text{params} \;=\; n_{\text{in}} \times n_{\text{out}} \;+\; n_{\text{out}}

一個全連接層的參數量:每一對輸入—輸出都有一個權重,再加上每個輸出一個偏置。

把它拆開,這個爆炸量就無從否認了。n_in 是輸入數(我們這張影像是 150,528),n_out 是該層的神經元數,而這一層需要為輸入與輸出之間的「每一條」連線各配一個權重——那就是 n_in × n_out 這一項——再加上每個神經元一個偏置,即 + n_out 那一項。現在代入一個僅有 n_out = 1,000 個神經元的隱藏層:params = 150,528 × 1,000 + 1,000 = 150,529,000 ≈ 1.5 億個權重。一億五千萬個數字,只為了「一層」,而我們連一個真正的網路都還沒搭起來。這浪費到了天文等級,而且——回想偏差—變異數那一課——一個有 1.5 億個自由參數的模型,是保證會過擬合的機器:它的容量綽綽有餘,足以把訓練集整個背下來。作為對照,請把這個數字記在心裡:我們接下來會遇到的「權重共享」卷積,只需要幾「千」個參數就能勝任全連接層的工作,足足少了四到五個數量級。

第二個失敗比單純的大小更深——它是結構性的。我們一把影像攤平,就摧毀了它的空間佈局。回想第 1 篇:一個像素的意義活在它的鄰域裡——邊緣是相鄰像素之間的「局部」躍變,紋理是「局部」的圖樣。攤平成形狀為 (150528,) 的張量,會把這一切全部丟掉——影像中緊鄰的像素 (0, 0) 與 (0, 1),在清單裡變成只是「輸入 #1」與「輸入 #2」,彼此的關聯不比任意兩個隨機項目多。MLP 完全不知道哪些輸入曾是鄰居。它必須僅憑資料、從零開始,重新發現我們本不該撕碎的空間結構。

而且情況更糟,因為多層感知器沒有任何內建的平移不變性觀念。假設它費盡千辛萬苦,從許多例子裡學會了一組能辨認「左上角有貓」的權重。現在給它看同一隻貓、往右平移了幾個像素。對 MLP 來說,這幾乎是一個全然不同的輸入向量,點亮的是另一組權重——它不會自動知道貓不管出現在哪裡都是貓。它得在影像裡「每一個可能的位置上,各自獨立地」把貓的偵測器「再學一遍」。這效率低到無可救藥:它把學習問題乘上了位置的數量,還浪費了那些本應全部用來強化同一個共享概念的例子。

通往卷積神經網路的橋樑

我們不會在這裡完整教卷積——那是第 6 學程的開場,值得有它自己的跑道。但我們可以先埋下三個大點子,它們正好修補我們剛診斷出的兩個 MLP 失敗,好讓你進入下一學程時,就已經知道每一個各自解決什麼問題。把這當成在讀下一本書的封底簡介。

點子一——局部連接。卷積神經元不把每個神經元都接到全部 150,528 個輸入,而是只看影像的一小塊——比如一個 3×3 的窗口。這直接遵循了第 1 篇的教訓:意義活在鄰域裡;同時把每個神經元的連線數從 150,528 砍到寥寥幾個。那面參數高牆立刻開始崩塌。

點子二——權重共享。這是神來一筆。我們取一小組「窗口權重」——一個「濾波器(filter)」——然後把「同一個」濾波器滑過影像的每一個位置,到處都重用一模一樣的權重。兩個巨大的勝利同時落下。第一,參數塌縮:不再是 1.5 億,一個濾波器也許只有 9 或 27 個權重,卻被用上數百萬次——正是我們先前預告的四到五個數量級的縮減。第二,同樣重要的是,它免費賦予了平移感知:因為同一個偵測貓的濾波器被套用在每一個位置,貓不管出現在哪裡都會被辨認出來。MLP 那個無望的「在每個位置各自重學一遍」的問題,就此煙消雲散——一個濾波器,學一次,到處都管用。

點子三——讓影像保持為空間張量。因為濾波器是在二維佈局上滑動的,卷積網路從一開始就不把影像攤平。它把影像保留成一個張量,高、寬、通道完好無損,於是鄰域關係能一路存活穿過整個網路。MLP 丟掉的空間結構,現在被保留下來並加以利用。請留意這三個點子如何乾淨俐落地對上第 5 節的兩項抱怨:局部連接加權重共享,殺死了參數爆炸;權重共享加空間張量,則恢復了對結構的理解。

下一學程的初窺:一個小濾波器在保有空間結構的影像上滑動,到處共享權重,餵入更深的層,最後仍以我們熟悉的「損失與更新」迴圈收尾。

一張流程圖:輸入影像為一個空間張量,一個小濾波器在其上滑動產生特徵圖,數個這樣的卷積階段,最後一個分類器產出標籤——外圍包著我們熟悉的「前向/損失/後向」訓練迴圈。

這裡有一個讓人安心的收尾妙語,也是整個學程最適合的結束音:卷積只改變了神經元如何被連接——並沒有改變它們是什麼。你在這五篇裡建起的每一樣東西,都原封不動地延續下去。卷積單元仍是一個計算加權和與激活的神經元(第 1 篇);濾波器仍堆疊成層與深度(第 2 篇);輸出仍以 logits、softmax 與損失函數來評分(第 3 篇);它仍透過計算圖上的前向與後向傳播來學習(第 4 篇);它仍需要明智的初始化與過擬合控制(本篇)。連多層感知器都沒有消失——CNN 幾乎總是以幾個全連接層收尾來做最終決定。你不是從頭開始。你現在已經擁有電腦視覺完整的神經網路根基,準備好教這些神經元在二維中「看見」了。而那,正是第 6 學程的起點。