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


内容

15stepで踏破 自然言語処理アプリケーション入門 を読み進めていくにあたっての自分用のメモです。
今回は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
 ・層が多いとニューラルネットワークの表現力が高いため、学習データへ過剰に適合しやすい
 ・エポック単位で学習を繰り返すが、テストデータに対する精度が下がる前に学習を切り上げること

Dropout
 ・学習時に一部のユニットを一定の割合でランダムに無視し、予測時には全ユニットを使う
 ・1回の学習で有効になるユニットの数が少ないので過学習しづらい
 ・複数のニューラルネットワークの足し合わせに似た予測の仕方が、アンサンブル学習と同様の効果を発揮する
学習がうまく進まない Batch normalization
 ・内部共変量シフトが起きる
  ・手前の層の重みの更新のせいで、後ろの層の重みの更新が妨げられる
 ・データの分布が平均0で分散1になるよう正規化する
 ・新たな層として追加したり、層中の活性化関数の前で実行したりする
計算量の増大 高速に並列処理ができるGPUを利用してニューラルネットワークを学習する
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で指定したコールバックが学習中に逐次呼び出される
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のコンストラクタ引数はユニットを無視する割合
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
・2のべき乗が多いがこれはただの慣習だが、小さい値は密に大きい値は疎に探索することに意味がある
Optimizer ・よく使われるのは後発のAdamだが、問題によってはシンプルなSGDが一番いい場合もある
・下の学習率とともにチューニングする
学習率 ・一度に更新する重みの比率で、KerasのAdamのデフォルトは0.001
活性化関数 ・広く使われるのはReLU(Rectified Linear Unit)だが、改良版のやLeaky ReLUやSeLU(Scaled Exponential Unit)も検討の余地あり。
 ・Leaky ReLU:入力が0以下の場合、傾きが小さな線形関数で変換
 ・ELU:入力が0以下の場合、指数関数から1引いた値で変換
正則化/荷重減衰 ・過学習を避けるために重みが大きくなりすぎないよう制約をかけ、下記のようなノルムを損失関数に加える
 ・l1ノルム:重みの各要素の絶対値の和
 ・l2ノルム:重みの各要素の2乗和
 ・l∞ノルム:重みの各要素の最大絶対値
重みの初期化 ・Kerasのデフォルトは乱数で初期化される
・分布を指定することも可能
活性化関数
model = Sequential()
model.add(Dense(.., activation = 'selu'))
model.add(Dense(.., activation = LeakyReLU(0.3)))
model.add(Dense(.., activation = 'softmax')
model.compile(..)
正則化
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(..)
重みの初期化
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の初期値の方が良さそう。