深層学習モデルの汎化性能を上げるためのポイント

#目的
深層学習ライブラリのTensorflowやChainerを使えば、誰でも簡単に深層学習を行えます。一方、__深層学習の結果はニューラルネットワーク(NN)の構造や学習条件に強く依存__する上に、汎化性能の高い「良いモデル」を構築するためのNN構造や学習条件は状況によって異なります。このため、__「良いモデル」を構築するためには、様々なNN構造・学習条件によるトライ＆エラーが必須__です。

なお、この記事は「深層学習(初版)、Ian Goodfellow, Yoshua Bengio and Aaron Courville 著、岩澤有佑・鈴木雅大・中山浩太郎・松尾豊 監訳、2018年2月28日発行」（以下、「深層学習」とよびます）を参考に作成しました。

この記事では、このようなトライ＆エラーを実施するにあたって、__私なりに重要と考えるポイントを整理__しました。また、__各項目について、Tensorflowでの実装例も記載__しました。

__・ポイント➀：多層ニューラルネットワークのパラメータの初期化__
__・ポイント➁：多層ニューラルネットワークのサイズの最適化__
__・ポイント➂：バッチ正規化__
__・ポイント➃：正則化__
__・ポイント➄：オプティマイザのハイパーパラメータの設定__
__・ポイント➅：Early Stopping__

#ポイント➀：多層ニューラルネットワークのパラメータの初期化
多層ニューラルネットワークの初期値は、与条件として設定する必要があります。さらに、深層学習のアルゴリズムは非常に難しいタスクであるため、ほとんどのアルゴリズムは初期設定の選択に大きな影響を受けます。

つまり、多層NNのパラメータの初期値は、最終的な多層NNの推定精度に大きく影響します。言い換えると、パラメータの初期値を適切に設定することで、推定精度の向上を図ることができます。Tensorflowでは、以下の例のように、各層の初期値の設定方法を指定することができます。

```python
# NNの構造を設定
import tensorflow as tf
import tensorflow.keras as keras
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import Adam
#
model=Sequential()
model.add(Dense(5,input_dim=20,activation='relu',
                kernel_initializer='random_uniform', #重みの初期値の設定
                bias_initializer='zero')) #バイアスの初期値の設定
model.add(Dense(1,activation='sigmoid'))
#
```

「深層学習」8.4章 によれば、現時点では、多層NNの初期値はトライ＆エラーで設定するしかないようです。Tensorflowでは、上の例のほかにも、初期値を以下の方法で指定できます。色々試してみて、最も精度が高くなるものを選ぶと良いと思います。__また、バイアスの初期値は0に設定しておき、重みに対して初期値の設定を行うが一般的のようです。__

'random_normal'：正規分布に従って重みを初期化
'random_uniform'：一様分布に従って重みを初期化
'orthogonal'：重み行列が直交テンソルとなるように初期化
'glorot_normal'：Glorot の正規分布による初期化
'glorot_uniform'：Glorot の一様分布による初期化
'he_normal'：He の正規分布による初期化
'lecun_normal'：LeCunの正規分布による初期化
'zeros'：初期値を0に設定

#ポイント➁：多層ニューラルネットワークのサイズの適正化
NNの中間層数やニューロン数を大きくすることで、NNの表現力は増大しますが、必要以上に表現力を拡大してしまうと、その分学習や汎化の難易度も増加します。__中間層数、ニューロン数を増やしすぎてしまったことで、深層学習モデルの予測精度が損なわれる__ことが頻繁に起こります。

中間層数が1層の場合が一番精度がよかったなんてこともざらにあります。__様々な中間層数・ニューロン数で学習を実施し、最も推定精度が高くなる、適切なサイズを調べる__とよいでしょう。

#ポイント➂：バッチ正規化
「深層学習」8.7章 によれば、バッチ正規化とは、中間層の各層の出力を標準化し、多層NNのパラメータ更新を行いやすくにする方法です。Tensorflowで書くと、以下のような感じになります。

```python
from tensorflow.keras.layers import BatchNormalization
#
model=Sequential()
model.add(Dense(5,input_dim=20,activation='relu',
                kernel_initializer='random_uniform',
                bias_initializer='zero'))
model.add(BatchNormalization()) #バッチ正規化
model.add(Dense(1,activation='sigmoid'))
#
```

#ポイント➃：正則化
正則化とは、学習時に制約を設けることで学習データへの過度な適合を防ぐ手法のことで、主なものに以下の3つがあります。

a) ミニバッチ勾配降下法
b) 重み減衰
c) ドロップアウト
### a) ミニバッチ勾配降下法
ミニバッチ勾配降下法とは、学習データをミニバッチと呼ばれる複数のデータに分割し、ミニバッチ毎に多層ニューラルネットワークのパラメータを更新することです。分割方法は学習毎にランダムに変更します。「深層学習」8.1.3章 によれば、この手法は正則化の効果をもたらすことがあるようです。これは、ミニバッチが学習過程に無作為性を追加するためのようです。Tensorflowでは、fit関数の引数'batchsize'でバッチサイズを設定できます。

```python
model=Sequential()
model.add(Dense(5,input_dim=20,activation='relu',
                kernel_initializer='random_uniform',
                bias_initializer='zero'))
model.add(Dense(1,activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.001))
res=model.fit(x=train,y=train_r,
              epochs=10000,
              batchsize=32 ) #バッチサイズの設定  
```

###b) 重みの正則化
多層NNの重みが発散してしまうと、ある特定の微小な入力データの変化に対して出力が極端に変動するようになり、汎化性能が損なわれることになります。重みの正則化により、これを解決できます。これは、誤差関数の代わりに、誤差関数に多層NNの重みの絶対値に比例する項を追加し、この関数を最小化するようにパラメータを更新することで、多層NNの重みの発散を防ぎながら学習を進めることです。

重みの正則化には、L1正則化、L2正則化の2種類があります。L1正則化は重みパラメータの一部を0にすることでモデルを疎にする効果があります。L2正則化は重みパラメータにペナルティを加えますがモデルを疎にすることありません。L2正則化のほうが一般的に使われているようです。

```python
model=Sequential()
model.add(Dense(5,input_dim=20,activation='relu',
             kernel_regularizer=keras.regularizers.l2(0.001), #重みの正則化考慮
             kernel_initializer='random_uniform',
             bias_initializer='zero'))
model.add(Dense(1,activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.001))
res=model.fit(x=train,y=train_r,
              epochs=10000,
              batchsize=32 )
```

###c) ドロップアウト
ドロップアウトとは、学習時において、中間層の各ニューロンからの出力を一定の割合でランダムに0にする操作を指します。「深層学習」7.12章 によれば、これは、インプットデータにノイズを付与するのと同様の効果を持っており、これにより過学習が抑えられることが知られています。Tensorflowでは、全結合層の間にドロップアウト層を追加することで実現します。

```python
from tensorflow.keras.layers import Dropout
#
model=Sequential()
model.add(Dense(5,input_dim=20,activation='relu',
             kernel_regularizer=keras.regularizers.l2(0.001),
             kernel_initializer='random_uniform',
             bias_initializer='zero'))
model.add(Dropout(0.1)) # ドロップアウト層・ドロップアウトさせる割合
model.add(Dense(1,activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.001))
res=model.fit(x=train,y=train_r,
              epochs=10000,
              batchsize=32 )
```

#ポイント➄：ハイパーパラメータの調整
オプティマイザ自身も、様々なパラメータを持っています。例えばAdamの場合は、学習1回当りのパラメータの更新量を決める「学習率」、勾配の1次モーメント・2次モーメントの減衰率を決めるβ1、β2、数値的安定性のためのε項等があります。

これらの__パラメータの汎化性能に対する感度分析を行うことで、適切なパラメータを決めることが望ましい__です。

また、汎化性能に特に大きく影響するのが学習率です。学習率は、徐々に値を小さくしていくのがよくいとされています。Tensorflowでは、学習率の減衰は以下で考慮できます。

```python
#--学習率の更新ルールを設定
lr_schedule=tf.keras.optimizers.schedules.ExponentialDecay( \
            initial_learning_rate=0.001, #初期の学習率
            decay_steps=100000, #減衰ステップ数
            decay_rate=0.01, #最終的な減衰率 
            staircase=True)
#
# 学習率 = initial_learning_rate * decay_rate ^ (step / decay_steps)
#
model=Sequential()
model.add(Dense(5,input_dim=20,activation='relu',
             kernel_regularizer=keras.regularizers.l2(0.001),
             kernel_initializer='random_uniform',
             bias_initializer='zero'))
model.add(Dropout(0.1))
model.add(Dense(1,activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer=Adam(learning_rate=lr_schedule))
                                                           # ↑ 学習率指定
res=model.fit(x=train,y=train_r,
              epochs=10000,
              batchsize=32 )
```

#ポイント➅：Early Stopping
学習の初期段階では、汎化性能は一様に増加しますが、学習が進み過ぎると、学習用のデータに過度に適合してしまう過学習のフェーズに入り、学習回数の増加と共に汎化性能が低下していきます。

これを防ぐための手法がEarly stopping法です。学習用のデータセットの一部を検証用データとして分離し、残りのデータセットのみを用いてNNのパラメータを更新しながら、分離した検証用データに対するNNの推定精度を逐次確認します。そして、過学習のフェーズに入る前に学習を打ち切ることができます。また、引数である'restore_best_weights'をTrueに設定すると、一連の学習プロセスの中で最も汎化性能が高い状態のモデルを抽出することができます。

Tensorflowでの実装例を以下に示します。

```python
from tensorflow.keras.callbacks import EarlyStopping #Early Stoppingを行うためのコールバック関数
#
# NNの構造を設定
model=Sequential()
model.add(Dense(5,input_dim=20,activation='relu'))
model.add(Dense(1,activation='sigmoid'))

#=Early Stopping のコールバック関数の設定==========================================
Ecall=EarlyStopping(monitor='val_loss',patience=1000,restore_best_weights=False)
#================================================================================

# モデル構築
model.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.001))
# 学習実行 ( train:入力データ(学習用) train_r:出力データ(学習用) val:入力用データ(検証用) val_r:出力データ(検証用) )	
res=model.fit(train,train_r,epochs=10000,callbacks=[Ecall],verbose=1,validation_data=[val,val_r] )	
#                                                     ↑ callbacksにEarly stoppingのコールバック関数を指定	
```

#参考文献
######1. 深層学習(初版)、Ian Goodfellow, Yoshua Bengio and Aaron Courville 著、岩澤有佑・鈴木雅大・中山浩太郎・松尾豊 監訳、2018年2月28日発行