L2.1 一個 η 不夠用:動量與 Adam 各買到什麼
本篇重用M0.0霧中下山:Gradient 與方向·M0.2導航的三十秒:Taylor 展開與「假設直線」·M5.2追一隻自己也在跑的狗:移動目標、EMA 與 Stop-Gradient
起點:一個純量背了三個工作
到目前為止, 已經被要求同時做三件事:
- 霧中下山,一步該走多大:梯度下降的一頁:它不能超過 ,跨過去是 。
- 同一篇:走完全程需要的步數由條件數 決定;那個 toy 上 ,所以在門檻上跑兩萬步仍然到不了直接解。
- 民調只問一千人:小批次與噪聲:它決定噪聲地板的高度()。
三件事的方向彼此衝突:第一件要 小,第二件要 大,第三件又要 小。而它們的根源不同——第一件是最陡方向的曲率,第二件是最平方向的曲率,第三件是抽樣的抖動。
一個純量沒辦法同時服務三個不同的需求。 出路是給最佳化器更多狀態:除了當前位置,再記一點別的東西。
走下山時,如果地面很陡就小步、很平就大步——
這件事能自動化嗎?
自動化之後,我還需要自己標準化特徵嗎?
課堂提問Q1
把「陡就小步、平就大步」翻成數學:最理想的更新是什麼?它為什麼在實務上算不出來?動量與 Adam 各是那個理想的哪一種便宜近似?
先想一想,再展開看整理後的答案
理想的更新是牛頓步。 把 在 附近展開到二階(導航的三十秒:Taylor 展開與「假設直線」),最小化那個二次近似,得到
這一步的意思正是「陡的方向小步、平的方向大步」:Hessian 的逆把每個方向除掉它自己的曲率。在二次問題上它一步到位,而且完全不需要調 。
它算不出來的理由有兩個。 參數有 個時,Hessian 是 —— 時它存不下,更別說求逆。而且非凸的地方 Hessian 可能不是正定的,那個「最小化」會把你送到鞍點甚至往上走。
兩種便宜的近似,取的方向不同。
- 動量不去估 Hessian,改成把過去的梯度累積起來。在細長的碗裡,來回擺盪的分量會互相抵消,而沿著溝底的一致分量會累加——等效於在最平的方向上放大步伐。它是一個純量方法(所有座標共用同一個 、同一個 ),買到的是有效條件數。
- Adam 反過來:它不管方向之間的耦合,只估每個座標自己的梯度尺度(用平方梯度的 EMA),再把該座標的步伐除掉它。等效於用一個對角矩陣近似 Hessian,買到的是逐座標的尺度。
兩者不是同一件事的兩個版本,而是拆了不同的東西:動量處理「方向之間的曲率差」,Adam 處理「座標之間的尺度差」。本篇會在同一個問題上量出它們各買到多少。
兩種鬆綁:記住方向,或記住尺度
動量的圖像是一顆有重量的球。 純 GD 是一個沒有慣性的質點:每一步只看當下的坡度。加上動量之後,球會保留上一步的速度:
在一個又長又窄的溝裡,橫向的坡度左右交替,累積起來互相抵消;沿著溝底的坡度方向一致,累積起來越滾越快。球在該慢的方向被抵消,在該快的方向被放大——這正是我們想要的「陡就小步、平就大步」,而且完全不需要知道 Hessian。
是梯度的一個幾何加權和:,權重以 衰減。這和 追一隻自己也在跑的狗:移動目標、EMA 與 Stop-Gradient 的移動平均是同一個物件,所以它的「記憶長度」大約是 步: 記十步, 記一千步。這個數字就是動量能買到的加速上限,下一節會把它和 對起來。
Adam 的圖像是每個座標自己的一把尺。 它同時維護梯度的一階與二階 EMA,再用後者當分母:
( 是除掉初期偏差的版本,見下面的 <Remark>)。關鍵在那個分母是逐座標的:如果某個座標的梯度一直是 、另一個一直是 ,兩者除完之後步伐都是 的量級。
這給了 Adam 一個很好用的性質,也給了它一個很容易被誤解的性質。 好用的是:把某個座標的梯度乘上一個常數,Adam 的軌跡不變——所以「特徵尺度不一致」這個 霧中下山,一步該走多大:梯度下降的一頁 的老病,它免疫。誤解的是:每一步移動的距離大約就是 ,與梯度多大無關。這意味著如果正確答案要求某個座標移動 、另一個座標移動 ,一個共用的 仍然辦不到。本篇最後會把這件事跑出來。
動量的加速由 β 決定,Adam 的步長由 η 決定
動量。 在二次問題上,帶動量的迭代對每個特徵值 的分量是一個二階遞迴,它的收縮率由 與 共同決定。最佳的一組是
對照 GD 的 。 換成 ——這就是動量的全部收益,而且它是理論上單靠一階資訊能拿到的最好結果。
但要注意 這個式子: 越大, 越靠近 1。本篇 toy 的 、,代進去得 。
的動量只買到大約 倍;要吃到 , 必須跟著 一起長。
這條式子解釋了一個很常見的失望:「我加了動量,可是沒什麼差」——因為 被當成一個不用調的預設值,而它其實是這個方法唯一的旋鈕。
Adam。 因為分母是梯度自己的尺度, 的單位變成「參數空間裡每一步走多遠」,不再和損失的尺度綁在一起。後果是 好調得多——下一節會看到 掃過 30 倍給同一個結果。代價寫在下面的 <Remark> 裡。
補充Adam 的兩個細節:偏差修正與 ε
偏差修正。 ,所以前幾步的 與 都被那個零拉小。第 步時 的權重總和是 ,所以除掉它:,。 時 在 還只有 ——不修正的話前一千步的步伐會被系統性地放大十倍,這是實作上最常見的 bug 之一。
不只是防除以零。 它同時是一個「地板」:梯度小到遠低於 的座標,步伐會回到與梯度成正比(也就是退化成 GD)。 設得太大,Adam 的逐座標尺度就失效;設得太小,一個長期梯度接近零的座標會被放大到不穩。本篇最後那個實驗把 從 調到 也救不回來——那不是 的問題。
展開細節Adam 的收斂性、AdamW,以及「Adam 泛化較差」這個說法
Adam 的原始論文給的收斂證明後來被指出有誤(Reddi et al., ICLR 2018 的 AMSGrad 一文給了一個 下 Adam 不收斂的反例),修法是對 取歷史最大值。實務上這個修正很少被採用,因為它在多數任務上沒有可觀察的差別——這是一個「理論反例存在但實務影響小」的例子,值得知道它存在,不必為它改預設值。
AdamW 改的是另一件事。原本的實作把 weight decay 當成一個加在梯度上的項 ,於是它也會被那個 分母除掉——梯度大的座標,衰減被除得比較小,衰減強度變成隱含地依賴梯度尺度。AdamW 把衰減直接寫在更新上(),讓它回到「與梯度尺度無關」。這件事與 限制筆記頁數,或考前一天停止補習:正則化與提早停止 的內容接在一起。
至於「Adam 泛化比 SGD 差」:這個說法在影像分類上有不少支持的實驗,在語言模型上則幾乎相反(那裡 Adam 家族是唯一實用的選擇)。目前沒有一個乾淨的機制解釋,而且大多數比較都沒有把 與 weight decay 同時調到各自的最佳。這一篇不下這個結論,只記錄:換最佳化器會改變你走到哪個解,那件事本身要用留出資料量,不能用直覺判。
回到情境:哪個病用哪個旋鈕
把三個「 背的工作」對回三個旋鈕:
| 症狀 | 根源 | 該動的旋鈕 |
|---|---|---|
| 訓練幾百步後突然 NaN | 越過 | 降 、加 warmup |
| 穩定但慢到走不完 | 條件數大 | 動量( 要跟著 調)、標準化、Adam |
| 座標之間的梯度差好幾個數量級 | 特徵/參數尺度不一致 | Adam、標準化、正規化層 |
| 曲線已經平了但停在高處 | 噪聲地板 | 降 、加 、學習率衰減 |
這一切依賴什麼。 一,上面動量的 是二次問題的結果;真實網路的 Hessian 會隨訓練變動, 也跟著動,所以實務上 是掃出來的而不是算出來的。二,Adam 的尺度免疫是對梯度的尺度,不是對參數該有的量級——這是下一節那個實驗的重點。三,這裡全部用全批次梯度量;加了 minibatch 噪聲之後,動量還多一個副作用:它同時平滑掉噪聲(記憶長度 步的平均),所以 也在偷偷改變 民調只問一千人:小批次與噪聲 的地板高度。
回到情境:在同一個問題上量三個方法
用 霧中下山,一步該走多大:梯度下降的一頁 的同一組設定:共用 toy 、五次多項式特徵(不標準化),、、直接解的訓練 MSE 。每個方法都跑兩萬步,各自用它能穩定的最大 。
import numpy as np
from ml_toy import toy_1d
x, y = toy_1d(n=30, seed=0)
V = np.vander(x, 6, increasing=True); n = len(x)
def final(kind, eta, mom=0.9, T=20000):
w = np.zeros(6); v = np.zeros(6); s = np.zeros(6)
for t in range(1, T+1):
g = (V.T @ (V@w - y))/n
if kind == "gd": w -= eta*g
elif kind == "mom": v = mom*v + g; w -= eta*v
elif kind == "adam":
v = 0.9*v + 0.1*g; s = 0.999*s + 0.001*g*g
w -= eta*(v/(1-0.9**t))/(np.sqrt(s/(1-0.999**t)) + 1e-8)
return ((V@w - y)**2).mean()
直接解 0.01466;兩萬步之後的訓練 MSE
GD η=0.3 0.03213 momentum β=0.9 η=0.11 0.02638
GD η=0.7 0.02891 momentum β=0.99 η=0.11 0.01668
GD η=1.05 0.02666 momentum β=0.999 η=0.11 0.01596
GD η=1.15 發散 momentum β=0.9999 η=0.002 0.02393
Adam η=0.003 0.01647 Adam η=0.01 0.01638
Adam η=0.03 0.01638 Adam η=0.1 0.01646
三個結論,三個不同的性質。
動量的旋鈕是 ,不是「有沒有加」。 給 ,而 GD 在門檻上是 ——差 ,實務上看不出來。把 推到 才掉到 , 是 。而理論算出來的 正好落在後兩者之間, 又太大(,過衝)。理論值直接命中實測的最佳位置。
Adam 幾乎不用調。 從 到 (30 倍)給的結果都在 –,而且全部優於任何 的動量。原因是那個逐座標的分母把 到 六個欄位的尺度差先除掉了——它處理的正是這個問題真正的病。
最好的動量與 Adam 差不多。 對 。所以在這個問題上兩條路都通,差別是 Adam 用預設值就到,動量要找對 。
刻意違反:把 換成「民國年」。 這是 霧中下山,一步該走多大:梯度下降的一頁 那個實驗——同一份資料,只把 換成 ,於是 從 變成 。GD 的門檻掉到 。Adam 對梯度尺度免疫,它應該沒事吧?
這組特徵的直接解 MSE = 0.01867
直接解的各座標量級:2.0e-03 8.5e-02 2.2e+00 6.4e-02 6.1e-04 1.9e-06
GD η=1e-24 兩萬步後 0.36383
momentum η=1e-24 兩萬步後 0.36377
Adam η=0.1 二十萬步後 21547
Adam η=1e-3 二十萬步後 7.6e+07
Adam η=1e-6 二十萬步後 169.56
Adam η=1e-9 二十萬步後 0.21414
Adam η=1e-12 二十萬步後 0.21416
每欄標準化之後:Adam η=0.01,兩萬步後 0.01466(=直接解)
沒有一個 讓 Adam 走到 。 最好的是 的 ——比 GD 的 好,但距離直接解還有一個數量級。而每欄標準化之後,Adam 用預設的 兩萬步就走到 ,正好是直接解。
原因在第二行那個量級表:正確答案要求 而 ,差六個數量級。Adam 每一步在每個座標上都移動大約 ,所以 要小到能刻出 , 就得走兩百萬步; 大到能快速刻出 , 就一步跨過去然後開始震盪。
Adam 對「每個座標的梯度尺度」免疫,對「每個座標該有的參數量級」不免疫。
所以那條很常聽到的建議——「用 Adam 就不用管特徵標準化了」——只對了一半。標準化不可省,因為它改的是參數該有的量級(霧中下山,一步該走多大:梯度下降的一頁 說它在改碗的形狀),而最佳化器改的只是走路的方式。
三個方法、三個旋鈕,在同一個碗上
互動 demo:三種模式:正常、損失乘一千、民國年。動量買有效條件數,Adam 買逐座標的尺度。
先消化一下
參考文獻
- Polyak, B. T. Some methods of speeding up the convergence of iteration methods. USSR Computational Mathematics and Mathematical Physics 1964.(heavy-ball 動量的原始論文與 收縮率。)
- Kingma, D., Ba, J. Adam: A Method for Stochastic Optimization. ICLR 2015.(更新式、偏差修正、 的角色與預設值的來源。)
- Goh, G. Why Momentum Really Works. Distill 2017.(動量在二次問題上的逐特徵值分析與可調的視覺化,本篇 的推導在這裡有完整版本。)