こんにちは、サイオステクノロジーの藤井です。
この記事では、ディープラーニングが具体的にどのようなアルゴリズムで動いているかを書いていこうと思います。ディープラーニングを使ったことのない方や、私と同じように動かしたことはあるけどなんで動いているかわからない方が、ディープラーニングを理解する助けになれれば良いなと思っております。
今までの記事では、ニューラルネットワークの学習について「勾配を求め、勾配の方向にパラメータを動かす」と書きました。
今回は、その「パラメータを動かす」の部分について詳しく書いていきます。
「パラメータをどう動かすか」を決める部分を最適化アルゴリズムといいます。この記事ではいくつかの最適化アルゴリズムとその考え方を紹介したいと思います。最後におまけとして各最適化アルゴリズムの比較を載せています。
結論
それぞれの最適化アルゴリズムはこんな考え方のもとパラメータを動かしています。
SGD | 最も基本となるアルゴリズム・勾配の方向に進む |
momentumSGD | SGD+「速度」と「慣性」の概念 |
AdaGrad | 学習が進むほど学習率を小さくしていく |
RMSprop | AdaGrad+最近の勾配ほど強く影響 |
AdaDelta | 単位をそろえる |
Adam | momentumSGD+RMSprop |
最適化アルゴリズムとは
ディープラーニングにおいて、最適化アルゴリズム(optimizer)とは主に損失関数の最小値(とその時のパラメータ)を求めるアルゴリズムです。
その1でニューラルネットワークを「重みとバイアスを変数として、lossを求める関数」として解釈すると書きました。
この時最適化アルゴリズムが行うのは以下のようなことです。
- パラメータ(上の図の場合は、w1~w10,b1~b4の14個の変数)の値を変更して、lossの値を出来る限り小さくする
- 最適化アルゴリズムが使うことのできる情報は、「パラメータの勾配」と「これまでにパラメータを動かした記録」のみ(関数の中身の構造などは見れない)
「パラメータの勾配」とは、それぞれのパラメータごとにあり、他のパラメータを動かさずにそのパラメータだけ増やしたときlossがどれだけ増えます。
例えば、w1の勾配が0.3の場合、w1以外を変えずにw1のみを1増やすとlossが約0.3増えます。
逆に、w1の勾配が-0.3の場合、w1のみを1増やすとlossが約0.3減ります。
実際にはw1とlossの関係は線形ではないので、ぴったり0.3ではないです。(なので「約」と付けています)
それぞれの最適化関数の仕組み
デフォルトパラメーター等はKerasのドキュメントを参考にしています。
コード内で出てくる変数や関数については以下の通りです。
steps | 学習回数(整数) |
parameter | 学習するパラメータ(行列) |
grad | パラメータの勾配(行列) |
lr | 学習率(learning rate)(小数) |
sqrt(x) | xの平方根 |
SGD
SGDはstochastic gradient descent(確率的勾配降下法)の略です。
SGDの考え方は、
「勾配を見ればどちらに動かせばlossが減るのか分かるなら、勾配の分だけパラメーターの値を減らせばよい」
です。
for i in range(steps): parameter = parameter - lr * grad
デフォルトパラメータ lr = 0.01
パラメータを勾配×学習率だけ減らします。
学習率は一度のパラメータの更新でどのぐらい学習を進めるかを調整します。小さすぎると学習が全然進まず、大きすぎるとパラメータが最適値(lossが最小になるときの値)を通り過ぎてしまいうまく学習できません。
もっとも簡単で基本的なアルゴリズムです。これ以降に紹介する最適化アルゴリズムは基本的にこれを改良していったものです。
確率的勾配降下法だけでなく、最急降下法やミニバッチSGDもSGDとして扱われることもあるため、この記事では、この3つをまとめてSGDとして書きます。
この3つの違いは、データが複数あった時に
- 最急降下法 → 全部のデータを一気に使う
- 確率的勾配降下法 → ランダムにデータを一個ずつ選び出し使う
- ミニバッチSGD → ランダムにデータをミニバッチに分けミニバッチごとに使う
といった違いです。(ちなみにKerasでは次に紹介するmomentumSGDまで、SGDに含まれています)
momentumSGD
momentumSGDは、SGDに「慣性」や「速度」の概念を付け足したアルゴリズムです。
v=0 #gradと同じサイズの行列 for i in range(steps): v = v * momentum - lr * grad parameter = parameter + v
デフォルトパラメータ lr = 0.01
momentum = 0.9
vは速度を表します。momentumSGDでは、速度を勾配×学習率だけ減らします。
momentumは、速度の減衰です。物体の運動における摩擦のようなものです。一定の割合で速度を減らしていきます。
速度の概念を導入することで、
何回も勾配が一定の方向を指す(=最適値はまだ遠い)場合は、パラメータの更新量が大きくなり、
勾配が+になったりーになったりを繰り返す(=最適値付近で行ったり来たりしている)場合は、パラメータの更新量が小さくなる。
という効果があり、SGDと比較してパラメータの振動が減り学習が速く進みます。
また、あるパラメーターを動かした場合のlossの値が下のグラフのようになる場合
赤矢印のところで勾配が0になってしまうためSGDでは学習がそこで止まってしまいますが、momentumSGDでは赤矢印のところを通り過ぎてさらにlossが小さくなるポイントまで進むことができます。
AdaGrad
パラメータが最適値までまだまだ遠い時(学習初期)は大きくパラメータを更新したいが、パラメータが最適値に近づいた時(学習終盤)は最適値を通り過ぎないようにパラメータの更新量を小さくしたい。という考えで学習が進むほど学習率を小さくしていくテクニックがあります。
これをパラメータごとに独立して行っているのがAdaGradです。Adagradでは、今までの更新量の大きいパラメータ(正確には今までの勾配が大きいパラメータ)ほど、更新量が小さくなります。
h=0 #gradと同じサイズの行列 for i in range(steps): h = h + grad * grad parameter = parameter - lr * grad / (sqrt(h) + epsilon)
デフォルトパラメータ lr = 0.01
epsilon = 0.0000001 0で割ることにならないために微小値を分母に足しています
パラメータごとに固有の値hを持ちます。↑のコードではparameterと同じサイズの行列に値を保存しています。hは、学習のたびに勾配の2乗ずつ増加していきます。そして、hの平方根でパラメータ更新量を割っているので、hが大きいほどパラメータ更新量は小さくなります。
ちなみにAdaGradは、adaptive gradient algorithmの略です。直訳すると、「適応性のある勾配アルゴリズム」となります。
RMSprop
AdaGradでは、hは増えていく一方、つまり学習率はどんどん小さくなっていきます。もし仮に、学習最初期にとても大きな勾配があった場合、そのパラメータは、その後ほとんど更新されなくなります。
この問題を解決するために、最近の勾配ほど強くhの大きさに影響するように(昔の勾配の影響がどんどん減っていくように)、したのがRMSPropです。
h=0 #gradと同じサイズの行列 for i in range(steps): h = rho * h + (1 - rho) * grad * grad parameter = parameter - lr * grad / (sqrt(h) + epsilon)
デフォルトパラメータ lr = 0.001
rho = 0.9 どの程度hを保存するか
epsilon = 0.0000001 0で割ることにならないために微小値を分母に足しています
デフォルトパラメータの場合、hに加算された勾配の情報は1ステップごとに0.9倍されていくので、昔の勾配ほど影響が少なくなります。これを指数移動平均といいます。あとはAdaGradと同じです。
AdaDelta
AdaDeltaは単位をそろえたアルゴリズムです。
例えば、x[秒]後の移動距離をy[m]とした時、y=axと書けます。
この時、xの単位は[秒]
yの単位は[m]
さらに、yの微分は、y’=(ax)’=aとなり、これは速さを意味します。
つまりy’の単位は[m/s]です。
話を戻して、SGDでは、パラメータから勾配を引いています。(実際には学習率がかかっていますが、”率”は単位がないのでここでは無視します)勾配はパラメータの微分であり、これは距離から速さを引いているようなもので単位がそろっていません。
この単位をそろえようという考えで出来たのがアルゴリズムがAdaDeltaです。
h=0 #gradと同じサイズの行列 s=0 #gradと同じサイズの行列 for i in range(steps): h = rho * h + (1 - rho) * grad * grad v = grad * sqrt(s+epsilon) / sqrt(h+epsilon) s = rho * s + (1 - rho) * v * v parameter = parameter - v
デフォルトパラメータ rho = 0.95 どの程度hやsを保存するか
epsilon = 0.0000001 0で割ることにならないために微小値を分母に足しています
hは過去の勾配の2乗の合計(の指数移動平均)、sは過去のパラメータ更新量の2乗の合計(の指数移動平均)を表しています。
vは「勾配×過去のパラメータ更新量÷過去の勾配」なので、パラメータと単位が一致します。
AdaDeltaは学習率を持たないという特徴もあります。
Adam
Adaptive Moment Estimationの略です。
AdamはmomentumSGDとRMSpropを合わせたようなアルゴリズムです。
m=0 #gradと同じサイズの行列 v=0 #gradと同じサイズの行列 for i in range(steps): m = beta_1 * m + (1 - beta_1) * grad v = beta_2 * v + (1 - beta_2) * grad^2 om = m / (1 - beta_1) ov = v / (1 - beta_2) parameter = parameter - lr * om / sqrt(ov + epsilon)
デフォルトパラメータ lr = 0.001
beta_1 = 0.9
beta_2 = 0.999 epsilon = 0.0000001 0で割ることにならないために微小値を分母に足しています
mによってmomentumSGDのようにこれまでの勾配の情報をため込みます。また、vによってRMSpropのように勾配の2乗の情報をため込みます。それぞれ指数移動平均で昔の情報は少しずつ影響が小さくなっていきます。
mでは勾配の情報をため込む前に、(1 – beta_1)がかけられてしまいます。(デフォルトパラメータなら0.1倍)そこで、omでは、mを(1 – beta_1)で割ることで勾配の影響の大きさをもとに戻します。ovも同様です。
最適化関数を比較してみた
ここまでで紹介した6つの最適化アルゴリズムを比較したので実際に比較します。
条件
・データセット Mnist手書き数字画像 0~9の10個に分類します ・モデル 入力784ノード ⇒ 全結合層 ⇒ 100ノード ⇒ 全結合層 ⇒ 100ノード ⇒ 全結合層 ⇒ 出力10ノード 活性化関数はReLU ・パラメータ 学習率はすべて0.01で統一(AdaDeltaを除く) それ以外のパラメータはデフォルトパラメー ミニバッチ学習すると収束が速すぎて比較しずらいのでバッチサイズは60000 ・実行環境 Anaconda 3 Python 3.7.7 Numpy 1.19.1 ディープラーニング用ライブラリは、Dezeroをもとに自作したライブラリを使用
結果
学習データのloss(クロスエントロピー誤差)は以下のようになりました。
縦軸がlossの値、横軸がステップ数です。AdaGrad・RMSprop・Adamの三つすぐにlossが小さくなっています。(=学習が速く進んでいます。)MomentumSGDは最初はゆっくりですが、だんだん下がっていきました。SGDとAdaDeltaはなかなかlossが下がりませんでした。
学習データでの精度(正解率)は以下のようになりました。
縦軸がlossの値、横軸がステップ数です。AdamやAdaGradは100ステップで95%程度まで学習が進んでいます。
どちらも学習の速度は
SGD≒AdaDelta<MomentumSGD<AdaGrad≒RMSprop≒Adam
となりました。
学習が速かったAdaGrad・RMSprop・Adamの中ではAdamが最もぶれが少なく安定して学習が進んでいました。
学習率などの調整やモデルによっても最適化アルゴリズムの優劣は変わりますがこの実験の場合ではAdamが最も良い結果になりました。
まとめ
この記事で書いたこと
- 最適化アルゴリズムとは何か
- SGDは最も基本となるアルゴリズムであり、勾配の情報のみを使ってパラメータを動かす
- momentumSGDはSGDに「速度」と「慣性」の概念を追加したもの
- AdaGradは「学習が進むほど学習率を小さくしていく」という機能をパラメータごとに独立して行う
- RMSpropはAdaGradを最近の勾配ほど強く影響するように改良したもの
- AdaDeltaは単位がそろうようにしたもの
- AdamはmomentumSGDとRMSpropを組み合わせたようなもの
次回の内容は、未定です。
ディープラーニングについて学ぶ上で「ゼロから作るDeep Leaning Pythonで学ぶディープラーニングの理論と実装」を参考にさせていただきました。
この記事で紹介したアルゴリズムはあくまで一例です。興味がある方はほかの方法も調べてみてください。
- [AI入門] ディープラーニングの仕組み ~その1:ニューラルネットワークってなに?~
- [AI入門] ディープラーニングの仕組み ~その2:学習を高速に行う仕組み~
- [AI入門] ディープラーニングの仕組み ~その3:CNNの仕組み~
- [AI入門] ディープラーニングの仕組み ~その4:最適化関数を比較してみた~