Python
機械学習
MachineLearning
scikit-learn

【翻訳】scikit-learn 0.18 User Guide 3.1. クロスバリデーション:推定器の成果を評価する

More than 1 year has passed since last update.

http://scikit-learn.org/0.18/modules/cross_validation.html を google翻訳した
scikit-learn 0.18 ユーザーガイド 3. モデルの選択と評価 より


3.1. クロスバリデーション:推定器の成果を評価する

予測関数のパラメータを学習して同じデータでテストすることは、方法論的な間違いです。既知のサンプルのラベルは完璧に返しますが、未知のデータに対して有用な予測をすることはできません。このような状況を オーバーフィット といいます。 それを避けるために、利用可能なデータの一部を テストセット X_test、y_test として保持する(教師あり)機械学習実験を実行するのが一般的な方法です。 "実験"という言葉は学術的な使用のみを意味するものではないことに注意してください。商業的な場面でさえ、機械学習は通常実験的に始まるからです。
scikit-learnでは、 train_test_split ヘルパー関数を使用して、トレーニングとテストセットのランダムな分割を素早く計算できます。 線形SVMを訓練するようにアイリスデータセットをロードしましょう:

>>> import numpy as np
>>> from sklearn.model_selection import train_test_split
>>> from sklearn import datasets
>>> from sklearn import svm

>>> iris = datasets.load_iris()
>>> iris.data.shape, iris.target.shape
((150, 4), (150,))

分類器をテスト(評価)するために、データの40%を保持しながら、トレーニングセットをサンプリングできます。

>>>
>>> X_train, X_test, y_train, y_test = train_test_split(
...     iris.data, iris.target, test_size=0.4, random_state=0)

>>> X_train.shape, y_train.shape
((90, 4), (90,))
>>> X_test.shape, y_test.shape
((60, 4), (60,))

>>> clf = svm.SVC(kernel='linear', C=1).fit(X_train, y_train)
>>> clf.score(X_test, y_test)                           
0.96...

SVMに手動で設定する必要がある C の値など、さまざまな設定(「ハイパーパラメータ」)を調整するとき、推定を最適化するようにパラメータを微調整するため、テストセットにオーバーフィッティングします。このようにして、テストセットに関する知識がモデルに「リーク」し、評価メトリックでは予測性能が汎用的ではなくなります。この問題を解決するために、データセットのさらに別の部分をいわゆる「検証セット」とすることができます。トレーニングセットでトレーニングを行い、その後検証セットで評価を行い、実験が成功したように見えるときテストセットに対して最終的な評価を行うことができます。
しかし、利用可能なデータを3つのセットに分割することにより、モデルの学習に使用できるサンプル数が大幅に削減されると、その結果は(訓練、検証)ペアの特定のランダム選択に依存する可能性があります。
この問題の解決策は、クロスバリデーション、交差検定 (略してCV)と呼ばれる手続きです。テストセットは最終評価のために引き続き保留する必要がありますが、CVを実行する際に検証セットは不要になります。 k-fold CVと呼ばれる基本的なアプローチでは、トレーニングセットはk個の小さなセットに分割されます(他のアプローチは以下で説明しますが、一般的に同じ原則に従います)。 k分割のそれぞれについて、

  • モデルは、k-1をトレーニングデータとして使用して訓練される。
  • 得られたモデルは、データの残りの部分で検証される(すなわち、精度のような性能尺度を計算するためのテストセットとして使用される)。

k-fold 交差検定によって報告される性能指標は、ループで計算された値の平均です。このアプローチは計算コストがかかるが、(任意のテストセットを固定する場合のように)あまりに多くのデータを浪費することはなく、サンプル数が非常に少ない逆推論などで大きなメリットがある。

3.1.1. クロスバリデーションされたメトリックの計算

クロスバリデーションを使用する最も簡単な方法は、推定器とデータセットで cross_val_score ヘルパー関数を呼び出すことです。
次の例は、データを分割し、モデルをフィッティングし、スコアを5回連続して(毎回異なるスプリットで)計算することによって、アイリスデータセット上の線形SVMの精度を推定する方法を示す。

>>> from sklearn.model_selection import cross_val_score
>>> clf = svm.SVC(kernel='linear', C=1)
>>> scores = cross_val_score(clf, iris.data, iris.target, cv=5)
>>> scores                                              
array([ 0.96...,  1.  ...,  0.96...,  0.96...,  1.        ])

したがって、スコアの平均スコアおよび95%信頼区間は、

>>>
>>> print("Accuracy: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))
Accuracy: 0.98 (+/- 0.03)

デフォルトでは、各CV反復で計算されたスコアは推定値のスコアメソッドです。 スコアリングパラメータを使用してこれを変更することができます:

>>>
>>> from sklearn import metrics
>>> scores = cross_val_score(
...     clf, iris.data, iris.target, cv=5, scoring='f1_macro')
>>> scores                                              
array([ 0.96...,  1.  ...,  0.96...,  0.96...,  1.        ])

詳細については、 スコアリング・パラメータ:モデル評価ルールの定義 を参照してください。 アイリスデータセットの場合、サンプルはターゲットクラスに渡ってバランスされているため、精度とF1スコアはほぼ同じです。
cv引数が整数の場合、 cross_val_score はデフォルトで KFold または StratifiedKFold を使用します。後者は、推定器が ClassifierMixin から派生した場合に使用されます。
クロスバリデーション用イテレータを渡すことによって、他のクロスバリデーション戦略を使用することもできます。

>>> from sklearn.model_selection import ShuffleSplit
>>> n_samples = iris.data.shape[0]
>>> cv = ShuffleSplit(n_splits=3, test_size=0.3, random_state=0)
>>> cross_val_score(clf, iris.data, iris.target, cv=cv)
...                                                     
array([ 0.97...,  0.97...,  1.        ])

保留データによるデータ変換

トレーニングから保留されたデータについて予想をテストすることが重要であるのと同様に、前処理(標準化、特徴選択など)や同様のデータ変換 もトレーニングセットから学習し、予測のために保留データに適用する必要があります :

>>>
>>> from sklearn import preprocessing
>>> X_train, X_test, y_train, y_test = train_test_split(
...     iris.data, iris.target, test_size=0.4, random_state=0)
>>> scaler = preprocessing.StandardScaler().fit(X_train)
>>> X_train_transformed = scaler.transform(X_train)
>>> clf = svm.SVC(C=1).fit(X_train_transformed, y_train)
>>> X_test_transformed = scaler.transform(X_test)
>>> clf.score(X_test_transformed, y_test)  
0.9333...

Pipeline を使用すると、簡単に合成推定器を作成でき、クロスバリデーションで利用できます。

>>>
>>> from sklearn.pipeline import make_pipeline
>>> clf = make_pipeline(preprocessing.StandardScaler(), svm.SVC(C=1))
>>> cross_val_score(clf, iris.data, iris.target, cv=cv)
...                                                 
array([ 0.97...,  0.93...,  0.95...])

パイプラインとFeatureUnion:推定器を組み合わせる を参照してください。

3.1.1.1. クロスバリデーションによる予測の取得

関数 cross_val_predict は、 cross_val_score と同様のインタフェースを持ちますが、予測した結果そのものを返します。すべての要素をテストセットに一度だけ割り当てる交差検定戦略のみが使用できます(それ以外の場合は例外が発生します)。
これらの予測を用いて分類器を評価することもできる。

>>>
>>> from sklearn.model_selection import cross_val_predict
>>> predicted = cross_val_predict(clf, iris.data, iris.target, cv=10)
>>> metrics.accuracy_score(iris.target, predicted) 
0.966...

この計算の結果は、要素が異なる方法でグループ化されるため、 cross_val_score を使用して得られた結果とわずかに異なる場合があることに注意してください。
利用可能なクロスバリデーションイテレータは、次のセクションで紹介しています。

3.1.2. クロスバリデーションイテレータ

次のセクションでは、さまざまなクロスバリデーション戦略に従ってデータセット分割のためのインデックスを生成するユーティリティを示します。

3.1.3. 独立同分布データへのクロスバリデーションイテレータ

いくつかのデータが独立同分布(Independent Identically Distributed = i.i.d.)であると仮定すると、すべてのサンプルが同じ生成プロセスから生じ、生成プロセスが過去に生成されたサンプルのメモリを持たないと仮定されている。
そのような場合には、以下のクロスバリデーターを使用することができます。

注意

i.i.d.データは機械学習理論における共通の仮定であり、実際にはほとんど成立しない。サンプルが時間依存プロセスを使用して生成されたことが分かっている場合、時系列認識クロスバリデーション方式を使用する方が安全です。生成プロセスにグループ構造があることがわかっている場合(異なるサブジェクト、実験、測定デバイス)、グループワイズクロスバリデーションを使用する方が安全です。

3.1.3.1. K-分割交差検証

KFold は、すべてのサンプルを、foldと呼ばれる $k$ 個の同じサイズ(可能な場合)のグループに分割します($k = n$ ならば、これはLeave One Out戦略と同じ)。予測関数は、 $k-1$ の fold を用いて学習され、残された fold は、テストに使用される。
4つのサンプルを有するデータセットにおける2分割交差検証の例:

>>> import numpy as np
>>> from sklearn.model_selection import KFold

>>> X = ["a", "b", "c", "d"]
>>> kf = KFold(n_splits=2)
>>> for train, test in kf.split(X):
...     print("%s %s" % (train, test))
[2 3] [0 1]
[0 1] [2 3]

各 fold は2つの配列で構成されています。最初の1つはトレーニングセット用、2つ目はテストセット用です。 したがって、numpyインデックスを使用してトレーニング/テストセットを作成することができます。

>>> X = np.array([[0., 0.], [1., 1.], [-1., -1.], [2., 2.]])
>>> y = np.array([0, 1, 0, 1])
>>> X_train, X_test, y_train, y_test = X[train], X[test], y[train], y[test]

3.1.3.2. Leave One Out(LOO)

LeaveOneOut(またはLOO)は簡単なクロスバリデーションです。 各学習セットは、1つを除くすべてのサンプルによって作成され、テストセットは除外された1サンプルです。 したがって、n個のサンプルに対して、n個の異なるトレーニングセットとn個の異なるテストセットがある。 このクロスバリデーション手順では、訓練セットから1つのサンプルのみが削除されるため、データを無駄にすることはありません。

>>> from sklearn.model_selection import LeaveOneOut

>>> X = [1, 2, 3, 4]
>>> loo = LeaveOneOut()
>>> for train, test in loo.split(X):
...     print("%s %s" % (train, test))
[1 2 3] [0]
[0 2 3] [1]
[0 1 3] [2]
[0 1 2] [3]

モデル選択のためのLOOの潜在的なユーザは、いくつかの既知の警告を考慮する必要があります。 $k$分割クロスバリデーションと比較すると、$n$ 個のサンプルから $k$ 個のモデルではなく $n$ 個のモデルが構築されます。さらに、それぞれは、 $(k-1)n / k$ ではなく、 $n-1$ サンプルに対して訓練される。両方の方法において、 $k$ が大きすぎず、 $k < n$ であると仮定すると、LOOは、k倍交差検証より計算上高価である。
精度の観点から、LOOは、推定器のテスト誤差として高い分散をもたらすことが多い。直観的には、 $n$ 個のサンプルの $n - 1$ 個が各モデルを構築するために使用されるので、foldから構築されたモデルはお互いに事実上同一であり、トレーニングセット全体から構築されたモデルとも同じです。
しかし、学習曲線が問題のトレーニングサイズに対して急峻である場合、5または10分割の交差検証は汎化誤差を過大評価する可能性があります。
一般的なルールとして、ほとんどの著者および経験的な証拠は、LOOよりも5または10分割の交差検証が好ましいと示唆しています。

3.1.3.3. Leave P Out(LPO)

LeavePOut は、LeaveOneOutと非常によく似ています。これは、完全なセットから $p$ 個のサンプルを削除することによって、可能なすべてのトレーニング/テストセットを作成するためです。 nサンプルの場合、これは ${n \choose p}$ 訓練-テスト・ペアを生成します。 LeaveOneOutおよびKFoldとは異なり、テストセットは $p> 1$で重複します。
4つのサンプルを持つデータセットのLeave-2-Outの例:

>>> from sklearn.model_selection import LeavePOut

>>> X = np.ones(4)
>>> lpo = LeavePOut(p=2)
>>> for train, test in lpo.split(X):
...     print("%s %s" % (train, test))
[2 3] [0 1]
[1 3] [0 2]
[1 2] [0 3]
[0 3] [1 2]
[0 2] [1 3]
[0 1] [2 3]

3.1.3.4. ランダム置換相互検証a.k.a.シャッフル&スプリット

ShuffleSplit イテレータは、独立した訓練/テストデータセット分割のユーザ定義数を生成します。サンプルを最初にシャッフルしてから、訓練とテストセットのペアに分割します。
random_state 擬似乱数ジェネレータを明示的にシードすることによって、結果の再現性制御することが可能です。
使用例を次に示します。

>>> from sklearn.model_selection import ShuffleSplit
>>> X = np.arange(5)
>>> ss = ShuffleSplit(n_splits=3, test_size=0.25,
...     random_state=0)
>>> for train_index, test_index in ss.split(X):
...     print("%s %s" % (train_index, test_index))
...
[1 3 4] [2 0]
[1 4 3] [0 2]
[4 0 2] [1 3]

このため、ShuffleSplitはKFoldのクロスバリデーションに代わる優れた方法であり、訓練/テストそれぞれのサンプル数と反復回数をより細かく制御できます。

3.1.4. クラスラベルに基づいた階層化を持つ相互検証イテレータ

いくつかの分類問題は、標的クラスの分布に大きな不均衡を示すことがある:例えば、陽性試料よりも数倍の陰性試料が存在する可能性がある。このような場合、StratifiedKFold および StratifiedShuffleSplit で実装された層別サンプリングを使用して、相対クラス頻度が各訓練および検証の倍数でほぼ保存されるようにすることが推奨されます。

3.1.4.1. 層状K分割

StratifiedKFoldは、層別foldを返すk分割のバリエーションです。各セットは、完全なセットと各ターゲットクラスのサンプルのほぼ同じパーセンテージを含んでいます。
わずかにアンバランスな2つのクラスからの10サンプルのデータセットに対する層別3分割クロスバリデーションの例:

>>> from sklearn.model_selection import StratifiedKFold

>>> X = np.ones(10)
>>> y = [0, 0, 0, 0, 1, 1, 1, 1, 1, 1]
>>> skf = StratifiedKFold(n_splits=3)
>>> for train, test in skf.split(X, y):
...     print("%s %s" % (train, test))
[2 3 6 7 8 9] [0 1 4 5]
[0 1 3 4 5 8 9] [2 6 7]
[0 1 2 4 5 6 7] [3 8 9]

3.1.5. グループ化されたデータの相互検証イテレータ

基本的な生成プロセスが従属サンプルのグループを生成する場合、i.i.d.仮定は破られます。
このようなデータのグループ分けは、ドメイン特有である。一例は、複数の患者から収集した医療データがあり、各患者から複数のサンプルを採取した場合である。そのようなデータは個々のグループに依存する可能性が高い。この例では、各サンプルの患者IDはグループ識別子になります。
この場合、特定のグループのセットで訓練されたモデルが他のグループでも適用できるかを知りたいと思います。これを測定するには、検証用サンプルがトレーニング用では全く利用されないグループから来るようにする必要があります。
次のクロスバリデーションスプリッタを使用してこれを行うことができます。サンプルのグループ化識別子は、groups パラメータで指定します。

3.1.5.1. グループk分割

GroupKFold は、同じグループがテストセットとトレーニングセットの両方に現れないようにするk分割のバリエーションです。例えば、データが被験者ごとに複数のサンプルを有する異なる被験者から得られ、モデルが高度に個人固有の特徴から学ぶのに十分柔軟である場合、新しい被験者に一般化することができない。GroupKFoldは、このような過密状態を検出することを可能にします。
3つのグループがあり、それぞれに1から3までの番号が関連付けられているとします。

>>> from sklearn.model_selection import GroupKFold

>>> X = [0.1, 0.2, 2.2, 2.4, 2.3, 4.55, 5.8, 8.8, 9, 10]
>>> y = ["a", "b", "b", "b", "c", "c", "c", "d", "d", "d"]
>>> groups = [1, 1, 1, 2, 2, 2, 3, 3, 3, 3]

>>> gkf = GroupKFold(n_splits=3)
>>> for train, test in gkf.split(X, y, groups=groups):
...     print("%s %s" % (train, test))
[0 1 2 3 4 5] [6 7 8 9]
[0 1 2 6 7 8 9] [3 4 5]
[3 4 5 6 7 8 9] [0 1 2]

各被験者は異なる試験段階にあり、同じ被験者は試験と訓練の両方にはいません。 データの不均衡のために正確に同じサイズにならないことに注意してください。

3.1.5.2. Leave One Group Out

LeaveOneGroupOut は、サードパーティが提供するグループ番号の配列に従ってサンプルを保留するクロスバリデーション方式です。 このグループ情報は、任意のドメイン特有の事前定義されたクロスバリデーション分割を符号化するために使用することができる。
したがって、各トレーニングセットは、特定のグループに関連するものを除くすべてのサンプルによって構成される。
たとえば、複数の実験の場合、LeaveOneGroupOutを使用して、異なる実験に基づく相互検証を作成することができます。1つを除くすべての実験のサンプルを使用してトレーニングセットを作成します。

>>> from sklearn.model_selection import LeaveOneGroupOut

>>> X = [1, 5, 10, 50, 60, 70, 80]
>>> y = [0, 1, 1, 2, 2, 2, 2]
>>> groups = [1, 1, 2, 2, 3, 3, 3]
>>> logo = LeaveOneGroupOut()
>>> for train, test in logo.split(X, y, groups=groups):
...     print("%s %s" % (train, test))
[2 3 4 5 6] [0 1]
[0 1 4 5 6] [2 3]
[0 1 2 3] [4 5 6]

別の一般的なアプリケーションは、時間情報を使用することです。たとえば、グループはサンプルを収集する年になる可能性があり、時間ベースの分割に対する相互検証が可能です。

3.1.5.3. Leave P Groups Out

LeavePGroupsOutLeaveOneGroupOut と似ていますが、各トレーニング/テストセットの $P$ グループに関連するサンプルを削除します。
Leave-2-Group Outの例:

>>> from sklearn.model_selection import LeavePGroupsOut

>>> X = np.arange(6)
>>> y = [1, 1, 1, 2, 2, 2]
>>> groups = [1, 1, 2, 2, 3, 3]
>>> lpgo = LeavePGroupsOut(n_groups=2)
>>> for train, test in lpgo.split(X, y, groups=groups):
...     print("%s %s" % (train, test))
[4 5] [0 1 2 3]
[2 3] [0 1 4 5]
[0 1] [2 3 4 5]

3.1.5.4. グループシャッフルスプリット

GroupShuffleSplit イテレータは、 ShuffleSplitLeavePGroupsOut の組み合わせとして動作し、各分割ごとにグループのサブセットが保持されるランダムなパーティションのシーケンスを生成します。
使用例を次に示します。

>>> from sklearn.model_selection import GroupShuffleSplit

>>> X = [0.1, 0.2, 2.2, 2.4, 2.3, 4.55, 5.8, 0.001]
>>> y = ["a", "b", "b", "b", "c", "c", "c", "a"]
>>> groups = [1, 1, 2, 2, 3, 3, 4, 4]
>>> gss = GroupShuffleSplit(n_splits=4, test_size=0.5, random_state=0)
>>> for train, test in gss.split(X, y, groups=groups):
...     print("%s %s" % (train, test))
...
[0 1 2 3] [4 5 6 7]
[2 3 6 7] [0 1 4 5]
[2 3 4 5] [0 1 6 7]
[4 5 6 7] [0 1 2 3]

このクラスは、LeavePGroupsOutの動作が必要な場合に便利ですが、グループの数が多く、 $P$ グループを保持しているすべての可能なパーティションを生成するのが非常に高価になります。このようなシナリオでは、GroupShuffleSplitは、LeavePGroupsOutによって生成された訓練/テスト分割のランダムなサンプル(置き換え)を提供します。

3.1.6. あらかじめ定義されたFold-Splits / Validation-Sets

一部のデータセットでは、事前定義されたデータのトレーニングと検証の分割またはいくつかのクロスバリデーション分割が既に存在します。 PredefinedSplit を使用すると、これらのfoldを使用することが可能です。例えばハイパーパラメータを検索するときに使用します。
たとえば、検証セットを使用する場合は、検証セットの一部であるすべてのサンプルに対して test_fold を0に設定し、他のすべてのサンプルについては-1に設定します。

3.1.7. 時系列データの相互検証

時系列データは、時間的に近い観測値間の相関によって特徴付けられる(自己相関)。しかし、KFoldやShuffleSplitなどの古典的なクロスバリデーション手法では、サンプルが独立して同一に分布していると想定され、訓練とテストのインスタンス間に不合理な相関が生じる(一般化エラーの推定が悪い)。したがって、モデルを訓練するのに使用されるような「未来の」観測に関する時系列データのモデルを評価することは非常に重要です。これを達成するために、 TimeSeriesSplit が提供する1つのソリューションがあります。

3.1.7.1. 時系列分割

TimeSeriesSplitは、最初のk個のfoldを列集合として返し、 $k + 1$ 回目のfoldをテスト集合として返すk-foldの変形です。標準的なクロスバリデーション方法とは異なり、連続するトレーニングセットはそれらの前に来るもののスーパーセットであることに注意してください。また、モデルをトレーニングするために常に使用される最初のトレーニングパーティションにすべての余剰データを追加します。
このクラスは、一定の時間間隔で観測される時系列データサンプルを相互検証するために使用できます。
6サンプルのデータセットに対する3分割時系列相互検証の例:

>>> from sklearn.model_selection import TimeSeriesSplit

>>> X = np.array([[1, 2], [3, 4], [1, 2], [3, 4], [1, 2], [3, 4]])
>>> y = np.array([1, 2, 3, 4, 5, 6])
>>> tscv = TimeSeriesSplit(n_splits=3)
>>> print(tscv)  
TimeSeriesSplit(n_splits=3)
>>> for train, test in tscv.split(X):
...     print("%s %s" % (train, test))
[0 1 2] [3]
[0 1 2 3] [4]
[0 1 2 3 4] [5]

3.1.8. シャッフルの注意

データの順序が恣意的でない場合(たとえば、同じクラスラベルを持つサンプルが連続している場合)、まずそれをシャッフルして、有意義なクロスバリデーション結果を得ることが不可欠な場合があります。しかし、サンプルが独立して同一に分布していない場合は、逆のことが当てはまる可能性があります。たとえば、サンプルがニュース記事に対応し、出版時間順に並べ替えられている場合、データをシャッフルすると、オーバーフィットしたモデルと水増しされた検証スコアにつながる可能性があります。それは人工的に類似している(時間的に近い)サンプルについて試験されたからです。
KFold などの相互検証イテレータの中には、分割する前にデータインデックスをシャッフルするための組み込みオプションがあります。

  • これは、データを直接シャフリングするよりもメモリ消費が少ない。
  • デフォルトでは、シャッフルは発生しません。cv = some_integer を指定した cross_val_score 、グリッド検索、(層化された)K分割のクロスバリデーションなど。 train_test_split はやはりランダムな分割を返すことを注意してください。
  • random_state パラメータのデフォルトは None です。つまり、 KFold(..., shuffle=True) が利用されるたびにシャッフルが異なることを意味します。ただし、 GridSearchCV は、 fit メソッドの1回の呼び出しで検証される各パラメータセットに対して同じシャッフルを使用します。
  • 結果が(同じプラットフォーム上で)繰り返し可能であるようにするには、 random_state に固定値を使用します。

3.1.9. クロスバリデーションとモデル選択

クロスバリデーションイテレータは、グリッド検索を使用してモデルの最適なハイパーパラメータを直接モデル選択するためにも使用できます。これは、次のセクションのトピックです。推定器のハイパーパラメータのチューニング


scikit-learn 0.18 ユーザーガイド 3. モデルの選択と評価 より

©2010 - 2016、scikit-learn developers(BSDライセンス)。