kaggleでよく使う交差検証テンプレ(LightGBM向け)

2021.06.28
2024.03.24
Kaggle
PythonLightGBMscikit-learn交差検証

本ページはAmazonアフィリエイトのリンクを含みます。

はじめに

kaggleなどの機械学習コンペでLightGBMを使ってクロスバリデーションをする際のテンプレとなる型をまとめました。

Kerasでのテンプレは以下でまとめています。内容については重複している部分もあるので、適宜読み飛ばしてください。

kaggleでよく使う交差検証テンプレ(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つでいいというわけではないので、問題によって適切なクロスバリデーションの手法を選択するのが重要になります。

参考

Support

\ この記事が役に立ったと思ったら、サポートお願いします! /

buy me a coffee
Share

Profile

author

Masa

都内のIT企業で働くエンジニア
自分が学んだことをブログでわかりやすく発信していきながらスキルアップを目指していきます!

buy me a coffee