はじめに
CatBoostは勾配ブースティング決定木の実装のひとつで、LightGBMやXGBoostと並ぶ選択肢です。特徴は大きく2つあります。
- カテゴリ変数をそのまま渡せる:文字列のカテゴリ列を前処理なしで学習できる
- 対称木(oblivious tree):すべての葉が同じ深さで、各深さで同じ分割を使う木構造
この記事ではpip installから始めて、タイタニックのデータでCatBoostを実際に動かしてみます。バージョンはcatboost==1.2.10です。
インストールと最小の使い方
インストールはpipだけで完結します。
pip install catboost
APIはscikit-learn互換で、fitとpredict_probaが使えます。カテゴリ列はfitのcat_featuresに列名(またはインデックス)を渡すだけです。
from catboost import CatBoostClassifier
model = CatBoostClassifier(iterations=300, learning_rate=0.05, depth=6,
random_seed=0, verbose=100)
model.fit(X_train, y_train, cat_features=cat_cols)
proba = model.predict_proba(X_test)[:, 1]
verbose=100にすると100本ごとに学習ログが出ます。
0: learn: 0.6645658 total: 62.3ms remaining: 18.6s
100: learn: 0.3960953 total: 167ms remaining: 329ms
200: learn: 0.3428773 total: 285ms remaining: 140ms
299: learn: 0.2951867 total: 388ms remaining: 0us
データ(タイタニック)
OpenML配布のタイタニックを使います。survived(生存したか)を当てる二値分類です。1309件あり、カテゴリ変数と欠損値の両方を含みます。boat(救命ボート番号)やbody(遺体番号)は生死そのものを表すリーク列なので除外し、次の7列だけ使います。
import pandas as pd
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
df = fetch_openml("titanic", version=1, as_frame=True).frame
cat_cols = ["pclass", "sex", "embarked"]
num_cols = ["age", "sibsp", "parch", "fare"]
y = df["survived"].astype(int)
X = df[cat_cols + num_cols].copy()
for c in cat_cols:
X[c] = X[c].astype(str) # 欠損(NaN)を 'nan' というカテゴリにするため
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=0, stratify=y)
cat_featuresに渡すカテゴリ列は、整数でも文字列でもかまいません(pclassはintのままでも動きます)。ただしカテゴリ列にNaNが含まれるとエラーになるため、上ではastype(str)で欠損を'nan'という1つのカテゴリに変換しています。一方、age(263件)やfare(1件)のような数値列の欠損はそのまま渡せます。CatBoostがNaNを分岐の片側に寄せて扱うので、補完は不要です。
カテゴリ変数をそのまま渡す
CatBoostはcat_featuresに列を指定するだけで、文字列カテゴリを内部で数値化します。one-hotへの展開やラベルエンコードといった前処理は要りません。
model.fit(X_train, y_train, cat_features=["pclass", "sex", "embarked"])
対称木という構造
CatBoostが既定で作るのは対称木(oblivious tree)です。ふつうの決定木はノードごとに好きな分割を選びますが、対称木は同じ深さのノードがすべて同じ分割条件を使うという制約があります。学習した木の1本目を取り出すと、その様子が分かります。

深さ0はsex、深さ1はage > 37.5、深さ2はage > 26.2と、各段で1つの分割が全ノードに共有されています。この制約のおかげで、1本の木は「深さ分の条件を順に判定して葉に落とす」だけになり、推論が速く、過学習も抑えられやすいという性質があります。
予測してみる
学習したモデルでテストデータを予測し、AUCを見てみます。
from sklearn.metrics import roc_auc_score
proba = model.predict_proba(X_test)[:, 1]
print(roc_auc_score(y_test, proba))
0.8874
注意点
cat_featuresを渡し忘れると、CatBoostはカテゴリ列を数値として扱おうとしてエラーになるか精度が落ちます。カテゴリ列は明示しましょう。cat_featuresに渡す列はint・stringどちらでもよいですが、NaNを含むとエラーになります(cat_features must be integer or string, real)。欠損はastype(str)で'nan'にするか、-1など専用のカテゴリで埋めておきます。数値列の欠損は変換不要です。- OpenMLのタイタニック(version=1)は1309件で、よく使われる891件版(学習用の分割)とは件数が違います。
まとめ
- CatBoostは
pip install catboostだけで入り、scikit-learn互換のfit/predict_probaで使える - カテゴリ列は
cat_featuresに列名を渡すだけ。数値の欠損もそのまま扱えるので前処理が少ない - 既定の木は対称木(各深さで同じ分割を共有)で、推論が速い