DL Notes 高度な勾配降下法

DL Notes 高度な勾配降下法のテクニック

ニューラルネットワークのトレーニングに使用される主な最適化アルゴリズム、Pythonでスクラッチから説明および実装されたもの

Photo by Jack Anstey / Unsplash

前の記事で勾配降下法について基本的な概念とこの種の最適化の主な課題を説明しました。

ただし、この記事では確率的勾配降下法(SGD)と勾配降下法の「バッチ」および「ミニバッチ」の実装のみをカバーしました。

他のアルゴリズムは収束速度、ランドスケープの特徴(勾配消失問題)、良好なパフォーマンスを達成するための学習率の選択に対する依存度の少ない点で利点を提供します。

そのため、本日はより高度な最適化アルゴリズムについて、Pythonでスクラッチから実装し、アニメーション化された可視化を通じて比較します。

この記事で使用した学習リソースもリストアップしています。これらは形式的な概念により深く立ち入るために役立ちます。

単純な目的関数を用いたアルゴリズムの比較

この記事では、Pythonで異なるアルゴリズムを実装する方法を示します。

ここで示されている図を作成するために使用されたコードをすべて見るために、GitHubでアクセスまたは直接Google Colabでアクセスしてください。

アニメーションの生成には、Pythonでアニメーション化された勾配降下法図を作成するという前の記事で示された方法を使用しました。

関数の定義では、次のコードが既に含まれていることを前提としています。これらはnumpyクラスとメソッドを使用し、関数fおよびその勾配grad_fを呼び出します。

import numpy as np# 表面を計算する関数を作成def f(theta):  x = theta[0]  y = theta[1]  return x**2 - y**2# 勾配を計算する関数を定義def grad_f(theta):    returnValue = np.array([0.,0.])    x = theta[0]    y = theta[1]    returnValue[0] += 2*x    returnValue[1] += - 2*y    return returnValue

運動量

Photo by Sharon Pittaway on Unsplash

最適化アルゴリズムを坂道を転がるボールと比較することができます。

もしも「ボール」が現実世界のように運動量を持っていた場合、十分なスピードで坂を下り加速した後も、局所的な最小値に引っかかることは少ないでしょう。

これが、勾配降下法が局所的な最小値に引っかかる問題に取り組む際に人々が認識したことです。

高校の物理学から、平行移動の運動量は物体の質量と速度の積で定義されることを知っています:

運動量

さらに、物体の質量mに比例する高さhにおける物体の重力ポテンシャルエネルギーも知られています:

重力ポテンシャルエネルギー

さらに、物体のポテンシャルエネルギーと物体に作用する力との直接的な関係があります

力はポテンシャルエネルギーの負の勾配と等しい

pUの関係は、ニュートンの第二法則から導かれることができます:

物体の運動の変化は、加えられた力に比例し、加えられた力の直線上で行われます。

ニュートンの第二法則

💡 実際には、この物理学の類似は勾配降下最適化に運動量を追加する利点とデメリットを網羅するにはあまりにも単純化されています。全体像を把握するために、運動量が本当に機能する理由は何ですか?をチェックすることをおすすめします。

運動量を追加する方法

最適化アルゴリズムを初期化する際に、私たちは「ボール」を高さhに置き、それにポテンシャルエネルギーUを与えます。

ボールにかかる力は、そのようなポテンシャルエネルギーの勾配に比例します。最適化している関数の勾配(移動している面)の勾配と同じです。

運動量が最適化に対して機能する方法は、勾配を使用して「粒子」の「速度」を変更し、それによって位置を変更することです。

運動量の更新

速度の項目のため、ボールは一定の勾配を持つどの方向でも加速度を蓄積します。

私はこのPython関数として次のように実装しました:

def gradient_descent(x_init, y_init, step_size, n_iters, momentum):  eta = step_size  mu = momentum  # 注意:mu = 0の場合、このアルゴリズムは単なるSGDです  # 結果を格納する配列を初期化  theta = np.tile([x_init, y_init], (n_iters,1) )  z = np.tile([f(theta[0])], n_iters )  # 速度項を初期化  v_t = np.array([0,0])  for k in range (1, n_iters):      # 速度を更新      v_t = mu*v_t - eta*grad_f(theta[k-1])   # 位置を更新      theta[k] = theta[k-1] + v_t      z[k] = f(theta[k])  # 位置の座標を保存  dataset = np.stack((theta[:,0], theta[:,1], z), 1)    return dataset

運動量の更新により、最適化は低曲率の方向で加速し、特徴的な「地形」やノイズの多いデータによる振動を滑らかにしています[3]。

一部の人々は、運動量アップデートは実際には摩擦係数の物理的効果とより一貫性があると主張しています。なぜならそれはシステムの運動エネルギーを減少させるからです [2]。

別の解釈方法は、それが最適化プロセスに「短期」メモリを与えるということです。

運動量パラメータは1より小さいため、それは以前の勾配の指数加重和のように機能し、速度のアップデートは以下のように書き換えることができます [3][5]:

Velocity term rewritten as a weighted sum.

ここでgは瞬時の勾配であり、vは滑らかな勾配推定量です。

パラメータβは瞬時の勾配の新しい値に対して前の値に比べどれだけの重みを与えるかを制御します。

通常は0.9に等しいですが、時には「スケジュール」され、イテレーションの進行に応じて0.5から0.99まで増加することがあります。

Nesterovの加速勾配(NAG)

1983年にNesterov, Y.によって提案されました。

Nesterovのアップデートは、凸関数の安定性と収束速度を向上させるために「先読み」機能を実装します。

NAG update.

運動量は現在の位置を使用して勾配を更新するのに対して、NAGは現在の位置の部分的な更新を行い、その時点での勾配の部分的な更新ならばもっと良い結果が期待できるということを知ります。

Intuition for lookahead update.
Vector representation of Momentum and NAG updates.

これをPythonの関数として実装するために、前述のコードの「速度更新」に次の修正を加えました:

# Update velocity# Momentumv_t = mu*v_t - eta *grad_f(theta[k-1])# NAGv_t = mu*v_t - eta *grad_f(theta[k-1] + mu * v_t)

この部分的な更新は最適化の精度を改善するのに役立ちます。実用上、これはMomentumと比較して局所的な最小値周りでの振動が少なくなることを意味します。

その違いは次の図で明らかです。

Comparing Momentum and NAG descent optimizations for a complex surface.

両方の最適化手法は同じ座標で初期化され、同じ運動量パラメータ(0.95、固定)を使用しています。

次のアニメーションも、運動量パラメータの「スケジューリング」または「退冷」の直感を見るのに役立ちます。

Comparison of different optimization algorithms getting past a vanishing gradient region. Momentum-based methods perform better in this case.

最初は、わずかな運動量は勾配の消失を突破するのに有益です。局所的な最小値に近づくと、より大きな運動量の値が見られる振動を和らげ、収束速度を向上させることができます。

適応的なメソッド

上記のアニメーションに表示されている他の最適化アルゴリズムは、適応的なメソッドであり、このセクションで説明します。

ローカルな最小値とグローバルな最小値を持つこの単純な例を見ると、モーメンタムとNAGは他のメソッドよりも優れているように思えるかもしれません。しかし、適応的なアルゴリズムの方が堅牢です。別の記事で具体例を示します。

適応勾配アルゴリズム(AdaGrad)

AdaGradは、2011年にJohn Duchi、Elad Hazan、Yoram Singerによって発表された、確率的最適化のためのサブグラディエントアルゴリズムの一種です。

彼らは、重みの各新しい更新ごとに勾配の履歴を組み込むことによって、勾配ベースの学習を改善することを提案しました。

Momentumが勾配自体にバイアスをかけるのに対して、AdaGradは学習率自体を動的に変更し、目的関数の各パラメータに対して別々に変更します。

これは、各モデルの重みごとに異なる学習率を持つことを意味します。これらは勾配の一貫性に基づいて調整されます。

これを行うために、勾配推定値のシーケンスを以下のように保存します:

平方和勾配または勾配履歴の外積。

n個の座標またはパラメータを最適化している場合、gₜはn要素のベクトルであり、Gₜも同様です。

次に、更新ルールが次のように与えられます:

AdaGradの更新。

パラメータεは、ゼロでの除算を回避するために使用され、通常は1e-08などの小さな値に設定されます。

興味深いことに、Gₜの定義は勾配分布の非中心化(ゼロ平均)分散に似ています。

分散の定義。

分散は分布の散開エネルギーの尺度です。

したがって、各パラメータθᵢについて、勾配の分散の逆比例で学習率が適応されます。

これを考慮すると、勾配分布の散開性が高いパラメータは、学習率をより大きな係数でスケールダウンさせる一方、より一貫した勾配(より低い分散)を持つパラメータはより大きな学習率を持つことができます。

AdaGradは、学習率の減衰も自動的に実装し、時間(以前の勾配の蓄積)と目的関数の曲率に基づきます(勾配分散が低い「領域」にはより小さなステップサイズが割り当てられます)。これにより、アルゴリズムの収束速度が向上します。

以下に、AdaGradをPython関数として実装しました:

def Adagrad(x_init, y_init, step_size, n_iters):  eta = step_size  G_t = 0  eps = 1e-8  theta = np.tile([x_init, y_init], (n_iters,1) )  z = np.tile([f(theta[0])], n_iters )  for k in range (1, n_iters):      # 勾配を計算      g_t = grad_f(theta[k-1])      # 二乗勾配を蓄積      G_t += g_t**2            # 位置を更新      theta[k] = theta[k-1] - eta * g_t / (np.sqrt(G_t) + eps)      z[k] = f(theta[k])  # 位置の座標を保存  dataSet = np.stack((theta[:,0], theta[:,1], z), 1)  return dataSet

AdaGradの欠点の一つは、学習率の減衰が訓練中に過剰になる場合があり、学習が早期に停止してしまうことです。各パラメータの更新は頑健ですが、変化が最適なポイントに向かう速度があまりにも低下する可能性があります。

もう一つの欠点は、学習率が自己調整されるものの、AdaGradは初期条件に対して敏感であることです。最適化の開始時に勾配が大きい場合、残りの学習の間、学習率は低くなってしまいます。

アニメーションで確認すると、AdaGradはすぐに対称性を破壊しますが、他のアルゴリズムと比較して学習が非常に遅いことが分かります。

これを補うために、学習率をより高い値に調整する必要がありますが、一部は自己調整の特性を損ねます。

Root Mean Square Propagation (RMSprop)

未公開のメソッドですが、講義「ニューラルネットワークのためのニューラルネットワーク」の第6回のスライドで言及されています。教授:ジェフリー・ヒントン

このアルゴリズムの概念はモーメンタムと似ています。勾配の大きさの短期的な履歴を組み込んで重みの更新を行います。

ただし、AdaGradと同様に、RMSPropは勾配ではなく学習率を変更します。

これにより、学習率は直近の勾配の大きさの移動平均で割られます。

最初に、アルゴリズムは二乗誤差値と前の誤差値の加重和を計算します:

指数加重和

これは短期的な平均のようなものであり、パラメータβは古いコスト値よりも新しいコスト値にどれだけ重みを与えるかを調整します。

これは、勾配ではなく二乗コストに適用される、以前に述べた形式のモーメンタムに類似しています。

次に、学習率をこの移動平均の平方根で割ります。

RMSPropの更新ルール

これにより、ステップサイズは勾配の大きさの履歴(短期記憶)に依存するようになります。

加重和(または加重平均)の平方根を計算することは、それらの値の平均二乗根(Root Mean Square; RMS)を計算することと同等です。

RMSの定義

信号のRMSは、その総エネルギーを表しており(分散はその分散エネルギーを表す)、RMSPropでは、勾配の総エネルギー(コスト関数とその過去の値)に応じて学習率を調整します。この調整は、動的に行われ、損失関数の各方向または成分(各重み)ごとに行われます。

大きな勾配の変化によるボラティリティを減らすために、そのような場合にステップサイズを小さくするのに役立ちます。

これにより、勾配消失の問題も改善されます。非常に小さな勾配の傾向がある場合は、より大きなステップを取ります。

以下は、Python関数としてコード化した方法です:

def RMSProp(x_init, y_init, step_size, n_iters, decay):  beta = decay # 0.8, 0.9, ..., 0.99  eta = step_size  eps = 1e-8  MSQ = 0  theta = np.tile([x_init, y_init], (n_iters,1) )  z = np.tile([f(theta[0])], n_iters )  for k in range (1, n_iters):      # 勾配の計算      g_t = grad_f(theta[k-1])      # 二乗値の重み付き平均の計算      MSQ = beta * MSQ + (1 - beta) * g_t**2            # 位置の更新(RMSでetaを割る)      theta[k] = theta[k-1] - eta * g_t / (np.sqrt(MSQ) + eps)      z[k] = f(theta[k])  # 位置座標の保存  dataSet = np.stack((theta[:,0], theta[:,1], z), 1)  return dataSet

「RMSprop」は学習率の初期選択に対して強力であり、自動的な学習率の減衰も実装されています。ただし、勾配値の過去の短期間の履歴に基づいているため、減衰はAdaGradよりも穏やかです。

Gonzalo Kaplanski氏の写真(提供: Unsplash)

AdaDelta

2012年にMatthew Zeilerによって提案されました。

この方法は、AdaGradの主な制限を克服するために開発されました。学習率の連続的な減衰による早期停止と、「グローバル」学習率の手動調整の必要性です。

連続的な学習率の減衰を克服するために、アルゴリズムは過去の勾配をウィンドウまたは固定サイズで蓄積します。

具体的には、RMSpropと同様に、固定サイズのウィンドウにおける前の勾配のRMSで学習率を除算することが含まれます:

RMSPropと似た学習率のスケーリング

次のAdaGradからの変更点は、最適化の更新の単位の修正です。

AdaGrad(およびこれまで説明してきた他の最適化アルゴリズム)では、最適化ステップの単位と、コスト関数を最適化するために修正するパラメータの単位が一致しません [9]:

学校で「りんごとオレンジは足せない」と習ったことがあります。しかし、これらの最適化アルゴリズムでは、現在のパラメータ値(θₜ)と最適化ステップ(Δθ)とを足し合わせて新しいパラメータ(θₜ ₊₁)を得ることが数学的に可能です。これはうまくいくものの、現実の生活では意味をなしません。

Zeilerは単位を修正することを決め、Newton’s methodからの更新項目を再配置し、損失関数の曲率を対角行列で近似できると仮定しました:

この観察結果をRMSPropと類似の更新調整規則と比較することで、Zeilerは正しい更新項目の形式を決定しました。

直感的な理解は元の論文に詳しく説明されていますが、実際には、更新項目の分子には前の更新値の指数加重平均の平方根が追加されることになります:

パラメータ更新のためのAdaDeltaステップ

これは基本的には、損失関数が小さなウィンドウ内で滑らか(曲率が小さい)であると仮定しています。したがって、Δθₜは前の値の指数的なRMSで近似できます。

Pythonの関数として実装した場合、アルゴリズムは次のようになります:

def AdaDelta(x_init, y_init, step_size, n_iters, decay):    eta = step_size  G_t = 0  eps = 1e-8  E_gsq = 0  E_xsq = 0  theta = np.tile([x_init, y_init], (n_iters,1) )  z = np.tile([f(theta[0])], n_iters )  for k in range (1, n_iters):      g_t = grad_f(theta[k-1])      E_gsq = decay * E_gsq + (1 - decay) * g_t**2      delta = - np.sqrt(E_xsq + eps) / np.sqrt(E_gsq + eps) * g_t      E_xsq = decay * E_xsq + (1 - decay) * delta**2      theta[k] = theta[k-1] + delta      z[k] = f(theta[k])  # Setting up Data Set for Animation  dataSet = np.stack((theta[:,0], theta[:,1], z), 1)  # Combining our position coordinates  return dataSet

AdaDeltaは、それが基づく最適化手法の利点を組み合わせています。

たとえば、分子にある前のパラメータ更新の短期記憶は、モーメンタムに似ており、勾配降下を加速する効果があります。

分母はAdaGradの次元ごとの精度を提供しますが、学習率の過剰な減衰はありません(RMSPropと同様)。

さらに、AdaDeltaは急激な勾配変化にも頑健であり、初期学習率の選択にも頑健です(この記事の最後のセクションで実践例を参照してください)。

Adam(適応的モメンタム)

これは今日最も人気のあるアルゴリズムの一つです。

これは2014年にDiederik P. KingmaとJimmy Lei Baによって発表され、計算効率が高く、大量のデータとパラメータを持つ問題に非常によく機能するため、非常に人気になりました。

Adamは、モメンタムとRMSpropの組み合わせのようなものであるため、損失関数の勾配と学習率の両方を動的に変更します。

これを行うために、アルゴリズムにはこの記事の前のセクションでおなじみの2つの項目の計算が含まれています。

最初に、モメンタムからの項目で、コスト関数の前の勾配の指数加重和(これは加重分散のようなもの)があります:

コストの勾配の指数加重平均

次に、RMSpropからの項目で、二乗勾配の指数加重移動平均があります。

コストの二乗勾配の指数加重平均

SGDアルゴリズムとの組み合わせにより、過去の勾配の情報が更新ステップに含まれます。短いウィンドウ(RMS)での総エネルギーは学習率のスケールに使用され、その分散(分散)は重みの更新に使用される現在の勾配値を調整します。

Adamの更新ルール

チルダ(~)がついた値は、学習が進むにつれて初期値のmとvからの寄与を減らすために導入されるバイアス補正項です:

Adamの初期化バイアス補正項

t = 現在の訓練エポック。

AdaDeltaとは異なり、Adamにはいくつかのハイパーパラメータの調整が必要ですが、それらは解釈が容易です。

項目β₁β₂は、勾配と二乗勾配の指数移動平均の減衰率です。

大きな値は前の勾配により重みを割り当て、滑らかな動作を提供し、最近の変更に対して反応性が低くなります。ゼロに近い値は勾配の最近の変化により重みを割り当てます。一般的な値はβ₁ = 0.9、β₂ = 0.999です。

εは、これまでの場合と同様に、ゼロ除算を回避するために追加される定数であり、通常は1e-8に設定されます。

さまざまな追加項目があるにもかかわらず、Adamは実装が容易です:

def Adam(x_init, y_init, step_size, n_iters,          beta_1 = 0.9, beta_2 = 0.999):  eps = 1e-8  eta = step_size  # ベクトルの初期化  m_t = np.array([0,0])  v_t = np.array([0,0])  theta = np.tile([x_init, y_init], (n_iters,1) )  z = np.tile([f(theta[0])], n_iters )  for k in range (1, n_iters):      # 勾配の計算      g_t = grad_f(theta[k-1])            # モメンタムのような項目の計算(加重平均)      m_t = beta_1 * m_t + (1 - beta_1)*g_t      # 二乗勾配値の平均の計算      v_t = beta_2 * v_t + (1 - beta_2)*g_t**2            # 初期化のバイアス補正項      m_t_hat = m_t/(1 - beta_1**k)      v_t_hat = v_t/(1 - beta_2**k)      # 調整された勾配と学習率で位置を更新      theta[k] = theta[k-1] - eta * m_t_hat/(np.sqrt(v_t_hat)+ eps)      z[k] = f(theta[k])  # 位置座標の保存  dataSet = np.stack((theta[:,0], theta[:,1], z), 1)   return dataSet

興味深いことに、論文の著者はこの用語が、

に似ていることを指摘しています。

Adamの学習率スケーリング.

したがって、小さいSNR値の場合、パラメータの更新はほぼゼロになります。これは、真の勾配の方向に進んでいるかどうかの不確実性が非常に高い場合には、大きな更新は行われないということを意味します。

Adamおよびその派生物は、DLモデルのトレーニング時に他のアルゴリズムよりも優れたパフォーマンスを発揮することが一般的です、特にノイズの多い勾配やスパースな勾配の場合。

パフォーマンスの違う学習率

異なるオプティマイザが異なる「グローバル」学習率で初期化された場合に、それぞれのオプティマイザのパフォーマンスを比較することにしました。

これは非常に単純な例ですが、学習率の選択がこれらの方法にどのように影響を与えるかを示しています。

異なるアルゴリズムで最適化中にxおよびy座標の進化を比較します。MomentumとNAGの場合、mu = 0.95。RMSPropとAdaDeltaの場合、減衰パラメータ=0.9。

AdaDeltaは、グローバル学習率設定に対して非常に堅牢であり、その場合でも同じ速度で「下降」します。また、AdaGradはこの場合でAdaDeltaと同等のパフォーマンスを実現するためにより大きな学習率が必要であることがわかります。

学習率が小さい場合、AdamとRMSPropは類似しており、MomentumとSGDよりも優れています。

ただし、学習率が大きい場合、RMSPropは最適なx値(x = 0)周りで一貫した振動を示しますが、Adamは初期の過渡期の後に安定化します。これは、分子のモーメンタム項の抑制効果のおかげです。

適応型アルゴリズムは、SGDおよびモーメンタム法よりも先に対象を切り離しますが、AdaDeltaではグローバル学習率が0.1のケースではモーメンタムとNAGがAdaDeltaよりも優れています。

また、これらの観察結果は特定のシナリオにのみ適用されます。

まとめ

これらの最適化アルゴリズムの利点は、上記のような単純な関数に適用した場合には完全に明らかではありません。

小規模なモデルやデータセットを対象とした他のシナリオでは、SGDの方がより良い結果をもたらすこともあるため、それぞれのオプティマイザが最も効果的に機能する領域を理解することが重要です。

ニューラルネットワークを訓練する際には、損失関数を最適化し、各ポイントの勾配の正確な値を持っていないため、その推定値を使用します。これは、ノイズや勾配のスパースさに対して頑健なAdamやAdaDeltaなどの手法が、データサイエンスコミュニティで広く使用されている理由です。

さらに、xとy座標だけでなく、大量のモデルの重みを扱うこともできます。これらのシナリオでは、パラメータごとの学習率を取得する能力が有益です。

次回の記事では、人工ニューラルネットワークを使用して、これらの手法のより現実的な比較を紹介します。

参考文献

参考文献

すべての数字、特に注記がない場合は筆者によって作成されたものです。

[1] オンラインコース「深層学習の深い理解」、Mike X Cohenによる(sincxpress.com

[2] スタンフォードオンライン:視覚認識のためのCS231畳み込みニューラルネットワーク

[3] Goh著。「なぜモメンタムは本当に機能するのか」、Distill、2017年。http://doi.org/10.23915/distill.00006

[4] Villalarga, D.「AdaGrad」。Cornell University Computational Optimization Open Textbook — Optimization Wikiで公開されました。

[5] Bengio, Yoshua.「深層アーキテクチャの勾配ベーストレーニングの実用的な推奨事項」。ニューラルネットワーク:トレードのトリック:第2版。ベルリン、ハイデルベルク:Springer Berlin Heidelberg、437–478、2012年。オンライン:arXiv:1206.5533 [cs.LG]

[6] Sutskever, I., Martens, J., Dahl, G. & Hinton, G.「深層学習における初期化とモメンタムの重要性について」。Machine Learning Research論文集、28(3):1139–1147、2013年。https://proceedings.mlr.press/v28/sutskever13.htmlから入手可能。

[7] Duchi, J., Hazan, E., Singer, Y.「オンライン学習と確率的最適化のための適応型サブグラディエント法」。Machine Learning Research論文集、12(61):2121−2159、2011年。入手先:https://jmlr.org/papers/v12/duchi11a.html

[8] Jason Brownlee、スクラッチからのAdagradを使用した勾配降下法。2021年

[9] Zeiler, M.「ADADELTA:適応的学習率の手法」、2012年。arXiv:1212.5701v1 [cs.LG]

[10] Kingma, D., Ba, J.「Adam:確率的最適化の方法」、2014年。arXiv:1412.6980 [cs.LG]

元記事は2023年12月5日にhttps://www.makerluis.comで公開されました。

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more