Djangoroidの奮闘記

python,django,angularJS1~三十路過ぎたプログラマーの奮闘記

Python DeepLearningに再挑戦 17 学習に関するテクニック 確率的勾配降下法~SGD

概要

Python DeepLearningに再挑戦 17 学習に関するテクニック 確率的勾配降下法~SGD

参考書籍

SGD 確率的勾配降下法について

SGDを数式で書くと以下の通り。

W <- W - η(∂L/∂W)
  • Wに関する損失関数の勾配 = ∂L/∂W
  • η=学習係数。これは前もって決めておく。
  • SGDは、勾配方向へ一定の距離だけ進む単純な方法。

pythonで実装すると以下のような感じ。

class SGD:
    def __init__(self, lr=0.01):# 学習率の初期化。デフォルトでは、0.01に設定してある。
        self.lr = lr
        
    def update(self, params, grads):
        for key in params.keys():#paramsの各要素から、学習率*勾配を除算して、重みなどの値を更新する
            params[key] -= self.lr * grads[key]

実際に学習するためのモデルに組み込むためには、以下のようにSGDclassのインスタンスのupdateメソッドに、params, gradsを引数として渡すだけでOK。 このように、部分的に最適化のメソッドを変更できるのが、なかなか便利だなぁ。

network = TwoLayerNet(...)
optimizer = SGD()#SGDのインスタンスをoptimizerに代入する。

for i in range(10000):
    ...
    x_batch, t_batch = get_mini_batch(...) #ミニバッチ
    grads = network.gradient(x_batch, t_batch)
    params = network.params
    optimizer.update(params, grads)# updateメソッドを、params, gradsを引数として実行する。

多くのディープラーニングのフレームワークでは、さまざまな最適化手法が実 装され、それらを簡単に切り替えることができるような仕組みが用意されてい ます。たとえば、Lasagne というディープラーニングのフレームワークでは、 updates.py(下記リンク参照)というファイルに最適化手法が、関数として まとめて実装されています。ユーザーは、その中から使いたい最適化手法を選 ぶことができます。 http://github.com/Lasagne/Lasagne/blob/master/lasagne/updates.py

なるほど!これは楽!

SGDの欠点

簡単にいうと、関数の形状が等方的でないと、かなり非効率な経路で最適化の行う可能性がある。

f(x, y) = 1/20 x**2 + y2 のように、y軸が傾斜が急で、x軸には緩やかな変化である場合は、そのような傾向が出やすい。

Momentum

Momentumの数式は以下の通り。

v <- αv - η(∂L/∂W)

W <- W + v
  • Wは更新する重みパラメータ
  • ∂L/∂W は、Wに関する損失関数の勾配
  • ηは、学習係数(学習率)
  • v は物理で言うところの速度に対応する。物体が勾配方向に力を受け、その力によって物体の速度が加算される。
  • αvは物体が何も力を受けないときに徐々に減速するための役割。αには0.9などの値を設定する。物理で言うと、地面の摩擦とか空気抵抗に対応する。

f:id:riikku:20161225181423p:plain

pythonでMomentumを実装すると以下のようなコードになる。

class Momentum:
    def __init__(self, lr=0.01, momentum=0.9):#インスタンス変数の初期化、momentumは、αに当たる部分。
        self.lr = lr
        self.momentum = momentum#αに当たる部分
        self.v = None
        
    def update(self, params, grads):
        if self.v is None:#self.vがNoneの場合はまず、v['W1'], v['W2']などの値を代入する。
            self.v = {}
            for key, val in params.items():#itemsなので、keyとvalをそれぞれゲットして利用する。
                self.v[key] = np.zeros_like(val)
                
        for key in params.keys():#params.keys()でW1などのkeyをゲットする。
            self.v[key] = self.momentum*self.v[key] - self.lr*grads[key] # v <- αv - η(∂L/∂W)の実装に当たる部分。
            params[key] += self.v[key] # vをプラスして、W1,W2などの重みパラメータを更新する
  • f(x, y) = 1/20* x2 + y2 の例でいうと、x軸は受ける力は小さいけど、常に同じ方向に進んでいくので、一定方向に加速しやすい。y軸方向は受ける力は大きいが、正と負の方向の力を交互に受ける。それが互いに打ち消しあうため、y軸方向への速度は安定しない。結果的にx軸方向へ効率よく近づくことができる。

物理もちらっと出てくるとはね(^ ^;)