機械学習 – ホールドアウト、クロスバリデーションについて

目次

概要

機械学習におけるモデルの評価方法であるホールドアウト、交差検証 (クロスバリデーション) について解説します。

学習とテストで別のデータセットを使用する理由

学習に使用したデータと同じデータでテストを行ってモデルの精度を評価した場合、そのモデルが学習データを認識することに特化したものとなる過剰適合が発生していることを見抜けず、モデルの汎化性能を正しく評価できません。そのため、通常はデータセットはモデルのパラメータ調整に使用する学習用と学習には一切使用しないテスト用に分割することが一般的です。

ホールドアウトと交差検証 (クロスバリデーション)

モデルに予め設定しておくパラメータ (ハイパーパラメータ) がある場合、学習を試行錯誤し、ハイパーパラメータを調整する必要があります。その評価にテストデータを使用してしまうと、ハイパーパラメータがテストデータに特化した過学習状態となる可能性を見抜けなくなります。そのため、データセットを学習用、テスト用の他にハイパーパラメータを調整する検証用の3つに分割します。しかし、この方法でも検証用のデータに特化したハイパーパラメータになってしまう可能性や3つに分割すると学習に使用するデータが減ってしまう欠点があるため、次に紹介する交差検証 (クロスバリデーション) により、ハイパーパラメータを決める方法もあります。

  • ハイパーパラメータの調整あり
    • ホールドアウト: データセットを学習用、検証用、テスト用の3つに分割し、検証用を使用してハイパーパラメータを調整します。テスト用は最終的なモデルの評価に使用します。
    • クロスバリデーション: データセットを学習及び検証用、テスト用の2つに分割し、クロスバリデーションにより、ハイパーパラメータを調整します。テスト用は最終的なモデルの評価に使用します。
  • ハイパーパラメータの調整なし
    • データセットを学習用、テスト用の2つに分割します。テスト用は最終的なモデルの評価に使用します。

データの分割

ホールドアウト、クロスバリデーションどちらを行うにしても、最低でもデータセットを学習用とテスト用に分割することは必須です。

ホールドアウト

sklearn.model_selection.train_test_split

scikit-learn の train_test_split() を使用すると、データセットを学習用とテスト用に分割できます。

sklearn.model_selection.train_test_split(
    *arrays,
    test_size=None,
    train_size=None,
    random_state=None,
    shuffle=True,
    stratify=None
)
  • arrays: 分割対象の配列 (複数個指定可)。
    • 配列の型は リスト、numpy 配列、scipy sparse 行列、pandas dataframes に対応
  • test_size: テストに利用する割合。float, int, None の指定が可能。
    • デフォルト: train_size を指定した場合は None、そうでない場合は 0.25。
    • float: 分割の割合を [0, 1] の範囲で指定する。
    • int: テストに使用するデータ数を指定する。
    • None: train_size の指定に従い、決める。
  • train_size: 学習に利用する割合。float, int, None の指定が可能。
    • デフォルト: test_size を指定した場合は None。
    • float: 分割の割合を [0, 1] の範囲で指定する。
    • int: テストに使用するデータ数を指定する。
    • None: test_size の指定に従い、決める。
  • random_state: 分割に使用する乱数に関する設定。
    • デフォルト: None
    • int: 乱数のシードに利用
    • None: np.random.RandomState オブジェクトを利用する。
  • shuffle: 分割前にシャッフルするかどうかを指定する。
    • デフォルト: True
  • stratify: 各クラスごとに同じ割合で分割したい場合は、クラス一覧を指定する。
    • デフォルト: None

複数の配列を一度に分割できます。データとラベルがある場合は両方指定します。

In [1]:
import numpy as np
from sklearn.model_selection import train_test_split

# 分割対象の配列を作成する。
data = np.arange(20)
labels = np.arange(20)

# array1, array2 をそれぞれ学習データ75%、テストデータ25%の割合で分割する。
data_train, data_test, labels_train, labels_test = train_test_split(
    data, labels, train_size=0.75, random_state=0
)

print("data train", data_train)
print("data test", data_test)
print("labels train", labels_train)
print("labels test", labels_test)
data train [17  6 13  4  2  5 14  9  7 16 11  3  0 15 12]
data test [18  1 19  8 10]
labels train [17  6 13  4  2  5 14  9  7 16 11  3  0 15 12]
labels test [18  1 19  8 10]

実際のデータセットを分割した例です。

In [2]:
from sklearn import datasets, metrics, svm
from sklearn.model_selection import train_test_split

X, y = datasets.load_iris(return_X_y=True)
print(X.shape, y.shape)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.15, random_state=0
)

print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)
(150, 4) (150,)
(127, 4) (127,)
(23, 4) (23,)

クロスバリデーション

クロスバリデーション (cross validation) は、まず学習用データセットをいくつかの Fold に分割します。

Fold の分割

次に、1つを除いた残りの Fold で学習を行い、残り1つの Fold で評価するというのを分割数だけ繰り返します。各評価の平均をとることでクロスバリデーションによるモデルの評価結果とします。ハイパーパラメータの調整を行う場合は、パラメータを変更し、クロスバリデーションを行うサイクルを繰り返し、最良の結果が出たハイパーパラメータを採用します。

クロスバリデーション

ハイパーパラメータが決定したら、そのパラメータで学習用をすべて使い学習を行い、テスト用で最終的なモデルの評価結果にします。

sklearn.model_selection.cross_val_score

scikit-learn の cross_val_score() を使用すると、クロスバリデーションが行えます。返り値は各試行のスコアです。

sklearn.model_selection.cross_val_score(estimator, X, y=None, *, groups=None, scoring=None, cv=None, n_jobs=None, verbose=0, fit_params=None, pre_dispatch='2*n_jobs', error_score=nan)
In [3]:
from sklearn import datasets, metrics, svm
from sklearn.model_selection import cross_val_score, train_test_split

X, y = datasets.load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.15, random_state=0
)

clf = svm.SVC(kernel="linear", C=1, random_state=0)
scores = cross_val_score(clf, X_train, y_train, cv=5)
print(scores)
[0.96153846 0.92307692 1.         1.         0.88      ]

sklearn.model_selection.cross_validate

scikit-learn の cross_validate() は、cross_val_score() とほとんど同じですが、以下の違いがあります。

  1. 複数の評価基準を指定できます。
  2. 返り値で学習データに対する評価値、学習や評価にかかった時間など追加の情報を取得できます。
sklearn.model_selection.cross_validate(
    estimator,
    X,
    y=None,
    *,
    groups=None,
    scoring=None,
    cv=None,
    n_jobs=None,
    verbose=0,
    fit_params=None,
    pre_dispatch="2*n_jobs",
    return_train_score=False,
    return_estimator=False,
    error_score=nan,
)
In [4]:
from sklearn import datasets, metrics, svm
from sklearn.model_selection import cross_validate, train_test_split

X, y = datasets.load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.15, random_state=0
)

clf = svm.SVC(kernel="linear", C=1, random_state=0)
scores = cross_validate(clf, X_train, y_train, cv=5)
print(scores)
{'fit_time': array([0.00070715, 0.00064349, 0.00065446, 0.00063968, 0.00063086]), 'score_time': array([0.00026226, 0.00024509, 0.00024509, 0.00024366, 0.00024295]), 'test_score': array([0.96153846, 0.92307692, 1.        , 1.        , 0.88      ])}

sklearn.model_selection.cross_val_predict

scikit-learn の cross_val_predict() は、1つの Fold を除いたデータで学習し、1つの Fold で予測した結果を返します。

sklearn.model_selection.cross_val_predict(
    estimator,
    X,
    y=None,
    *,
    groups=None,
    cv=None,
    n_jobs=None,
    verbose=0,
    fit_params=None,
    pre_dispatch="2*n_jobs",
    method="predict",
)
  • estimator: モデル
  • X: データ
  • y: ラベル
  • cv: 分割方法

cross_val_predict

In [5]:
from sklearn import datasets, metrics, svm
from sklearn.model_selection import cross_val_predict, train_test_split

X, y = datasets.load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.15, random_state=0
)

clf = svm.SVC(kernel="linear", C=1, random_state=0)
y_pred = cross_val_predict(clf, X_train, y_train, cv=5)
print(y_pred)
[0 2 0 0 1 1 0 2 1 0 2 2 1 0 2 1 1 2 0 2 0 0 1 2 2 2 2 1 2 1 1 2 1 1 2 1 2
 1 0 2 1 1 1 1 2 0 0 2 1 0 0 1 0 2 1 0 1 2 1 0 2 2 2 2 0 0 2 2 0 2 0 2 2 0
 0 2 0 0 0 1 2 2 0 0 0 1 1 0 0 1 0 2 1 2 1 0 2 0 2 0 0 2 0 2 1 1 1 2 2 2 2
 0 1 2 2 0 1 1 2 1 0 0 0 2 1 2 0]

コメント

コメント一覧 (0件)

  • In5の例に関して、質問があります。
    scores = cross_val_predict(clf, X_train, y_train, cv=5)
    scoreは、y_pred(予測値)だと思います。cross_val_predictの内部では、X_trainを5つのholdに分けて
    そのうちの4つで学習、残りの1つで評価をする、これを準繰りで5回やって、そのうちの最も良い値を採用。その値で使って、改めてX_trainを投入して得られた予測値だという理解です。
    confusion_matrix、classification_reportに投入するデータは、
    y_true=y_train, y_pred=scoresで良いと考えますが、正しいでしょうか?

    print(confusion_matrix(y_true=y_train, y_pred=scores))
    print(classification_report(y_true=y_train, y_pred=scores))

    titanicの場合、trainデータとtestデータが与えられていますので、こういう場合は、train_test_splitは不要、trainデータをそのまま、cross_val_predictに入れてやればよい。
    理解は正しいでしょうか?
    宜しくお願い致します。

    • コメントありがとうございます。

      > scoreは、y_pred(予測値)だと思います。

      返り値は予測値 (ラベル) でした。記事の記述のほうを修正させていただきました。

      > scoreは、y_pred(予測値)だと思います。cross_val_predictの内部では、X_trainを5つのholdに分けて
      > そのうちの4つで学習、残りの1つで評価をする、これを準繰りで5回やって、そのうちの最も良い値を採用。その値で使って、改めてX_trainを投入して得られた予測値だという理解です。

      ソースコードのほうを確認しましたが、cross_val_predict() では、データを K 個の Fold に分割して、交差検証する際の各試行時の予測ラベルを結合した結果を返していると思います。

      ソースコード:
      https://github.com/scikit-learn/scikit-learn/blob/7e1e6d09b/sklearn/model_selection/_validation.py#L933
      関連するQA:
      https://stackoverflow.com/questions/41458834/how-is-scikit-learn-cross-val-predict-accuracy-score-calculated

      例えば、cv=3 でデータが A、B、C の3つの Fold に分割された場合
      A、B で学習 → そのモデルで C のデータを予測したラベルが得られる C_pred
      B、C で学習 → そのモデルで A のデータを予測したラベルが得られる A_pred
      A、C で学習 → そのモデルで B のデータを予測したラベルが得られる B_pred

      このとき、関数の返り値は予測ラベルを結合した [A_pred, B_pred, C_pred] が cross_val_predict() の返り値

      > confusion_matrix、classification_reportに投入するデータは、
      > y_true=y_train, y_pred=scoresで良いと考えますが、正しいでしょうか?

      投入するデータとしては正しいと思いますが、cross_val_predict() が返す予測ラベルは、各検証の異なるデータで学習した異なるモデルによる予測ラベルであることにご留意ください。

      > titanicの場合、trainデータとtestデータが与えられていますので、こういう場合は、train_test_splitは不要、trainデータをそのまま、cross_val_predictに入れてやればよい。
      > 理解は正しいでしょうか?

      交差検証を利用する場合、train_test_split() は不要で、train データをそのまま入れればよいです。
      test データは、データがリークするのを防ぐため、学習データを作成する過程では使用せず、最終的に作成したモデルを評価する目的にのみ使用するのがよいと思います。

コメントする

目次