u++の備忘録

Cross Validationはなぜ重要なのか【kaggle Advent Calendar 3日目】

本記事は、kaggle Advent Calendar 2018の3日目の記事ということにします。本日、このAdvent Calendarに空きがあると気付いたので、穴埋めの形で急遽記事を執筆しました。僕が遅刻したわけではありません。

qiita.com

TwitterでこのAdvent Calendarに書く話題を募集したところ、次のようなリプを頂きました。

本記事ではまず、そもそも「Cross Validationはなぜ重要なのか」について言及しようと思います。

Cross Validationの重要性

機械学習の文脈における「validation」は、一般的に「モデルの汎化性能の検証」を意味します。汎化性能とは「未知のデータに対する性能」のことです。

validationの重要性を確認するため、「validationがない場合」を想定して、簡単な例を見てみます。

validationがない場合

パッケージやデータの準備
import pandas as pd
from sklearn import datasets, model_selection
from sklearn.ensemble import GradientBoostingClassifier

iris = datasets.load_iris()
iris_data = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_label = pd.Series(data=iris.target)
X_train, X_test, y_train, y_test = model_selection.train_test_split(iris_data, iris_label)
訓練と予測
clf = GradientBoostingClassifier(random_state=0)
clf.fit(X_train, y_train)

y_pred = clf.predict(X_test)
精度の検証

ここで、この予測結果の精度はどのように確認すれば良いでしょうか?

f:id:upura:20181225141306p:plain

y_predとy_testを比較すれば良いと思うかもしれませんが、例えばKaggleではコンペ終了時までy_testの全容は分かりません。一部データはpublic LBに利用されておりスコアを確認できますが、以下の二つの問題があります。

  • public LBで良いスコアが出ても、一部のデータのみに過学習した結果の可能性がある
  • サブミットできる回数に制限がある

前者について、public LBに使われているデータはtestのデータセットの10〜30%が一般的で、仮に新しいハイパーパラメータや特徴量を使ってスコアが上がっても、一分のデータのみに効果がある「過学習」に陥っている可能性があります。

また、public LBにどのようなデータが使われているかも分かりません。極端な例ですが、二値分類の問題で0のラベルが付いたデータのみがpublic LBに使われている可能性もあります。この場合、仮にpublic LBで高いスコアを出すモデルが作成できても、そのモデルは1のラベルを当てる性能がどれだけあるか確認できていません。

後者について、Kaggleでは1日の提出回収が3〜5回とコンペごとに制限が決まっています。public LBのスコアは提出しないと分からないので、1日の提出回数分しかモデルや特徴量の試行錯誤ができない状況になってしまいます。

ホールドアウト検証を実行した場合

これらの問題に対応するのが、validationです。validationのためのデータセットは、trainのデータセットから分割することで作成します。

f:id:upura:20181225141445p:plain

このvalidationのデータセットは、自分でtrainのデータセットから作成したため、全容を把握できています。validationのデータセットを「上手に」作成すれば、public LBのスコアよりも信頼性の高いスコアを得られると分かります。

またこのスコアは提出することなく、手元で確認可能です。自分の気の済むだけ試行錯誤を回し、良いスコアを得た場合に提出するような運用が可能です。

この例のような検証を「ホールドアウト検証」と呼びます。

パッケージやデータの準備
from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline

X_train, X_valid, y_train, y_valid = model_selection.train_test_split(X_train, y_train, test_size=0.25, random_state=0)
訓練と予測
clf = GradientBoostingClassifier(random_state=0)
clf.fit(X_train, y_train)

y_pred = clf.predict(X_valid)
精度の検証
from sklearn.metrics import accuracy_score
accuracy_score(y_valid, y_pred)
0.9642857142857143
conf_mat = confusion_matrix(y_valid, y_pred)

index = list("012")
columns = list("012")
df = pd.DataFrame(conf_mat, index=index, columns=columns)
fig = plt.figure(figsize = (7,7))
sns.heatmap(df, annot=True, square=True, fmt='.0f', cmap="Blues")
plt.title('iris dataset')
plt.xlabel('truth')
plt.ylabel('prediction')

f:id:upura:20181204200353p:plain

交差検証(Cross Validation、CV)を実行した場合

さて、ここで「Cross Validation」を実行すると、ホールドアウト検証の例よりも、更に汎用的に性能を確認できます。

f:id:upura:20181225141559p:plain

Cross Validationでは、上図のように複数回に(図では3回)わたって異なるようにデータセットを分割し、それぞれでホールドアウト検証を実行します。その平均のスコアを確認することで、1回のホールドアウト検証で生じうる偏りに対する懸念を弱めることができます。

パッケージやデータの準備
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
kf = KFold(n_splits=3, shuffle=True, random_state=0)

iris_data = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_label = pd.Series(data=iris.target)
X_train, X_test, y_train, y_test = model_selection.train_test_split(iris_data, iris_label, test_size=0.25, random_state=0)
訓練と予測と精度の検証
clf = GradientBoostingClassifier(random_state=0)
scores = cross_val_score(clf, X_train, y_train, cv=kf)
scores
array([ 0.97368421,  0.91891892,  0.97297297])

1回のみのホールドアウト検証のスコアより高いスコアが2件、低いスコアが1件出ています。このこと自体が、1回のホールドアウト検証でスコアを判断することの危うさを表しています。Cross Validationでは、全体の平均をスコアとします。

scores.mean()
0.95519203413940268

世界ランキング1位 bestfitting氏のコメント

f:id:upura:20181204163842p:plain

世界ランキング1位のbestfitting氏も、Kaggleのインタビューでvalidationの重要性を強調するコメントを残しています。括弧内は私による日本語訳です。

medium.com

What is your approach to solid cross-validation/final submission selection and LB fit?
(確かなCVを作るための手法とどのように最終的な提出ファイルを決めているかを教えてください)

A good CV is half of success. I won’t go to the next step if I can’t find a good way to evaluate my model.
(良いCVが得られれば、成功の道半ばに到達したと言える。私は、モデルの良い評価方法が得られるまで、次のステップへ進まない)


To build a stable CV, you must have a good understanding of the data and the challenges faced. I’ll also check and make sure the validation set has similar distribution to the training set and test set and I’ll try to make sure my models improve both on my local CV and on the public LB.
(確かなCVを作るためには、データと解くべき問題を明確に理解しなければならない。そしてvalidationのデータセットがtrainのデータセットとtestのデータセットと同様の分布になっているか確かめ、local CVとpublic LBの両者を改善するようなモデルを作っていく)


In some time series competitions, I set aside data for a period of time as a validation set.
(時系列データを扱うコンペでは、一定期間をvalidationのデータセットとして確保する)


I often choose my final submissions in a conservative way, I always choose a weighted average ensemble of my safe models and select a relatively risky one (in my opinion, more parameters equate to more risks). But, I never chose a submission I can’t explain, even with high public LB scores.
(私は基本的に保守的な考え方で、最終的な提出ファイルの二つを決める。一つは「安全な」モデルの重み付きアンサンブルで、もう一つは比較的「危険な」モデルを選ぶ。パラメータが多いモデルの方が一般的に「危険」である。たとえpublic LBがどれほど良くても、私は決して自分で説明ができないモデルは選ばない)

おわりに

本記事では、「Cross Validationはなぜ重要なのか」を具体例とともに説明しました。

次回は、Twitterで頂いたリプの本題である「validationの切り方」についてまとめたいと思います。

本記事のソースコードGitHubで公開しました。
github.com

続き
upura.hatenablog.com