ミニバッチと確率的勾配降下法(SGD)
第4章の勾配降下法では、損失の坂を下る向き(勾配)を求め、次のように重み w w w を少しずつ動かしました。
w ← w − η × 勾配 w \leftarrow w - \eta \times \text{勾配} w ← w − η × 勾配
η \eta η (イータ)は 学習率 で、1回にどれだけ動かすかを決める数です。← \leftarrow ← は「右の値で、左を置きかえる」という意味です。
勾配を、何件のデータで計算するか
勾配は、データの損失の平均から計算します。何件のデータを使うかで、3つのやり方があります。
全部のデータで正確な勾配を求め、その向きに下っていく方法(バッチ学習の勾配降下法)は、最急降下法 とも呼ばれます。実務ではほとんどの場合、ミニバッチ学習 を使います。第7章のミニプロジェクトでも、訓練データを32枚ずつに分けて学習させていました。ミニバッチは、毎周、訓練データの順番をでたらめに並べ替えてから作ります。でたらめに選んだデータで勾配を計算するので、この方法を 確率的勾配降下法 (SGD、Stochastic Gradient Descent)と呼びます。
訓練データが1347件、ミニバッチが32件なら、1周で 1347 ÷ 32 = 42.09 … 1347 \div 32 = 42.09\ldots 1347 ÷ 32 = 42.09 … なので、43回重みを更新します(最後の1回は、残りの3件で計算します)。
学習率の大きさで、進み方が大きく変わる
手書き数字のデータで、ニューラルネットワーク(64 → 32 → 10)をSGDで20周学習させました。
学習率0.01では、坂をほんの少しずつしか下れず、20周しても損失がなかなか下がりません。学習率を大きくすれば速く進みますが、大きすぎると、谷を飛び越えて行ったり来たりし、かえって学習が進まなくなります(第4章)。ちょうどよい学習率を見つけるのは、意外に難しい のです。次のスライドから、この問題を和らげる工夫を見ていきます。
モメンタムとAdam:坂の下り方を工夫する
モメンタム:転がるボールのように、勢いをつける
SGDは、毎回その場の勾配だけを見て進みます。坂道を転がるボールを想像してください。ボールは、今の坂の向きだけでなく、それまでの 勢い を持って進みます。同じ向きの坂が続けば、どんどん速くなります。
この勢いを取り入れたのが モメンタム です。勢い v v v を、次のように更新します。
v ← β × v − η × 勾配 , w ← w + v v \leftarrow \beta \times v - \eta \times \text{勾配}, \qquad w \leftarrow w + v v ← β × v − η × 勾配 , w ← w + v
β \beta β (ベータ)は、前の勢いをどれだけ残すかで、0.9がよく使われます。同じ向きの勾配が続くと、勢いが積み重なって速く進みます。勾配の向きが毎回逆になる(谷を行ったり来たりする)ときは、勢いが打ち消し合って、振れ幅が小さくなります。細長い谷のように、ある向きには急で、別の向きにはゆるやかな形の損失(学習が進みにくい形)で、特に効果があります。勢いで進んだ 先の位置 で勾配を計算する改良版を、Nesterovのモメンタム (ネステロフ)と呼びます。
Adam:重みごとに、進む幅を自動で調整する
重みによって、勾配がいつも大きいものと、いつも小さいものがあります。全部の重みを同じ学習率で動かすと、大きい方に合わせれば小さい方が進まず、小さい方に合わせれば大きい方が暴れます。
Adam (アダム)は、重みごとに、
勾配の平均(勢い、モメンタムと同じもの)
勾配の2乗の平均(その重みの勾配が、ふだんどれくらい大きいか)
を記録し、「勾配の平均 ÷ ふだんの大きさ」の向きに進みます。ふだん勾配が大きい重みは控えめに、小さい重みは大きめに動くので、重みごとに学習率を自動で調整しているようなものです。同じ考え方の前の世代の方法に、AdaGradやRMSPropがあります。
実験:4つの方法を比べる
前のスライドと同じ条件で、20周学習させました(モメンタムの β \beta β は0.9)。
周 損失 0 5 10 15 20 0 1 2 SGD(学習率0.01) SGD(学習率0.1) モメンタム(学習率0.01) Adam(学習率0.01)周ごとの訓練データの損失。SGD(学習率0.01)は1周目の約2.2からゆっくり下がり、20周目でも約0.92。SGD(学習率0.1)は速く下がるが、9周目(約0.37)や15周目(約0.28)のように、ところどころで損失が跳ね上がる。モメンタム(学習率0.01)は、跳ね上がらずになめらかに下がる。どちらも20周目に約0.11。Adam(学習率0.01)は最も速く、5周目に約0.10、20周目に約0.01まで下がる
モメンタムは、SGDの 10分の1の学習率 で、SGD(学習率0.1)とほぼ同じ速さで進みました。勢いが積み重なったためです。しかも、SGD(学習率0.1)のように損失が跳ね上がることがなく、なめらかに下がりました。勢いが、毎回の勾配のばらつきをならしているのです
Adam(学習率0.01)が最も速く下がりました。ただし、学習率0.001にすると、モメンタムより遅く なりました。Adamでも、学習率の選び方は大切です
Pythonでモメンタムを書く、よくある誤解と振り返り
モメンタムの更新は、勢い v を1つ増やすだけで書けます。
import numpy as np
def momentum_step(w, v, grad, lr=0.01, beta=0.9):
v = beta * v - lr * grad # 前の勢いを少し残し、今の勾配の分を足す
w = w + v # 勢いの分だけ重みを動かす
return w, v
w = np.array([1.0, -2.0]) # 重み
v = np.zeros_like(w) # 勢い(最初は0)
for grad in [np.array([0.5, -0.1]), np.array([0.5, -0.1]), np.array([0.5, -0.1])]:
w, v = momentum_step(w, v, grad)
print(v) # 同じ勾配が続くと、勢いがだんだん大きくなる
同じ勾配 ( 0.5 , − 0.1 ) (0.5, -0.1) ( 0.5 , − 0.1 ) が3回続くと、1つ目の重みの勢いは − 0.005 → − 0.0095 → − 0.01355 -0.005 \to -0.0095 \to -0.01355 − 0.005 → − 0.0095 → − 0.01355 と、だんだん大きくなります。SGDなら、毎回 − 0.005 -0.005 − 0.005 ずつしか動きません。
PyTorchでは、torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9) や torch.optim.Adam(model.parameters(), lr=0.001) のように、1行で方法を選べます(第7章の対応表の optimizer です)。
よくある誤解
「Adamを使えば、学習率を気にしなくてよい」 : 実験では、学習率0.001のAdamは、学習率0.01のモメンタムより遅くなりました。Adamでも学習率は大切な設定です(ただし、よく使われる0.001前後で、まずまずうまくいくことが多い)
「ミニバッチは大きいほどよい」 : 大きいほど勾配は正確になりますが、1回の更新が重くなり、メモリも多く使います。また、ばらつきが少なすぎると、過学習しやすくなる場合があることも知られています。32〜256件がよく使われます
「速く損失が下がる方法が、いつも一番よい」 : 見るべきは、訓練データの損失の速さだけではなく、検証データでの結果です(前のレッスン)
振り返り
ミニバッチ学習: データを少しずつに分けて、勾配を計算し重みを更新する。でたらめに選ぶので、確率的勾配降下法(SGD)と呼ぶ
モメンタム: 前の勢いを残して進む。同じ向きなら速く、行ったり来たりは打ち消し合う
Adam: 重みごとに、勾配の大きさに合わせて進む幅を調整する。速いが、学習率の選び方は大切