TensorFlow使ってみた~IMDB~L2正則化&ドロップアウト
映画レビューのテキスト分類問題では、過学習が起きていました。
この記事のチュートリアルでは過学習を抑制する手法を実装します。
このノートブックでは、重みの正則化とドロップアウトという、よく使われる2つの正則化テクニックをご紹介します。
これらを使って、IMDBの映画レビューを分類するノートブックの改善を図ります。
この記事ではGoogle Colabを使用していません。
Colabだとクラッシュしてしまうので、ローカルで環境を用意しました。
諸々インポート
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__)
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)
結果として得られるマルチホットベクトルの1つを見てみましょう。
単語のインデックスは頻度順にソートされています。
このため、インデックスが0に近いほど1が多く出現するはずです。分布を見てみましょう。
plt.plot(train_data[0])
出力
過学習のデモ
オーバーフィッティングを防ぐ最も簡単な方法は、小さなモデルから始めることです。
学習可能なパラメータの数が少ないモデル(層の数と層ごとのユニットの数によって決定される)。
ディープラーニングでは、モデルの学習可能なパラメータの数は、しばしばモデルの「キャパシティ」と呼ばれます。
直感的には、より多くのパラメータを持つモデルは、より多くの「記憶容量」を持ち、したがって、訓練サンプルとそのターゲットの間の完全な辞書のようなマッピングを簡単に学習することができますが、一般化する力のないマッピングは、以前に見たことのないデータで予測を行う場合には役に立たないでしょう。
このことを常に念頭に置いてください:深層学習モデルは訓練データにフィットするのが得意な傾向がありますが、本当の課題は一般化であって、フィットすることではありません。
一方、ネットワークの記憶リソースが限られている場合、マッピングを簡単に学習することはできません。
その損失を最小限に抑えるためには、より予測力の高い圧縮表現を学習する必要があります。
同時に、モデルを小さくしすぎると、学習データにフィットするのが難しくなります。
「容量が多すぎる」と「容量が足りない」のバランスがあります。
残念ながら、モデルの正しいサイズやアーキテクチャ(レイヤーの数や各レイヤーの正しいサイズ)を決定する魔法の公式はありません。一連の異なるアーキテクチャを使用して実験する必要があります。
適切なモデル・サイズを見つけるには、比較的少数のレイヤとパラメータから始めて、検証損失のリターンが減少するのがわかるまでレイヤのサイズを大きくするか、新しいレイヤを追加するのがベストです。
ベースラインとして layers.Dense のみを使用したシンプルなモデルから始め、より大きなバージョンを作成して比較します。
※www.DeepL.com/Translator(無料版)で翻訳しました。
比較基準を作る
baseline_model = keras.Sequential([ # `.summary` を見るために`input_shape`が必要 keras.layers.Dense(16, activation='relu', input_shape=(NUM_WORDS,)), keras.layers.Dense(16, activation='relu'), keras.layers.Dense(1, activation='sigmoid') ]) baseline_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy', 'binary_crossentropy']) baseline_model.summary()
出力
Model: "sequential" _________________________________________________________________ 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 25000/25000 - 5s - loss: 0.4640 - accuracy: 0.8100 - binary_crossentropy: 0.4640 - val_loss: 0.3234 - val_accuracy: 0.8834 - val_binary_crossentropy: 0.3234 Epoch 2/20 25000/25000 - 5s - loss: 0.2385 - accuracy: 0.9175 - binary_crossentropy: 0.2385 - val_loss: 0.2840 - val_accuracy: 0.8882 - val_binary_crossentropy: 0.2840 Epoch 3/20 25000/25000 - 4s - loss: 0.1757 - accuracy: 0.9401 - binary_crossentropy: 0.1757 - val_loss: 0.2949 - val_accuracy: 0.8831 - val_binary_crossentropy: 0.2949 Epoch 4/20 25000/25000 - 3s - loss: 0.1409 - accuracy: 0.9538 - binary_crossentropy: 0.1409 - val_loss: 0.3154 - val_accuracy: 0.8780 - val_binary_crossentropy: 0.3154 Epoch 5/20 25000/25000 - 3s - loss: 0.1156 - accuracy: 0.9639 - binary_crossentropy: 0.1156 - val_loss: 0.3466 - val_accuracy: 0.8743 - val_binary_crossentropy: 0.3466 Epoch 6/20 25000/25000 - 4s - loss: 0.0929 - accuracy: 0.9731 - binary_crossentropy: 0.0929 - val_loss: 0.3798 - val_accuracy: 0.8696 - val_binary_crossentropy: 0.3798 Epoch 7/20 25000/25000 - 4s - loss: 0.0759 - accuracy: 0.9796 - binary_crossentropy: 0.0759 - val_loss: 0.4200 - val_accuracy: 0.8658 - val_binary_crossentropy: 0.4200 Epoch 8/20 25000/25000 - 4s - loss: 0.0583 - accuracy: 0.9852 - binary_crossentropy: 0.0583 - val_loss: 0.4577 - val_accuracy: 0.8632 - val_binary_crossentropy: 0.4577 Epoch 9/20 25000/25000 - 3s - loss: 0.0432 - accuracy: 0.9918 - binary_crossentropy: 0.0432 - val_loss: 0.4984 - val_accuracy: 0.8619 - val_binary_crossentropy: 0.4984 Epoch 10/20 25000/25000 - 3s - loss: 0.0307 - accuracy: 0.9949 - binary_crossentropy: 0.0307 - val_loss: 0.5467 - val_accuracy: 0.8585 - val_binary_crossentropy: 0.5467 Epoch 11/20 25000/25000 - 3s - loss: 0.0214 - accuracy: 0.9974 - binary_crossentropy: 0.0214 - val_loss: 0.5864 - val_accuracy: 0.8577 - val_binary_crossentropy: 0.5864 Epoch 12/20 25000/25000 - 3s - loss: 0.0152 - accuracy: 0.9988 - binary_crossentropy: 0.0152 - val_loss: 0.6238 - val_accuracy: 0.8576 - val_binary_crossentropy: 0.6238 Epoch 13/20 25000/25000 - 4s - loss: 0.0110 - accuracy: 0.9992 - binary_crossentropy: 0.0110 - val_loss: 0.6535 - val_accuracy: 0.8570 - val_binary_crossentropy: 0.6535 Epoch 14/20 25000/25000 - 3s - loss: 0.0082 - accuracy: 0.9997 - binary_crossentropy: 0.0082 - val_loss: 0.6824 - val_accuracy: 0.8559 - val_binary_crossentropy: 0.6824 Epoch 15/20 25000/25000 - 3s - loss: 0.0064 - accuracy: 0.9998 - binary_crossentropy: 0.0064 - val_loss: 0.7166 - val_accuracy: 0.8560 - val_binary_crossentropy: 0.7166 Epoch 16/20 25000/25000 - 3s - loss: 0.0049 - accuracy: 0.9999 - binary_crossentropy: 0.0049 - val_loss: 0.7451 - val_accuracy: 0.8553 - val_binary_crossentropy: 0.7451 Epoch 17/20 25000/25000 - 3s - loss: 0.0038 - accuracy: 1.0000 - binary_crossentropy: 0.0038 - val_loss: 0.7688 - val_accuracy: 0.8551 - val_binary_crossentropy: 0.7688 Epoch 18/20 25000/25000 - 3s - loss: 0.0031 - accuracy: 1.0000 - binary_crossentropy: 0.0031 - val_loss: 0.7921 - val_accuracy: 0.8550 - val_binary_crossentropy: 0.7921 Epoch 19/20 25000/25000 - 3s - loss: 0.0026 - accuracy: 1.0000 - binary_crossentropy: 0.0026 - val_loss: 0.8113 - val_accuracy: 0.8545 - val_binary_crossentropy: 0.8113 Epoch 20/20 25000/25000 - 3s - loss: 0.0022 - accuracy: 1.0000 - binary_crossentropy: 0.0022 - val_loss: 0.8320 - val_accuracy: 0.8546 - val_binary_crossentropy: 0.8320
より小さいモデルの構築
今作成したばかりの比較基準となるモデルに比べて隠れユニット数が少ないモデルを作りましょう。
smaller_model = keras.Sequential([ keras.layers.Dense(4, activation='relu', input_shape=(NUM_WORDS,)), keras.layers.Dense(4, activation='relu'), keras.layers.Dense(1, activation='sigmoid') ]) smaller_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy', 'binary_crossentropy']) smaller_model.summary()
出力
Model: "sequential_3" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_9 (Dense) (None, 4) 40004 _________________________________________________________________ dense_10 (Dense) (None, 4) 20 _________________________________________________________________ dense_11 (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 25000/25000 - 5s - loss: 0.6147 - accuracy: 0.7212 - binary_crossentropy: 0.6147 - val_loss: 0.5274 - val_accuracy: 0.8317 - val_binary_crossentropy: 0.5274 Epoch 2/20 25000/25000 - 3s - loss: 0.4284 - accuracy: 0.8729 - binary_crossentropy: 0.4284 - val_loss: 0.3866 - val_accuracy: 0.8688 - val_binary_crossentropy: 0.3866 Epoch 3/20 25000/25000 - 3s - loss: 0.3101 - accuracy: 0.8996 - binary_crossentropy: 0.3101 - val_loss: 0.3217 - val_accuracy: 0.8806 - val_binary_crossentropy: 0.3217 Epoch 4/20 25000/25000 - 3s - loss: 0.2485 - accuracy: 0.9165 - binary_crossentropy: 0.2485 - val_loss: 0.2967 - val_accuracy: 0.8848 - val_binary_crossentropy: 0.2967 Epoch 5/20 25000/25000 - 3s - loss: 0.2114 - accuracy: 0.9279 - binary_crossentropy: 0.2114 - val_loss: 0.2843 - val_accuracy: 0.8878 - val_binary_crossentropy: 0.2843 Epoch 6/20 25000/25000 - 3s - loss: 0.1851 - accuracy: 0.9366 - binary_crossentropy: 0.1851 - val_loss: 0.2826 - val_accuracy: 0.8868 - val_binary_crossentropy: 0.2826 Epoch 7/20 25000/25000 - 3s - loss: 0.1646 - accuracy: 0.9450 - binary_crossentropy: 0.1646 - val_loss: 0.2857 - val_accuracy: 0.8863 - val_binary_crossentropy: 0.2857 Epoch 8/20 25000/25000 - 3s - loss: 0.1483 - accuracy: 0.9512 - binary_crossentropy: 0.1483 - val_loss: 0.2956 - val_accuracy: 0.8827 - val_binary_crossentropy: 0.2956 Epoch 9/20 25000/25000 - 3s - loss: 0.1348 - accuracy: 0.9564 - binary_crossentropy: 0.1348 - val_loss: 0.3016 - val_accuracy: 0.8822 - val_binary_crossentropy: 0.3016 Epoch 10/20 25000/25000 - 3s - loss: 0.1227 - accuracy: 0.9608 - binary_crossentropy: 0.1227 - val_loss: 0.3140 - val_accuracy: 0.8802 - val_binary_crossentropy: 0.3140 Epoch 11/20 25000/25000 - 3s - loss: 0.1120 - accuracy: 0.9644 - binary_crossentropy: 0.1120 - val_loss: 0.3268 - val_accuracy: 0.8777 - val_binary_crossentropy: 0.3268 Epoch 12/20 25000/25000 - 3s - loss: 0.1024 - accuracy: 0.9689 - binary_crossentropy: 0.1024 - val_loss: 0.3407 - val_accuracy: 0.8760 - val_binary_crossentropy: 0.3407 Epoch 13/20 25000/25000 - 3s - loss: 0.0938 - accuracy: 0.9721 - binary_crossentropy: 0.0938 - val_loss: 0.3576 - val_accuracy: 0.8735 - val_binary_crossentropy: 0.3576 Epoch 14/20 25000/25000 - 3s - loss: 0.0858 - accuracy: 0.9750 - binary_crossentropy: 0.0858 - val_loss: 0.3744 - val_accuracy: 0.8709 - val_binary_crossentropy: 0.3744 Epoch 15/20 25000/25000 - 3s - loss: 0.0784 - accuracy: 0.9776 - binary_crossentropy: 0.0784 - val_loss: 0.3936 - val_accuracy: 0.8692 - val_binary_crossentropy: 0.3936 Epoch 16/20 25000/25000 - 3s - loss: 0.0719 - accuracy: 0.9810 - binary_crossentropy: 0.0719 - val_loss: 0.4107 - val_accuracy: 0.8679 - val_binary_crossentropy: 0.4107 Epoch 17/20 25000/25000 - 3s - loss: 0.0652 - accuracy: 0.9839 - binary_crossentropy: 0.0652 - val_loss: 0.4301 - val_accuracy: 0.8666 - val_binary_crossentropy: 0.4301 Epoch 18/20 25000/25000 - 3s - loss: 0.0595 - accuracy: 0.9857 - binary_crossentropy: 0.0595 - val_loss: 0.4518 - val_accuracy: 0.8650 - val_binary_crossentropy: 0.4518 Epoch 19/20 25000/25000 - 3s - loss: 0.0541 - accuracy: 0.9881 - binary_crossentropy: 0.0541 - val_loss: 0.4726 - val_accuracy: 0.8635 - val_binary_crossentropy: 0.4726 Epoch 20/20 25000/25000 - 3s - loss: 0.0498 - accuracy: 0.9897 - binary_crossentropy: 0.0498 - val_loss: 0.4932 - val_accuracy: 0.8623 - val_binary_crossentropy: 0.4932
より大きなモデルの構築
練習として、より大きなモデルを作成し、どれほど急速に過学習が起きるかを見ることもできます。
次はこのベンチマークに、この問題が必要とするよりはるかに容量の大きなネットワークを追加しましょう。
bigger_model = keras.models.Sequential([ keras.layers.Dense(512, activation='relu', input_shape=(NUM_WORDS,)), keras.layers.Dense(512, activation='relu'), keras.layers.Dense(1, activation='sigmoid') ]) bigger_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy','binary_crossentropy']) bigger_model.summary() bigger_history = bigger_model.fit(train_data, train_labels, epochs=20, batch_size=512, validation_data=(test_data, test_labels), verbose=2)
出力
Model: "sequential_10" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_30 (Dense) (None, 512) 5120512 _________________________________________________________________ dense_31 (Dense) (None, 512) 262656 _________________________________________________________________ dense_32 (Dense) (None, 1) 513 ================================================================= Total params: 5,383,681 Trainable params: 5,383,681 Non-trainable params: 0 _________________________________________________________________ Train on 25000 samples, validate on 25000 samples Epoch 1/20 25000/25000 - 23s - loss: 0.3399 - accuracy: 0.8558 - binary_crossentropy: 0.3399 - val_loss: 0.2920 - val_accuracy: 0.8816 - val_binary_crossentropy: 0.2920 Epoch 2/20 25000/25000 - 24s - loss: 0.1404 - accuracy: 0.9493 - binary_crossentropy: 0.1404 - val_loss: 0.3293 - val_accuracy: 0.8756 - val_binary_crossentropy: 0.3293 Epoch 3/20 25000/25000 - 24s - loss: 0.0463 - accuracy: 0.9864 - binary_crossentropy: 0.0463 - val_loss: 0.4360 - val_accuracy: 0.8695 - val_binary_crossentropy: 0.4360 Epoch 4/20 25000/25000 - 24s - loss: 0.0075 - accuracy: 0.9988 - binary_crossentropy: 0.0075 - val_loss: 0.6165 - val_accuracy: 0.8703 - val_binary_crossentropy: 0.6165 Epoch 5/20 25000/25000 - 25s - loss: 7.3355e-04 - accuracy: 1.0000 - binary_crossentropy: 7.3355e-04 - val_loss: 0.7182 - val_accuracy: 0.8700 - val_binary_crossentropy: 0.7182 Epoch 6/20 25000/25000 - 25s - loss: 2.1531e-04 - accuracy: 1.0000 - binary_crossentropy: 2.1531e-04 - val_loss: 0.7602 - val_accuracy: 0.8720 - val_binary_crossentropy: 0.7602 Epoch 7/20 25000/25000 - 25s - loss: 1.3107e-04 - accuracy: 1.0000 - binary_crossentropy: 1.3107e-04 - val_loss: 0.7923 - val_accuracy: 0.8717 - val_binary_crossentropy: 0.7923 Epoch 8/20 25000/25000 - 24s - loss: 9.3464e-05 - accuracy: 1.0000 - binary_crossentropy: 9.3464e-05 - val_loss: 0.8164 - val_accuracy: 0.8721 - val_binary_crossentropy: 0.8164 Epoch 9/20 25000/25000 - 24s - loss: 7.0720e-05 - accuracy: 1.0000 - binary_crossentropy: 7.0720e-05 - val_loss: 0.8392 - val_accuracy: 0.8719 - val_binary_crossentropy: 0.8392 Epoch 10/20 25000/25000 - 24s - loss: 5.5254e-05 - accuracy: 1.0000 - binary_crossentropy: 5.5254e-05 - val_loss: 0.8583 - val_accuracy: 0.8715 - val_binary_crossentropy: 0.8583 Epoch 11/20 25000/25000 - 24s - loss: 4.4350e-05 - accuracy: 1.0000 - binary_crossentropy: 4.4350e-05 - val_loss: 0.8763 - val_accuracy: 0.8715 - val_binary_crossentropy: 0.8763 Epoch 12/20 25000/25000 - 24s - loss: 3.6208e-05 - accuracy: 1.0000 - binary_crossentropy: 3.6208e-05 - val_loss: 0.8923 - val_accuracy: 0.8716 - val_binary_crossentropy: 0.8923 Epoch 13/20 25000/25000 - 24s - loss: 2.9936e-05 - accuracy: 1.0000 - binary_crossentropy: 2.9936e-05 - val_loss: 0.9085 - val_accuracy: 0.8713 - val_binary_crossentropy: 0.9085 Epoch 14/20 25000/25000 - 24s - loss: 2.5072e-05 - accuracy: 1.0000 - binary_crossentropy: 2.5072e-05 - val_loss: 0.9230 - val_accuracy: 0.8716 - val_binary_crossentropy: 0.9230 Epoch 15/20 25000/25000 - 24s - loss: 2.1205e-05 - accuracy: 1.0000 - binary_crossentropy: 2.1205e-05 - val_loss: 0.9372 - val_accuracy: 0.8717 - val_binary_crossentropy: 0.9372 Epoch 16/20 25000/25000 - 24s - loss: 1.8097e-05 - accuracy: 1.0000 - binary_crossentropy: 1.8097e-05 - val_loss: 0.9511 - val_accuracy: 0.8714 - val_binary_crossentropy: 0.9511 Epoch 17/20 25000/25000 - 24s - loss: 1.5544e-05 - accuracy: 1.0000 - binary_crossentropy: 1.5544e-05 - val_loss: 0.9639 - val_accuracy: 0.8716 - val_binary_crossentropy: 0.9639 Epoch 18/20 25000/25000 - 24s - loss: 1.3450e-05 - accuracy: 1.0000 - binary_crossentropy: 1.3450e-05 - val_loss: 0.9770 - val_accuracy: 0.8713 - val_binary_crossentropy: 0.9770 Epoch 19/20 25000/25000 - 24s - loss: 1.1714e-05 - accuracy: 1.0000 - binary_crossentropy: 1.1714e-05 - val_loss: 0.9885 - val_accuracy: 0.8715 - val_binary_crossentropy: 0.9885 Epoch 20/20 25000/25000 - 24s - loss: 1.0258e-05 - accuracy: 1.0000 - binary_crossentropy: 1.0258e-05 - val_loss: 1.0009 - val_accuracy: 0.8713 - val_binary_crossentropy: 1.0009
訓練時と検証時の損失をグラフにする
実線は訓練用データセットの損失、破線は検証用データセットでの損失です
(検証用データでの損失が小さい方が良いモデルです)。
これをみると、小さいネットワークのほうが比較基準のモデルよりも過学習が始まるのが遅いことがわかります
(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)])
出力
より大きなネットワークでは、すぐに、1エポックで過学習が始まり、その度合も強いことに注目してください。
ネットワークの容量が大きいほど訓練用データをモデル化するスピードが早くなり(結果として訓練時の損失値が小さくなり)ますが、より過学習しやすく(結果として訓練時の損失値と検証時の損失値が大きく乖離しやすく)なります。
過学習防止の戦略
重みの正規化を追加
オッカムの剃刀の原理をご存知かもしれません:
何かについて2つの説明が与えられた場合、最も正しい可能性が高い説明は「最も単純な」ものであり、仮定の量が最も少ないものです。
これはニューラルネットワークによって学習されるモデルにも当てはまります:
いくつかの学習データとネットワークアーキテクチャが与えられると、データを説明できる重み値の複数のセット(複数のモデル)が存在し、単純なモデルは複雑なモデルよりもオーバーフィットする可能性が低くなります。
この文脈での「単純なモデル」とは、パラメータ値の分布がエントロピーが少ないモデル(または上のセクションで見たように、完全に少ないパラメータを持つモデル)のことです。
したがって、オーバーフィッティングを緩和する一般的な方法は、ネットワークの複雑さに制約を加えることで、重みの値の分布をより「規則的」にすることです。
これは「重みの正則化」と呼ばれ、ネットワークの損失関数に大きな重みを持つことに関連したコストを加えることで行われます。このコストには2つの種類があります。
- L1正則化では、追加されるコストは重み係数の絶対値に比例します(すなわち、重みの「L1ノルム」と呼ばれるものに比例します)。
- L2 正則化,ここで追加されるコストは,重み係数の値の2乗に比例する(すなわち,重みの2乗 "L2ノルム "と呼ばれるものに比例する).L2正則化は、ニューラルネットワークの文脈では、重みの減衰とも呼ばれます。名前の違いで混乱しないようにしてください:重み減衰は数学的にはL2正則化と全く同じです。
L1正則化は、重みを正確にゼロに向かって押し上げ、疎なモデルを促進します。
L2正則化は、小さな重みではペナルティがゼロになるので、疎なモデルにすることなく重みパラメータにペナルティを与えます。
tf.kerasでは、キーワード引数として重み正則化インスタンスをレイヤーに渡すことで重み正則化を追加します。
ここではL2の重み正則化を追加してみましょう。
※www.DeepL.com/Translator(無料版)で翻訳しました。
l2_model = keras.models.Sequential([ keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001), activation='relu', input_shape=(NUM_WORDS,)), keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001), activation='relu'), keras.layers.Dense(1, activation='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) plot_history([('baseline', baseline_history), ('l2', l2_model_history)])
出力
Train on 25000 samples, validate on 25000 samples Epoch 1/20 25000/25000 - 5s - loss: 0.4987 - accuracy: 0.8208 - binary_crossentropy: 0.4577 - val_loss: 0.3676 - val_accuracy: 0.8802 - val_binary_crossentropy: 0.3244 Epoch 2/20 25000/25000 - 3s - loss: 0.2957 - accuracy: 0.9120 - binary_crossentropy: 0.2491 - val_loss: 0.3363 - val_accuracy: 0.8866 - val_binary_crossentropy: 0.2872 Epoch 3/20 25000/25000 - 3s - loss: 0.2487 - accuracy: 0.9317 - binary_crossentropy: 0.1975 - val_loss: 0.3375 - val_accuracy: 0.8860 - val_binary_crossentropy: 0.2849 Epoch 4/20 25000/25000 - 3s - loss: 0.2257 - accuracy: 0.9418 - binary_crossentropy: 0.1716 - val_loss: 0.3489 - val_accuracy: 0.8818 - val_binary_crossentropy: 0.2936 Epoch 5/20 25000/25000 - 4s - loss: 0.2114 - accuracy: 0.9488 - binary_crossentropy: 0.1548 - val_loss: 0.3639 - val_accuracy: 0.8782 - val_binary_crossentropy: 0.3065 Epoch 6/20 25000/25000 - 3s - loss: 0.2004 - accuracy: 0.9518 - binary_crossentropy: 0.1420 - val_loss: 0.3797 - val_accuracy: 0.8752 - val_binary_crossentropy: 0.3207 Epoch 7/20 25000/25000 - 3s - loss: 0.1931 - accuracy: 0.9549 - binary_crossentropy: 0.1332 - val_loss: 0.3975 - val_accuracy: 0.8722 - val_binary_crossentropy: 0.3369 Epoch 8/20 25000/25000 - 3s - loss: 0.1858 - accuracy: 0.9579 - binary_crossentropy: 0.1246 - val_loss: 0.4124 - val_accuracy: 0.8696 - val_binary_crossentropy: 0.3507 Epoch 9/20 25000/25000 - 3s - loss: 0.1811 - accuracy: 0.9596 - binary_crossentropy: 0.1188 - val_loss: 0.4319 - val_accuracy: 0.8652 - val_binary_crossentropy: 0.3690 Epoch 10/20 25000/25000 - 3s - loss: 0.1768 - accuracy: 0.9622 - binary_crossentropy: 0.1131 - val_loss: 0.4407 - val_accuracy: 0.8662 - val_binary_crossentropy: 0.3768 Epoch 11/20 25000/25000 - 3s - loss: 0.1727 - accuracy: 0.9632 - binary_crossentropy: 0.1085 - val_loss: 0.4680 - val_accuracy: 0.8612 - val_binary_crossentropy: 0.4031 Epoch 12/20 25000/25000 - 3s - loss: 0.1694 - accuracy: 0.9648 - binary_crossentropy: 0.1038 - val_loss: 0.4760 - val_accuracy: 0.8616 - val_binary_crossentropy: 0.4102 Epoch 13/20 25000/25000 - 3s - loss: 0.1636 - accuracy: 0.9669 - binary_crossentropy: 0.0973 - val_loss: 0.4963 - val_accuracy: 0.8585 - val_binary_crossentropy: 0.4297 Epoch 14/20 25000/25000 - 4s - loss: 0.1657 - accuracy: 0.9664 - binary_crossentropy: 0.0985 - val_loss: 0.5019 - val_accuracy: 0.8607 - val_binary_crossentropy: 0.4335 Epoch 15/20 25000/25000 - 3s - loss: 0.1609 - accuracy: 0.9681 - binary_crossentropy: 0.0916 - val_loss: 0.5214 - val_accuracy: 0.8571 - val_binary_crossentropy: 0.4518 Epoch 16/20 25000/25000 - 3s - loss: 0.1535 - accuracy: 0.9731 - binary_crossentropy: 0.0836 - val_loss: 0.5408 - val_accuracy: 0.8537 - val_binary_crossentropy: 0.4706 Epoch 17/20 25000/25000 - 3s - loss: 0.1464 - accuracy: 0.9759 - binary_crossentropy: 0.0760 - val_loss: 0.5308 - val_accuracy: 0.8563 - val_binary_crossentropy: 0.4604 Epoch 18/20 25000/25000 - 3s - loss: 0.1463 - accuracy: 0.9763 - binary_crossentropy: 0.0751 - val_loss: 0.5585 - val_accuracy: 0.8519 - val_binary_crossentropy: 0.4867 Epoch 19/20 25000/25000 - 4s - loss: 0.1384 - accuracy: 0.9804 - binary_crossentropy: 0.0663 - val_loss: 0.5737 - val_accuracy: 0.8513 - val_binary_crossentropy: 0.5015 Epoch 20/20 25000/25000 - 3s - loss: 0.1354 - accuracy: 0.9826 - binary_crossentropy: 0.0628 - val_loss: 0.5671 - val_accuracy: 0.8559 - val_binary_crossentropy: 0.4941
L2正則化ありのモデルは比較基準のモデルに比べて過学習しにくくなっています。
つまり、オレンジ破線の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='relu', input_shape=(NUM_WORDS,)), keras.layers.Dropout(0.5), keras.layers.Dense(16, activation='relu'), keras.layers.Dropout(0.5), keras.layers.Dense(1, activation='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) plot_history([('baseline', baseline_history), ('dropout', dpt_model_history)])
出力
Train on 25000 samples, validate on 25000 samples Epoch 1/20 25000/25000 - 6s - loss: 0.6167 - accuracy: 0.6617 - binary_crossentropy: 0.6167 - val_loss: 0.4929 - val_accuracy: 0.8520 - val_binary_crossentropy: 0.4929 Epoch 2/20 25000/25000 - 4s - loss: 0.4515 - accuracy: 0.8164 - binary_crossentropy: 0.4515 - val_loss: 0.3508 - val_accuracy: 0.8819 - val_binary_crossentropy: 0.3508 Epoch 3/20 25000/25000 - 4s - loss: 0.3519 - accuracy: 0.8689 - binary_crossentropy: 0.3519 - val_loss: 0.2900 - val_accuracy: 0.8899 - val_binary_crossentropy: 0.2900 Epoch 4/20 25000/25000 - 4s - loss: 0.2878 - accuracy: 0.9005 - binary_crossentropy: 0.2878 - val_loss: 0.2775 - val_accuracy: 0.8881 - val_binary_crossentropy: 0.2775 Epoch 5/20 25000/25000 - 4s - loss: 0.2470 - accuracy: 0.9138 - binary_crossentropy: 0.2470 - val_loss: 0.2814 - val_accuracy: 0.8863 - val_binary_crossentropy: 0.2814 Epoch 6/20 25000/25000 - 4s - loss: 0.2147 - accuracy: 0.9257 - binary_crossentropy: 0.2147 - val_loss: 0.2904 - val_accuracy: 0.8859 - val_binary_crossentropy: 0.2904 Epoch 7/20 25000/25000 - 4s - loss: 0.1864 - accuracy: 0.9372 - binary_crossentropy: 0.1864 - val_loss: 0.3154 - val_accuracy: 0.8831 - val_binary_crossentropy: 0.3154 Epoch 8/20 25000/25000 - 4s - loss: 0.1644 - accuracy: 0.9426 - binary_crossentropy: 0.1644 - val_loss: 0.3114 - val_accuracy: 0.8826 - val_binary_crossentropy: 0.3114 Epoch 9/20 25000/25000 - 3s - loss: 0.1521 - accuracy: 0.9464 - binary_crossentropy: 0.1521 - val_loss: 0.3334 - val_accuracy: 0.8824 - val_binary_crossentropy: 0.3334 Epoch 10/20 25000/25000 - 4s - loss: 0.1365 - accuracy: 0.9523 - binary_crossentropy: 0.1365 - val_loss: 0.3649 - val_accuracy: 0.8807 - val_binary_crossentropy: 0.3649 Epoch 11/20 25000/25000 - 4s - loss: 0.1240 - accuracy: 0.9562 - binary_crossentropy: 0.1240 - val_loss: 0.3704 - val_accuracy: 0.8789 - val_binary_crossentropy: 0.3704 Epoch 12/20 25000/25000 - 5s - loss: 0.1132 - accuracy: 0.9598 - binary_crossentropy: 0.1132 - val_loss: 0.4014 - val_accuracy: 0.8794 - val_binary_crossentropy: 0.4014 Epoch 13/20 25000/25000 - 4s - loss: 0.1057 - accuracy: 0.9599 - binary_crossentropy: 0.1057 - val_loss: 0.4179 - val_accuracy: 0.8791 - val_binary_crossentropy: 0.4179 Epoch 14/20 25000/25000 - 4s - loss: 0.0983 - accuracy: 0.9621 - binary_crossentropy: 0.0983 - val_loss: 0.4239 - val_accuracy: 0.8774 - val_binary_crossentropy: 0.4239 Epoch 15/20 25000/25000 - 3s - loss: 0.0908 - accuracy: 0.9646 - binary_crossentropy: 0.0908 - val_loss: 0.4614 - val_accuracy: 0.8782 - val_binary_crossentropy: 0.4614 Epoch 16/20 25000/25000 - 3s - loss: 0.0847 - accuracy: 0.9662 - binary_crossentropy: 0.0847 - val_loss: 0.4900 - val_accuracy: 0.8770 - val_binary_crossentropy: 0.4900 Epoch 17/20 25000/25000 - 3s - loss: 0.0813 - accuracy: 0.9670 - binary_crossentropy: 0.0813 - val_loss: 0.4878 - val_accuracy: 0.8768 - val_binary_crossentropy: 0.4878 Epoch 18/20 25000/25000 - 4s - loss: 0.0789 - accuracy: 0.9684 - binary_crossentropy: 0.0789 - val_loss: 0.5279 - val_accuracy: 0.8758 - val_binary_crossentropy: 0.5279 Epoch 19/20 25000/25000 - 4s - loss: 0.0737 - accuracy: 0.9702 - binary_crossentropy: 0.0737 - val_loss: 0.5535 - val_accuracy: 0.8756 - val_binary_crossentropy: 0.5535 Epoch 20/20 25000/25000 - 3s - loss: 0.0754 - accuracy: 0.9675 - binary_crossentropy: 0.0754 - val_loss: 0.5403 - val_accuracy: 0.8754 - val_binary_crossentropy: 0.5403
ドロップアウトを追加することで、比較対象モデルより明らかに改善が見られます。
組み合わせてみよう
# 組み合わせ hybrid_model = keras.models.Sequential([ keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001), activation='relu', input_shape=(NUM_WORDS,)), keras.layers.Dropout(0.5), keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001), activation='relu'), keras.layers.Dropout(0.5), keras.layers.Dense(1, activation='sigmoid') ]) hybrid_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy', 'binary_crossentropy']) hybrid_model_history = hybrid_model.fit(train_data, train_labels, epochs=20, batch_size=512, validation_data=(test_data, test_labels), verbose=2) # 基準モデルと組み合わせモデルを比較 plot_history([('baseline', baseline_history), ('hybrid', hybrid_model_history)])
出力
学習結果は省略・・・
かなり過学習を抑えられています。
まとめ
要約すると、ニューラルネットワークのオーバーフィットを防ぐ最も一般的な方法は以下の通りです。
このガイドでは取り上げていない2つの重要なアプローチは以下の通りです。
- データの補強
- 一括正規化
それぞれの方法はそれだけでも役立ちますが、組み合わせることでさらに効果的になることが多いことを覚えておいてください。
最後に今回のチュートリアル実装をまとめて記載します。
一部コメントアウトしています。
全部をまともに実行すると結構時間かかります。
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__) # IMDBデータセットのダウンロード 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) # 比較基準を作る baseline_model = keras.Sequential([ # `.summary` を見るために`input_shape`が必要 keras.layers.Dense(16, activation='relu', input_shape=(NUM_WORDS,)), keras.layers.Dense(16, activation='relu'), keras.layers.Dense(1, activation='sigmoid') ]) baseline_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy', 'binary_crossentropy']) baseline_model.summary() # モデルにフィットさせる baseline_history = baseline_model.fit(train_data, train_labels, epochs=20, batch_size=512, validation_data=(test_data, test_labels), verbose=2) # より小さいモデルの構築 # smaller_model = keras.Sequential([ # keras.layers.Dense(4, activation='relu', input_shape=(NUM_WORDS,)), # keras.layers.Dense(4, activation='relu'), # keras.layers.Dense(1, activation='sigmoid') # ]) # smaller_model.compile(optimizer='adam', # loss='binary_crossentropy', # metrics=['accuracy', 'binary_crossentropy']) # smaller_model.summary() # smaller_history = smaller_model.fit(train_data, # train_labels, # epochs=20, # batch_size=512, # validation_data=(test_data, test_labels), # verbose=2) # より大きいモデルの構築 # bigger_model = keras.models.Sequential([ # keras.layers.Dense(512, activation='relu', input_shape=(NUM_WORDS,)), # keras.layers.Dense(512, activation='relu'), # keras.layers.Dense(1, activation='sigmoid') # ]) # bigger_model.compile(optimizer='adam', # loss='binary_crossentropy', # metrics=['accuracy','binary_crossentropy']) # bigger_model.summary() # bigger_history = bigger_model.fit(train_data, train_labels, # epochs=20, # batch_size=512, # validation_data=(test_data, test_labels), # verbose=2) 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)]) # L2正則化モデルを追加 # l2_model = keras.models.Sequential([ # keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001), # activation='relu', input_shape=(NUM_WORDS,)), # keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001), # activation='relu'), # keras.layers.Dense(1, activation='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) # 基準モデルとL2正則化追加モデルを比較 # plot_history([('baseline', baseline_history), # ('l2', l2_model_history)]) # ドロップアウトモデルを追加 # dpt_model = keras.models.Sequential([ # keras.layers.Dense(16, activation='relu', input_shape=(NUM_WORDS,)), # keras.layers.Dropout(0.5), # keras.layers.Dense(16, activation='relu'), # keras.layers.Dropout(0.5), # keras.layers.Dense(1, activation='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) # 基準モデルとドロップアウトモデルを比較 # plot_history([('baseline', baseline_history), # ('dropout', dpt_model_history)]) # 組み合わせ hybrid_model = keras.models.Sequential([ keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001), activation='relu', input_shape=(NUM_WORDS,)), keras.layers.Dropout(0.5), keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001), activation='relu'), keras.layers.Dropout(0.5), keras.layers.Dense(1, activation='sigmoid') ]) hybrid_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy', 'binary_crossentropy']) hybrid_model_history = hybrid_model.fit(train_data, train_labels, epochs=20, batch_size=512, validation_data=(test_data, test_labels), verbose=2) # 基準モデルと組み合わせモデルを比較 plot_history([('baseline', baseline_history), ('hybrid', hybrid_model_history)])