2019-08-04

実装とともに学ぶ交差検証のお話

はじめに

機械学習を活用した研究に取り組んでいると,論文中に “k-fold CV” や “cross-validation”,「交差検証」 といった表記を見かけるときが多々あると思います.また実際に機械学習モデルを構築してその性能を評価したいときに,より厳密に性能を測定するには,「交差検証」の適用は避けられません.

そこで,本稿では,「ゼロから作るDeep Learningとともに学ぶフレームワーク」で用いられていたソースコードを基に,交差検証の基本的な考え方とその適用方法について紹介します.ソースコードとともに本稿の内容を理解することで,交差検証に関する基礎的な知識が身につくものと想定されます.

それでは交差検証の解説から始めていきましょう.

1. 交差検証とは

交差検証とはデータセットを細切れに分割して,異なる組合せで複数回機械学習モデルを学習させて,それらの平均をとることによりモデルの性能を測る手法のことを指します.

交差検証を使わない場合,データセットを学習用 (training set)・検証用 (validation/development set)・テスト用 (test set) の3つに分割して,モデルの性能を評価することが多いと思われます.しかしながらテスト・検証用セットを設けることで,実際に学習に使えるデータ数が減少してしまうというデメリットがあります.また最終的な評価結果もデータセットのサンプリング方法によって左右される恐れもあります.

交差検証を用いることで検証用セットだけのためのデータを確保する必要がなくなるため,データセットを存分に活用することができ,異なる組合せで複数回学習させた結果の平均をとるので,より正確な性能評価が可能となります.

交差検証の主な種類としては,以下の二つが挙げられます.

  1. K分割交差検証 (k-fold cross validation)
    データセットを $k$ 個に分割をし,$k-1$ 個のデータのかたまりで機械学習モデルを学習させて,残りの$1$ 個の未知データでモデルの検証(テスト)を行います.この流れを異なる組合せで $k$ 回行い,最終的な結果はそれぞれの組合せでの実験結果の平均として得られます.

  2. Leave One Out 交差検証
    Leave One Out 交差検証はその名の通りデータを一個だけ残して交差検証を行うことを指します.つまり,K分割交差検証で,$K = n$ ($n$ はデータ数)とすることを意味しています.しかしこの手法はかなり計算コストが高いので,あまり深層学習を適用している研究分野で使われている例を見たことがありません…

念の為確認しておくと,機械学習モデルの学習・評価の流れとしては,まず学習用セットでモデルを学習させて,検証用セットで学習したモデルを評価(early stopping の適用 や ハイパーパラメータの最適化など)します.最後に検証用セットで良好な結果を残したモデルについて,最終的な評価を下すためにテストセットを一度だけ適用します.(テストセットはある学習済みモデルにとって未知(unseen)であることが求められます.)

1.1 K分割交差検証

K分割交差検証の例として,データセットを3分割にしたときの,3分割交差検証の流れを見ていきましょう.

流れとしては,まずデータ①と②で学習させたモデルをデータ③で性能評価し,その後データ①と③で学習させたモデルをデータ②でテストし,最後にデータ②と③で学習させたモデルをデータ①で評価します.最終的な結果は各性能評価の平均となります.

K分割交差検証の例
例:3分割交差検証のときの可視化図

より厳密にモデルの評価を行う場合はあらかじめデータセットを2分割にして,うち片方をテストセットとしてキープしておきます.(ホールドアウトともいう.)最終的な性能評価はテストセットで行います.

ここまで厳密にやるのは,イメージ的にはコンペ系のタスクが多いのかなと感じます.この場合,テストセットはリークを防ぐために公開されていないケースが多いです.

K分割交差検証の厳密な例
例:3分割交差検証のときの可視化図(厳密なとき)

1.2 層化K分割交差検証

これまで見てきた交差検証の例はデータセットのクラスの偏りについて考慮していませんでした.分類問題に対して交差検証を適用する場合には,データセット中の各クラスの出現確率に注意を払う必要がでてきます.具体的にはデータセットが不均衡となっている場合に,通常のK分割交差検証ではなく,「層化K分割交差検証」(stratified k-fold CV)を適用する必要があります.

層化K分割交差検証は,各fold(データのかたまり)での各クラスの出現確率が分割前のデータでの出現確率とほぼ同一になるようにデータを分割して,K分割交差検証を適用する手法です.これによってあるfoldにデータが偏ったり,全く存在しないといった事象を防ぐことができ,より正確に精度の計測を行うことができます.

2. 実装

「ゼロから作るDeep Learningとともに学ぶフレームワーク」で紹介したCIFAR10の分類モデルのソースコードを,k分割交差検証を適用できるように改造していきましょう.

K分割交差検証の適用方法は簡単で,scikit-learnに含まれている,model_selection.KFold クラスを使えば良いだけです.層化K分割交差検証の場合は,model_selection.StratifiedKFold クラスになります.

2.1 適用方法

まず,KFold クラスのインスタンスを生成します.

from sklearn.model_selection import KFold
kf = KFold(n_splits=5, random_state=1234)

KFold の引数は次の表のようになっています.

引数 説明
n_splits データセットを何分割するかを指定します.
shuffle データセットを分割前にシャッフルするかを指定します.
デフォルトは False になっています.
random_state データをランダム選択する際のシード値を指定できます.

次に,KFold クラスのメソッドである,split を使ってデータのインデックスを受け取るだけです.擬似コードは以下のようになります.

for train_index, val_index in kf.split(x_train, y_train):
    model = build_model()
    model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
    model.fit(x=x_train[train_index], y=_y_train[train_index], batch_size=100, epochs=10, verbose=1)
    _history.append(model.evaluate(x=x_train[val_index], y=_y_train[val_index], batch_size=100))

split はジェネレータになっているので,各foldでデータのインデックスを提供してくれます.したがって,与えられたインデックスをそのままデータセットのNumPy配列に与えれば,自動的にデータが選別されます.

参照: (scikit-learn KFoldのドキュメント)

2.2 適用例

以上の一連の流れをまとめたソースコードは,cross-validationディレクトリ下に run.py として置いてあります.基になっているソースコードは,ゼロから作るDeep Learningとともに学ぶフレームワークで実装した cnn.py です.

参照: (GitHub: run.py)

run.py を用いた学習のさせ方は簡単で,python run.py で実行できます.出力としてはコマンドライン上に学習の経過が表示されるようになっており,最後に全foldでの結果の平均と標準偏差を出力させるようになっています.出力例は下記の通りです.

loss: 1.0935191447734833 ± 0.05668393814738748
acc: 0.6322800013422967 ± 0.012688641313732466

(注意) 学習にはそれなりの時間を要するので,ノートパソコン等で動作させる際には,n_splits を小さい値: 3などに設定してご利用ください.

2.3 注意点

run.py で層化K分割交差検証を適用したい場合には,main関数の引数 stratifiedTrue を与えれば適用可能となります.ただしCIFAR10データセットは不均衡データセットではないため,実行結果への影響は軽微なものです.

まとめ

今回は交差検証について簡単に紹介しました.ゼロからKerasシリーズとの連携を想定して作成したので,GitHubの実装例もぜひ合わせて確認することをおすすめします.

参照: (GitHub: ゼロからKerasシリーズ)