<目次>
■オーバーフィッティングの対策(ドロップアウト)をご紹介(Kerasプログラムあり)
■オーバーフィッティング問題とは?
■対策の概要
■(1-1) ドロップアウトの数式
■(1-2) ドロップアウトの構文
■(1-3) Kerasサンプルプログラム
■オーバーフィッティングの対策(ドロップアウト)をご紹介(Kerasプログラムあり)
■オーバーフィッティング問題とは?
オーバーフィッティングとは、学習データに対して過剰に適合してしまう問題です。これはモデルが学習データに含まれるノイズまでも覚えてしまい、汎化性能(未知データへの対応力)が低下する現象です。
→(参考)オーバーフィッティング問題とは?
■対策の概要
- オーバーフィッティングへの代表的な対策として「ドロップアウト」があります。
- これは、学習中にランダムに一定割合のニューロンを無効化(ドロップ)し、複数のネットワークを擬似的に構築するような手法です。
- 通常は確率P=0.5などがよく用いられます。
- 予測・評価時にはドロップアウトを行わず、全体のネットワークで推論します。
(図121)
■(1-1) ドロップアウトの数式
多層パーセプトロンに対するドロップアウトの数式は以下の通りです。
ベクトル \( \boldsymbol{m} \) をドロップアウト用マスクとし、多層パーセプトロンの基本アルゴリズムと比較します。
●正伝播(隠れ層→出力層の式)
●逆伝播(多層パーセプトロン)
\( \boldsymbol{W}^{(i+1)} \) \( \quad=\boldsymbol{W}^{(i)}+\eta f(\boldsymbol{p})(\boldsymbol{I}-f(\boldsymbol{p})) \odot \boldsymbol{V}^\mathsf{T} \begin{pmatrix} \frac{\partial E}{\partial q_{1}} \\ \vdots \\ \frac{\partial E}{\partial q_{k}} \\ \vdots \\ \frac{\partial E}{\partial q_{K}} \end{pmatrix} \boldsymbol{x}^\mathsf{T} \)
↓
\( \boldsymbol{W}^{(i+1)} \) \( \quad=\boldsymbol{W}^{(i)}+\eta f(\boldsymbol{p})(\boldsymbol{I}-f(\boldsymbol{p})) \odot \boldsymbol{m} \odot \boldsymbol{V}^\mathsf{T} \begin{pmatrix} \frac{\partial E}{\partial q_{1}} \\ \vdots \\ \frac{\partial E}{\partial q_{k}} \\ \vdots \\ \frac{\partial E}{\partial q_{K}} \end{pmatrix} \boldsymbol{x}^\mathsf{T} \)
■(1-2) ドロップアウトの構文
以下にオーバーフィッティング問題が起きているKerasプログラムを掲載します。
以下にKerasでドロップアウトを適用する構文を示します。
from keras.layers.core import Dropout # モデルの生成 init_model = Sequential() init_model.add(Dense(units=Ja, activation='relu', input_shape=(words_num,))) init_model.add(Dropout(0.5)) # Dropout追加 init_model.add(Dense(units=Jb, activation='relu')) init_model.add(Dropout(0.5)) # Dropout追加 init_model.add(Dense(units=K, activation='softmax'))
■(1-3) Kerasサンプルプログラム
以下にオーバーフィッティングが発生していたKerasプログラムに、ドロップアウト対応を施したコードを掲載します。
→(参考)勾配消失問題とは?
# 基本的なパッケージ from mimetypes import init import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' import pandas as pd import numpy as np import re import matplotlib.pyplot as plt from pathlib import Path # データ準備のためのパッケージ from sklearn.model_selection import train_test_split import nltk as nltk nltk.download("stopwords") from nltk.corpus import stopwords from keras.preprocessing.text import Tokenizer from keras.utils.np_utils import to_categorical from sklearn.preprocessing import LabelEncoder # モデルを作るためのパッケージ from keras.models import Sequential from keras.layers import Dense, Activation # ★追記 # ドロップアウト対応用 from keras.layers.core import Dropout # 入力xの次元 # 突合用のdictionaryに保持する単語の数を指定 words_num = 10000 # 隠れ層haの次元(クラス数) Ja = 64 # 隠れ層hbの次元(クラス数) Jb = 64 # 出力yの次元(クラス数) K = 3 # 学習のepoc数 epoch_num = 20 # ミニバッチ勾配降下法で利用するバッチサイズ mini_batch_size = 512 # 入力データファイルのパス input_path = Path('C:\\dev\\Python\\00_LocalPythonApp\\Deeplearning\\input\\') # ■ 目的・用途 # 入力したPandas Seriesに対して、英語のstopwordsを除外する # ■ 入力パラメータ # 綺麗にしたいテキスト # ■ 出力 # stopwordsが除去されたPandas Series def remove_stopwords(input_text): stopwords_list = stopwords.words('english') # 何かの感情を意味する可能性があるものは、whitelistに登録して削除の対象外にする whitelist = ["n't", "not", "no"] words = input_text.split() clean_words = [word for word in words if (word not in stopwords_list or word in whitelist) and len(word) > 1] return " ".join(clean_words) # ■ 目的・用途 # 入力したPandas Seriesに対して、メンション(@XXXX)を削除する # ■ 入力パラメータ # 綺麗にしたいテキスト # ■ 出力 # メンションが除去されたPandas Series def remove_mentions(input_text): return re.sub(r'@\w+', '', input_text) # ■ 目的・用途 # 多クラスモデルの学習。epocやbatch_sizeはグローバル変数で指定。 # ■ 入力パラメータ # X_train : 学習の入力値 # y_train : 学習の正解値 # X_valid : 評価の入力値 # Y_valid : 評価の正解値 # ■ 出力 # モデルの学習履歴 def create_deeplearning_model(model, X_train, y_train, X_valid, y_valid): model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy']) history = model.fit(X_train, y_train, epochs=epoch_num, batch_size=mini_batch_size, validation_data=(X_valid, y_valid)) return history def display_training_result(model,X_train,T_train,model_hist): # 分類が正しい結果になっているか?の確認 # model.predict(X_train,batch_size=mini_batch_size) # ⇒各「入力」に対する「出力」の確率(ソフトマックス)の確認 # np.argmax(●)で[X,X,X]の最大のインデックスを抽出 # (例) [[0,1,0],[0,0,1],[1,0,0]]⇒[1,2,0] # axis=1の場合は横軸単位で比較する。 # axis=0の場合は縦軸同士で比較する。 classes = np.argmax(model.predict(X_train,batch_size=mini_batch_size),axis=1) # 正解のインデックス情報 # t_でデータ数N回ループし、正解のindexを抽出してリスト化け # (例)[[0,1,0],[0,0,1],[1,0,0]]⇒[1,2,0] t_index = np.argmax(T_train,axis=1) # 別の方法で、forループを使ったワンライナーでも記述可能。 #t_index = [index for i in range(len(T_train)) for index in range(K) if T_train[i][index] == 1] # ネットワークの出力「y」の計算結果を取得 prob = model.predict(X_train,batch_size=mini_batch_size) # 評価誤差が最小となるepoc番号 min_epoch = np.argmin(model_hist.history['val_loss']) + 1 print("*******************************") print("minimul loss epoch: {}".format(min_epoch)) # 分類出来たか?はyの最大index(classes)と、tの最大index(T_trainindex)を比較 print("classified: ",t_index==classes) print("probability: ",prob) print("*******************************") # ■ 目的・用途 # 学習したモデルを、指定した指標(metric)で評価する。 # 学習と評価の内容はepoc単位でグラフに描画する。 # ■ 入力パラメータ # history:モデルの学習history # metric_name:loss / accuracy # ■ 出力 # x軸:epoc、y軸:指標の値 def eval_metric(model, history, metric_name): metric = history.history[metric_name] val_metric = history.history['val_' + metric_name] e = range(1, epoch_num + 1) plt.plot(e, metric, 'bo', label='Training: ' + metric_name) plt.plot(e, val_metric, 'b', label='Evaluation: ' + metric_name) plt.xlabel('Epoch数',fontname="Meiryo") plt.ylabel(metric_name) plt.title(model.name + ' - ' + '「学習」と「評価」における「'+ metric_name +'」の比較',fontname="Meiryo") plt.legend() plt.show() def main(): df = pd.read_csv(input_path / 'Tweets.csv') df = df.reindex(np.random.permutation(df.index)) df = df[['text', 'airline_sentiment']] # 分析に不要な単語は除外する # (例)Twitterのメンション # (例)it's, you've, I, didn't, aなどのStemワード df.text = df.text.apply(remove_stopwords).apply(remove_mentions) # データを「学習用」と「テスト用」に分ける # X:Twitterの投稿文章 # y:感情分析の正解(Positive/Negative/Neautral) X_train, X_test, T_train, T_test = train_test_split(df.text, df.airline_sentiment, test_size=0.1, random_state=37) # Xの方は文の状態なので、 # Tokenizerで単語を分解してdictionaryに格納する # indexが小さい程、高頻度で登場する単語になる。 tk = Tokenizer(num_words=words_num, filters='!"#$%&()*+,-./:;<=>?@[\\]^_`{"}~\t\n', lower=True, char_level=False, split=' ') tk.fit_on_texts(X_train) # 生成したdictionaryのindexと、各文章を突合したマトリクスを作成 # (例)文章にindex=2の単語「am」がある ⇒ 2要素目に「1」を立てる # ⇒[0. 1. 1. ... 0. 0. 0.] X_train_binary = tk.texts_to_matrix(X_train, mode='binary') X_test_binary = tk.texts_to_matrix(X_test, mode='binary') # y要素もLabelEncoderで値をラベル変換 # 0:negative, 1:neutral, 2:positive # (例)[1 0 2 2 0 1 1 2] le = LabelEncoder() T_train_le = le.fit_transform(T_train) T_test_le = le.transform(T_test) # 0,1の2値に変換 # (例) # [1 0 2 2] # ↓ # [[0.1.0.][1.0.0.][0.0.1.][0.0.1.]] T_train_binary = to_categorical(T_train_le) T_test_binary = to_categorical(T_test_le) # トレーニング用と評価用(モデルパラメータをチューニング時に使用)に分解 X_train_rest, X_valid, T_train_rest, T_valid = train_test_split(X_train_binary, T_train_binary, test_size=0.1, random_state=37) # モデルの生成 init_model = Sequential() init_model.add(Dense(units=Ja, activation='relu', input_shape=(words_num,))) # ★追記 init_model.add(Dropout(0.5)) init_model.add(Dense(units=Jb, activation='relu')) # ★追記 init_model.add(Dropout(0.5)) init_model.add(Dense(units=K, activation='softmax')) init_model._name = 'Initial_Model' # モデルの学習 init_history = create_deeplearning_model(init_model, X_train_rest, T_train_rest, X_valid, T_valid) # モデルの学習結果を表示 display_training_result(init_model,X_train_rest,T_train_rest,init_history) # モデルの評価 eval_metric(init_model, init_history, 'loss') if __name__ == "__main__": main()
(図131)Before
Epoch 19/20 24/24 [==============================] - 1s 47ms/step - loss: 0.0327 - accuracy: 0.9893 - val_loss: 1.4530 - val_accuracy: 0.7595 Epoch 20/20 24/24 [==============================] - 1s 45ms/step - loss: 0.0299 - accuracy: 0.9906 - val_loss: 1.5189 - val_accuracy: 0.7602
(図132)After
Epoch 19/20 24/24 [==============================] - 1s 61ms/step - loss: 0.1425 - accuracy: 0.9529 - val_loss: 0.8554 - val_accuracy: 0.7838 Epoch 20/20 24/24 [==============================] - 1s 56ms/step - loss: 0.1317 - accuracy: 0.9555 - val_loss: 0.8911 - val_accuracy: 0.7830
このように、ドロップアウトを導入することで、学習精度を保ちつつ評価精度が改善される傾向が見られます。