kaggleでよく使う交差検証テンプレ(LightGBM向け)
はじめに
kaggleなどの機械学習コンペでLightGBMを使ってクロスバリデーションをする際のテンプレとなる型をまとめました。
Kerasでのテンプレは以下でまとめています。内容については重複している部分もあるので、適宜読み飛ばしてください。
kaggleでよく使う交差検証テンプレ(Keras向け)
:::affiliate-message 本ページはAmazonアフィリエイトのリンクを含みます。
対象読者
- LightGBMでクロスバリデーションが使いたい人
クロスバリデーション(交差検証)とは
まずは、そもそもクロスバリデーションとは何かについて、簡単に説明します。
クロスバリデーション(交差検証)とは、学習用のデータを複数の分割パターンで学習データと検証データに分けてモデルの汎化性能(未知のデータに対する予測能力)を検証することです。
分割した学習データで学習し、検証データで予測し精度を検証します。複数の分割パターンで検証を行うことでより正確なモデルの精度を測ることができます。最終的なテストデータへの予測は、各モデルでの平均とすることが多いです。
分割したデータはfoldと呼ばれます。
scikit-learnのドキュメントより
kaggleでは提出用のテストデータは、PublicとPrivateに分かれており、最終的な順位はPrivateのテストデータで行われます。Publicのスコアが良くても、たまたまPublicのテストデータをうまく予測できていただけの可能性もあるため、Privateになった時に順位が大きく下がってしまうことがあります。そこで、クロスバリデーションのスコアでモデルの精度を検証することで、より正確にモデルの精度を検証することができるようになります。
よく使うクロスバリデーションとテンプレ
具体的なクロスバリデーションの手法とテンプレを紹介していきます。
前提
テンプレの前に、前提となる学習データの変数を定義しておきます。基本的に、前処理などが終わったら以下の形にするようにします。
1"""
2X : numpy.ndarray
3 学習データの特徴量
4y : numpy.ndarray
5 学習データの目的変数
6"""
K-fold(k分割交差検証)
最もよく使う基本的なクロスバリデーションです。
学習データをk個に分割し、k-1個分の学習データと1個分の検証データに分けてモデルの汎化性能を検証します。k回学習を繰り返し、分割したデータはそれぞれ1回ずつ検証データとして使います。
最終的にモデルの汎化性能を測る時は、各foldにおけるスコアを平均します。
scikit-learnのドキュメントより
テンプレ
- scikit-learnの
KFold
を利用 - 回帰問題を想定
- 評価関数はMAE
1from sklearn.metrics import mean_absolute_error
2from sklearn.model_selection import KFold
3import lightgbm as lgb
4import numpy as np
5
6FOLD = 5
7NUM_ROUND = 100
8VERBOSE_EVAL = -1
9
10params = {
11 'objective': 'regression',
12 'verbose': -1,
13}
14
15valid_scores = []
16models = []
17kf = KFold(n_splits=FOLD, shuffle=True, random_state=42)
18
19for fold, (train_indices, valid_indices) in enumerate(kf.split(X)):
20 X_train, X_valid = X[train_indices], X[valid_indices]
21 y_train, y_valid = y[train_indices], y[valid_indices]
22 lgb_train = lgb.Dataset(X_train, y_train)
23 lgb_eval = lgb.Dataset(X_valid, y_valid)
24
25 model = lgb.train(
26 params,
27 lgb_train,
28 valid_sets=lgb_eval,
29 num_boost_round=NUM_ROUND,
30 verbose_eval=VERBOSE_EVAL
31 )
32
33 y_valid_pred = model.predict(X_valid)
34 score = mean_absolute_error(y_valid, y_valid_pred)
35 print(f'fold {fold} MAE: {score}')
36 valid_scores.append(score)
37
38 models.append(model)
39
40cv_score = np.mean(valid_scores)
41print(f'CV score: {cv_score}')
Shuffle Split
データ全体からランダムにサンプリングして分割する交差検証です。
K-foldの代替手段として利用でき、ランダムにサンプリングしているため各foldでのデータを重複がありえます。そのため、検証データとして利用されてないデータも出てくる可能性があります。
全体を重複なく分割しているわけではないので、分割数と学習データと検証データの比率を自由に調整することができます。
K-foldでshuffle=True
のオプションがありますが、これはデータ全体をシャッフルしてから重複なくデータを分割しているので、Shuffle Splitとは異なる結果(分割)になります。
scikit-learnのドキュメントより
テンプレ
- scikit-learnの
ShuffleSplit
を利用 - 回帰問題を想定
- 評価関数はMAE
- 検証データは25%
1from sklearn.metrics import mean_absolute_error
2from sklearn.model_selection import ShuffleSplit
3import lightgbm as lgb
4import numpy as np
5
6FOLD = 5
7NUM_ROUND = 100
8VERBOSE_EVAL = -1
9
10params = {
11 'objective': 'regression',
12 'verbose': -1,
13}
14
15valid_scores = []
16models = []
17ss = ShuffleSplit(n_splits=FOLD, test_size=0.25, random_state=42)
18
19for fold, (train_indices, valid_indices) in enumerate(ss.split(X)):
20 X_train, X_valid = X[train_indices], X[valid_indices]
21 y_train, y_valid = y[train_indices], y[valid_indices]
22 lgb_train = lgb.Dataset(X_train, y_train)
23 lgb_eval = lgb.Dataset(X_valid, y_valid)
24
25 model = lgb.train(
26 params,
27 lgb_train,
28 valid_sets=lgb_eval,
29 num_boost_round=NUM_ROUND,
30 verbose_eval=VERBOSE_EVAL
31 )
32
33 y_valid_pred = model.predict(X_valid)
34 score = mean_absolute_error(y_valid, y_valid_pred)
35 print(f'fold {fold} MAE: {score}')
36 valid_scores.append(score)
37
38 models.append(model)
39
40cv_score = np.mean(valid_scores)
41print(f'CV score: {cv_score}')
Stratified k-fold(層化k分割交差検証)
各foldに含まれるクラスの割合を等しくするk分割交差検証です。
分類問題で使用され、テストデータと学習データのクラスの割合が等しいと仮定される時に使用される手法です。多クラス分類問題で、ランダムに分割すると各foldのクラスの割合が偏ってしまうような場合(例えば頻度の少ないクラスがある場合)に重要となってきます。
scikit-learnのドキュメントより
テンプレ
- scikit-learnの
StratifiedKFold
を利用 - 多クラス分類を想定
- 評価関数はlog loss
1from sklearn.metrics import log_loss
2from sklearn.model_selection import StratifiedKFold
3import lightgbm as lgb
4import numpy as np
5
6FOLD = 5
7NUM_ROUND = 100
8VERBOSE_EVAL = -1
9NUM_CLASS = 3
10
11params = {
12 'objective': 'multiclass',
13 'verbose': -1,
14 'num_class': NUM_CLASS
15}
16
17valid_scores = []
18models = []
19kf = StratifiedKFold(n_splits=FOLD, shuffle=True, random_state=42)
20
21for fold, (train_indices, valid_indices) in enumerate(kf.split(X, y)):
22 X_train, X_valid = X[train_indices], X[valid_indices]
23 y_train, y_valid = y[train_indices], y[valid_indices]
24 lgb_train = lgb.Dataset(X_train, y_train)
25 lgb_eval = lgb.Dataset(X_valid, y_valid)
26
27 model = lgb.train(
28 params,
29 lgb_train,
30 valid_sets=lgb_eval,
31 num_boost_round=NUM_ROUND,
32 verbose_eval=VERBOSE_EVAL
33 )
34
35 y_valid_pred = model.predict(X_valid)
36 score = log_loss(y_valid, y_valid_pred)
37 print(f'fold {fold} log loss: {score}')
38 valid_scores.append(score)
39
40 models.append(model)
41
42cv_score = np.mean(valid_scores)
43print(f'CV score: {cv_score}')
Stratified Shuffle Split
Stratified k-foldのShuffle Split版になります。
クラスの割合を保ったままShuffle Splitをおこないます。Shuffle Split同様、検証データにならないデータがある可能性があります。
scikit-learnのドキュメントより
テンプレ
- scikit-learnの
StratifiedShuffleSplit
を利用 - 多クラス分類を想定
- 評価関数はlog loss
- 検証データは25%
1from sklearn.metrics import log_loss
2from sklearn.model_selection import StratifiedShuffleSplit
3import lightgbm as lgb
4import numpy as np
5
6FOLD = 5
7NUM_ROUND = 100
8VERBOSE_EVAL = -1
9NUM_CLASS = 3
10
11params = {
12 'objective': 'multiclass',
13 'verbose': -1,
14 'num_class': NUM_CLASS
15}
16
17valid_scores = []
18models = []
19sss = StratifiedShuffleSplit(n_splits=FOLD, test_size=0.25, random_state=42)
20
21for fold, (train_indices, valid_indices) in enumerate(sss.split(X, y)):
22 X_train, X_valid = X[train_indices], X[valid_indices]
23 y_train, y_valid = y[train_indices], y[valid_indices]
24 lgb_train = lgb.Dataset(X_train, y_train)
25 lgb_eval = lgb.Dataset(X_valid, y_valid)
26
27 model = lgb.train(
28 params,
29 lgb_train,
30 valid_sets=lgb_eval,
31 num_boost_round=NUM_ROUND,
32 verbose_eval=VERBOSE_EVAL
33 )
34
35 y_valid_pred = model.predict(X_valid)
36 score = log_loss(y_valid, y_valid_pred)
37 print(f'fold {fold} log loss: {score}')
38 valid_scores.append(score)
39
40 models.append(model)
41
42cv_score = np.mean(valid_scores)
43print(f'CV score: {cv_score}')
Group k-fold(グループk分割交差検証)
同じグループ(顧客や被験者など特定の人物を表すものなど)が同じfoldになるようにデータを分割するk分割交差検証です。
テストデータで学習データのグループが現れないような場合に利用します。つまり、未知のグループを予測するような問題であるため、同じグループが異なるfoldに存在してしまうと、検証データにそのグループが含まれている時に予測しやすくなってしまい、適切な検証ができなくなってしまいます。
scikit-learnのドキュメントより
テンプレ
- scikit-learnの
GroupKFold
を利用- GroupKFoldはシャッフルと乱数の指定ができない
- 回帰問題を想定
- 評価関数はMAE
1from sklearn.metrics import mean_absolute_error
2from sklearn.model_selection import GroupKFold
3import lightgbm as lgb
4import numpy as np
5
6FOLD = 5
7NUM_ROUND = 100
8VERBOSE_EVAL = -1
9
10params = {
11 'objective': 'regression',
12 'verbose': -1,
13}
14
15group = train['id']
16valid_scores = []
17models = []
18kf = GroupKFold(n_splits=FOLD)
19
20for fold, (train_indices, valid_indices) in enumerate(kf.split(X, y, group)):
21 X_train, X_valid = X[train_indices], X[valid_indices]
22 y_train, y_valid = y[train_indices], y[valid_indices]
23 lgb_train = lgb.Dataset(X_train, y_train)
24 lgb_eval = lgb.Dataset(X_valid, y_valid)
25
26 model = lgb.train(
27 params,
28 lgb_train,
29 valid_sets=lgb_eval,
30 num_boost_round=NUM_ROUND,
31 verbose_eval=VERBOSE_EVAL
32 )
33
34 y_valid_pred = model.predict(X_valid)
35 score = mean_absolute_error(y_valid, y_valid_pred)
36 print(f'fold {fold} MAE: {score}')
37 valid_scores.append(score)
38
39 models.append(model)
40
41cv_score = np.mean(valid_scores)
42print(f'CV score: {cv_score}')
Group Shuffle Split
Group k-foldのShuffle Split版になります。
検証データで学習データのグループが現れないようにShuffle Splitをおこないます。Shuffle Split同様、検証データにならないデータがある可能性があります。
scikit-learnのドキュメントより
テンプレ
- scikit-learnの
GroupShuffleSplit
を利用 - 回帰問題を想定
- 評価関数はMAE
- 検証データは25%
1from sklearn.metrics import mean_absolute_error
2from sklearn.model_selection import GroupShuffleSplit
3import lightgbm as lgb
4import numpy as np
5
6FOLD = 5
7NUM_ROUND = 100
8VERBOSE_EVAL = -1
9
10params = {
11 'objective': 'regression',
12 'verbose': -1,
13}
14
15group = train['id']
16valid_scores = []
17models = []
18gss = GroupShuffleSplit(n_splits=FOLD, test_size=0.25, random_state=42)
19
20for fold, (train_indices, valid_indices) in enumerate(gss.split(X, y, group)):
21 X_train, X_valid = X[train_indices], X[valid_indices]
22 y_train, y_valid = y[train_indices], y[valid_indices]
23 lgb_train = lgb.Dataset(X_train, y_train)
24 lgb_eval = lgb.Dataset(X_valid, y_valid)
25
26 model = lgb.train(
27 params,
28 lgb_train,
29 valid_sets=lgb_eval,
30 num_boost_round=NUM_ROUND,
31 verbose_eval=VERBOSE_EVAL
32 )
33
34 y_valid_pred = model.predict(X_valid)
35 score = mean_absolute_error(y_valid, y_valid_pred)
36 print(f'fold {fold} MAE: {score}')
37 valid_scores.append(score)
38
39 models.append(model)
40
41cv_score = np.mean(valid_scores)
42print(f'CV score: {cv_score}')
まとめ
今回は、以下のクロスバリデーションのテンプレを用意しました。
- K Fold
- Shuffle Split
- Stratified K Fold
- Stratified Shuffle Split
- Group K Fold
- Group Shuffle Split
ただし、なんでもこの6つでいいというわけではないので、問題によって適切なクロスバリデーションの手法を選択するのが重要になります。
参考
- API Reference — scikit-learn 0.24.0 documentation
- 3.1. Cross-validation: evaluating estimator performance — scikit-learn 0.24.1 documentation
- Kaggleに登録したら次にやること ~ これだけやれば十分闘える!Titanicの先へ行く入門 10 Kernel ~ - Qiita
- python - What's the difference between KFold and ShuffleSplit CV? - Stack Overflow