L2.4 同一張地圖從不同地方出發:損失曲面與初始化
同一張地圖,兩個登山口
前幾篇用二次損失算清楚了步長、momentum 與逐方向縮放。但那些公式不能原封不動套到一般網路上:若損失像一張有許多山谷的地圖,同樣沿下坡走,換個登山口會不會走到不同地方?
線性模型的平方損失是凸的;設計矩陣滿秩時才有唯一最小點。非線性網路則一般不保證凸,所以初始化可能影響走到的解,也會影響一開始的訊號與梯度。
這在實務上表現成一件每個人都遇過的事:同一份程式、只換 random seed,跑出來的分數不一樣。
問題是這件事有多嚴重。常見的說法是「高維空間裡到處都是局部極小」,聽起來像是每次都在賭運氣。這一篇要把它量出來——結果比那個說法溫和得多,而真正該花時間調的是另一個東西。
同一個架構、同一份資料,只換隨機起點,
會下到同一個谷底嗎?
如果不會,那我該調的是哪個設定?
課堂提問Q1
把「同一張地圖、不同登山口」翻成數學:非凸到底讓哪一個保證失效了?初始化在這件事裡扮演什麼角色——它只是「隨機起點」,還是一個要調的超參數?
先想一想,再展開看整理後的答案
失效的是「局部訊息足以判斷全域」這個保證。 凸函數有一個很強的性質: 就是全域最小。梯度是局部的量(霧中下山:Gradient 與方向),而凸性讓這個局部的量帶著全域的資訊。非凸之後這個橋斷了—— 只告訴你「這裡是平的」,它可能是局部極小、鞍點、或一片平原。
在光滑、步長夠小且梯度非零時,Taylor 的局部下降論證仍成立;但局部下降不保證走到全域最小,也不保證有限時間內收斂。同一個方法還要考慮起點、步長與停止時間。
初始化不只是「隨機起點」,它至少決定三件事。
- 落在哪個盆地。 這是上面說的那件事,它給了 seed 之間的散佈。
- 一開始的梯度有多大。 權重的尺度直接決定前向訊號的尺度,而前向訊號的尺度又決定反向梯度的尺度。太小 → 幾乎沒有梯度可走;太大 → 落在飽和區( 的導數趨近零)或直接越過穩定門檻。
- 每一層拿到的梯度比例。 反向傳播會逐層乘上 Jacobian。尺度、激發函數與深度共同決定這個乘積是否變小或變大;不能把原因只歸給其中一項。
所以 gain(初始化的標準差乘數)是一個和學習率同等地位的超參數,而不是一個實作細節。
兩件要分開的事:散佈與尺度
散佈是「同一個設定跑很多次會差多少」。它決定你該怎麼報告結果(同一個實驗跑三次結果都不同,該報哪一個? 會處理報告的部分),以及「A 比 B 好 」這句話有沒有意義。
尺度是「初始化的權重該多大」。它決定訓練能不能開始。這是一個可以先驗算出來的東西,而經典的答案是讓每一層的前向訊號變異數大致守恆:權重取標準差 (Glorot/He 初始化,參考文獻 1、2)。本篇用的 gain 就是在那個基準上再乘一個倍數, 表示照基準。
兩件事的診斷方式不同:散佈要跑很多個 seed 看分布;尺度要在初始化時量前向與反向的統計量,不必訓練。
圖說: seed 散布決定該報哪些統計量;初始化尺度則決定訊號與梯度能否進入可訓練區間。
深度會不會讓梯度消失
先把「梯度消失」寫成一個可以量的東西。深層網路裡第一層的梯度是一串因子的乘積(鏈式法則):
這裡 , 描述這一層如何改變輸入;參數梯度還會乘上對應層的輸入。如果每層在梯度方向上的放大比例都大約是 ,乘積就可能像 :
- :指數衰減 → 梯度消失,深層的前段學不動。
- :指數放大 → 梯度爆炸。
- :比較有機會維持量級,但矩陣的方向與奇異值仍重要。
關鍵是 由什麼決定。 它是「權重的尺度」乘上「激發函數導數的典型值」——兩個都由初始化的 gain 控制。 這個選擇的整個目的就是把 調到 附近。
深度讓逐層相乘的效應累積;初始化是在調整每一層的因子,不是消除深度的影響。
固定 gain 掃深度,是檢查這件事的一種方法。但除了第一層/最後一層的比值,也要量兩者的絕對範數:兩邊一起變小時,比值仍可能不變。
注意殘差連接把問題換成了另一個
殘差連接 讓局部 Jacobian 多出 identity:。這提供直接傳遞的分支,但總梯度仍由所有分支共同決定;若 抵消 identity,梯度仍可能縮小,不能把殘差當成不消失的保證。
但那條通路也不會縮小:每一層都把訊號加上一份自己的輸出,所以前向與反向的量級都隨深度成長。本篇最後量到的比值是 (深度 到 )——從「平的」變成「指數爆炸」。
修法是把那一份縮小: 取 或 可學(初始化為 ),或者在每個 block 裡放一個正規化層把量級拉回來。這就是為什麼殘差幾乎總是和正規化層一起出現——它們修的是相反方向的兩個病。一張圖裡同時有紋理、物件和整個場景,一個固定大小的核怎麼辦? 會回到這件事。
兩個設定各怎麼調
尺度先調,而且不用訓練就能調。 做法:初始化之後跑一個 batch 的前向,看每一層輸出的標準差。理想是各層大致相同(不隨深度衰減或放大);同時反向一次,看第一層與最後一層的梯度大小比。這兩個數字五分鐘就量得到,而它們決定訓練能不能開始。
散佈用來決定報告方式,不是用來挑 seed。 跑 5–10 個 seed,報中位數與範圍。挑最好的那個 seed 來報告,就是 老師用考古題教學又出題:為什麼要切資料 講的選擇偏差再來一次——只是這次選的是隨機性而不是超參數。
這一切依賴什麼。 一,本篇的網路很小(兩層 32 單元)。散佈的大小隨規模改變,而且方向不是單調的:更大的網路在很多任務上散佈更小(過參數化讓不同盆地的品質趨近),但更深的網路對初始化更敏感。二,本篇量的是訓練損失的散佈;泛化的散佈是另一個量,通常更大。三,用 Adam 訓練會壓掉一部分初始化的影響(它的逐座標尺度會自我調整),所以同一組實驗用 SGD 跑,尺度那條曲線會更陡。
把散佈與尺度量出來
模型是一個 的 網路,Adam 跑 4000 步,資料是共用 toy 的 30 個點。
第一段:200 個隨機起點。
import numpy as np, mlp # mlp.py:三十行的 numpy MLP,見本節末
from ml_toy import toy_1d
x, y = toy_1d(n=30, seed=0); X = x[:,None]
fin = []
for s in range(200):
P = mlp.init([1,32,32,1], gain=1.0, rng=np.random.default_rng(s))
P, L = mlp.adam_train(P, X, y, T=4000, eta=0.01)
fin.append(L)
fin = np.array(fin)
print(f"最小 {fin.min():.4f} 中位數 {np.median(fin):.4f} 第90百分位 {np.percentile(fin,90):.4f} 最大 {fin.max():.4f}")
print(f"比中位數差兩倍以上的比例:{(fin > 2*np.median(fin)).mean()*100:.1f}%")
最小 0.0023 中位數 0.0034 第90百分位 0.0046 最大 0.0085
比中位數差兩倍以上的比例:3.0%
散佈存在,但它不是「每次都在賭運氣」。 中位數 、第 90 百分位 ——九成的 seed 落在中位數的 倍以內。最大值 是中位數的 倍,而落在那個尾巴裡的只有 。
這提醒我們不要只報一個 seed;但第 90 百分位相對中位數的比例,不是統計顯著性的門檻。比較兩種方法時,要直接看它們的多次結果與配對差異。
這些是訓練 4000 步後的損失,不一定是局部極小值。損失數字散在一段範圍,也不能證明參數空間中的低損失區域連通;要談地形還需要另外的分析。
第二段:初始化尺度的掃描。 同一個架構,只改 gain(每個 gain 跑 12 個 seed 取中位數):
gain=0.01 最終訓練 MSE 中位數 0.0064 最差 0.0135
gain=0.1 最終訓練 MSE 中位數 0.0057 最差 0.0059
gain=1 最終訓練 MSE 中位數 0.0031 最差 0.0061
gain=3 最終訓練 MSE 中位數 0.0066 最差 0.0089
gain=10 最終訓練 MSE 中位數 0.0108 最差 0.0133
gain=30 最終訓練 MSE 中位數 0.0156 最差 0.0523
又是一個 U 形,谷底正好在 gain(也就是 那個基準)。兩端壞得方式不同:gain 時權重太小,前向訊號幾乎是零、反向梯度也幾乎是零,4000 步還沒走到(,而且最差的那個 seed 是 );gain 時大部分單元一開始就落在 的飽和區(導數趨近零),,而且最差的那個 seed 是 ——尺度太大時 seed 之間的散佈也跟著變大。
注意這個掃描的範圍:從谷底往兩邊各差 倍,損失只變差 到 倍。它比學習率溫和(霧中下山,一步該走多大:梯度下降的一頁 的 跨過門檻是 ),但它是一個實實在在的設定,而且因為它在訓練開始前就決定,調錯了不會給你任何錯誤訊息。
第三段:深度與梯度的比值。 量初始化時 與 的比(8 個 seed 的中位數):
(tanh,每層 32 單元,無殘差)
depth gain=0.5 gain=1 gain=2
2 1.61e-01 1.64e-01 1.90e-01
8 1.75e-01 2.26e-01 4.41e-01
16 1.87e-01 2.28e-01 7.99e-01
32 1.86e-01 2.19e-01 7.63e+00
gain 與 的梯度比值在這個實驗中沒有明顯下降。但只看比例還不能排除兩端一起變小,應把各層的絕對梯度與更新量一起畫出來。
而 gain 那一欄從 爬到 ——同一個深度、只把初始化放大兩倍,就從「平的」變成「放大 40 倍」。第三個結論因此是:
這組結果顯示 gain 很重要;它不是「深度無關」的證明。
刻意違反:加上未縮放的殘差連接。
(同上,加 h_{ℓ+1} = h_ℓ + tanh(W_ℓ h_ℓ))
depth 無殘差 有殘差
2 1.64e-01 2.26e-01
4 1.93e-01 5.20e-01
8 2.26e-01 1.36e+00
16 2.28e-01 1.30e+01
32 2.19e-01 3.28e+02
殘差把「平的」變成「指數爆炸」:深度 32 時比值是 ,而無殘差是 。原因寫在上面的 <Remark> 裡——每一層都把訊號加上一份自己的輸出,所以量級隨深度累積。
這個未縮放的殘差例子提醒我們檢查量級,不代表所有殘差一定爆炸。縮放、零初始化的殘差分支與正規化都是可能的做法;要由具體架構決定,而不是宣稱殘差與正規化必須成對出現。
補充本篇用的三十行 numpy MLP
為了讓上面的實驗可以逐行檢查(而不是呼叫一個框架),mlp.py 只做三件事:init(sizes, gain, rng) 用 初始化每一層;grads(P, X, y, skip) 手寫前向與反向( 的導數是 ,殘差那一支在反向時多加一個恆等項);adam_train(P, X, y, T, eta) 是 一個 η 不夠用:動量與 Adam 各買到什麼 那個更新式,含偏差修正。
刻意沒有做的事:沒有 minibatch(全批次,30 筆資料不需要)、沒有正規化層、沒有 weight decay。這樣一來每一個實驗只有一個變數在動——這是能把 gain 的效果和其他東西分開的原因,也是為什麼這裡的數字比在一個完整框架上跑出來的乾淨。
換 seed、換 gain、換深度
互動 demo:散佈與尺度是兩件事:seed 的散佈量的是前者,梯度比量的是後者。
檢查理解
參考文獻
- Glorot, X., Bengio, Y. Understanding the difficulty of training deep feedforward neural networks. AISTATS 2010.(把「讓每層訊號變異數守恆」寫成初始化條件, 的來源。)
- He, K. et al. Delving Deep into Rectifiers. ICCV 2015.(ReLU 下的對應版本,以及深度與初始化尺度如何一起決定訓練成不成立。)
- Li, H. et al. Visualizing the Loss Landscape of Neural Nets. NeurIPS 2018.(損失曲面的視覺化方法,以及殘差與寬度如何改變曲面的樣子。)