交差検証(Cross Validation)は何故行うのか?
機械学習でモデルの性能評価を行う場合、一般的にやられているのは、全体のデータを訓練データとテストデータに分割を行い、訓練データを用いてモデルを作成後、テストデータでモデルの性能評価を行います。
何を予測するにしても重要なのは、手元にあるデータの性能のみではなく、未知のデータに対する予測精度のため、このように訓練データとテストデータに分けることで、疑似的に未知データを作るのが理由となります。
pythonの場合train_test_split関数がsklearnにあり、これを用いると訓練データとテストデータをそれぞれ75%と25%にランダムに分割することができます(任意の値に設定できます)
しかし、この方法だと訓練データとテストデータで異なる性質のものが偏って分割された場合(例えば極端に予測が難しいデータがテストデータに分割された場合)など、本来の目的である「未知のデータに対する予測精度」を適切に見積もれなくなってしまいます。
データの性質にもよりますが、kaggleなどのコンペでは交差検証(Cross Validation)と呼ばれる、4~5分割(任意の分割数)を行うことによるモデルの評価方法が使用されています。
今回はメジャーな下記3点について紹介していきます。
・k-分割交差検証(k-fold cross-validation)
・層化k分割交差検証(stratified k-fold cross-validation)
・グループ付き交差検証(GroupKFold)
k分割交差検証(k-fold cross-validation)
k分割交差検証はよく用いられる分割方法で、データセットを任意の数k個に分割します。ここでは、k=5とした場合、全体のデータの1/5をテストデータ、残りの4/5を訓練データとしてモデルを構築して評価を行います。
分割数が5なので、それぞれのデータ(全体の4/5のデータ×4)に対するモデルを作成することができるため、ここでは5個のモデルが作成できます。

分割されたデータをFold 1~5と呼びます。特徴としては分割の仕方がランダムであることが挙げられます。したがってクラスの偏りを考慮した分割ではないので冒頭に上げた問題は残ったままになるので注意が必要です。
ここでは、5個モデルを作成しており、全てのデータは必ず一度はテストデータとなるため、全てのデータでの評価が可能となります。評価の仕方は2つの考え方があり、各Fold毎で評価指標を計算して平均を取る場合と、全体のデータ評価指標を計算して評価する場合に分かれます。
1 2 3 4 5 6 7 |
from sklearn.model_selection import KFold kf = KFold(n_splits=4, shuffle=True, random_state=1)\ .split(train_df,train_df[target].values) for train_idx, valid_idx in kf: # CV loop model.train(df.loc[train_idx,:], df.loc[valid_idx,:]) |
層化k分割検証(stratified k-fold cross-validation)
次に層化k分割検証(k-stratified)ですが、先ほど紹介したKFoldでは下記のようにターゲットのクラスに偏りがある場合、

シャッフルをせずに順番にkfoldで分割して学習を行うと、適切なモデルができないのは直感的に理解できます。
そこで層化k分割交差検証では各分割内(Fold内)でクラスの比率が同じようになるように分割を行います。

こうすることで訓練データとテストデータのラベルの分布が同じになるため、先ほどのirisのようクラスの極端な偏りを防ぐことができます。
irisのデータは極端でしたが、分類問題を解くときにクラスの偏りが等しい場合というのは少なく、どちらかのサンプルが少なくなるケースが多い(不正送金検知、工場の検知など異常事態のデータが少ないケースで機械学習を適用させるケースが多い)ので、そのような時はkfoldではなくてk stratified foldで評価を行うのが一般的です。
1 2 3 4 5 6 7 |
from sklearn.model_selection import StratifiedKFold kf = StratifiedKFold(n_splits=4, shuffle=True, random_state=1)\ .split(train_df,train_df[target].values) for train_idx, valid_idx in kf: # CV loop model.train(df.loc[train_idx,:], df.loc[valid_idx,:]) |
グループ付き交差検証(GroupKFold)
最後にグループ付き交差検証です。これはデータの中に密接に関係するグループがある場合に用いられます。
例えば顔の画像から感情を予測しようとするときに、同じ人の笑ってる顔と泣いている顔がテストデータと訓練データに入っていたとすると、別々の人の感情認識より楽になるというのは直感的に想像ができます。
そのほかにも医療データなどを扱いモデルを作成する際、同じ被験者から得られたデータが学習データとテストデータ両方に入るとそうでない場合と比較してテストデータの予測精度が高くなることが想像できます。
この場合、未知データに学習時に用いられた人と同一人物が含まれる可能性が大きい場合はテストデータと訓練データに同一の人が含まれていても問題ないケースもあります。
ただ、レントゲンから診断を行うモデルなどを考えた場合、未知データとして入手するのは非同一人物になるのが一般的かと思われるので、そのような場合にgroupkfoldを使用するべきとなります。
groupkfoldの必要性は中々伝わりにくいところでもあるので、groupkfoldを使うべき問題でkfoldで予測精度を評価してしまい実運用で精度が出ない…というケースは割とあるのではないかと思います。
1 2 3 4 5 6 7 8 |
from sklearn.model_selection import GroupKFold # split_groupsでグループにしたい変数を指定する kf = GroupKFold(n_splits=4).split(train_df, train_df[ target].values, groups=train_df[split_groups]) for train_idx, valid_idx in kf: # CV loop model.train(df.loc[train_idx,:], df.loc[valid_idx,:]) |
まとめ
kaggleでは最初から訓練データとテストデータが分けて与えられており、交差検証を行うのは訓練データに対してとなります。(なので訓練データ、検証データ、テストデータの3種類のデータセットに分けられます)
今回紹介した方法以外にもgroupkfoldとkstratifiedを合わせた、groupkfoldstratifiedなどgroup関係を維持したまま、ターゲットのクラスの比率を同一にする方法などが使われていたりします。
交差検証が正しくできていないと、適切な評価ができずにモデルの工夫や特徴量設計などの意味がなくなってしまうので、機械学習を社会で使うのには絶対に抑えなければならない要素だと思います。ただ、中々自分で手を動かしてみないと重要性がわかりにくいので、コンペなどで失敗しながら学ぶのがコスパが良いのかと思います。