ニューラルネットの訓練は結局のところ「損失関数を最小化するパラメータを見つける」ゲーム。そのゲームのルールを決めるのがオプティマイザ。
SGDから始まって、Momentum、Adagrad、RMSprop、Adam、AdamW、そして最近のLionやSophiaまで、全部「前のやつの弱点を潰す」という進化の連鎖になっている。数式で追いかけるという記事。
オプティマイザの系譜
1. 問題設定
パラメータ θ∈Rd を持つモデルの損失関数 L(θ) を最小化したい。
θ∗=argθminL(θ)
訓練データ全体の勾配は計算コストが高すぎるから、ミニバッチ B⊂D からの確率的勾配を使う。
gt=∇θLBt(θt)
gt は真の勾配 ∇L(θt) の不偏推定量。ここから先、全てのオプティマイザはこの gt をどう加工して更新量を決めるかの話になる。
2. Vanilla SGD
更新則
一番シンプル。勾配の方向にそのまま進む。
θt+1=θt−ηgt
η は学習率。これだけ。だけどこれだけじゃダメ。
問題点は損失関数の等高線が細長い楕円(条件数が大きい)だと、勾配方向が最適解への最短経路と大きくずれてジグザグに振動する。学習率を大きくすると発散し、小さくすると収束が遅すぎる。全パラメータに同じ学習率を使うから、頻出パラメータと稀少パラメータで最適な学習率が違う場合に対処できない。
凸関数ならSGDでもなんとかなる。問題は非凸の世界
3. SGD with Momentum
これってSGDに慣性を追加したやつなんだよね。
まぁボールが丘を転がるイメージ。前の更新方向を覚えておいて、慣性で進み続ける。谷底を横切るジグザグを抑えて、一貫した方向に加速する。
ゴルフボールがバンカーの谷の一番深いところを発見するような感じ。
更新則
vt=βvt−1+gt
θt+1=θt−ηvt
β が慣性の強さ(典型的には β=0.9)。vt は勾配の 指数移動平均 になっている。展開すると、
vt=gt+βgt−1+β2gt−2+⋯
過去の勾配が指数的に減衰しながら蓄積されるから、一貫した方向の勾配は強化されて、振動方向の勾配は相殺される。
Nesterov Accelerated Gradient(NAG)
Nesterov (1983) はMomentumの「先読み」バージョンを提案した。まず慣性で仮の位置に進んでから、そこでの勾配を計算する。
θlook=θt−ηβvt−1
vt=βvt−1+∇L(θlook)
θt+1=θt−ηvt
「先に行ってみて、そこの景色を見てから修正する」という戦略。凸最適化での収束レートが O(1/t2) に改善される(Momentumは O(1/t))。理論的にはきれいだけど、実務では普通のMomentumと大差ないことも多い。
4. Adagrad
ここからが面白い。SGD系はすべてのパラメータに同じ学習率を使う。だけどNLPの埋め込み層を考えてみると、"the" のベクトルは毎バッチで更新されるのに、"serendipity" のベクトルはめったに更新されない。稀少なパラメータにはもっと大きなステップを踏ませたい。
更新則 (Duchi et al., 2011)
勾配の二乗を累積して、パラメータごとに学習率を適応させる。
Gt=Gt−1+gt⊙gt=i=1∑tgi⊙gi
θt+1=θt−Gt+ϵη⊙gt
⊙ はアダマール積、ϵ≈10−8 はゼロ除算防止。除算もすべて要素ごと。
頻繁に大きな勾配をもらうパラメータは Gt が大きくなって学習率が下がり、稀にしか勾配をもらわないパラメータは Gt が小さいまま学習率が高く保たれる。スパースなデータに強い。
だけど致命的な欠陥がある。Gt は単調増加。訓練が進むにつれて学習率は η/Gt→0 に収束して、事実上学習が止まる。「Adagradの学習率消失問題」。深いネットワークでは使い物にならない。
凸問題ではリグレットバウンド R(T)=O(T)、収束レート O(1/T) が証明されている。だけどDeep Learningの非凸問題ではこの保証は役に立たない。
5. RMSprop
Hintonの講義スライドから生まれた
Geoffrey Hinton が Coursera の講義で提案した(2012年、論文なし)。Adagrad の Gt が単調増加する問題を、指数移動平均 で解決する。
E[g2]t=γE[g2]t−1+(1−γ)gt⊙gt
θt+1=θt−E[g2]t+ϵη⊙gt
γ=0.9 が標準。Adagrad との違いは、Gt が全履歴の 和 だったのに対して、E[g2]t は 指数重み付き平均 になっていること。古い勾配情報は自動的に忘却されるから、学習率が際限なく縮むことがない。
数学的に言えば、Adagrad は勾配の L2 ノルムで割っていたのに対して、RMSprop は RMS(Root Mean Square) で割っている。名前の由来。
6. AdaDelta
Zeiler (2012) はRMSprop を独立に再発明して、さらに一歩進めた。学習率 η 自体を不要にする。
E[g2]t=ρE[g2]t−1+(1−ρ)gt2
Δθt=−E[g2]t+ϵE[Δθ2]t−1+ϵ⊙gt
E[Δθ2]t=ρE[Δθ2]t−1+(1−ρ)Δθt2
θt+1=θt+Δθt
分子に「過去の更新量のRMS」を使うことで、学習率のハイパーパラメータが消える。単位解析(dimensional analysis)的にも正しくなる(Adagrad/RMSpropでは勾配の単位と更新量の単位が合わない)。ρ=0.95 が標準。
面白いアイデアだけど、実務ではAdamに完全に喰われた。次はadamの話。
7. Adam
AdamはKingma & Ba (2014) が提案した、現時点でもっとも広く使われているオプティマイザ。Momentum(一次モーメント)と RMSprop(二次モーメント)を組み合わせた。
更新則
mt=β1mt−1+(1−β1)gt(一次モーメント:勾配の平均)
vt=β2vt−1+(1−β2)gt2(二次モーメント:勾配分散)
バイアス補正
m0=v0=0 で初期化するから、訓練初期は mt,vt がゼロに偏る。展開すると、
E[mt]=E[gt]⋅(1−β1t)
だから補正する。
m^t=1−β1tmt,v^t=1−β2tvt
t が小さいとき 1−βt は 1 より大幅に小さい(β1=0.9 で t=1 なら 0.1)から、補正項が10倍のブーストをかける。t→∞ で 1−βt→1 になって補正は消える。
パラメータ更新
θt+1=θt−v^t+ϵη⊙m^t
デフォルトは β1=0.9、β2=0.999、η=10−3、ϵ=10−8。
直感的理解
m^t は「どの方向に進むか」(Momentum)、v^t は「どのくらい慎重に進むか」(適応的学習率)。勾配の分散が大きいパラメータは慎重に、安定しているパラメータは大胆に更新する。これはパラメータごとの 信頼区間 を暗黙的に計算しているとも解釈できる。
別の見方をすると、Adamは損失ランドスケープの局所的な曲率に適応している。曲率が大きい方向(急な谷)では歩幅を縮め、曲率が小さい方向(なだらかな斜面)では歩幅を広げる。ニュートン法の対角近似とも解釈できる。Adamがほぼ全てのタスクで「そこそこ動く」のはこれが理由。
8. Adamの問題とAdamW
Weight Decayの罠
L2正則化の損失は Lreg=L+2λ∥θ∥2。SGDではL2正則化とweight decayは等価。
θt+1=θt−η(∇L+λθt)=(1−ηλ)θt−η∇L
だけどAdamでは ∇L+λθ が一次/二次モーメントの推定に混入して、正則化項が適応的スケーリングの影響を受けてしまう。大きな勾配を持つパラメータの正則化が弱まり、小さな勾配のパラメータの正則化が強くなる。これはおかしい。
Decoupled Weight Decay (Loshchilov & Hutter, 2017)
AdamWはweight decayをAdam の更新ステップから 分離 する。
θt+1=(1−ηλ)θt−v^t+ϵη⊙m^t
weight decay項 (1−ηλ)θt がモーメント推定の外側にある。これによりweight decayがパラメータの大きさにのみ比例し、適応的スケーリングの影響を受けない。
Transformer系モデル(BERT、GPT、ViT)の訓練はほぼ全てAdamWを使っている。「Adam使ってます」と言う人の大半は実際にはAdamWを使っている。PyTorchの torch.optim.AdamW がそのまま標準。デファクト。
9. ここまでの整理
各オプティマイザが解決した問題
数式で比較するとこうなる。
| アルゴリズム | 更新則 |
|---|
| SGD | θ−ηg |
| Momentum | θ−η(βv+g) |
| Adagrad | θ−∑g2+ϵη⊙g |
| RMSprop | θ−E[g2]+ϵη⊙g |
| Adam | θ−v^+ϵη⊙m^ |
| AdamW | (1−ηλ)θ−v^+ϵη⊙m^ |
10. LAMB / LARS:大バッチ訓練
バッチサイズを 8192 以上にすると、従来のオプティマイザは収束が劣化する。バッチが大きいと勾配の分散が小さくなって汎化性能が落ちる(Sharp Minimaに収束しやすい)問題もあるけど、もっと直接的な問題は レイヤー間で更新量のスケールがバラバラ になること。
LARS (You et al., 2017)
Layer-wise Adaptive Rate Scaling。各レイヤー l の更新量をパラメータノルムと勾配ノルムの比(trust ratio)でスケーリングする。
ϕl=∥gl∥+λ∥θl∥∥θl∥
θl←θl−η⋅ϕl⋅(gl+λθl)
更新量がパラメータの大きさに対して「信頼できる」範囲に収まるように調整する。
LAMB (You et al., 2019)
LARS のアイデアをAdamWに適用したもの。BERTのバッチサイズ 32768 での訓練を76分に短縮した。
rt=v^t+ϵm^t+λθt
θt+1=θt−η⋅∥rt∥∥θt∥⋅rt
∥θt∥/∥rt∥ がtrust ratio。レイヤーごとの更新量をパラメータノルムに比例させて安定化する。
11. Lion:遺伝的アルゴリズムが見つけたオプティマイザ
Chen et al. (2023, Google Brain) はオプティマイザの更新則自体をプログラムとして表現して、遺伝的アルゴリズム(AutoML-Zero)で探索した。見つかったのがLion(EvoLved Sign Momentum)。人間は設計していないんだよね。面白くない?
更新則
θt+1=(1−ηλ)θt−η⋅sign(β1mt+(1−β1)gt)
mt+1=β2mt+(1−β2)gt
デフォルトは β1=0.9、β2=0.99。
更新量は sign(⋅) で すべてのパラメータが同じ絶対値 η で更新される。Adamのように v^t で割るステップがないから、メモリが約50%削減(二次モーメント vt を保持しない)。
sign 演算は暗黙的な正則化として機能する。大きな勾配も小さな勾配も同じ ±1 になるから、外れ値的に大きな勾配の影響が抑えられる。
実用上のポイントとして、LionはAdamWの学習率の1/3〜1/10に設定する。weight decayは逆に3〜10倍に。大バッチ(1024以上)で特に効果的。小バッチだとAdamWの方が安定することが多い。
12. Sophia:二次情報を安く使う
ニュートン法の更新則は θt+1=θt−H−1gt(H はヘシアン)。曲率情報を使うから収束が速い。だけどヘシアン H∈Rd×d の逆行列はパラメータ数 d が数十億のLLMでは計算不能。
Sophia の戦略 (Liu et al., 2023)
対角ヘシアンの推定値 h^t をプリコンディショナーとして使い、クリッピングで安定化する。
mt=β1mt−1+(1−β1)gt
h^t=β2h^t−1+(1−β2)ht
θt+1=θt−η⋅clip(max(h^t,ϵ)mt,γ)
ht はヘシアンの対角成分の推定値。clip(x,γ)=max(−γ,min(γ,x)) は要素ごとのクリッピング。
ヘシアンの推定方法
フルヘシアンは O(d2) で無理だから、2つの軽量推定法を提案している。
Hutchinson推定量: ランダムベクトル u∼N(0,I) に対して、
diag(H)≈u⊙(Hu)
Hu はヘシアン-ベクトル積で、自動微分で O(d) で計算できる。
Gauss-Newton-Bartlett推定量(自己回帰LM向け): 損失が交差エントロピーのとき、
ht=B1b=1∑B(∇θℓb)2
ミニバッチの個々の勾配の二乗平均。実質的にFisher情報行列の対角近似。
Adamでは mt/vt でスケーリングするから、vt が非常に小さいと更新量が爆発しうる。Sophiaのクリッピングは最悪ケースの更新量を γ に制限する。非凸問題でヘシアンが急変しても安全。
GPT-2の事前学習でAdamWの 2倍の速度(同じperplexityに到達するステップ数が半分)。ヘシアン推定は10ステップに1回でよく、オーバーヘッドは約5%。
13. Muon:勾配を直交化する
Muon(Multiplied by Orthogonalized Nesterov momentum、あるいは単に Momentum + Orthogonalization)は、勾配のモーメンタムを直交化してからパラメータを更新する。たぶん今のところ最新のやつ。
コアアイデア
重み行列 W∈Rm×n に対する勾配 Gt のモーメンタム Mt を計算して、Mt を 極分解(polar decomposition) で直交化する。
Mt=βMt−1+(1−β)Gt
M~t=Mt(Mt⊤Mt)−1/2
Wt+1=Wt−ηM~t
(Mt⊤Mt)−1/2 はグラム行列の逆平方根。M~t は Mt の特異値を全て 1 に揃えた行列。つまり更新方向の「向き」だけ残して「大きさ」を均一化する。
Newton-Schulz反復
(M⊤M)−1/2 を直接計算するのは O(n3) で高い。Muonは Newton-Schulz反復 で近似する。
X0=M/∥M∥F
Xk+1=21Xk(3I−Xk⊤Xk)
5回程度の反復で十分な精度が得られる。各反復は行列積2回(O(mn⋅min(m,n)))。
実装上は係数を最適化した quintic variant が使われる。
Xk+1=aXk+bXk(Xk⊤Xk)+cXk(Xk⊤Xk)2
a=3.4445,b=−4.7750,c=2.0315。3回の反復で相対誤差 10−7 以下。
なぜ直交化が効くのか
勾配のスペクトル(特異値)は通常、数個の大きな成分に支配されている。Adamは各要素を独立にスケーリングするから、このスペクトルの偏りに対処できるけど、要素間の相関は無視する。Muonは行列全体のスペクトルを均一化するから、支配的な方向だけでなく全方向に均等に更新が行き渡る。これは スペクトルノルム制約 の下での最適化に相当する。
注意点として、Muonは行列パラメータ(Linear層、Attention層)に適用する。埋め込み層やLayerNormのような1次元パラメータにはAdamWを使う。ハイブリッド構成が標準。
14. 全体比較
| アルゴリズム | メモリ(パラメータあたり) | 二次情報 | 得意な場面 |
|---|
| SGD + Momentum | 1 状態 | なし | 小規模、画像分類 |
| Adagrad | 1 状態 | 対角 | スパース勾配、NLP埋め込み |
| RMSprop | 1 状態 | 対角EMA | RNNの訓練 |
| Adam | 2 状態 | 対角EMA | ほぼ何でも |
| AdamW | 2 状態 | 対角EMA | Transformer全般 |
| LAMB | 2 状態 + trust ratio | 対角EMA | 大バッチ(8K+) |
| Lion | 1 状態 | なし(sign) | メモリ制約、大バッチ |
| Sophia | 2 状態 | 対角ヘシアン | LLM事前学習 |
| Muon | 1 状態 + Newton-Schulz | フルスペクトル(行列) | 行列パラメータ全般 |
15. Pythonで書くとこうなる
import numpy as np
class Adam:
def __init__(self, params, lr=1e-3, beta1=0.9, beta2=0.999, eps=1e-8):
self.lr, self.beta1, self.beta2, self.eps = lr, beta1, beta2, eps
self.m = [np.zeros_like(p) for p in params]
self.v = [np.zeros_like(p) for p in params]
self.t = 0
def step(self, params, grads):
self.t += 1
for i, (p, g) in enumerate(zip(params, grads)):
self.m[i] = self.beta1 * self.m[i] + (1 - self.beta1) * g
self.v[i] = self.beta2 * self.v[i] + (1 - self.beta2) * g**2
m_hat = self.m[i] / (1 - self.beta1**self.t)
v_hat = self.v[i] / (1 - self.beta2**self.t)
p -= self.lr * m_hat / (np.sqrt(v_hat) + self.eps)
class AdamW:
def __init__(self, params, lr=1e-3, beta1=0.9, beta2=0.999,
eps=1e-8, weight_decay=0.01):
self.lr, self.beta1, self.beta2 = lr, beta1, beta2
self.eps, self.wd = eps, weight_decay
self.m = [np.zeros_like(p) for p in params]
self.v = [np.zeros_like(p) for p in params]
self.t = 0
def step(self, params, grads):
self.t += 1
for i, (p, g) in enumerate(zip(params, grads)):
# Decoupled weight decay(ここがAdamとの違い)
p *= (1 - self.lr * self.wd)
self.m[i] = self.beta1 * self.m[i] + (1 - self.beta1) * g
self.v[i] = self.beta2 * self.v[i] + (1 - self.beta2) * g**2
m_hat = self.m[i] / (1 - self.beta1**self.t)
v_hat = self.v[i] / (1 - self.beta2**self.t)
p -= self.lr * m_hat / (np.sqrt(v_hat) + self.eps)
class Lion:
def __init__(self, params, lr=1e-4, beta1=0.9, beta2=0.99,
weight_decay=0.1):
self.lr, self.beta1, self.beta2 = lr, beta1, beta2
self.wd = weight_decay
self.m = [np.zeros_like(p) for p in params]
def step(self, params, grads):
for i, (p, g) in enumerate(zip(params, grads)):
# sign of interpolation(メモリは1状態だけ)
update = np.sign(self.beta1 * self.m[i] + (1 - self.beta1) * g)
p *= (1 - self.lr * self.wd)
p -= self.lr * update
# モーメンタム更新(次ステップ用)
self.m[i] = self.beta2 * self.m[i] + (1 - self.beta2) * g
class SophiaG:
"""Sophia with Gauss-Newton-Bartlett estimator"""
def __init__(self, params, lr=1e-4, beta1=0.965, beta2=0.99,
gamma=2.0, eps=1e-12, weight_decay=0.1):
self.lr, self.beta1, self.beta2 = lr, beta1, beta2
self.gamma, self.eps, self.wd = gamma, eps, weight_decay
self.m = [np.zeros_like(p) for p in params]
self.h = [np.ones_like(p) for p in params] # ヘシアン推定
def step(self, params, grads, hessians=None):
for i, (p, g) in enumerate(zip(params, grads)):
self.m[i] = self.beta1 * self.m[i] + (1 - self.beta1) * g
if hessians is not None:
self.h[i] = self.beta2 * self.h[i] + (1 - self.beta2) * hessians[i]
ratio = self.m[i] / np.maximum(self.h[i], self.eps)
update = np.clip(ratio, -self.gamma, self.gamma)
p *= (1 - self.lr * self.wd)
p -= self.lr * update
16. 学習率スケジュール
オプティマイザだけじゃ話は半分なんだよね。学習率スケジュールが性能を左右することも多い。
Warmup + 各種Decay。Transformer訓練ではWarmup + Cosine Decayが標準
Warmup: 最初の数ステップで学習率を 0 から目標値まで線形に増加。Adamの二次モーメント vt が安定するまでの時間を稼ぐ。
Cosine Decay: Warmup後、コサイン関数に従って学習率を徐々に下げる。
ηt=ηmin+21(ηmax−ηmin)(1+cosTπt)
GPT系の訓練ではほぼこれ。RAdam はWarmupの自動化を試みたけど、結局手動Warmup + Cosineが勝つことが多い。
参考文献
- Kingma, D.P. & Ba, J. (2014). "Adam: A Method for Stochastic Optimization." ICLR 2015
- Loshchilov, I. & Hutter, F. (2017). "Decoupled Weight Decay Regularization." ICLR 2019
- Duchi, J., Hazan, E. & Singer, Y. (2011). "Adaptive Subgradient Methods for Online Learning." JMLR, 12, 2121-2159
- Zeiler, M. (2012). "ADADELTA: An Adaptive Learning Rate Method." arXiv:1212.5701
- Hinton, G. (2012). "Neural Networks for Machine Learning." Coursera Lecture 6e(RMSpropの提案)
- You, Y. et al. (2017). "Large Batch Training of Convolutional Networks." arXiv:1708.03888(LARS)
- You, Y. et al. (2019). "Large Batch Optimization for Deep Learning." ICLR 2020(LAMB)
- Chen, X. et al. (2023). "Symbolic Discovery of Optimization Algorithms." NeurIPS 2023(Lion)
- Liu, H. et al. (2023). "Sophia: A Scalable Stochastic Second-order Optimizer for Language Model Pre-training." ICLR 2024
- Muon Optimizer (2024). Newton-Schulz iteration for gradient orthogonalization