Chainerで学習率のスケジューリングをする方法

## 学習率のスケジューリング
Deep Learningで学習を進めるためには、学習率の減衰(**learning rate decay**)が必要です。Chainer(v4)では`Trainer`の`Extension`で学習率の変更ができます。`Extension`については[Chainer v4 ビギナー向けチュートリアル](https://qiita.com/mitmul/items/1e35fba085eb07a92560#4-trainer%E3%81%ABextension%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B)に詳しく説明されています。

## Optimizerのパラメータを変更するExtensions
学習率`lr`に限らず、Optimizerのパラメータを学習途中で変更することができます。`ExponentialShift`と`LinearShift`があり、`ExponentialShift`はパラメータを指数関数的に変化させ、`LinearShift`はパラメータを線形に変化させることができます。今回はこのうちの`ExponentialShift`を使ってみます。

- [chainer.training.extensions.ExponentialShift](https://docs.chainer.org/en/stable/reference/generated/chainer.training.extensions.ExponentialShift.html#chainer.training.extensions.ExponentialShift)
- [chainer.training.extensions.LinearShift](https://docs.chainer.org/en/stable/reference/generated/chainer.training.extensions.LinearShift.html#chainer.training.extensions.LinearShift)

## triggerについて
[Chainer v4 ビギナー向けチュートリアル](https://qiita.com/mitmul/items/1e35fba085eb07a92560#4-trainer%E3%81%ABextension%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B)では学習率を変更する方法として、

```python

trainer.extend(extensions.ExponentialShift('lr', 0.1), trigger=(30, 'epoch'))
```

という指定をしており、これは『30epochごとに学習率を0.1倍する』という指定です。ここでポイントとなるのは`trigger`オブジェクトです。`trigger`は`Extension`の実行タイミングを指定するものです。デフォルトでは、`trigger`をタプルで指定すると、`IntervalTrigger`という`trigger`オブジェクトにタプルが渡され、一定期間ごとに呼び出されます。`trigger`の一覧は以下のドキュメントの一番下に書いてあります。

- [Training Tools — Chainer documentation](https://docs.chainer.org/en/stable/reference/training.html)

## ManualScheduleTriggerを使ってみる
`IntervalTrigger`でも良いときは多いのですが、例えば学習の50%, 75%, 90%のタイミングで学習率を変更するなどの指定はできません。このような場合は`ManualScheduleTrigger`を用います。例えば以下のように`Extension`を書いてみます。

```python

trainer.extend(extensions.ExponentialShift('lr', 0.1),   
               trigger=triggers.ManualScheduleTrigger([1,3,6],'epoch'))
```

このように書くと、1,3,6 epoch目で学習率が0.1倍されます。適当に学習をさせてみると次のようになりました。

```
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  lr          elapsed_time
0           2.02627                           0.262109                                 0.01        5.88272       
1           0.988403    1.34453               0.655859       0.544899                  0.001       89.8289       
2           0.780562    0.840948              0.726953       0.704239                  0.001       178.381       
3           0.795094    0.833529              0.723437       0.707273                  0.0001      266.596       
4           0.730944    0.792991              0.74375        0.718957                  0.0001      362.734       
5           0.708854    0.786186              0.761328       0.722208                  0.0001      455.003       
6           0.708609    0.784013              0.748438       0.72531                   1e-05       552.303       
```
ちゃんと指定したepochで学習率が下がっています。`'epoch'`は`'iteration'`とすることもできます。詳しくは[Document](https://docs.chainer.org/en/stable/reference/generated/chainer.training.triggers.ManualScheduleTrigger.html#chainer.training.triggers.ManualScheduleTrigger)を読んでください。

ちなみに`lr`の変化を見るには

```python

trainer.extend(extensions.observe_lr(), trigger=(1, 'iteration'))
trainer.extend(extensions.PrintReport(['epoch', 'main/loss', 'validation/main/loss',
                                       'main/accuracy', 'validation/main/accuracy',
                                       'lr', 'elapsed_time']), trigger=(1, 'iteration'))
```

のように`extensions.observe_lr()`を実行し、`extensions.PrintReport`の引数に`lr`を付け加えると良いです。

というわけで学習の50%, 75%, 90%のタイミングで学習率を変更するには

```python

epoch = 100
points = [epoch*0.5, epoch*0.75, epoch*0.9]
trainer.extend(extensions.ExponentialShift('lr', 0.1), 
               trigger=triggers.ManualScheduleTrigger(points, 'epoch'))
```
とします。`points`は`float`も受け取れます。例えば、

```python

points = 37.5
```

37 epoch目の50%の段階で`ExponentialShift`が実行されます。

## 参考
- [Chainer v4 ビギナー向けチュートリアル - Qiita](https://qiita.com/mitmul/items/1e35fba085eb07a92560#%E5%8F%82%E8%80%83%E6%96%87%E7%8C%AE)
- [Training Tools — Chainer documentation](https://docs.chainer.org/en/stable/reference/training.html)
- [Loss値の変化を監視して学習率を制御する方法 - Google グループ](https://groups.google.com/forum/#!searchin/chainer-jp/%E5%AD%A6%E7%BF%92%E7%8E%87%7Csort:date/chainer-jp/Q3VIAIwPrbY/XNWXYkC7CAAJ)
- [勤労感謝の日なのでChainerの勤労(Training)に感謝してextensionsを全部試した話 - EnsekiTT Blog](http://ensekitt.hatenablog.com/entry/2016/11/24/012539)