Google Colaboratory で Chainer を触ってみるvol.6 ~optimizerの理解、いままでのまとめ~

※この記事では, 一般的なニューラルネットワークの特徴, CNN のしくみなどは取り上げません. それらは各自で学習済みと想定しています.

# 前回
[Google Colaboratory で Chainer を触ってみるvol.5 ~エポック数, バッチサイズを理解する~](https://qiita.com/4no3/items/cc2322d654e279d55596) 

# 進捗
隠れ層の数, 活性化関数, optimizer, 学習時のパラメータなどが学習時間と学習精度にどのような影響を及ぼすか, 「[00 Colaboratory で Chainer を動かしてみよう](https://chainer-colab-notebook.readthedocs.io/ja/latest/notebook/hands_on/chainer/begginers_hands_on/00_How_to_use_chainer_on_colaboratory.html)」で実験中です. 今回は, 「optimizer の影響」を確認しました.

**理解したこと表**

|どの影響？|学習時間|学習精度|結論|
|:-:|:-:|:-:|:-:|
|隠れ層の数|+ 0.3秒/1layer|8層でほぼ頭打ち|8層でよさそう|
|隠れ層の node 数|ほぼ変わらず|500 node で +1.5%|GPU 使えば何でもよさそうだが, ひとまず500とする|
|活性化関数|+2.04s(selu)|+2.7%(selu)|selu がよさそう. パラメータをいじるともう少し精度上がる|
|エポック数|エポック数増加に伴い増加|5 epoch で十分|-|
|バッチサイズ|バッチサイズの減少に伴い増加|128 バッチがよさそう|-|
|optimizer|ほぼ変わらず|+9%|Adam がよい|

## optimizer とは
「学習パラメータ（入力にかけ合わせる重み）を最適化するもの」です. ニューラルネットワークでは, ネットワークの出力と実際の結果の差分が少なくなるように学習が行われます. 差分を少なくするために学習パラメータを更新する最適化手法のことを「optimizer」と呼びます.

機械学習のフレームワークでは, 様々な optimizer が用意されています. [Chainer で準備されている optimizerはこちら](https://docs.chainer.org/en/stable/reference/optimizers.html)です.  最近では, Adam という手法が高速かつ良好な結果を生み出すのでよく使われているようです. それぞれの特徴については, 各自でご調査ください.

# 試行錯誤するコード
いつものやつです. そのほかのコードは, [Vo.1](https://qiita.com/4no3/items/53b350a4fa06c60cd8da)で投稿したものと同じです.

```.py
# class_model.py
import chainer.functions as F
import chainer.links as L
from chainer import Chain
class MLPNew(Chain):
  
  def __init__(self):
      super(MLPNew, self).__init__()
      with self.init_scope():
          # Add more layers?
          self.l1 = L.Linear(784, 200) # Increase output node as (784, 300)?
          self.l2 = L.Linear(200, 200) # Increase nodes as (300, 300)?
          self.l3 = L.Linear(200, 10)  # Increase nodes as (300, 10)?
  
  def forward(self, x):
      h1 = F.tanh(self.l1(x))  # Replace F.tanh with F.sigmoid or F.relu?
      h2 = F.tanh(self.l2(h1)) # Replace F.tanh with F.sigmoid or F.relu?
      y = self.l3(h2)
      return y

# do_train_and_validate.py
device = 0
n_epoch = 5     # Add more epochs?
batchsize = 256 # Increase/Decrease mini-batch size?

model = MLPNew()
classifier_model = L.Classifier(model)
optimizer = optimizers.SGD() # Default SGD(). Use other optimizer, Adam()?(Are there Momentum and AdaGrad?)

train_and_validate(
    classifier_model, optimizer, train, validation, n_epoch, batchsize, device)
```
今回は, 下記 optimizer を変更すれば試せます.

```.py
optimizer = optimizers.SGD() # Default SGD(). Use other optimizer, Adam()?(Are there Momentum and AdaGrad?)
```

# 結果詳細
**デフォルト(HandsOn 通りの設定)**

* 隠れ層 : 3
* 隠れ層の node 数 : 200
* 活性化関数 : tanh
* epoch 数 : 5
* batchsize : 256
* optimizer : SGD ←これを変更する

## エポック数
|optimizer|合計学習時間(sec)|学習結果|テスト結果|過学習|
|:-:|:-:|:-:|:-:|:-:|
|SGD(デフォルト)|15.6061(基準)|0.794692(基準)|0.78916013(基準)|
|AdaDelta|16.1793|0.861298|0.8604492|
|AdaGrad|16.0922|0.837039|0.8296875|
|Adam|16.5583|0.884515|0.8711914|
|CorrectedMomentumSGD|16.8676|0.859475|0.84384763|
|MomentumSGD|16.6274|0.861358|0.84365237|
|NesterovAG|16.0728|0.861318|0.8443359|
|RMSprop|16.3129|0.78131|0.8113281|
|RMSpropGraves|16.042|0.857532|0.8444336|
|SMORMS3|16.5822|0.894291|0.87470704|

optimizer を変更しただけで, 判定精度が最大 9% 程度向上しました. Adam か SMORMS3 がよさそうです. 学習結果とテスト結果の差分が少ない Adam に軍配を上げます. これで「理解したこと表」が完成しました!

## optimizer のパラメータ
optimizer にはそれぞれに固有のパラメータを設定できます（学習率, モーメントなどなど）. 各 optimizer に対してパラメータを試行錯誤すればもっと良い結果が得られるかもしれませんが, この場では検証を省略します. 詳細は技術書などに委ねます.

ちなみに, 学習によって自動的に更新されるパラメータではなく, ヒトが試行錯誤するパラメータのことを「ハイパーパラメータ」と呼びます.

# 理解したこと表に基づいて学習してみる
**理解したこと表の設定**

* 隠れ層 : 8
* 隠れ層の node 数 : 500
* 活性化関数 : selu
* epoch 数 : 5
* batchsize : 128
* optimizer : Adam

**結果**

```
epoch       main/loss   main/accuracy  val/main/loss  val/main/accuracy  elapsed_time
1           0.298742    0.889808       0.334972       0.873242           2.85188       
2           0.257542    0.905769       0.313557       0.883398           6.05124       
3           0.232992    0.914403       0.313934       0.882812           9.26391       
4           0.212636    0.922812       0.306932       0.889844           12.5181       
5           0.197163    0.928626       0.300073       0.890137           15.7684

Test accuracy: 0.88740236
```
目標の 88% は超えましたが, ちょっと過学習気味ですかね...調整が難しいです. とはいえ, ひとまず目標達成ですので, HandsOn を進めようと思います!

# まとめ
ニューラルネットの学習に一番影響を及ぼすのは optimizer ということがわかりました. パラメータを細かくチューニングすれば, もっとよい性能が出るかもしれません.

optimizer だけでなく, ほかの要素もチューニングをすることでより良い性能が引き出せるかもしれません.

# 追記
88% 超えた条件で何度か学習を行ってみると, Test accuracy が 88% を超えないことが多々ありました...(87%台が多い)
バッチに含まれるデータの組み合わせがランダムなので, 性能が出やすいデータ組合せとそうでない組合せがあるのかもしれませんね.

また, 表の条件での学習時間も, 二回目以降はトータル 60 秒以上かかってます. 最初の一回は何だったのだろうか...うーん, 奥が深い.

# 次回
Chainer HandsOn の再開.