はじめに
以前[Python 決定木 vs LightGBM 精度差を徹底比較してみた]を投稿しましたが、
最近、師匠から5-fold クロスバリデーション を教えてもらいました。
…え、クロスバリデーション?知らなかった…😇
ということで、今回は 5-fold クロスバリデーション を取り入れてコードをバージョンアップしてみました。
5-fold クロスバリデーションを使うと何が良いの❓
1️⃣ モデルの性能をしっかり評価できる
データを5つに分けて順番にテストするので、1回の分割に左右されずに、モデルの本当の性能がわかります。
2️⃣ 評価のばらつきが少なくなる
AccuracyやF1スコアを複数回計算して平均するので、数値が安定して「だいたいこれくらい」と安心して見られます。
3️⃣ データをムダなく使える
少ないデータでも、全部のデータを訓練とテストで活用できるので、学習効率がアップします
5-fold クロスバリデーションとは
「1回だけの分割に頼らず、何回もデータを分けて学習→評価して平均を取る」 という方法です。
5-fold クロスバリデーションの例
データを5つに分けた場合のイメージです
データ: [D1 D2 D3 D4 D5] (5 fold)
1回目: 訓練=[D2,D3,D4,D5], テスト=[D1]
2回目: 訓練=[D1,D3,D4,D5], テスト=[D2]
3回目: 訓練=[D1,D2,D4,D5], テスト=[D3]
4回目: 訓練=[D1,D2,D3,D5], テスト=[D4]
5回目: 訓練=[D1,D2,D3,D4], テスト=[D5]
💡ポイント
- 1回だけの分割に依存せず、平均で安定した評価 ができる
-
fold
ごとの精度のばらつきは、標準偏差 で確認できる - イメージとしては「模試を何回も受けて平均点を出す」感じです
コード
1️⃣ 前回コードのおさらい
# train_test_split で 1回だけ分割して評価
X_train, X_test, y_train, y_test = train_test_split(
X_df, y_enc, test_size=0.25, random_state=42, stratify=y_enc
)
dt_model = DecisionTreeClassifier(random_state=42)
dt_model.fit(X_train, y_train)
y_pred_dt = dt_model.predict(X_test)
lgb_model = LGBMClassifier(random_state=42)
lgb_model.fit(X_train, y_train)
y_pred_lgb = lgb_model.predict(X_test)
print("【DecisionTree】 Accuracy:", accuracy_score(y_test, y_pred_dt))
print("【DecisionTree】 F1 Score:", f1_score(y_test, y_pred_dt, average="weighted"))
print("【LightGBM】 Accuracy:", accuracy_score(y_test, y_pred_lgb))
print("【LightGBM】 F1 Score:", f1_score(y_test, y_pred_lgb, average="weighted"))
2️⃣ 5-fold クロスバリデーション に書き換えてみます
from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
dt_acc, dt_f1 = [], []
lgb_acc, lgb_f1 = [], []
for train_idx, test_idx in skf.split(X_df, y_enc):
X_train, X_test = X_df.iloc[train_idx], X_df.iloc[test_idx]
y_train, y_test = y_enc[train_idx], y_enc[test_idx]
# DecisionTree
dt_model = DecisionTreeClassifier(random_state=42)
dt_model.fit(X_train, y_train)
y_pred_dt = dt_model.predict(X_test)
dt_acc.append(accuracy_score(y_test, y_pred_dt))
dt_f1.append(f1_score(y_test, y_pred_dt, average="weighted"))
# LightGBM
lgb_model = LGBMClassifier(random_state=42)
lgb_model.fit(X_train, y_train)
y_pred_lgb = lgb_model.predict(X_test)
lgb_acc.append(accuracy_score(y_test, y_pred_lgb))
lgb_f1.append(f1_score(y_test, y_pred_lgb, average="weighted"))
print(f"【DecisionTree】 Accuracy: {np.mean(dt_acc):.3f} ± {np.std(dt_acc):.3f}")
print(f"【DecisionTree】 F1 Score: {np.mean(dt_f1):.3f} ± {np.std(dt_f1):.3f}")
print(f"【LightGBM】 Accuracy: {np.mean(lgb_acc):.3f} ± {np.std(lgb_acc):.3f}")
print(f"【LightGBM】 F1 Score: {np.mean(lgb_f1):.3f} ± {np.std(lgb_f1):.3f}")
評価方法の部分だけで、データ前処理やモデルの宣言などは前回記事と同じです。
実行結果の比較
1️⃣ 単一分割(train_test_split)👈前回の
【DecisionTree】 Accuracy: 0.65
【DecisionTree】 F1 Score: 0.641
【LightGBM】 Accuracy: 0.73
【LightGBM】 F1 Score: 0.727
- 1回だけの分割で評価
- データの分け方によって結果が左右されやすい
2️⃣ 5-fold クロスバリデーション
【DecisionTree】 Accuracy: 0.680 ± 0.032
【DecisionTree】 F1 Score: 0.666 ± 0.034
【LightGBM】 Accuracy: 0.710 ± 0.024
【LightGBM】 F1 Score: 0.706 ± 0.026
- データを5分割して5回評価 → 平均値を算出
- ± の値は fold ごとの標準偏差
- DecisionTree は平均的に少し改善(0.65 → 0.68)
- LightGBM は平均的に少し低下(0.73 → 0.71)
💡 ポイント
- CV 平均は 真の性能に近い推定値
- 標準偏差を見ることで精度の安定性が確認できる
- 数字が微妙に変わるのは正しい挙動
❓なんで数字が微妙に変わるの❓
単一分割(train_test_split)では、テストデータと訓練データを 1回だけ分けて評価 しています。
そのため、その1回の結果次第で精度がちょっと高くなったり低くなったりすることがあります。
一方、5-fold クロスバリデーション では次のように評価します
- データを5つの
fold
に分ける - それぞれを順番にテストデータにして学習&評価
- 5回分の精度を平均 → CV 平均
このとき fold
の分け方や、LightGBM の初期値など 学習のちょっとしたランダム性 によって、各 fold
の精度は少しずつ変わることがあります。
その結果、平均値も単一分割のときとは少し異なるのが普通です。
💡ポイント
-
単一分割
→ 1回の結果がそのまま精度 -
k-fold CV
→ 複数回の結果を平均するので単一分割とは少しずれる -
±
の値(標準偏差)で、どれくらい精度が揺れるか確認できる
数字が微妙に変わるのは正常な挙動。
CV 平均を見ることで、モデルの 本当の実力に近い値 を知ることができます。
foldとは
クロスバリデーションでデータを分割した「ひと区切り(グループ)」 のことです。
-
fold
= データを分割した1グループ -
k-fold CV
では、データを k個に分けて順番にテスト* します
例:5つのグループを順番にテストデータとして使います。
CV 平均とは
CV 平均 = クロスバリデーションでの評価値の平均 です。
例:データを5分割して5回学習・評価したとします。それぞれの Accuracy
は次の通りです
Fold1: Accuracy = 0.65
Fold2: Accuracy = 0.70
Fold3: Accuracy = 0.68
Fold4: Accuracy = 0.66
Fold5: Accuracy = 0.72
これを平均すると
CV 平均 Accuracy = (0.65 + 0.70 + 0.68 + 0.66 + 0.72) / 5
= 0.682
これが CV 平均 です。
複数回の評価結果を平均することで、モデルの 本当の実力に近い値 を知ることができます。
平均を使う意味
1回の train_test_split だと分け方次第で結果が変わる ため信頼性が低くなります。
一方、CV 平均 を使うと、複数回の評価結果を平均することで ばらつきが慣らされ、より安定した値 を得られるメリットがあります。
まとめ
- 単一分割の評価は 分割方法によって結果が変わりやすく 評価が安定しません
- 5-fold クロスバリデーションを使うと、より安定した推定値 を得られます
- 標準偏差を確認することで、モデルの 精度の安定性 も把握できます
今回の検証では、LightGBM が必ずしも圧倒的に強いわけではないことがわかりました。