機械学習モデルの汎化性能を検証する方法の基本まとめ【Pythonサンプルコードあり/交差検証法/ホールドアウト法】
目次
こんにちは。
今回は機械学習で作ったモデルの汎化性能を検証する方法をまとめます。
最近このブログではペンギンデータセットを使った機械学習入門というシリーズで記事を書いているのですが、その続編です。
主に教師あり学習の分類モデルに対して「ホールドアウト法」「K分割交差検証法」という2つの手法についてご紹介します。
機械学習モデルの 「汎化性能」 とは
機械学習で作成されたモデルは使用する教師データ(訓練データ)に依存するため、その用意されたデータに過剰に適合している場合、他の未知のデータに対して正しく予測・分類する性能が落ちてしまいます。
機械学習のモデルの目的はできるだけ未知のデータに対しても精度良く予測することなので、どのようなデータが来てもそれなりに正しく予測できる性能が求められます。これが「汎化性能」(generalization performance)と呼ばれている性能です。
モデルを作成する際には、「手元にあるデータを使って、いかに汎化性能を高くするか?」ということを考える必要があります。
ホールドアウト法とは
汎化性能を検証するために最もシンプルで簡単な方法が「ホールドアウト法」と呼ばれています。
限られたデータを一度にすべて訓練に使用するのではなく、訓練用のデータセットと検証用のデータセットを何回かに分割する手法のことです。訓練用データに過剰適合していると、検証用のデータに対して正しい予測結果が得られず、逆に汎化性能があるモデルが作成できていれば、検証用のデータセットに対しても高い精度で予測結果が得られるはず、という仮設に基づいて利用します。
実は過去の記事でも少し使っていました。
ペンギンデータセットでデータサイエンス入門 〜 教師あり学習・分類編【Python/scikit-learn/機械学習/ガウシアンナイーブベイズ】
手持ちのデータを訓練用データ75%、テスト用データ25%に分割しています。
サンプルコード用のデータの前準備
今回は決定木分析のモデルに対して各検証法を使ってみます。例によってペンギンデータセットをロードして必要な列を抽出しておきます。
関連記事 : ペンギンデータセットで機械学習/データサイエンスをはじめよう〜ダウンロード編
※ちなみに、X
とy
が用意できればペンギンデータセットでなくても何でもいいです。
import pandas as pd
df = pd.read_csv('penguins.csv')
df = df.drop(columns=['island','sex','year'])
df = df.rename(columns={
'bill_length_mm' : 'bill_length',
'flipper_length_mm' : 'flipper_length',
})
df = df[['species','flipper_length','bill_length']]
df = df[~df.isnull().any(axis=1)]
X = df.drop(columns='species').values
species = df.species.unique()
species_number_map = { species[i] : i for i in range(len(species)) }
species_number_map # {'Adelie': 0, 'Gentoo': 1, 'Chinstrap': 2}
y = df.species.map(species_number_map)

説明変数X
は羽の長さとくちばしの長さ、目的変数y
はこのspeciesを数値変換したものです。
ホールドアウト法をPython scikit-learnで実現する方法
それでは上記のデータを使ってホールドアウト法を実践してみます。
scikit-learnのmodel_selection
モジュールのtrain-test_split
関数を使います。
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,y,random_state=42)
X_train.shape # (256,2)
X_test.shape # (86,2)
y_train.shape # (256,)
y_test.shape # (86,)
参考 : scikit learn | sklearn.model_selection.train_test_split
デフォルトだと、75%と25%でデータをランダムに分割してくれます。y_train
、y_test
の中身を見ると、元々のy
がラベル順に並んでいたのに対して、ラベルがランダムに並んでいるので、シャッフルされていることがわかります。
random_state
は乱数生成の種(ランダムシード)ですが、0か42を使うのが一般的なようです。同じ値を使うと同じ生成結果となります。
分割する比を変更したい場合は、引数にtrain_size
もしくはtest_size
を指定して訓練データもしくはテストデータの割合を指定できます。
X_train, X_test, y_train, y_test = train_test_split(X,y,train_size=0.8, random_state=42)
X_train.shape # (273,2)
X_test.shape # (69,2)
y_train.shape # (273,)
y_test.shape # (69,)
上記の例だと、train_size=0.8
にしたので、訓練データとテストデータの比を8:2にできました。
ちなみに、shuffle=Flase
を指定することで、シャッフルではなくデータの先頭から分割することもできます。(何も指定しないとTrue
になっています。)
それでは、上記の分割した訓練データに対してモデルをフィッティングし、テストデータで予測結果を見てみます。
# 訓練
from sklearn.tree import DecisionTreeClassifier
tree = DecisionTreeClassifier(max_depth=3) # インスタンスの作成
tree.fit(X_train,y_train) # 訓練データでフィッティング
tree.score(X_train,y_train) # 訓練データのスコア : 0.9560439560439561
# テスト
y_pred = tree.predict(X_test) # 訓練したモデルでテストデータのラベルを予測
from sklearn.metrics import accuracy_score
accuracy_score(y_test,y_pred) # テストデータのスコア : 0.9420289855072463
スコアは正解率で、真のデータラベルと予測したデータラベルが一致している割合です。訓練データのスコアはtree.score(X_train,y_train)
で確認でき、テストデータの正解率はmetrics
モジュールのaccuracy_score
関数で求めるのが簡単です。
このようにホールドアウト法を使うと訓練データ以外でモデルのテストができるため、汎化性能が確認できます。訓練データは95%、テストデータは94%の正解率でモデルを分類できています。1ポイントの差があるものの、訓練データに過剰に適合しているということはなさそうですね。
とはいえ、ランダムに一度分割しているだけなので、「この分割でたまたまうまくいっただけかもなあ...」という不安が残ります。この不安を拭う徹底した分割方法があります。それが次にご紹介する交差検証法です。
k分割交差検証法 k-Fold Cross Validation
ホールドアウト法に対して、「一度ではなく何度もデータを分割して複数のモデルを訓練する方法」が交差検証法です。
k分割とは、任意の回数(k回)データを分割することを意味します。計算時間がk倍になるというデメリットはあるものの、徹底した検証により安心感が得られるというメリットがあります。
k分割交差検証法をPython scikit learnで行う方法
scikit-learnではmodel_selection
モジュールのcross_val_score
関数を使います。
from sklearn.model_selection import cross_val_score
model = DecisionTreeClassifier(max_depth=3) # 決定木モデルインスタンスの作成
scores = cross_val_score(model,X,y,cv=3) # 3分割交差検証のスコア
# array([0.95614035, 0.93859649, 0.92982456])
参考 : scikit learn | sklearn.model_selection.cross_val_score
このように、モデルに対して一度に交差検証を行うことができます。cv
パラメータに任意の数を指定することで、その数だけ分割を繰り返します。k分割で言うところのkですね。
cvパラメータには交差検証分割器(splitter)を与えることで単純な数による分割だけではなく詳細な制御をすることも可能になります。たとえば、データシャッフルの有無やランダムシードを指定するにはKFold
を使います。
from sklearn.model_selection import KFold
kfold = KFold(n_splits=3, shuffle=True, random_state=0) # 3分割、シャッフルあり、ランダムシード0
model = DecisionTreeClassifier(max_depth=3)
scores = cross_val_score(model, X, y, cv=kfold)
splitterには他にも様々なバリエーションが用意されています。
参考 : scikit learn | Model Selection - Splitter Classes
交差検証スコアの利用方法
交差検証のスコアは分割した回数分、つまりk回分計算されるので、長さkの配列となって取得できます。
結果をまとめるには一般的にはこのスコアの「平均値」を交差検証のスコアとするようです。
scores.mean() # スコアの平均値
# 0.9415204678362574
なので、この場合おおよそ「このモデルは94%の正解率」ということになります。
また、平均せずともすべてのスコア(先程の3分割の例だと、3つのスコア)を見ることで、最悪と最良のスコアを比較することができるのも交差検証のメリットです。「悪くても92%、良くて95%」といった言い方ができます。
試しにcvパラメータを10にして、10分割してみた結果です。
array([0.97142857, 0.97142857, 0.94117647, 0.94117647, 0.91176471,
0.97058824, 0.91176471, 0.88235294, 0.94117647, 0.91176471])
なんと、最悪88%から97%まで大きく触れました。そう考えると、このアルゴリズムとハイパーパラメータでモデルを作成するのは、少し危険な気もしてきましたね。交差検証はあくまで「汎化性能の検証方法」であり、「汎化性能の高いモデルを作る方法」ではありません。「今回の分類をこのデータセットで学習させるのに、このアルゴリズムとパラメータで良さげか?」といった検証を手早く行うのに利用するのが良いかと思います。
まとめ
というわけで今回は機械学習の分類モデルに対して汎化性能を確認するために「ホールドアウト法」「k分割交差検証法」という2つの検証方法をご紹介しました。
Pythonのscikit-learnを使えば簡単に実装できますね。
ご参考になれば幸いです。
それでは〜
関連記事
機械学習の分類モデルの評価指標・混同行列についての基本まとめ【Pythonサンプルコードあり/3分類以上でも簡単】
【データサイエンス入門】決定木分析をPythonで簡単に試す & 分類と条件分岐を可視化する方法まとめ【サンプルコードあり】
ペンギンデータセットでデータサイエンス入門 〜 教師あり学習・分類編【Python/scikit-learn/機械学習/ガウシアンナイーブベイズ】
ペンギンデータセットでデータサイエンス入門 〜 機械学習の基本・単回帰編【Python/scikit-learn/教師あり学習】