書籍「15Stepで踏破 自然言語処理アプリケーション開発入門」をやってみる - 3章Step10メモ「ニューラルネットワークの詳細と改善」

# 内容
[15stepで踏破 自然言語処理アプリケーション入門](https://www.amazon.co.jp/dp/4865941320) を読み進めていくにあたっての自分用のメモです。
今回は3章Step10で、自分なりのポイントをメモります。

# 準備
- 個人用MacPC：MacOS Mojave バージョン10.14.6
- docker version：Client, Server共にバージョン19.03.2

# 章の概要
8章では単純パーセプトロンから発展して多層パーセプトロンを導入し、9章では多クラス識別器を実装してみた。
10章ではニューラルネットワークの改善を目指す。

- ニューラルネットワークの層を深くする難しさ
- 重みの最適化手法
- ニューラルネットワークのチューニング方法

# 10.1 Deep Neural Networks
もともとは3層以上のニューラルネットワークをdeepと呼んでいた。
層を増やすのは `model.add` で層を増やすだけで良い。

## ニューラルネットワークの層を深くする難しさ
|項目 |解決策 |
|--- |--- |
|過学習しやすくなる |・**Early stopping**<br>　・層が多いとニューラルネットワークの表現力が高いため、学習データへ過剰に適合しやすい<br>　・エポック単位で学習を繰り返すが、テストデータに対する精度が下がる前に学習を切り上げること<br><br>・**Dropout**<br>　・学習時に一部のユニットを一定の割合でランダムに無視し、予測時には全ユニットを使う<br>　・1回の学習で有効になるユニットの数が少ないので過学習しづらい<br>　・複数のニューラルネットワークの足し合わせに似た予測の仕方が、アンサンブル学習と同様の効果を発揮する|
|学習がうまく進まない |・**Batch normalization**<br>　・**内部共変量シフト**が起きる<br>　　・手前の層の重みの更新のせいで、後ろの層の重みの更新が妨げられる<br>　・データの分布が平均0で分散1になるよう正規化する<br>　・新たな層として追加したり、層中の活性化関数の前で実行したりする|
|計算量の増大 |高速に並列処理ができるGPUを利用してニューラルネットワークを学習する |

```python:EarlyStopping
model.fit(X, y,
    epochs = 100,
    validation_split = 0.1,
    callbacks = [EarlyStopping(min_delta = 0.0, patience = 1)])

    # epochs：ある程度大きくして、EarlyStoppingで切り上げるより前に終わらないようにする
    # validation_split：入力した学習データのうち、学習データとバリデーションデータの割合を指定できる
    # callbacks：listで指定したコールバックが学習中に逐次呼び出される
```

```python:Dropout
model = Sequential()
model.add(Dense(..))
model.add(Dropout(0.5))
model.add(Dense(..))
model.add(Dropout(0.5))
model.add(Dense(..))
model.add(Dropout(0.5))
model.add(Dense(.., activation = 'softmax')
model.compile(..)

# Dropoutのコンストラクタ引数はユニットを無視する割合
```

```python:BatchNormalization
model = Sequential()

# 新たな層として追加
model.add(Dense(.., activation = 'relu'))
model.add(BatchNormalization(0.5))

# 活性化関数の前に追加
model.add(Dense(..))
model.add(BatchNormalization(0.5))
model.add(Activation('relu')

model.add(Dense(.., activation = 'softmax')
model.compile(..)
```

# 10.2 ニューラルネットワークの学習
## 勾配降下法
誤差関数を重みで微分することで傾きを求め、傾きの逆方向に重みの値を更新し、ニューラルネットワークの学習を進める。

## 大域最適解と局所最適解
- 大域最適解：求めたい最適解。取りうる重みの中で最も誤差が最小となる重み（最適解）
- 局所最適解：部分的に見れば最適解だが、最も適した解が他にも存在する

## 確率的勾配降下法とミニバッチ法
- 勾配降下法（バッチ法）
    - 一度に全データを投入して全データの平均に対して重みを更新する
    - 学習が進みづらい
    - 局所最適解に陥りやすい
- 確率的勾配降下法（SGD）
    - 一度に一個だけランダムに選んだ学習データを投入し、それに対して重みを更新する
    - ノイズに対して弱く、重みの更新が悪い方向にブレると学習が安定しない
- ミニバッチ法
    - バッチ法とSGDの中間
    - 一度に数個だけランダムに選んだ学習データを投入し、それらのデータの平均に対して重みを更新する

# 10.3 ニューラルネットワークのチューニング
|項目 |内容 |
|--- |--- |
|バッチサイズ |・学習時のバッチサイズで、Kerasのデフォルトは32<br>・2のべき乗が多いがこれはただの慣習だが、小さい値は密に大きい値は疎に探索することに意味がある|
|Optimizer |・よく使われるのは後発のAdamだが、問題によってはシンプルなSGDが一番いい場合もある<br>・下の学習率とともにチューニングする|
|学習率 |・一度に更新する重みの比率で、KerasのAdamのデフォルトは0.001 |
|活性化関数 |・広く使われるのはReLU(Rectified Linear Unit)だが、改良版のやLeaky ReLUやSeLU(Scaled Exponential Unit)も検討の余地あり。<br>　・Leaky ReLU：入力が0以下の場合、傾きが小さな線形関数で変換<br>　・ELU：入力が0以下の場合、指数関数から1引いた値で変換|
|正則化/荷重減衰 |・過学習を避けるために重みが大きくなりすぎないよう制約をかけ、下記のようなノルムを損失関数に加える<br>　・l1ノルム：重みの各要素の絶対値の和<br>　・l2ノルム：重みの各要素の2乗和<br>　・l∞ノルム：重みの各要素の最大絶対値|
|重みの初期化 |・Kerasのデフォルトは乱数で初期化される<br>・分布を指定することも可能|

```python:活性化関数
model = Sequential()
model.add(Dense(.., activation = 'selu'))
model.add(Dense(.., activation = LeakyReLU(0.3)))
model.add(Dense(.., activation = 'softmax')
model.compile(..)
```

```python:正則化
model = Sequential()
model.add(Dense(.., activation = 'relu',
    kernel_regularizer = regularizers.l2(0.1)))
model.add(Dense(.., activation = 'softmax',
    kernel_regularizer = regularizers.l2(0.1)))
model.compile(..)
```

```python:重みの初期化
model = Sequential()
model.add(Dense(.., activation = 'relu',
    kernel_initializer = initializers.glorot_normal()))
model.add(Dense(.., activation = 'softmax',
    kernel_initializer = initializers.glorot_normal()))
model.compile(..)
```

glorot_normal()：Glolotの正規分布のことで、Xavierの正規分布とも呼ばれる（こっちの方が聞いたことがあった）。
Xavierの初期値はsigmoid関数やtanh関数に適しているが、活性化関数にReLUを用いる場合は**ReLUに特化したHeの初期値**の方が良さそう。