📚 背景知識(読んでから問題へ)
🎯
Day 063 — Day 061(XGBoost)、Day 062(LightGBM)に続き、3大GBDTの最後の一つ CatBoost を学びます。Yandex 開発。「カテゴリカル変数の王様」かつ「過学習防止の革新」。
CatBoost が解決した2つの問題
| 問題 | 他のGBDTでの状況 | CatBoostの解決策 |
|---|---|---|
| カテゴリカル変数の処理 | ラベルエンコードやOne-Hotが必要。情報損失やメモリ増加 | 文字列のまま渡せる。内部で Ordered Target Statistics を自動計算 |
| ターゲットリーク(過学習) | 残差計算時に自分自身の y を含む → 過学習バイアス | Ordered Boosting: 自分より前のデータのみで残差を計算 → リークなし |
💡
直感的な例え: 通常のGBDT = 自分が書いた答案を自分で採点(採点が甘くなる = 過学習)。CatBoost = 自分より前に座っている人の答案だけ参考にして採点(公平・リークなし)
🔬 3大GBDT 総合比較
XGBoost
- 木の成長: level-wise
- カテゴリカル: ラベルエンコード必須
- 過学習対策: L1/L2正則化
- デフォルト性能: 普通(要チューニング)
- 大規模速度: 普通
- 小規模データ: 良好
- GPU対応: あり
- 主なパラメータ: max_depth, n_estimators
LightGBM
- 木の成長: leaf-wise(最大損失の葉)
- カテゴリカル: dtype='category' で直接
- 過学習対策: num_leaves + min_child_samples
- デフォルト性能: 普通(要チューニング)
- 大規模速度: 最速
- 小規模データ: 良好
- GPU対応: あり
- 主なパラメータ: num_leaves, learning_rate
CatBoost
- 木の成長: symmetric(対称木)
- カテゴリカル: 文字列のままOK
- 過学習対策: Ordered Boosting
- デフォルト性能: 高(チューニング不要も多い)
- 大規模速度: やや遅い(GPU推奨)
- 小規模データ: 特に強い
- GPU対応: あり(高速)
- 主なパラメータ: depth, iterations
3大GBDT の性能比較(相対評価)
大規模データ(>10万行)の学習速度
カテゴリカル変数が多い場合のCV精度
小規模データ(<1000行)でのデフォルト性能
🔄 Ordered Boosting — 情報リークを防ぐ仕組み
「自分自身の y を使って自分を予測する」バイアスを排除する
🏷️ Ordered Target Statistics — カテゴリカル処理の仕組み
3種類のカテゴリカル処理を比較する
| 方法 | 計算式 | リスク | CatBoostの対応 |
|---|---|---|---|
| One-Hot Encoding | カテゴリ数だけ列を追加(0/1) | 高カーディナリティで次元爆発 | — |
| Label Encoding | カテゴリに番号を付与(A=0,B=1,...) | 順序情報が入り込む | — |
| Target Encoding(通常) | 各カテゴリの目的変数平均 | 自身が含まれリーク発生 | — |
| Ordered Target Statistics | 自分より前のデータの目的変数平均 | リークなし | 自動適用 |
CatBoostへのカテゴリカル変数の渡し方
| 方法 | コード | 推奨度 |
|---|---|---|
| cat_features にカラム名 | CatBoostClassifier(cat_features=['Sex','Embarked']) |
★★★ DataFrameなら最も簡単 |
| cat_features にインデックス | CatBoostClassifier(cat_features=[1, 6]) |
★★★ numpy配列使用時 |
| Pool で指定 | Pool(X, label=y, cat_features=['Sex']) |
★★★ 大規模・再利用する場合に最適 |
🌲 対称木(Symmetric Tree)の構造
各深さで全ノードが同じ分岐条件を使う — ルックアップテーブルで超高速推論
対称木は推論速度が非常に速く、正則化効果(対称な構造が過学習を防ぐ)もある。 表現力はやや劣るため、depth を 6〜10 程度にして補う(XGBoostより深くする必要がある)。
⚙️ 主要パラメータ
iterations
木の本数(n_estimators 相当)。デフォルト=1000。 早期終了と組み合わせるのが定石。チューニングでは500〜3000。
depth
対称木の深さ。デフォルト=6。 XGBoostと同じ max_depth 相当。4〜10が多い。深くすると過学習。
learning_rate
各木の寄与縮小率。デフォルト=0.03(迷ったら)。 低い値は高精度だが遅い。
l2_leaf_reg
L2正則化係数。デフォルト=3。 大きくすると過学習抑制。XGBoostの reg_lambda 相当。
cat_features
カテゴリカル特徴量の列名またはインデックスリスト。必ず指定。 指定しないと文字列はエラー。
boosting_type
'Ordered'(デフォルト、リークなし)または 'Plain'(通常GBDT相当)。小規模データでは Ordered が有利。
verbose
必ず 0 または False を設定。 デフォルトで大量ログ。verbose=0 でサイレント化。
random_seed
乱数シード。XGBoostの random_state / LightGBMの random_state 相当。CatBoostは random_seed と呼ぶ(注意)。
🗂️ データスキーマ(Titanic 拡張版 n=5,000)
| 列名 | 型 | 値の範囲 | 説明 | CatBoostでの取扱い |
|---|---|---|---|---|
Pclass | int | 1, 2, 3 | 旅客クラス | 数値として使用 |
Sex | str | male, female | 性別 | 文字列のまま cat_features に指定 |
Age | float | 1〜80 | 年齢 | NaN自動処理 |
SibSp | int | 0〜8 | 同乗兄弟/配偶者数 | 数値として使用 |
Parch | int | 0〜6 | 同乗親/子供数 | 数値として使用 |
Fare | float | 0〜∞ | 運賃 | 数値として使用 |
Embarked | str | S, C, Q | 乗船港 | 文字列のまま cat_features に指定 |
Cabin_type | str | A,B,C,D,E,None | キャビン種別 | 文字列のまま cat_features に指定 |
Survived | int | 0 / 1 | 生存(目的変数) | — |
📝 問題
セットアップコード(最初に実行)
import numpy as np
import pandas as pd
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.preprocessing import LabelEncoder
import time
import warnings
warnings.filterwarnings('ignore')
try:
from catboost import CatBoostClassifier, Pool
print("CatBoost: OK")
except ImportError:
print("pip install catboost")
try:
import lightgbm as lgb
print("LightGBM: OK")
except ImportError:
print("pip install lightgbm")
try:
import xgboost as xgb
print("XGBoost: OK")
except ImportError:
print("pip install xgboost")
np.random.seed(42)
n = 5000
data = pd.DataFrame({
'Pclass': np.random.choice([1, 2, 3], n, p=[0.24, 0.21, 0.55]),
'Sex': np.random.choice(['male', 'female'], n, p=[0.65, 0.35]),
'Age': np.random.normal(29.7, 14.5, n).clip(1, 80),
'SibSp': np.random.choice(range(9), n, p=[0.68,0.23,0.04,0.02,0.01,0.01,0.005,0.005,0.01]),
'Parch': np.random.choice(range(7), n, p=[0.76,0.13,0.09,0.005,0.005,0.005,0.005]),
'Fare': np.abs(np.random.normal(32, 50, n)),
'Embarked': np.random.choice(['S', 'C', 'Q'], n, p=[0.72, 0.19, 0.09]),
'Cabin_type': np.random.choice(['A','B','C','D','E','None'], n, p=[0.05,0.08,0.12,0.1,0.07,0.58]),
})
data['Survived'] = (
(data['Sex'] == 'female') * 0.55 + (data['Pclass'] == 1) * 0.25 +
(data['Age'] < 16) * 0.2 + np.random.normal(0, 0.12, n)
) > 0.4
data['Survived'] = data['Survived'].astype(int)
cat_features = ['Sex', 'Embarked', 'Cabin_type']
data_enc = data.copy()
for col in cat_features:
data_enc[col] = LabelEncoder().fit_transform(data_enc[col])
features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked', 'Cabin_type']
X_raw = data[features]
X_enc = data_enc[features].values
y = data['Survived'].values
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
問1 — CatBoost の基本使い方(文字列のまま渡す)
CatBoostClassifier(iterations=200, verbose=0)を使い、cat_features=['Sex','Embarked','Cabin_type']を指定して学習せよ- 文字列カテゴリカルのままデータを渡せることを確認せよ(エンコード不要)
- CV精度と訓練精度を出力せよ
問2 — 3大GBDT(XGBoost / LightGBM / CatBoost)の総合比較
- 学習時間(time.time() で計測)を比較せよ
- CV精度(StratifiedKFold 5分割)を比較せよ
- 訓練精度(過学習度 = 訓練精度 - CV精度)を比較せよ
- どのモデルがこのデータセットに最も向いているか理由も述べよ
問3 — CatBoost の主要パラメータ実験
depth:[3, 5, 6, 8, 10]で CV精度と過学習度を確認せよlearning_rate:[0.01, 0.05, 0.1, 0.3](iterations=200 固定)で CV精度を確認せよl2_leaf_reg:[1, 3, 10, 30]で過学習度がどう変わるか確認せよ
問4 — Pool オブジェクトの使い方
Pool(X_raw, label=y, cat_features=['Sex','Embarked','Cabin_type'])を作成せよCatBoostClassifier.fit(pool)で学習せよget_feature_importance(pool)で特徴量重要度を取得・表示せよ- Pool を使う利点を2点説明せよ
問5 — Ordered vs Plain Boosting
boosting_type='Ordered'とboosting_type='Plain'を比較せよ- 小規模データ(n=200)と通常データ(n=5000)でどちらの差が大きいか確認せよ
- なぜ小規模データで Ordered が有利かを説明せよ
💡 ヒント
ヒント1(方向性)
pip install catboostでインストール。sklearn API があるのでCatBoostClassifierを他のGBDTと同じように使える- CatBoostは文字列のカテゴリカルをそのまま渡せる。
cat_featuresにカラム名またはインデックスを指定 verbose=0を必ず指定。デフォルトだと大量のログが出るcross_val_scoreは X_raw(DataFrameのまま)を渡すと cat_features の情報が保持されないため、手動CVが安全
ヒント2(アプローチ)
from catboost import CatBoostClassifier, Pool
# 基本的な使い方
clf_cat = CatBoostClassifier(
iterations=200,
depth=6,
learning_rate=0.1,
l2_leaf_reg=3,
cat_features=['Sex', 'Embarked', 'Cabin_type'],
random_seed=42,
verbose=0
)
# 文字列のまま渡せる!
clf_cat.fit(X_raw, y)
print(f"訓練精度: {clf_cat.score(X_raw, y):.4f}")
# Pool オブジェクト
pool = Pool(X_raw, label=y, cat_features=['Sex', 'Embarked', 'Cabin_type'])
clf_cat.fit(pool)
importances = clf_cat.get_feature_importance(pool)
ヒント3(コード骨格)
# DataFrameにも配列にも対応した手動CV
from sklearn.metrics import accuracy_score
def manual_cv(clf, X, y, cv):
scores, tr_scores = [], []
for tr_idx, val_idx in cv.split(X, y):
if isinstance(X, pd.DataFrame):
X_tr, X_val = X.iloc[tr_idx], X.iloc[val_idx]
else:
X_tr, X_val = X[tr_idx], X[val_idx]
y_tr, y_val = y[tr_idx], y[val_idx]
clf.fit(X_tr, y_tr)
scores.append(accuracy_score(y_val, clf.predict(X_val)))
tr_scores.append(accuracy_score(y_tr, clf.predict(X_tr)))
return np.mean(scores), np.mean(tr_scores)
# 問5: Ordered vs Plain
for size, label in [(200, "n=200"), (5000, "n=5000")]:
idx = np.random.choice(n, size, replace=False)
X_sub, y_sub = X_raw.iloc[idx], y[idx]
for btype in ['Ordered', 'Plain']:
cv_s, tr_s = manual_cv(
CatBoostClassifier(iterations=200, boosting_type=btype,
cat_features=['Sex','Embarked','Cabin_type'],
random_seed=42, verbose=0),
X_sub, y_sub, cv
)
print(f"[{label}] {btype}: CV={cv_s:.4f}, 過学習={tr_s-cv_s:.4f}")
✅ 模範解答
import numpy as np
import pandas as pd
import time
from catboost import CatBoostClassifier, Pool
import lightgbm as lgb
import xgboost as xgb
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import accuracy_score
import warnings
warnings.filterwarnings('ignore')
# ── データ準備 ──
np.random.seed(42)
n = 5000
data = pd.DataFrame({
'Pclass': np.random.choice([1, 2, 3], n, p=[0.24, 0.21, 0.55]),
'Sex': np.random.choice(['male', 'female'], n, p=[0.65, 0.35]),
'Age': np.random.normal(29.7, 14.5, n).clip(1, 80),
'SibSp': np.random.choice(range(9), n, p=[0.68,0.23,0.04,0.02,0.01,0.01,0.005,0.005,0.01]),
'Parch': np.random.choice(range(7), n, p=[0.76,0.13,0.09,0.005,0.005,0.005,0.005]),
'Fare': np.abs(np.random.normal(32, 50, n)),
'Embarked': np.random.choice(['S', 'C', 'Q'], n, p=[0.72, 0.19, 0.09]),
'Cabin_type': np.random.choice(['A','B','C','D','E','None'], n, p=[0.05,0.08,0.12,0.1,0.07,0.58]),
})
data['Survived'] = (
(data['Sex'] == 'female') * 0.55 + (data['Pclass'] == 1) * 0.25 +
(data['Age'] < 16) * 0.2 + np.random.normal(0, 0.12, n)
) > 0.4
data['Survived'] = data['Survived'].astype(int)
cat_features = ['Sex', 'Embarked', 'Cabin_type']
data_enc = data.copy()
for col in cat_features:
data_enc[col] = LabelEncoder().fit_transform(data_enc[col])
features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked', 'Cabin_type']
X_raw = data[features]
X_enc = data_enc[features].values
y = data['Survived'].values
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
def manual_cv(clf, X, y, cv):
scores, tr_scores = [], []
for tr_idx, val_idx in cv.split(X, y):
if isinstance(X, pd.DataFrame):
X_tr, X_val = X.iloc[tr_idx], X.iloc[val_idx]
else:
X_tr, X_val = X[tr_idx], X[val_idx]
y_tr, y_val = y[tr_idx], y[val_idx]
clf.fit(X_tr, y_tr)
scores.append(accuracy_score(y_val, clf.predict(X_val)))
tr_scores.append(accuracy_score(y_tr, clf.predict(X_tr)))
return np.mean(scores), np.mean(tr_scores)
# ── 問1: CatBoost 基本使い方 ──
print("=" * 65)
print("問1: CatBoost 基本使い方(文字列カテゴリカルをそのまま渡す)")
print("=" * 65)
clf_cat = CatBoostClassifier(
iterations=200, depth=6, learning_rate=0.1,
cat_features=['Sex', 'Embarked', 'Cabin_type'],
random_seed=42, verbose=0
)
clf_cat.fit(X_raw, y)
tr = clf_cat.score(X_raw, y)
cv_s, _ = manual_cv(
CatBoostClassifier(iterations=200, depth=6, learning_rate=0.1,
cat_features=['Sex','Embarked','Cabin_type'],
random_seed=42, verbose=0),
X_raw, y, cv
)
print(f" 訓練精度: {tr:.4f}")
print(f" CV精度: {cv_s:.4f}")
print(f" 過学習度: {tr - cv_s:.4f}")
print(" → 文字列カテゴリカルをそのまま渡せることを確認!")
# ── 問2: 3大GBDT 総合比較 ──
print("\n" + "=" * 65)
print("問2: 3大GBDT 総合比較(XGBoost / LightGBM / CatBoost)")
print("=" * 65)
models = [
("XGBoost ", xgb.XGBClassifier(n_estimators=200, eval_metric='logloss', random_state=42), X_enc),
("LightGBM", lgb.LGBMClassifier(n_estimators=200, random_state=42, verbose=-1), X_enc),
("CatBoost", CatBoostClassifier(iterations=200, cat_features=['Sex','Embarked','Cabin_type'],
random_seed=42, verbose=0), X_raw),
]
for name, clf, X_use in models:
t0 = time.time()
cv_s, tr_s = manual_cv(clf.__class__(**clf.get_params()), X_use, y, cv)
t_cv = time.time() - t0
clf.fit(X_use, y)
tr = clf.score(X_use, y) if hasattr(clf, 'score') else accuracy_score(y, clf.predict(X_use))
print(f"[{name}] CV={cv_s:.4f}, 訓練={tr:.4f}, 過学習={tr-cv_s:.4f}, 時間={t_cv:.2f}s")
# ── 問3: パラメータ実験 ──
print("\n" + "=" * 65)
print("問3: 主要パラメータ実験")
print("=" * 65)
print(" [3-1] depth:")
for d in [3, 5, 6, 8, 10]:
cv_s, tr_s = manual_cv(
CatBoostClassifier(iterations=200, depth=d,
cat_features=['Sex','Embarked','Cabin_type'],
random_seed=42, verbose=0),
X_raw, y, cv
)
star = " ★デフォルト" if d == 6 else ""
print(f" depth={d:2d}: CV={cv_s:.4f}, 訓練={tr_s:.4f}, 過学習={tr_s-cv_s:.4f}{star}")
print(" [3-2] learning_rate:")
for lr in [0.01, 0.05, 0.1, 0.3]:
cv_s, _ = manual_cv(
CatBoostClassifier(iterations=200, learning_rate=lr,
cat_features=['Sex','Embarked','Cabin_type'],
random_seed=42, verbose=0),
X_raw, y, cv
)
print(f" lr={lr:.2f}: CV={cv_s:.4f}")
print(" [3-3] l2_leaf_reg:")
for l2 in [1, 3, 10, 30]:
cv_s, tr_s = manual_cv(
CatBoostClassifier(iterations=200, l2_leaf_reg=l2,
cat_features=['Sex','Embarked','Cabin_type'],
random_seed=42, verbose=0),
X_raw, y, cv
)
star = " ★デフォルト" if l2 == 3 else ""
print(f" l2={l2:2d}: CV={cv_s:.4f}, 過学習={tr_s-cv_s:.4f}{star}")
# ── 問4: Pool ──
print("\n" + "=" * 65)
print("問4: Pool オブジェクト")
print("=" * 65)
pool = Pool(X_raw, label=y, cat_features=['Sex', 'Embarked', 'Cabin_type'])
clf_pool = CatBoostClassifier(iterations=200, random_seed=42, verbose=0)
clf_pool.fit(pool)
print(f" Pool で学習完了: 訓練精度={clf_pool.score(pool):.4f}")
print("\n 特徴量重要度:")
importances = clf_pool.get_feature_importance(pool)
for feat, imp in sorted(zip(features, importances), key=lambda x: -x[1]):
bar = "█" * int(imp / max(importances) * 30)
print(f" {feat:12s}: {imp:6.2f} {bar}")
print("\n Pool の利点:")
print(" 1. cat_features をデータとセットで管理(渡し忘れ防止)")
print(" 2. 大規模データの前処理をPoolに任せて効率化")
# ── 問5: Ordered vs Plain ──
print("\n" + "=" * 65)
print("問5: Ordered vs Plain Boosting")
print("=" * 65)
for size, label in [(200, "小規模 n=200"), (5000, "通常 n=5000")]:
idx = np.random.choice(n, size, replace=False)
X_sub, y_sub = X_raw.iloc[idx], y[idx]
cv_sub = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
results = {}
for btype in ['Ordered', 'Plain']:
cv_s, tr_s = manual_cv(
CatBoostClassifier(iterations=200, boosting_type=btype,
cat_features=['Sex','Embarked','Cabin_type'],
random_seed=42, verbose=0),
X_sub, y_sub, cv_sub
)
results[btype] = (cv_s, tr_s)
print(f"\n [{label}]")
for btype, (cv_s, tr_s) in results.items():
print(f" {btype:7s}: CV={cv_s:.4f}, 過学習={tr_s-cv_s:.4f}")
diff = results['Ordered'][0] - results['Plain'][0]
print(f" Ordered の優位性: {diff:+.4f}")
print("\n 考察: 小規模データほど Ordered が有利(n が小さいほど1点の情報漏洩の割合が大)")
🪜 Step-by-Step 解説
1CatBoost のインストールと基本確認
# pip install catboost
from catboost import CatBoostClassifier
# sklearn API は XGBoost / LightGBM と同じインターフェース
clf = CatBoostClassifier(
iterations=200, # n_estimators 相当
depth=6, # 対称木の深さ(デフォルト=6)
learning_rate=0.1,
l2_leaf_reg=3, # L2正則化(デフォルト=3)
cat_features=['Sex', 'Embarked'],
random_seed=42,
verbose=0 # ログ非表示(必須)
)
import pandas as pd
X_demo = pd.DataFrame({'Sex': ['male','female','male'], 'Age': [25, 30, 22]})
y_demo = [0, 1, 0]
clf_demo = CatBoostClassifier(iterations=10, cat_features=['Sex'], verbose=0)
clf_demo.fit(X_demo, y_demo)
print("文字列カテゴリカルをそのまま学習できた!")
2Pool の詳細な使い方
from catboost import Pool
pool_train = Pool(
data=X_raw,
label=y,
cat_features=['Sex', 'Embarked', 'Cabin_type'],
)
clf = CatBoostClassifier(iterations=200, verbose=0)
clf.fit(pool_train)
preds = clf.predict(pool_train)
importances = clf.get_feature_importance(pool_train)
print("特徴量重要度:", importances)
3Ordered Boosting の直感をシミュレーション
import numpy as np
np.random.seed(42)
n_small = 10
y_small = np.array([0,1,0,1,1,0,1,0,1,0])
# 通常のTarget Encoding(情報リーク)
target_mean_all = y_small.mean()
print("全データ平均(リークあり):", target_mean_all)
# Ordered Target Statistics(リークなし)
print("\nOrdered Target Statistics(リークなし):")
for i in range(n_small):
if i == 0:
ts = 0.5 # 最初は事前分布
else:
ts = y_small[:i].mean()
print(f" データ点{i}: y={y_small[i]}, TS={ts:.3f}")
43大GBDT アンサンブルの標準パターン
from sklearn.model_selection import cross_val_predict
from sklearn.metrics import accuracy_score
oof_xgb = cross_val_predict(xgb.XGBClassifier(n_estimators=200, random_state=42), X_enc, y, cv=cv)
oof_lgb = cross_val_predict(lgb.LGBMClassifier(n_estimators=200, random_state=42, verbose=-1), X_enc, y, cv=cv)
oof_cat = np.zeros(len(y))
cat_clf = CatBoostClassifier(iterations=200, cat_features=['Sex','Embarked','Cabin_type'],
random_seed=42, verbose=0)
for tr_idx, val_idx in cv.split(X_raw, y):
cat_clf.fit(X_raw.iloc[tr_idx], y[tr_idx])
oof_cat[val_idx] = cat_clf.predict(X_raw.iloc[val_idx])
oof_ensemble = ((oof_xgb + oof_lgb + oof_cat) >= 2).astype(int)
print(f"XGBoost OOF: {accuracy_score(y, oof_xgb):.4f}")
print(f"LightGBM OOF: {accuracy_score(y, oof_lgb):.4f}")
print(f"CatBoost OOF: {accuracy_score(y, oof_cat):.4f}")
print(f"アンサンブル: {accuracy_score(y, oof_ensemble):.4f}")
📐 数学・統計の補足(文系向け)
Target Statistics の計算式(数式を日本語で)
| 概念 | 数式 | 日本語での説明 |
|---|---|---|
| 通常のTarget Encoding | TS(x) = mean(y | category=x) |
カテゴリ x のデータ全件の目的変数の平均 |
| Ordered TS(CatBoost) | TS_i(x) = (sum(y_j, j<i, x_j=x) + a*p) / (count(j<i, x_j=x) + a) |
i より前のデータで同じカテゴリのものの平均(a は平滑化係数、p は事前確率) |
💡
平滑化係数 a の役割: データが少ないカテゴリ(例: Cabin_type=A が5件しかない)は統計量が不安定になる。a=1 程度の平滑化で全体の事前確率 p に引き寄せることで安定させる。ベイズ推定の考え方と同じ。
対称木を選挙で例える
| 概念 | 選挙の例え | 機械学習の対応 |
|---|---|---|
| 非対称木の分岐 | 各選挙区が独自のルールで候補者を決める | 各ノードが異なる条件を持つ → 複雑 |
| 対称木の分岐 | 全選挙区が「年収 > 500万か否か」で一斉に決める | 深さ毎に同じ条件 → シンプル・高速 |
| ルックアップテーブル | 「Yes/Yes/No = 候補者A確定」の表を事前に作る | 2^depth 個の葉の値を配列に格納 → 推論 O(1) |
🏆 Kaggleでの実践的な使い方
| 場面 | 推奨モデル | 理由 |
|---|---|---|
| カテゴリカル変数が多い(>5個) | CatBoost | Ordered TSで最適処理。エンコード作業不要 |
| 100万行以上の大規模データ | LightGBM | ヒストグラム法で最速。CatBoostはGPU必須 |
| 小規模データ(<1万行) | CatBoost | Ordered Boostingが過学習を防ぐ |
| 最終アンサンブル | 3つ全部 | 各モデルの誤りが独立 → 多数決で精度向上 |
| コンペ序盤のベースライン | LightGBM or CatBoost | デフォルトで高スコアが出やすい |
# Kaggle典型: Optuna で CatBoost をチューニング
import optuna
optuna.logging.set_verbosity(optuna.logging.WARNING)
def objective(trial):
params = {
'iterations': trial.suggest_int('iterations', 100, 1000),
'depth': trial.suggest_int('depth', 4, 10),
'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3, log=True),
'l2_leaf_reg': trial.suggest_float('l2_leaf_reg', 1.0, 30.0, log=True),
'cat_features': ['Sex', 'Embarked', 'Cabin_type'],
'random_seed': 42,
'verbose': 0,
}
clf = CatBoostClassifier(**params)
cv_s, _ = manual_cv(clf, X_raw, y, cv)
return cv_s
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=30)
print(f"最良スコア: {study.best_value:.4f}")
print(f"最良パラメータ: {study.best_params}")
🚫 よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| 文字列をそのまま渡したらエラーになると思う | XGBoost/LightGBMの経験から | CatBoostはOK。ただし cat_features の指定が必要 |
| depth=6 は max_depth=6 と同じ表現力 | パラメータ名が似ている | 対称木なので同じ深さでも葉数が少ない。深めに設定することもある |
| random_state と書く | sklearn API の命名慣習 | CatBoostは random_seed。sklearn の random_state ではエラーになる |
| verbose を設定しない | デフォルト確認不足 | デフォルトで大量ログ。verbose=0 を必ず設定 |
| get_feature_importance に Pool が不要と思う | sklearn の feature_importances_ の感覚 | CatBoostは Pool を引数に渡す必要がある |
| CatBoost は常に最強 | コンペ実績のイメージ | 大規模データはLightGBMが圧倒的に速い。必ず3モデル比較する |
🚀 次のステップ
- 発展: Optuna で3大GBDTを同時チューニング。
use_best_model=True, eval_set=...で CatBoost の early_stopping を活用する - 次回予告(Day 064): Optuna によるハイパーパラメータ最適化 — GBDTのチューニングを自動化する(XGBoost / LightGBM / CatBoost 対応)
📋 自己評価(解いた後に記入)
✍️
理解度: [ ] 完全理解 [ ] おおむね理解 [ ] 要復習
自分の回答:
気づき・メモ: