ハイパーパラメータ調整手順　備忘録 (R01)

# 概要

Courseraで開講されているStanford大学のAndrew Ng先生の[ディープラーニング専門講座](https://www.coursera.org/specializations/deep-learning)をなんとか修了することができた。勤め人なので毎週土曜or日曜に集中して受講し、結局4カ月もかかってしまった。
この中でモデルの設計手順について解説されていたので、それについて共有したい。
中でもハイパーパラメータ調整は一般的に記述することができず、各位が四苦八苦する中から生まれる経験則しかないので、この方法がベストではないかもしれない。ただ先生よりも深層学習で経験を積まれた方は全世界でも多くはないはずで、小生のような素人には大変ありがたい指標となる。
経験は少ないがおいらのテクニカルなやり方も併せて残し、良い方法が見つかれば随時アップデートする。

## DL用語
Keras用語というわけではないが、わかりやすいので参照
[What do "sample", "batch", and "epoch" mean?](https://keras.io/getting_started/faq/#what-do-sample-batch-epoch-mean)

またKerasにおけるTraining中のLossとValidation(testing)中のLossの計算タイミングなどは知っておく必要がある。
Fit中に、なぜval_lossがlossより小さくなるのかも理由がわかる。
[Why is my training loss much higher than my testing loss?](https://keras.io/getting_started/faq/#why-is-my-training-loss-much-higher-than-my-testing-loss)

よく参照させていただいているが、下記にもBaisとVaridationが定義されている。
Jason先生のブログはどれもこれも秀逸で、気合い入れてこれだけ精読していればどんな教材も不要
[How to Calculate the Bias-Variance Trade-off with Python](https://machinelearningmastery.com/calculate-the-bias-variance-trade-off/)

## データの前処理
Training setにモデルを効率よくFitさせるため必ず前処理を行う。
下記の2つの方法があり、散らかったデータをまとめることができる。
#### *Normalization*
Training setのデータ範囲を0 ~ 1に縮める処理のこと。
MNISTのサンプルコードなどで必ず実施しているTraining setを255.0で割るやつ。
MNISTは8bit階調なので255.0で割るが、データの意味的に上限が予めわかっていれば、この処理を行う。

#### *Standardization*
データ範囲が一意に決められない場合、Training setの平均と分散を求め、
　全Training set - 平均値
で減点中心にデータを移動させ、
　(全Training set - 平均値) / 標準偏差
で散らばり集約する。

少なくともどちらかの処理は行わなければならない。

## モデル開発の流れ
下図が全体の流れになる。
古い文献に掛かれているようなBiasとVarianceはトレードオフの関係などではない。両方を調整すべきだということがわかる。
条件を変え、Fitを繰り返しながらLossの挙動を見る。

![DL Design flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/c3947b0b-c740-1ec5-3c51-e12ad88544e0.png)


#### Bigger NN
大きなニューラルネットワークは複雑な非線形問題をマップできるといわれている。
大きくするとは一般的にレイヤ数を増やすということで、ノード数を増やす方向ではない。
もちろんハイパーパラメータ調整中はノード数を増減させLossやAccuracyの変化は観測する必要はある。

#### Train longer
Lossが適切に計算されるようにしなさい、ということ。指標がLossなので一番大切なように感じるかもしれないが、モデルが変わればLossの計算式も変わるので最初に**Bigger NN**でモデルをおおよそ確定させる。
Lossの計算とは具体的にはmodel.[compile](https://keras.io/api/models/model_training_apis/#compile-method)(...)に引数で指定する[loss](https://keras.io/api/losses/)=???と[optimizer](https://keras.io/api/optimizers/)=???で指定する。
問題がregressionかclassificationによりloss=???は、mse, categorical_crossentropy, binary_crossentropyなどおおよそ一意に決まるが、そのLossを効率よく求めるようにOptimizeする部分は重要（ハイパーパラメータ調整で後述）。ここで可能な限りTraining setに対しLossが最小になるように調整しなければならない。
ハイパーパラメータが引数になっているので、optimizerの原理は理解しておくのがおすすめ。
下記がとても分かりやすい。Kerasで実装済みのOptimizerはすべて説明されている。
[Optimizers - EXPLAINED!](https://www.youtube.com/watch?v=mdKjMPmcWjY)

#### Regularization
上図の通り、Overfitを抑える効果がある。やりすぎるとUnderfitになってしまう。
講座で説明されたRegularizationは下記の2つ

*L1L2 [Regularization](https://keras.io/api/layers/regularizers/)*
*[Dropout](https://keras.io/api/layers/regularization_layers/dropout/)*

前者はCost関数（Loss関数の対数）にペナルティ項を加算する手法。先生曰くL1ノルムを使う機会はあまりない。
Kerasでは各レイヤでペナルティを与えられるように実装されている。
regularizers.l1_l2のL1=..., L2=...で指定する。大きくするとRegularizationがより大きく働く。

後者はLoss計算に使用しないノード数をランダムに比率で指定する。
Fit中、同じようなweightパラメータを持つノードを減らすことができ、これをレイヤごとに指定する。

例えば下記のように使用する。

```Python
my_model.add(Dense(10, activation='relu', activity_regularizer=tf.keras.regularizers.l1_l2(l1=0.0, l2=0.005)))
my_model.add(Dropout(0.1, seed=10))
```
L1ノルムは使用しないのでL1=0.0としているが、おいらはざっくり(0.01, 0.001, 0.0001くらい)振るようにしている。
また、ここでは1行目のDenseレイヤのノード数が10個なので10%(0.1)は使用しないことになる。つまり9個のノードを使用する。
どのノードが使用されないかはForward-Backward propのたびに入れ替わる（と思っている）。
モデル間でLoss比較できるようにおいらはランダムseedを指定するようにしている。

## ハイパーパラメータ調整
今更だが、モデルのパラメータ（Weight, bias, CNNではkernel filterも）を最適にするためのパラメータをハイパーパラメータと呼ぶ。
上述のほとんどがハイパーパラメータであるが、これの調整順序は下記が経験的におすすめらしい。

1. OptimizerのLearning rate α
2. Optimizerのモーメンタム係数β (Default=0.9)
3. モデルのHiddenレイヤのノード数
4. Fitする際のmini-batchサイズ
5. Learning rate decay
6. Adam optimaizerを使っていれば、β1 (Default=0.9), β2 (Default=0.999), ε (Default=10^-8)


また、実験的にハイパーパラメータを振って何度のFitを実行するが、ハイパーパラメータは広くランダムに振ってFitを繰り返すようにしなければならない。グリッドに沿うように振ると無駄な実験パターンが増えてしまう。
ある程度目星がついたらさらにそのハイパーパラメータを細かくランダムに振り答えに近づける。

![param.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/adf0c661-c015-95d3-7470-8acf427f1788.png)

#### OptimizerのLearning rate α
Learning rateは10のべき乗で指定するので、KerasのCallbacksの[LearningRateScheduler](https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/LearningRateScheduler)で最初にあたりを探る。
ラムダ式を見てわかる通り、epochが進むたびに10のべき乗でrateを増やしている。

```Python
## Fit
lr_schedule = tf.keras.callbacks.LearningRateScheduler(lambda epoch: 1e-8 * 10**(epoch / 100))
optimizer = tf.keras.optimizers.Adam(lr=1e-8)
model.compile(loss=tf.keras.losses.Huber(), optimizer=optimizer)
history = model.fit(dataset, epochs=1000, callbacks=[lr_schedule])

## Fit後、lr-loss片対数チャートで確認
plt.semilogx(history.history["lr"], history.history["loss"])
plt.axis([1e-8, 1e-4, 0, 30])
```

lr = 0.5 * 10^-4 くらいでLossが最小になっているので、2以降の調整はこのlrで行う。
（めんどくさがり屋なので、decayで横着することもあり。）
![lr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/f9d41e00-4640-09d0-6ab1-0f87691b98b6.png)

やはりCouseraの[DeepLearning.AI TensorFlow Developer](https://www.coursera.org/professional-certificates/tensorflow-in-practice)のLSTMのパートで教わったが、講師（Tensorflow開発者）からLossには[Huber](https://www.tensorflow.org/api_docs/python/tf/keras/losses/Huber)も試すように勧められた。最初に試すのは[mae](https://www.tensorflow.org/api_docs/python/tf/keras/losses/MAE)や、誤差の外れ値を意識するなら[mse](https://www.tensorflow.org/api_docs/python/tf/keras/losses/MSE)が一般的だが、Huberも結構いい感じ。
当然モデルによるが、比較して結局Huberが勝つケースがおいらは多いかな。

## Hidden Layer数の意味とそのNode数調整
講座では説明はなかったが、Washington大学のJeff先生のサイトに調整目安の記載あり。
[The Number of Hidden Layers](https://www.heatonresearch.com/2017/06/01/hidden-layers.html)
要約すると、まずHidden layerの数が意味するのは、

0枚:  線形分離可能問題のみ
1枚:  有限空間から別の有限空間への連続マッピングを含む関数を近似
2枚:  Activation関数を使用し、任意の境界線や滑らかなマッピングを任意の精度で近似
3枚以上:  複雑な表現が可能（一種の特徴量エンジニアリング）

そのうえで、Hidden layerのnode数はJeff先生の経験則から、

・入力層のnode数と出力層のnode数の間にある必要があります。
・入力層のnode数の2/3に出力層のnode数を加えたものでなければなりません。
・入力層のnode数の2倍未満にする必要があります。

Jeff先生も[You Tube](https://www.youtube.com/user/HeatonResearch)で機械学習の解説をされているので、見てみるとよいかも。

## Early Stopping
Fitにおけるepoch数は任意に指定する。
Early StoppingはそのEpoch数に達する前にVal Lossが底を打ったとKerasが判断したとき、Fitをそこで終わらせパラメータを確定させる[Callback](https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/EarlyStopping)機能である。
ハイパーパラメータ調整中にはこれを使わないほうがよい。十分にweightパラメータが大きくなる前にFitをやめてしまう恐れがあるからである。

![early stopping.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/312b5b94-0591-19cf-c260-d05dd1a042c5.png)



とりあえずこんなところ。
思いついたら追記修正し更新する。