為什麼樸素梯度下降還不夠
到現在你已經掌握了核心動作:隨機梯度下降讀取一個小批量,計算損失的梯度,然後把每個參數朝下坡方向輕推一小步。重複幾百萬次,原則上你就能抵達一個不錯的解。但實際中,樸素的 SGD 就像在濃霧中下山,每一步都朝當下最陡的方向邁——而這條簡單規則有兩個惱人的失敗模式,會浪費掉大量的訓練時間。
第一是峽谷。許多損失曲面形如又長又窄的山谷:橫向很陡,縱向幾乎平坦。最陡方向指向谷壁而非谷底,於是 SGD 在兩壁間來回彈跳,前進得慢得令人抓狂。第二是噪聲:因為每一步用的是不同的小批量,梯度會抖動,即使在簡單的坡面上路徑也會曲折往復。這兩個問題有同一個解法——設法把最近的若干步做平均,讓一致的方向被強化、隨機的抖動被抵消。
動量:讓小球有點重量
動量借用了一個物理比喻。與其把參數看成沿每個梯度瞬移的無質量質點,不如想像一顆沉重的小球在山坡上滾下。這顆球保有一個速度——一段關於它一直朝哪個方向走的運行記憶——而梯度只是去輕推這個速度,而非獨斷整個步子。一致的下坡推力會累積成真正的速度,而峽谷裡左右的抖動因為不斷變號,大體會相互抵消。
# v starts at zero. beta is the momentum (e.g. 0.9) v = beta * v + (1 - beta) * grad # update the running velocity param = param - lr * v # step along velocity, not raw gradient
唯一的旋鈕 `beta`(常取 0.9)控制過去有多重要:越大意味著球越重、慣性越大、運動越平滑,但也越容易衝過頭——它可能在掉頭前越過谷底。這種衝過頭通常是優點而非缺陷,因為它能幫助球滑過那些會困住膽小優化器的小坎和淺局部凹陷。一種常見的改良叫 Nesterov 動量,它在落子前先朝前探一步,從而稍微抑制衝過頭。
自適應優化器:每個參數各有步長
動量解決的是*方向*問題。自適應優化器攻克的是另一個問題:對於一個有些參數梯度極大、有些極小的模型,用單一的全域步長是不合適的。AdaGrad是第一個流行的答案。它為每個參數維護其歷史梯度平方的累加和,並把該參數的步子除以這個和的平方根——於是經常出現大梯度的權重得到小而謹慎的步子,很少更新的權重得到大步子。這對稀疏資料極好,但累加和只增不減,於是步子會縮向零,學習最終停滯。
RMSprop用一個小改動解決了停滯:它不再永遠累加所有歷史梯度平方,而是維護它們的*指數移動平均*。舊梯度會逐漸淡出,於是每個參數的步長能隨訓練進入新地形而增大或減小,學習永不停擺。RMSprop 本質上就是帶有一段短滑動記憶的 AdaGrad——它至今仍是個穩妥的選擇,尤其適合循環網路。
Adam——是 *Adaptive Moment Estimation*(自適應矩估計)的縮寫——是你幾乎能在每個現代配方裡見到的主力。它的訣竅很簡單,就是把上面兩個想法合二為一:它既保有動量那種梯度的移動平均(「一階矩」,給出方向並平滑),*又*保有 RMSprop 那種梯度平方的移動平均(「二階矩」,給出逐參數的縮放)。一個小小的偏差修正項讓早期步子在兩個平均值尚未熱身完畢前保持靠譜。結果就是一個開箱即用、大多時候直接能用的優化器——這正是它得以接管一切的原因。
學習率調度與預熱
即使是出色的優化器也需要合適的步長,而最佳步長會隨訓練而變化。學習率是你要設定的最重要的單個數字——太大,損失會爆炸或振盪;太小,訓練則慢如蝸牛。深層的洞見是:沒有任何一個*常數*是理想的——訓練初期你想邁大步去覆蓋地形,但接近極小值時你想邁微小而謹慎的步子,以免永遠在它周圍彈跳。學習率調度做的事很簡單,就是隨訓練推進改變學習率。
最常見的形狀有*階梯衰減*(在幾個里程碑處把學習率砍為十分之一)、*餘弦衰減*(沿餘弦曲線平滑地把學習率滑降到接近零)、以及*指數衰減*。餘弦衰減是大模型的現代寵兒,因為它溫和的收尾往往能落在更平坦、泛化更好的區域。無論你選哪種,原理都一樣:開局大膽,收尾溫柔。
預熱則是開局階段一個反直覺的相反操作:在最初的幾百到幾千步裡,你把學習率從接近零*逐漸升高*到峰值,然後才開始衰減。為什麼?在初始化時模型是隨機的,Adam 的方差估計也不可靠,所以一個全尺寸的步子可能把權重炸開,損失再也回不來。預熱讓優化器的運行平均值先穩定下來,再放心交給它去邁大步。對於訓練大型的、用 Adam 訓練的 Transformer,先預熱再餘弦衰減幾乎是放之四海的預設配方。
實用調參:到底該怎麼做
理論之外,調參很大程度上是有紀律的試錯。好消息是,搜尋空間遠比看上去要小:把學習率大致調對,其餘大多數旋鈕幾乎都無關緊要。下面是一套最省算力的可靠操作順序。
- 從一個公認好用的預設值起步:Adam(或 AdamW),學習率 3e-4,beta 取 0.9/0.999。僅憑這一組就能把很大一部分模型訓練到尚可的程度。
- 先單獨把學習率調好。在十的冪上掃描——比如 1e-2、1e-3、1e-4——每個跑一小段。挑出損失仍能平滑下降、不出現尖刺的最大那個學習率。
- 盯住損失曲線,而不只是最終數字。損失爆炸或出現 NaN,說明學習率太高;損失沿著近乎水平的直線緩降,說明它太低。
- 在峰值學習率定好後再加調度:前約 2–5% 的步數做預熱,然後餘弦衰減到接近零。這通常能白賺一點精度提升。
- 到這一步才去動批量大小、權重衰減或 beta——並記住那條粗略規則:更大的批量允許你調高學習率。
幾句誠實的警告。著名的「3e-4 是 Adam 的最佳學習率」是個梗,不是定理——它是個不錯的*起點*,僅此而已。要警惕:在極小的試驗性運行裡看起來完美的學習率,一旦把模型或資料放大,往往就偏高了。還有,訓練損失的收斂不等於大功告成:一個模型完全可以漂亮地收斂到某個過擬合的點上——而這正是本階下一篇要直面的那場戰鬥。