備忘録とか日常とか

学んだこととかを書きます。

theano モメンタム項を導入する

theanoを使って色々実験中。
学習率の制約等に関してはまだ試してなかったのでやってみる。
参考元は以下
http://nbviewer.ipython.org/github/craffel/theano-tutorial/blob/master/Theano%20Tutorial.ipynb
python - Clarification in the Theano tutorial - Stack Overflow


データセットにもよるだろうが、自分が使っているものについていえばかなりValidation errorが振動してしまうことが多かった。つまり誤差関数が急勾配な位置では重みの更新量が大きすぎて、なかなか極小値までたどり着かないという問題である。これは重みの更新式にモメンタム項を加えればある程度改善される。


追加するのは至って単純。畳みこみ、全結合、softmax層はそれぞれオブジェクトとして定義しておき、paramsというインスタンスに重み、バイアスを入れておくようにする。詳しくは前回。モメンタム項なしの場合の学習を以下のように書いたとする(layerの定義とかは省きます)。

cost = layer4.negative_log_likelihood(y)
params = (layer4.params + layer3.params + layerC2.params 
            + layerC1.params + layerC0.params)
grads = T.grad(cost, params)

updates = [
    (param_i, param_i - learning_rate * grad_i)
    for param_i, grad_i in zip(params, grads)
]
    
train_model = theano.function(
    [index],
    cost,
    updates=updates,
    givens={
        x: train_set_x[index * batch_size: (index + 1) * batch_size],
        y: train_set_y[index * batch_size: (index + 1) * batch_size]
    }
)

train_set_xtrain_set_yにはそれぞれ訓練データとラベルが入ってます。Tutorialと変えてません。


updateの定義の部分を関数化し、以下のようにすることでモメンタム項を追加できる。

def gradient_updates_momentum(cost, params, learning_rate, momentum):
    # momentumが正しい値か確認
    assert momentum < 1 and momentum >= 0
    updates = []
    for param in params:
        # 一つ前の状態のparamを保存しておくparam_updateという共有変数を導入する
        param_update = theano.shared(param.get_value()*0., broadcastable=param.broadcastable)
        # paramの更新式を定義
        updates.append((param, param - learning_rate*param_update))
        # param_updateの更新式を定義, momentum項の追加
        updates.append((param_update, momentum*param_update + (1. - momentum)*T.grad(cost, param)))
    return updates

こうして、

cost = layer4.negative_log_likelihood(y)
params = (layer4.params + layer3.params + layerC2.params 
            + layerC1.params + layerC0.params)

updates = gradient_updates_momentum(cost, params, learning_rate, momentum)
    
train_model = theano.function(
    [index],
    cost,
    updates=updates,
    givens={
        x: train_set_x[index * batch_size: (index + 1) * batch_size],
        y: train_set_y[index * batch_size: (index + 1) * batch_size]
    }
)

こうする。
重みの更新式は

{
\mathbf{w}^{(t+1)} = \mathbf{w}^{(t)} - \epsilon \nabla E + \mu \Delta \mathbf{w}^{(t-1)}
}
{
\Delta \mathbf{w}^{(t)} = \mu \Delta \mathbf{w}^{(t-1)} - \epsilon \nabla E
}

と表される(MLP「深層学習」より)。更新に一つ前の状態の重みを用いる。{\epsilon}は学習率、{E}がコスト関数であり、{\mu}をモメンタムのパラメータとして調整する。
{\nabla E}が一定となる間は、

{
\Delta \mathbf{w}^{(t)} \rightarrow \frac{1}{1-\mu}(- \epsilon \nabla E)
}

と収束し、これは学習率を{1/(1-\mu)}倍するのと同等であると言える。そのため何もしていない状態からモメンタム項を追加するときは、学習率を少し調整する必要が出てくる。


ここによると、まず{\mu}は0.5ぐらいに設定し、徐々に上げて0.9ぐらいにするのが望ましいらしい。その間で誤差の減少が不安定になるようなら学習率を1/2倍ずつ減らしていくといいという。とりあえず学習率を1/2にして{\mu=0.5}とするとすぐ使えそうである。

使ってみたところ、かなり安定して学習が行えるようになった。
次は重み減衰とか? 順番めちゃくちゃなのは気にしない