過学習と学習不足について知る

View on TensorFlow.org Run in Google Colab View source on GitHub

いつものように、この例のプログラムはtf.keras APIを使用します。詳しくはTensorFlowのKeras guideを参照してください。

これまでの例、つまり、映画レビューの分類と燃費の推定では、検証用データでのモデルの正解率が、数エポックでピークを迎え、その後低下するという現象が見られました。

言い換えると、モデルが訓練用データを過学習したと考えられます。過学習への対処の仕方を学ぶことは重要です。訓練用データセットで高い正解率を達成することは難しくありませんが、我々は、(これまで見たこともない)テスト用データに汎化したモデルを開発したいのです。

過学習の反対語は学習不足(underfitting)です。学習不足は、モデルがテストデータに対してまだ改善の余地がある場合に発生します。学習不足の原因は様々です。モデルが十分強力でないとか、正則化のしすぎだとか、単に訓練時間が短すぎるといった理由があります。学習不足は、訓練用データの中の関連したパターンを学習しきっていないということを意味します。

モデルの訓練をやりすぎると、モデルは過学習を始め、訓練用データの中のパターンで、テストデータには一般的ではないパターンを学習します。我々は、過学習と学習不足の中間を目指す必要があります。これから見ていくように、ちょうどよいエポック数だけ訓練を行うというのは必要なスキルなのです。

過学習を防止するための、最良の解決策は、より多くの訓練用データを使うことです。多くのデータで訓練を行えば行うほど、モデルは自然により汎化していく様になります。これが不可能な場合、次善の策は正則化のようなテクニックを使うことです。正則化は、モデルに保存される情報の量とタイプに制約を課すものです。ネットワークが少数のパターンしか記憶できなければ、最適化プロセスにより、最も主要なパターンのみを学習することになり、より汎化される可能性が高くなります。

このノートブックでは、重みの正則化とドロップアウトという、よく使われる2つの正則化テクニックをご紹介します。これらを使って、IMDBの映画レビューを分類するノートブックの改善を図ります。

from __future__ import absolute_import, division, print_function, unicode_literals

import tensorflow as tf
from tensorflow import keras

import numpy as np
import matplotlib.pyplot as plt

print(tf.__version__)
1.12.0

IMDBデータセットのダウンロード

以前のノートブックで使用したエンベディングの代わりに、ここでは文をマルチホットエンコードします。このモデルは、訓練用データセットをすぐに過学習します。このモデルを使って、過学習がいつ起きるかということと、どうやって過学習と戦うかをデモします。

リストをマルチホットエンコードすると言うのは、0と1のベクトルにするということです。具体的にいうと、例えば[3, 5]というシーケンスを、インデックス3と5の値が1で、それ以外がすべて0の、10,000次元のベクトルに変換するということを意味します。

NUM_WORDS = 10000

(train_data, train_labels), (test_data, test_labels) = keras.datasets.imdb.load_data(num_words=NUM_WORDS)

def multi_hot_sequences(sequences, dimension):
    # 形状が (len(sequences), dimension)ですべて0の行列を作る
    results = np.zeros((len(sequences), dimension))
    for i, word_indices in enumerate(sequences):
        results[i, word_indices] = 1.0  # 特定のインデックスに対してresults[i] を1に設定する
    return results


train_data = multi_hot_sequences(train_data, dimension=NUM_WORDS)
test_data = multi_hot_sequences(test_data, dimension=NUM_WORDS)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb.npz
17465344/17464789 [==============================] - 1s 0us/step

結果として得られるマルチホットベクトルの1つを見てみましょう。単語のインデックスは頻度順にソートされています。このため、インデックスが0に近いほど1が多く出現するはずです。分布を見てみましょう。

plt.plot(train_data[0])
[<matplotlib.lines.Line2D at 0x7f94ea9e2400>]

png

過学習のデモ

過学習を防止するための最も単純な方法は、モデルのサイズ、すなわち、モデル内の学習可能なパラメータの数を小さくすることです(学習パラメータの数は、層の数と層ごとのユニット数で決まります)。ディープラーニングでは、モデルの学習可能なパラメータ数を、しばしばモデルの「キャパシティ」と呼びます。直感的に考えれば、パラメータ数の多いモデルほど「記憶容量」が大きくなり、訓練用のサンプルとその目的変数の間の辞書のようなマッピングをたやすく学習することができます。このマッピングには汎化能力がまったくなく、これまで見たことが無いデータを使って予測をする際には役に立ちません。

ディープラーニングのモデルは訓練用データに適応しやすいけれど、本当のチャレレンジは汎化であって適応ではないということを、肝に銘じておく必要があります。

一方、ネットワークの記憶容量が限られている場合、前述のようなマッピングを簡単に学習することはできません。損失を減らすためには、より予測能力が高い圧縮された表現を学習しなければなりません。同時に、モデルを小さくしすぎると、訓練用データに適応するのが難しくなります。「多すぎる容量」と「容量不足」の間にちょうどよい容量があるのです。

残念ながら、(層の数や、層ごとの大きさといった)モデルの適切なサイズやアーキテクチャを決める魔法の方程式はありません。一連の異なるアーキテクチャを使って実験を行う必要があります。

適切なモデルのサイズを見つけるには、比較的少ない層の数とパラメータから始めるのがベストです。それから、検証用データでの損失値の改善が見られなくなるまで、徐々に層の大きさを増やしたり、新たな層を加えたりします。映画レビューの分類ネットワークでこれを試してみましょう。

比較基準として、Dense層だけを使ったシンプルなモデルを構築し、その後、それより小さいバージョンと大きいバージョンを作って比較します。

比較基準を作る

baseline_model = keras.Sequential([
    # `.summary` を見るために`input_shape`が必要
    keras.layers.Dense(16, activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
    keras.layers.Dense(16, activation=tf.nn.relu),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

baseline_model.compile(optimizer='adam',
                       loss='binary_crossentropy',
                       metrics=['accuracy', 'binary_crossentropy'])

baseline_model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 16)                160016    
_________________________________________________________________
dense_1 (Dense)              (None, 16)                272       
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 17        
=================================================================
Total params: 160,305
Trainable params: 160,305
Non-trainable params: 0
_________________________________________________________________
baseline_history = baseline_model.fit(train_data,
                                      train_labels,
                                      epochs=20,
                                      batch_size=512,
                                      validation_data=(test_data, test_labels),
                                      verbose=2)
Train on 25000 samples, validate on 25000 samples
Epoch 1/20
 - 3s - loss: 0.4572 - acc: 0.8064 - binary_crossentropy: 0.4572 - val_loss: 0.3080 - val_acc: 0.8812 - val_binary_crossentropy: 0.3080
Epoch 2/20
 - 3s - loss: 0.2280 - acc: 0.9167 - binary_crossentropy: 0.2280 - val_loss: 0.2822 - val_acc: 0.8874 - val_binary_crossentropy: 0.2822
Epoch 3/20
 - 3s - loss: 0.1691 - acc: 0.9413 - binary_crossentropy: 0.1691 - val_loss: 0.2952 - val_acc: 0.8834 - val_binary_crossentropy: 0.2952
Epoch 4/20
 - 3s - loss: 0.1351 - acc: 0.9542 - binary_crossentropy: 0.1351 - val_loss: 0.3252 - val_acc: 0.8777 - val_binary_crossentropy: 0.3252
Epoch 5/20
 - 3s - loss: 0.1122 - acc: 0.9626 - binary_crossentropy: 0.1122 - val_loss: 0.3567 - val_acc: 0.8719 - val_binary_crossentropy: 0.3567
Epoch 6/20
 - 3s - loss: 0.0915 - acc: 0.9723 - binary_crossentropy: 0.0915 - val_loss: 0.3901 - val_acc: 0.8681 - val_binary_crossentropy: 0.3901
Epoch 7/20
 - 3s - loss: 0.0743 - acc: 0.9794 - binary_crossentropy: 0.0743 - val_loss: 0.4459 - val_acc: 0.8595 - val_binary_crossentropy: 0.4459
Epoch 8/20
 - 3s - loss: 0.0612 - acc: 0.9830 - binary_crossentropy: 0.0612 - val_loss: 0.4757 - val_acc: 0.8610 - val_binary_crossentropy: 0.4757
Epoch 9/20
 - 3s - loss: 0.0493 - acc: 0.9887 - binary_crossentropy: 0.0493 - val_loss: 0.5196 - val_acc: 0.8580 - val_binary_crossentropy: 0.5196
Epoch 10/20
 - 3s - loss: 0.0391 - acc: 0.9923 - binary_crossentropy: 0.0391 - val_loss: 0.5753 - val_acc: 0.8542 - val_binary_crossentropy: 0.5753
Epoch 11/20
 - 3s - loss: 0.0325 - acc: 0.9942 - binary_crossentropy: 0.0325 - val_loss: 0.6151 - val_acc: 0.8554 - val_binary_crossentropy: 0.6151
Epoch 12/20
 - 3s - loss: 0.0247 - acc: 0.9964 - binary_crossentropy: 0.0247 - val_loss: 0.6616 - val_acc: 0.8540 - val_binary_crossentropy: 0.6616
Epoch 13/20
 - 3s - loss: 0.0187 - acc: 0.9975 - binary_crossentropy: 0.0187 - val_loss: 0.7033 - val_acc: 0.8525 - val_binary_crossentropy: 0.7033
Epoch 14/20
 - 3s - loss: 0.0140 - acc: 0.9990 - binary_crossentropy: 0.0140 - val_loss: 0.7440 - val_acc: 0.8506 - val_binary_crossentropy: 0.7440
Epoch 15/20
 - 3s - loss: 0.0106 - acc: 0.9996 - binary_crossentropy: 0.0106 - val_loss: 0.7841 - val_acc: 0.8509 - val_binary_crossentropy: 0.7841
Epoch 16/20
 - 3s - loss: 0.0083 - acc: 0.9997 - binary_crossentropy: 0.0083 - val_loss: 0.8163 - val_acc: 0.8512 - val_binary_crossentropy: 0.8163
Epoch 17/20
 - 3s - loss: 0.0066 - acc: 0.9999 - binary_crossentropy: 0.0066 - val_loss: 0.8483 - val_acc: 0.8506 - val_binary_crossentropy: 0.8483
Epoch 18/20
 - 3s - loss: 0.0054 - acc: 0.9999 - binary_crossentropy: 0.0054 - val_loss: 0.8772 - val_acc: 0.8509 - val_binary_crossentropy: 0.8772
Epoch 19/20
 - 3s - loss: 0.0044 - acc: 1.0000 - binary_crossentropy: 0.0044 - val_loss: 0.8990 - val_acc: 0.8510 - val_binary_crossentropy: 0.8990
Epoch 20/20
 - 3s - loss: 0.0037 - acc: 1.0000 - binary_crossentropy: 0.0037 - val_loss: 0.9258 - val_acc: 0.8497 - val_binary_crossentropy: 0.9258

より小さいモデルの構築

今作成したばかりの比較基準となるモデルに比べて隠れユニット数が少ないモデルを作りましょう。

smaller_model = keras.Sequential([
    keras.layers.Dense(4, activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
    keras.layers.Dense(4, activation=tf.nn.relu),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

smaller_model.compile(optimizer='adam',
                loss='binary_crossentropy',
                metrics=['accuracy', 'binary_crossentropy'])

smaller_model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_3 (Dense)              (None, 4)                 40004     
_________________________________________________________________
dense_4 (Dense)              (None, 4)                 20        
_________________________________________________________________
dense_5 (Dense)              (None, 1)                 5         
=================================================================
Total params: 40,029
Trainable params: 40,029
Non-trainable params: 0
_________________________________________________________________

同じデータを使って訓練します。

smaller_history = smaller_model.fit(train_data,
                                    train_labels,
                                    epochs=20,
                                    batch_size=512,
                                    validation_data=(test_data, test_labels),
                                    verbose=2)
Train on 25000 samples, validate on 25000 samples
Epoch 1/20
 - 4s - loss: 0.6442 - acc: 0.5927 - binary_crossentropy: 0.6442 - val_loss: 0.5938 - val_acc: 0.6904 - val_binary_crossentropy: 0.5938
Epoch 2/20
 - 3s - loss: 0.5411 - acc: 0.7832 - binary_crossentropy: 0.5411 - val_loss: 0.5197 - val_acc: 0.8193 - val_binary_crossentropy: 0.5197
Epoch 3/20
 - 3s - loss: 0.4740 - acc: 0.8598 - binary_crossentropy: 0.4740 - val_loss: 0.4784 - val_acc: 0.8411 - val_binary_crossentropy: 0.4784
Epoch 4/20
 - 3s - loss: 0.4297 - acc: 0.8968 - binary_crossentropy: 0.4297 - val_loss: 0.4540 - val_acc: 0.8619 - val_binary_crossentropy: 0.4540
Epoch 5/20
 - 3s - loss: 0.3974 - acc: 0.9180 - binary_crossentropy: 0.3974 - val_loss: 0.4420 - val_acc: 0.8609 - val_binary_crossentropy: 0.4420
Epoch 6/20
 - 3s - loss: 0.3711 - acc: 0.9333 - binary_crossentropy: 0.3711 - val_loss: 0.4298 - val_acc: 0.8705 - val_binary_crossentropy: 0.4298
Epoch 7/20
 - 3s - loss: 0.3487 - acc: 0.9435 - binary_crossentropy: 0.3487 - val_loss: 0.4235 - val_acc: 0.8710 - val_binary_crossentropy: 0.4235
Epoch 8/20
 - 3s - loss: 0.3291 - acc: 0.9519 - binary_crossentropy: 0.3291 - val_loss: 0.4224 - val_acc: 0.8704 - val_binary_crossentropy: 0.4224
Epoch 9/20
 - 3s - loss: 0.3114 - acc: 0.9586 - binary_crossentropy: 0.3114 - val_loss: 0.4243 - val_acc: 0.8691 - val_binary_crossentropy: 0.4243
Epoch 10/20
 - 3s - loss: 0.2951 - acc: 0.9642 - binary_crossentropy: 0.2951 - val_loss: 0.4238 - val_acc: 0.8694 - val_binary_crossentropy: 0.4238
Epoch 11/20
 - 3s - loss: 0.2797 - acc: 0.9683 - binary_crossentropy: 0.2797 - val_loss: 0.4269 - val_acc: 0.8680 - val_binary_crossentropy: 0.4269
Epoch 12/20
 - 3s - loss: 0.2659 - acc: 0.9717 - binary_crossentropy: 0.2659 - val_loss: 0.4317 - val_acc: 0.8671 - val_binary_crossentropy: 0.4317
Epoch 13/20
 - 3s - loss: 0.2529 - acc: 0.9759 - binary_crossentropy: 0.2529 - val_loss: 0.4333 - val_acc: 0.8670 - val_binary_crossentropy: 0.4333
Epoch 14/20
 - 3s - loss: 0.2407 - acc: 0.9790 - binary_crossentropy: 0.2407 - val_loss: 0.4378 - val_acc: 0.8659 - val_binary_crossentropy: 0.4378
Epoch 15/20
 - 3s - loss: 0.2293 - acc: 0.9811 - binary_crossentropy: 0.2293 - val_loss: 0.4441 - val_acc: 0.8655 - val_binary_crossentropy: 0.4441
Epoch 16/20
 - 3s - loss: 0.2188 - acc: 0.9830 - binary_crossentropy: 0.2188 - val_loss: 0.4477 - val_acc: 0.8650 - val_binary_crossentropy: 0.4477
Epoch 17/20
 - 3s - loss: 0.2091 - acc: 0.9848 - binary_crossentropy: 0.2091 - val_loss: 0.4528 - val_acc: 0.8641 - val_binary_crossentropy: 0.4528
Epoch 18/20
 - 3s - loss: 0.2000 - acc: 0.9864 - binary_crossentropy: 0.2000 - val_loss: 0.4696 - val_acc: 0.8623 - val_binary_crossentropy: 0.4696
Epoch 19/20
 - 3s - loss: 0.1915 - acc: 0.9871 - binary_crossentropy: 0.1915 - val_loss: 0.4717 - val_acc: 0.8631 - val_binary_crossentropy: 0.4717
Epoch 20/20
 - 3s - loss: 0.1780 - acc: 0.9883 - binary_crossentropy: 0.1780 - val_loss: 0.4837 - val_acc: 0.8611 - val_binary_crossentropy: 0.4837

より大きなモデルの構築

練習として、より大きなモデルを作成し、どれほど急速に過学習が起きるかを見ることもできます。次はこのベンチマークに、この問題が必要とするよりはるかに容量の大きなネットワークを追加しましょう。

bigger_model = keras.models.Sequential([
    keras.layers.Dense(512, activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
    keras.layers.Dense(512, activation=tf.nn.relu),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

bigger_model.compile(optimizer='adam',
                     loss='binary_crossentropy',
                     metrics=['accuracy','binary_crossentropy'])

bigger_model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_6 (Dense)              (None, 512)               5120512   
_________________________________________________________________
dense_7 (Dense)              (None, 512)               262656    
_________________________________________________________________
dense_8 (Dense)              (None, 1)                 513       
=================================================================
Total params: 5,383,681
Trainable params: 5,383,681
Non-trainable params: 0
_________________________________________________________________

このモデルもまた同じデータを使って訓練します。

bigger_history = bigger_model.fit(train_data, train_labels,
                                  epochs=20,
                                  batch_size=512,
                                  validation_data=(test_data, test_labels),
                                  verbose=2)
Train on 25000 samples, validate on 25000 samples
Epoch 1/20
 - 5s - loss: 0.3478 - acc: 0.8508 - binary_crossentropy: 0.3478 - val_loss: 0.2948 - val_acc: 0.8807 - val_binary_crossentropy: 0.2948
Epoch 2/20
 - 4s - loss: 0.1407 - acc: 0.9484 - binary_crossentropy: 0.1407 - val_loss: 0.3640 - val_acc: 0.8633 - val_binary_crossentropy: 0.3640
Epoch 3/20
 - 4s - loss: 0.0511 - acc: 0.9850 - binary_crossentropy: 0.0511 - val_loss: 0.4281 - val_acc: 0.8696 - val_binary_crossentropy: 0.4281
Epoch 4/20
 - 4s - loss: 0.0092 - acc: 0.9986 - binary_crossentropy: 0.0092 - val_loss: 0.5757 - val_acc: 0.8693 - val_binary_crossentropy: 0.5757
Epoch 5/20
 - 4s - loss: 0.0014 - acc: 1.0000 - binary_crossentropy: 0.0014 - val_loss: 0.6685 - val_acc: 0.8695 - val_binary_crossentropy: 0.6685
Epoch 6/20
 - 4s - loss: 3.1179e-04 - acc: 1.0000 - binary_crossentropy: 3.1179e-04 - val_loss: 0.7137 - val_acc: 0.8698 - val_binary_crossentropy: 0.7137
Epoch 7/20
 - 4s - loss: 1.7906e-04 - acc: 1.0000 - binary_crossentropy: 1.7906e-04 - val_loss: 0.7421 - val_acc: 0.8696 - val_binary_crossentropy: 0.7421
Epoch 8/20
 - 4s - loss: 1.2417e-04 - acc: 1.0000 - binary_crossentropy: 1.2417e-04 - val_loss: 0.7642 - val_acc: 0.8698 - val_binary_crossentropy: 0.7642
Epoch 9/20
 - 4s - loss: 9.1611e-05 - acc: 1.0000 - binary_crossentropy: 9.1611e-05 - val_loss: 0.7842 - val_acc: 0.8702 - val_binary_crossentropy: 0.7842
Epoch 10/20
 - 4s - loss: 6.9493e-05 - acc: 1.0000 - binary_crossentropy: 6.9493e-05 - val_loss: 0.8023 - val_acc: 0.8702 - val_binary_crossentropy: 0.8023
Epoch 11/20
 - 4s - loss: 5.3470e-05 - acc: 1.0000 - binary_crossentropy: 5.3470e-05 - val_loss: 0.8204 - val_acc: 0.8704 - val_binary_crossentropy: 0.8204
Epoch 12/20
 - 4s - loss: 4.1468e-05 - acc: 1.0000 - binary_crossentropy: 4.1468e-05 - val_loss: 0.8380 - val_acc: 0.8704 - val_binary_crossentropy: 0.8380
Epoch 13/20
 - 4s - loss: 3.2461e-05 - acc: 1.0000 - binary_crossentropy: 3.2461e-05 - val_loss: 0.8547 - val_acc: 0.8704 - val_binary_crossentropy: 0.8547
Epoch 14/20
 - 4s - loss: 2.5679e-05 - acc: 1.0000 - binary_crossentropy: 2.5679e-05 - val_loss: 0.8716 - val_acc: 0.8705 - val_binary_crossentropy: 0.8716
Epoch 15/20
 - 4s - loss: 2.0488e-05 - acc: 1.0000 - binary_crossentropy: 2.0488e-05 - val_loss: 0.8873 - val_acc: 0.8708 - val_binary_crossentropy: 0.8873
Epoch 16/20
 - 4s - loss: 1.6536e-05 - acc: 1.0000 - binary_crossentropy: 1.6536e-05 - val_loss: 0.9012 - val_acc: 0.8708 - val_binary_crossentropy: 0.9012
Epoch 17/20
 - 4s - loss: 1.3504e-05 - acc: 1.0000 - binary_crossentropy: 1.3504e-05 - val_loss: 0.9159 - val_acc: 0.8704 - val_binary_crossentropy: 0.9159
Epoch 18/20
 - 4s - loss: 1.1171e-05 - acc: 1.0000 - binary_crossentropy: 1.1171e-05 - val_loss: 0.9283 - val_acc: 0.8708 - val_binary_crossentropy: 0.9283
Epoch 19/20
 - 4s - loss: 9.3210e-06 - acc: 1.0000 - binary_crossentropy: 9.3210e-06 - val_loss: 0.9402 - val_acc: 0.8708 - val_binary_crossentropy: 0.9402
Epoch 20/20
 - 4s - loss: 7.8828e-06 - acc: 1.0000 - binary_crossentropy: 7.8828e-06 - val_loss: 0.9518 - val_acc: 0.8707 - val_binary_crossentropy: 0.9518

訓練時と検証時の損失をグラフにする

実線は訓練用データセットの損失、破線は検証用データセットでの損失です(検証用データでの損失が小さい方が良いモデルです)。これをみると、小さいネットワークのほうが比較基準のモデルよりも過学習が始まるのが遅いことがわかります(4エポックではなく6エポック後)。また、過学習が始まっても性能の低下がよりゆっくりしています。

def plot_history(histories, key='binary_crossentropy'):
  plt.figure(figsize=(16,10))

  for name, history in histories:
    val = plt.plot(history.epoch, history.history['val_'+key],
                   '--', label=name.title()+' Val')
    plt.plot(history.epoch, history.history[key], color=val[0].get_color(),
             label=name.title()+' Train')

  plt.xlabel('Epochs')
  plt.ylabel(key.replace('_',' ').title())
  plt.legend()

  plt.xlim([0,max(history.epoch)])


plot_history([('baseline', baseline_history),
              ('smaller', smaller_history),
              ('bigger', bigger_history)])

png

より大きなネットワークでは、すぐに、1エポックで過学習が始まり、その度合も強いことに注目してください。ネットワークの容量が大きいほど訓練用データをモデル化するスピードが早くなり(結果として訓練時の損失値が小さくなり)ますが、より過学習しやすく(結果として訓練時の損失値と検証時の損失値が大きく乖離しやすく)なります。

戦略

重みの正則化を加える

「オッカムの剃刀」の原則をご存知でしょうか。何かの説明が2つあるとすると、最も正しいと考えられる説明は、仮定の数が最も少ない「一番単純な」説明だというものです。この原則は、ニューラルネットワークを使って学習されたモデルにも当てはまります。ある訓練用データとネットワーク構造があって、そのデータを説明できる重みの集合が複数ある時(つまり、複数のモデルがある時)、単純なモデルのほうが複雑なものよりも過学習しにくいのです。

ここで言う「単純なモデル」とは、パラメータ値の分布のエントロピーが小さいもの(あるいは、上記で見たように、そもそもパラメータの数が少ないもの)です。したがって、過学習を緩和するための一般的な手法は、重みが小さい値のみをとることで、重み値の分布がより整然となる(正則)様に制約を与えるものです。これを「重みの正則化」と呼ばれ、ネットワークの損失関数に、重みの大きさに関連するコストを加えることで行われます。このコストには2つの種類があります。

  • L1正則化 重み係数の絶対値に比例するコストを加える(重みの「L1ノルム」と呼ばれる)。

  • L2正則化 重み係数の二乗に比例するコストを加える(重み係数の二乗「L2ノルム」と呼ばれる)。L2正則化はニューラルネットワーク用語では重み減衰(Weight Decay)と呼ばれる。呼び方が違うので混乱しないように。重み減衰は数学的にはL2正則化と同義である。

tf.kerasでは、重みの正則化をするために、重み正則化のインスタンスをキーワード引数として層に加えます。ここでは、L2正則化を追加してみましょう。

l2_model = keras.models.Sequential([
    keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001),
                       activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
    keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001),
                       activation=tf.nn.relu),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

l2_model.compile(optimizer='adam',
                 loss='binary_crossentropy',
                 metrics=['accuracy', 'binary_crossentropy'])

l2_model_history = l2_model.fit(train_data, train_labels,
                                epochs=20,
                                batch_size=512,
                                validation_data=(test_data, test_labels),
                                verbose=2)
Train on 25000 samples, validate on 25000 samples
Epoch 1/20
 - 3s - loss: 0.5459 - acc: 0.7954 - binary_crossentropy: 0.5056 - val_loss: 0.3951 - val_acc: 0.8721 - val_binary_crossentropy: 0.3546
Epoch 2/20
 - 3s - loss: 0.3129 - acc: 0.9053 - binary_crossentropy: 0.2676 - val_loss: 0.3332 - val_acc: 0.8878 - val_binary_crossentropy: 0.2839
Epoch 3/20
 - 3s - loss: 0.2542 - acc: 0.9287 - binary_crossentropy: 0.2020 - val_loss: 0.3371 - val_acc: 0.8859 - val_binary_crossentropy: 0.2828
Epoch 4/20
 - 3s - loss: 0.2260 - acc: 0.9410 - binary_crossentropy: 0.1700 - val_loss: 0.3526 - val_acc: 0.8824 - val_binary_crossentropy: 0.2954
Epoch 5/20
 - 3s - loss: 0.2115 - acc: 0.9481 - binary_crossentropy: 0.1526 - val_loss: 0.3654 - val_acc: 0.8788 - val_binary_crossentropy: 0.3053
Epoch 6/20
 - 3s - loss: 0.1969 - acc: 0.9542 - binary_crossentropy: 0.1361 - val_loss: 0.3830 - val_acc: 0.8750 - val_binary_crossentropy: 0.3217
Epoch 7/20
 - 3s - loss: 0.1874 - acc: 0.9594 - binary_crossentropy: 0.1249 - val_loss: 0.3965 - val_acc: 0.8725 - val_binary_crossentropy: 0.3332
Epoch 8/20
 - 3s - loss: 0.1780 - acc: 0.9637 - binary_crossentropy: 0.1139 - val_loss: 0.4089 - val_acc: 0.8710 - val_binary_crossentropy: 0.3445
Epoch 9/20
 - 3s - loss: 0.1697 - acc: 0.9662 - binary_crossentropy: 0.1047 - val_loss: 0.4261 - val_acc: 0.8689 - val_binary_crossentropy: 0.3604
Epoch 10/20
 - 3s - loss: 0.1643 - acc: 0.9706 - binary_crossentropy: 0.0981 - val_loss: 0.4436 - val_acc: 0.8663 - val_binary_crossentropy: 0.3769
Epoch 11/20
 - 3s - loss: 0.1570 - acc: 0.9724 - binary_crossentropy: 0.0899 - val_loss: 0.4530 - val_acc: 0.8651 - val_binary_crossentropy: 0.3853
Epoch 12/20
 - 3s - loss: 0.1510 - acc: 0.9752 - binary_crossentropy: 0.0832 - val_loss: 0.4743 - val_acc: 0.8613 - val_binary_crossentropy: 0.4062
Epoch 13/20
 - 3s - loss: 0.1468 - acc: 0.9776 - binary_crossentropy: 0.0781 - val_loss: 0.4804 - val_acc: 0.8628 - val_binary_crossentropy: 0.4113
Epoch 14/20
 - 3s - loss: 0.1380 - acc: 0.9806 - binary_crossentropy: 0.0691 - val_loss: 0.4893 - val_acc: 0.8617 - val_binary_crossentropy: 0.4206
Epoch 15/20
 - 3s - loss: 0.1311 - acc: 0.9846 - binary_crossentropy: 0.0627 - val_loss: 0.5023 - val_acc: 0.8598 - val_binary_crossentropy: 0.4337
Epoch 16/20
 - 3s - loss: 0.1278 - acc: 0.9851 - binary_crossentropy: 0.0592 - val_loss: 0.5153 - val_acc: 0.8582 - val_binary_crossentropy: 0.4466
Epoch 17/20
 - 3s - loss: 0.1242 - acc: 0.9866 - binary_crossentropy: 0.0556 - val_loss: 0.5230 - val_acc: 0.8585 - val_binary_crossentropy: 0.4540
Epoch 18/20
 - 3s - loss: 0.1218 - acc: 0.9866 - binary_crossentropy: 0.0528 - val_loss: 0.5487 - val_acc: 0.8536 - val_binary_crossentropy: 0.4794
Epoch 19/20
 - 3s - loss: 0.1184 - acc: 0.9887 - binary_crossentropy: 0.0490 - val_loss: 0.5426 - val_acc: 0.8582 - val_binary_crossentropy: 0.4731
Epoch 20/20
 - 3s - loss: 0.1149 - acc: 0.9911 - binary_crossentropy: 0.0454 - val_loss: 0.5491 - val_acc: 0.8575 - val_binary_crossentropy: 0.4794

l2(0.001)というのは、層の重み行列の係数全てに対して0.001 * 重み係数の値 **2をネットワークの損失値合計に加えることを意味します。このペナルティは訓練時のみに加えられるため、このネットワークの損失値は、訓練時にはテスト時に比べて大きくなることに注意してください。

L2正則化の影響を見てみましょう。

plot_history([('baseline', baseline_history),
              ('l2', l2_model_history)])

png

ご覧のように、L2正則化ありのモデルは比較基準のモデルに比べて過学習しにくくなっています。両方のモデルのパラメータ数は同じであるにもかかわらずです。

ドロップアウトを追加する

ドロップアウトは、ニューラルネットワークの正則化テクニックとして最もよく使われる手法の一つです。この手法は、トロント大学のヒントンと彼の学生が開発したものです。ドロップアウトは層に適用するもので、訓練時に層から出力された特徴量に対してランダムに「ドロップアウト(つまりゼロ化)」を行うものです。例えば、ある層が訓練時にある入力サンプルに対して、普通は[0.2, 0.5, 1.3, 0.8, 1.1] というベクトルを出力するとします。ドロップアウトを適用すると、このベクトルは例えば[0, 0.5, 1.3, 0, 1.1]のようにランダムに散らばったいくつかのゼロを含むようになります。「ドロップアウト率」はゼロ化される特徴の割合で、通常は0.2から0.5の間に設定します。テスト時は、どのユニットもドロップアウトされず、代わりに出力値がドロップアウト率と同じ比率でスケールダウンされます。これは、訓練時に比べてたくさんのユニットがアクティブであることに対してバランスをとるためです。

tf.kerasでは、Dropout層を使ってドロップアウトをネットワークに導入できます。ドロップアウト層は、その直前の層の出力に対してドロップアウトを適用します。

それでは、IMDBネットワークに2つのドロップアウト層を追加しましょう。

dpt_model = keras.models.Sequential([
    keras.layers.Dense(16, activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
    keras.layers.Dropout(rate=0.5),
    keras.layers.Dense(16, activation=tf.nn.relu),
    keras.layers.Dropout(rate=0.5),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

dpt_model.compile(optimizer='adam',
                  loss='binary_crossentropy',
                  metrics=['accuracy','binary_crossentropy'])

dpt_model_history = dpt_model.fit(train_data, train_labels,
                                  epochs=20,
                                  batch_size=512,
                                  validation_data=(test_data, test_labels),
                                  verbose=2)
Train on 25000 samples, validate on 25000 samples
Epoch 1/20
 - 3s - loss: 0.6269 - acc: 0.6266 - binary_crossentropy: 0.6269 - val_loss: 0.5108 - val_acc: 0.8409 - val_binary_crossentropy: 0.5108
Epoch 2/20
 - 3s - loss: 0.4765 - acc: 0.7870 - binary_crossentropy: 0.4765 - val_loss: 0.3601 - val_acc: 0.8811 - val_binary_crossentropy: 0.3601
Epoch 3/20
 - 3s - loss: 0.3658 - acc: 0.8643 - binary_crossentropy: 0.3658 - val_loss: 0.2986 - val_acc: 0.8880 - val_binary_crossentropy: 0.2986
Epoch 4/20
 - 3s - loss: 0.2965 - acc: 0.8958 - binary_crossentropy: 0.2965 - val_loss: 0.2739 - val_acc: 0.8898 - val_binary_crossentropy: 0.2739
Epoch 5/20
 - 3s - loss: 0.2542 - acc: 0.9153 - binary_crossentropy: 0.2542 - val_loss: 0.2733 - val_acc: 0.8900 - val_binary_crossentropy: 0.2733
Epoch 6/20
 - 3s - loss: 0.2135 - acc: 0.9298 - binary_crossentropy: 0.2135 - val_loss: 0.2824 - val_acc: 0.8870 - val_binary_crossentropy: 0.2824
Epoch 7/20
 - 3s - loss: 0.1911 - acc: 0.9363 - binary_crossentropy: 0.1911 - val_loss: 0.2975 - val_acc: 0.8848 - val_binary_crossentropy: 0.2975
Epoch 8/20
 - 3s - loss: 0.1657 - acc: 0.9451 - binary_crossentropy: 0.1657 - val_loss: 0.3169 - val_acc: 0.8840 - val_binary_crossentropy: 0.3169
Epoch 9/20
 - 3s - loss: 0.1500 - acc: 0.9532 - binary_crossentropy: 0.1500 - val_loss: 0.3330 - val_acc: 0.8826 - val_binary_crossentropy: 0.3330
Epoch 10/20
 - 3s - loss: 0.1317 - acc: 0.9577 - binary_crossentropy: 0.1317 - val_loss: 0.3516 - val_acc: 0.8808 - val_binary_crossentropy: 0.3516
Epoch 11/20
 - 3s - loss: 0.1194 - acc: 0.9611 - binary_crossentropy: 0.1194 - val_loss: 0.3699 - val_acc: 0.8801 - val_binary_crossentropy: 0.3699
Epoch 12/20
 - 3s - loss: 0.1088 - acc: 0.9646 - binary_crossentropy: 0.1088 - val_loss: 0.4074 - val_acc: 0.8794 - val_binary_crossentropy: 0.4074
Epoch 13/20
 - 3s - loss: 0.1014 - acc: 0.9666 - binary_crossentropy: 0.1014 - val_loss: 0.4365 - val_acc: 0.8782 - val_binary_crossentropy: 0.4365
Epoch 14/20
 - 3s - loss: 0.0911 - acc: 0.9698 - binary_crossentropy: 0.0911 - val_loss: 0.4547 - val_acc: 0.8778 - val_binary_crossentropy: 0.4547
Epoch 15/20
 - 3s - loss: 0.0842 - acc: 0.9716 - binary_crossentropy: 0.0842 - val_loss: 0.4530 - val_acc: 0.8753 - val_binary_crossentropy: 0.4530
Epoch 16/20
 - 3s - loss: 0.0810 - acc: 0.9736 - binary_crossentropy: 0.0810 - val_loss: 0.4832 - val_acc: 0.8764 - val_binary_crossentropy: 0.4832
Epoch 17/20
 - 3s - loss: 0.0785 - acc: 0.9734 - binary_crossentropy: 0.0785 - val_loss: 0.5130 - val_acc: 0.8767 - val_binary_crossentropy: 0.5130
Epoch 18/20
 - 3s - loss: 0.0706 - acc: 0.9756 - binary_crossentropy: 0.0706 - val_loss: 0.5086 - val_acc: 0.8748 - val_binary_crossentropy: 0.5086
Epoch 19/20
 - 3s - loss: 0.0693 - acc: 0.9766 - binary_crossentropy: 0.0693 - val_loss: 0.5258 - val_acc: 0.8746 - val_binary_crossentropy: 0.5258
Epoch 20/20
 - 3s - loss: 0.0650 - acc: 0.9781 - binary_crossentropy: 0.0650 - val_loss: 0.5562 - val_acc: 0.8741 - val_binary_crossentropy: 0.5562
plot_history([('baseline', baseline_history),
              ('dropout', dpt_model_history)])

png

ドロップアウトを追加することで、比較対象モデルより明らかに改善が見られます。

まとめ:ニューラルネットワークにおいて過学習を防ぐ最も一般的な方法は次のとおりです。

  • 訓練データを増やす
  • ネットワークの容量をへらす
  • 重みの正則化を行う
  • ドロップアウトを追加する

このガイドで触れていない2つの重要なアプローチがあります。データ拡張とバッチ正規化です。

#@title MIT License
#
# Copyright (c) 2017 François Chollet
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.