LightGBM mempercepat gradient boosting pada data numerik besar, tetapi dua masalah lama masih menunggu jawaban: bagaimana memakai fitur kategorikal tanpa encoding manual yang rentan bocor label, dan bagaimana mencegah boosting menghafal dataset kecil yang berderau. CatBoost — pustaka dari Yandex yang namanya berasal dari categorical boosting — dibangun tepat untuk keduanya: statistik target terurut (ordered target statistics) menggantikan one-hot, ordered boosting menekan bias residual yang menjadi akar overfitting, dan pohon simetris menjaga struktur model tetap sederhana. Mekanisme koreksi berantainya tetap gradient boosting seperti Chapter Gradient Boosting — yang berubah adalah cara data kategorikal masuk dan cara residual dihitung. Chapter ini membedah ketiga gagasan tersebut lalu mengujinya pada data churn pelanggan toko online berisi tiga kolom kategorikal, tanpa satu baris encoding pun.
Masalah Encoding Kategorikal di Boosting #
Dua jalur standar untuk fitur kategorikal sama-sama menyimpan duri. One-hot encoding mengubah satu kolom kota berisi 4 nilai menjadi 4 kolom biner — tidak masalah di sini, tetapi pada kolom bertingkat tinggi seperti kode_pos dengan ribuan nilai, dimensi fitur melonjak cepat sementara tiap kolom hasil encoding menjadi sparse; pemindaian pemecahan menjadi mahal dan pohon individual cenderung mengandalkan satu kolom dummy yang sepi. Target encoding — mengganti tiap kategori dengan rata-rata label kelompoknya — lebih hemat dimensi, tetapi rata-rata itu dihitung dari seluruh data latih termasuk baris yang sedang diprediksi sendiri: baris berlabel 1 menyumbang angka pada fiturnya sendiri, dan model boosting yang tajam akan memburu kebocoran ini hingga akurasi latih menipu.
CatBoost menolak kedua jalur tersebut: kolom kategorikal dikirim apa adanya lewat parameter cat_features, dan konversi ke angka dikerjakan di dalam dengan statistik target terurut.
Ordered Target Statistics dan Ordered Boosting #
Ide statistik target terurut sederhana namun manjur. Data latih diacak menurut suatu permutasi; untuk baris ke-i, kategorinya diganti rata-rata label dari baris-baris sebelum baris ke-i pada permutasi itu (plus penghalang prior agar kategori sepi tidak melonjak). Rata-rata untuk baris pertama hampir kosong, untuk baris terakhir hampir setara rata-rata penuh — informasi label baris itu sendiri tidak pernah masuk ke fiturnya, sehingga pintu kebocoran tertutup. Beberapa permutasi diacak berbeda dan hasilnya dirata-rata agar urutan tunggal tidak meninggalkan artefak.
Ordered boosting menerapkan disiplin yang sama pada residual. Pada gradient boosting biasa, residual baris ke-i di ronde t dihitung dari model yang dilatih pada seluruh data latih — termasuk baris ke-i itu sendiri; residual tersebut dengan demikian terlalu optimistis (prediction shift). CatBoost menghitung residual baris ke-i hanya dengan model yang dilatih pada baris-baris sebelumnya pada permutasi, lalu membangun pohon berikutnya dari residual yang lebih jujur. Biayanya komputasi bertambah, sehingga CatBoost menyediakan mode perkiraan yang lebih cepat — tetapi arah desainnya konsisten: menutup setiap celah label menyusup ke fitur.
Pohon Simetris untuk Regularisasi dan Kecepatan Inferensi #
Pohon CatBoost tumbuh simetris (oblivious tree): satu kondisi pemecahan yang sama dipakai pada seluruh tingkat kedalaman — mirip daftar keputusan bertingkat, bukan pohon bebas. Konsekuensinya, pohon berkedalaman depth selalu memiliki paling banyak 2^depth daun dan prediksinya bisa dibaca sebagai tabel pencarian: beberapa pembandingan bitwise lalu selesai. Untuk data yang berderau, kekakuan struktur ini berfungsi sebagai regularisasi — pohon tidak bisa membentuk cabang istimewa yang menghafal segelintir baris. Parameter kapasitasnya depth (bawaan 6, sehingga maksimum 64 daun), bukan num_leaves seperti LightGBM.
| Pendekatan kategorikal | Cara kerja | Risiko utama |
|---|---|---|
| One-hot + estimator biasa | Tiap kategori menjadi kolom biner di hulu | Dimensi melonjak pada kategori bertingkat tinggi |
| Target encoding manual | Kategori diganti rata-rata label kelompoknya | Kebocoran label bila dihitung pada seluruh data |
categorical_feature LightGBM | Split kategorikal saat pelatihan pohon | Perlu penandaan tipe kategori di hulu |
cat_features CatBoost | Statistik target terurut di dalam pelatihan | Ukuran dataset sangat kecil dengan kategori sepi |
Antarmuka scikit-learn dan Parameternya #
CatBoostClassifier dan CatBoostRegressor mengikuti kontrak fit()/predict() scikit-learn dan menerima pandas DataFrame secara langsung — kolom kategorikal cukup didaftarkan lewat argumen cat_features pada fit() berupa daftar nama kolom. Tiga parameter kebersihan yang wajib melekat di lingkungan kerja kita: random_seed=42 untuk reproduksibilitas, verbose=False agar pelatihan tidak menyapa konsol setiap iterasi, dan allow_writing_files=False supaya CatBoost tidak menaruh direktori catboost_info di sekitar skrip. Parameter kapasitas utamanya depth, iterations, dan learning_rate — maknanya sama dengan n_estimators pada pustaka sebelumnya.
Eksperimen: Data Churn dengan Tiga Kolom Kategorikal #
Skrip berikut membangun data churn pelanggan toko online — 180 baris, tiga kolom kategorikal (kota, perangkat, paket) dan tiga numerik — lalu mempertemukan CatBoost dengan pembanding klasik: pipeline one-hot + GradientBoostingClassifier. Empat bagian skrip memeriksa struktur data, perilaku pelatihan, biaya encoding, dan akhirnya prediksi probabilitas untuk tiga pelanggan baru.
# CatBoost: ordered boosting, pohon simetris, dan fitur kategorikal native.
# Bagian 1: data churn pelanggan toko online (pandas DataFrame, 3 kolom
# kategorikal) + tampilan 5 baris pertama.
# Bagian 2: CatBoostClassifier dengan cat_features -- tanpa encoding manual.
# Bagian 3: pembanding one-hot + GradientBoostingClassifier (pipeline).
# Bagian 4: prediksi probabilitas churn untuk 3 pelanggan baru.
from collections import Counter
import numpy as np
import pandas as pd
from catboost import CatBoostClassifier
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
# --- Bagian 1: data churn sintetis --------------------------------------
rng = np.random.default_rng(42)
n = 180
kota = rng.choice(["Jakarta", "Bandung", "Surabaya", "Medan"], size=n,
p=[0.35, 0.25, 0.25, 0.15])
perangkat = rng.choice(["mobile", "desktop", "tablet"], size=n,
p=[0.6, 0.3, 0.1])
paket = rng.choice(["reguler", "premium"], size=n, p=[0.7, 0.3])
umur_akun = rng.integers(1, 37, size=n) # bulan
jumlah_transaksi = rng.poisson(6, size=n) # transaksi 90 hari
skor_kepuasan = rng.integers(1, 6, size=n) # skala 1-5
skor = (3.0
- 0.075 * umur_akun
- 0.27 * jumlah_transaksi
- 0.52 * skor_kepuasan
+ 1.35 * (paket == "reguler")
+ 0.6 * (perangkat == "mobile")
+ 0.3 * (kota == "Medan"))
peluang = 1 / (1 + np.exp(-skor))
churn = rng.binomial(1, peluang)
data = pd.DataFrame({
"id_pelanggan": [f"P{i:03d}" for i in range(1, n + 1)],
"kota": kota,
"perangkat": perangkat,
"paket": paket,
"umur_akun_bulan": umur_akun,
"jumlah_transaksi_90hari": jumlah_transaksi,
"skor_kepuasan": skor_kepuasan,
"churn": churn,
})
fitur = ["kota", "perangkat", "paket", "umur_akun_bulan",
"jumlah_transaksi_90hari", "skor_kepuasan"]
kategorikal = ["kota", "perangkat", "paket"]
print("Bagian 1 - data churn pelanggan toko online:")
print(f" jumlah baris : {len(data)}")
print(f" rasio churn : {data['churn'].mean():.3f}")
print(f" 5 baris pertama:")
print(data.head().to_string(index=False))
X_latih, X_uji, y_latih, y_uji = train_test_split(
data[fitur], data["churn"], test_size=54, random_state=42)
# --- Bagian 2: CatBoost dengan cat_features ----------------------------
model = CatBoostClassifier(iterations=200, learning_rate=0.1, depth=6,
random_seed=42, verbose=False,
allow_writing_files=False, thread_count=1)
model.fit(X_latih, y_latih, cat_features=kategorikal)
daun_per_pohon = model.get_tree_leaf_counts()
print("\nBagian 2 - CatBoostClassifier (cat_features native):")
print(f" akurasi latih : {model.score(X_latih, y_latih):.3f}")
print(f" akurasi uji : {model.score(X_uji, y_uji):.3f}")
print(f" jumlah pohon : {model.tree_count_}")
print(f" daun per pohon (distribusi): {dict(sorted(Counter(int(d) for d in daun_per_pohon).items()))}")
urutan_penting = np.argsort(model.feature_importances_)[::-1]
print(" kepentingan fitur (semua) :")
for i in urutan_penting:
print(f" {model.feature_names_[i]:<24} "
f"{model.feature_importances_[i]:5.1f}")
# --- Bagian 3: pembanding one-hot + gradient boosting ------------------
prepros = ColumnTransformer(
transformers=[("kategorikal", OneHotEncoder(handle_unknown="ignore"),
kategorikal)],
remainder="passthrough")
pembanding = Pipeline([
("prepros", prepros),
("model", GradientBoostingClassifier(n_estimators=200, learning_rate=0.1,
max_depth=3, random_state=42)),
])
pembanding.fit(X_latih, y_latih)
nama_kolom = (pembanding.named_steps["prepros"]
.get_feature_names_out().tolist())
print("\nBagian 3 - pembanding one-hot + GradientBoostingClassifier:")
print(f" kolom sebelum one-hot : {X_latih.shape[1]}")
print(f" kolom setelah one-hot : {len(nama_kolom)}")
print(f" akurasi latih : {pembanding.score(X_latih, y_latih):.3f}")
print(f" akurasi uji : {pembanding.score(X_uji, y_uji):.3f}")
# --- Bagian 4: probabilitas churn pelanggan baru -----------------------
pelanggan_baru = pd.DataFrame({
"kota": ["Jakarta", "Bandung", "Surabaya"],
"perangkat": ["desktop", "mobile", "desktop"],
"paket": ["premium", "reguler", "premium"],
"umur_akun_bulan": [30, 4, 14],
"jumlah_transaksi_90hari": [12, 2, 5],
"skor_kepuasan": [5, 2, 3],
})
proba = model.predict_proba(pelanggan_baru)[:, 1]
print("\nBagian 4 - probabilitas churn pelanggan baru:")
for nama, p in zip(["Budi", "Siti", "Dewi"], proba):
print(f" {nama:<5} : {p:.3f}")Output yang Dihasilkan:
Bagian 1 - data churn pelanggan toko online:
jumlah baris : 180
rasio churn : 0.461
5 baris pertama:
id_pelanggan kota perangkat paket umur_akun_bulan jumlah_transaksi_90hari skor_kepuasan churn
P001 Surabaya desktop premium 34 4 3 0
P002 Bandung desktop reguler 36 7 5 0
P003 Medan mobile reguler 35 3 4 1
P004 Surabaya mobile reguler 21 6 5 0
P005 Jakarta desktop reguler 8 8 1 0
Bagian 2 - CatBoostClassifier (cat_features native):
akurasi latih : 0.913
akurasi uji : 0.741
jumlah pohon : 200
daun per pohon (distribusi): {1: 1, 2: 2, 4: 2, 8: 3, 32: 3, 64: 189}
kepentingan fitur (semua) :
umur_akun_bulan 25.5
jumlah_transaksi_90hari 19.1
skor_kepuasan 16.8
kota 15.5
perangkat 14.5
paket 8.7
Bagian 3 - pembanding one-hot + GradientBoostingClassifier:
kolom sebelum one-hot : 6
kolom setelah one-hot : 12
akurasi latih : 1.000
akurasi uji : 0.704
Bagian 4 - probabilitas churn pelanggan baru:
Budi : 0.004
Siti : 0.942
Dewi : 0.383Mari kita telaah bagian-bagian kunci skrip tersebut:
- Baris 20–29 (generator data):
rngberseed 42 menarik tiga kolom kategorikal dari distribusi eksplisit dan tiga kolom numerik —umur_akun1–36 bulan, transaksi 90 hari ber-Poisson, skor kepuasan skala 1–5. - Baris 31–39 (mekanisme label): skor laten menjumlahkan kontribusi semua fitur — paket reguler dan perangkat mobile menaikkan kecenderungan berhenti, akun tua dan skor kepuasan tinggi menurunkannya — lalu label diundi dari Bernoulli berpeluang sigmoid. Komponen acak inilah lantai galat dataset: tidak ada model yang berhak menyentuh akurasi 1,000.
- Baris 61–62 (split tetap): 126 baris latih dan 54 baris uji, keduanya
DataFramementah — kolom tekskota/perangkat/paketdikirim tanpa disentuh. - Baris 65–68 (pelatihan): konstruktor memasang
random_seed=42,verbose=False,allow_writing_files=False, danthread_count=1; pemanggilanfit(..., cat_features=kategorikal)adalah satu-satunya penandaan yang dibutuhkan — tidak adaOneHotEncoder, tidak adaget_dummies. - Baris 70–80 (pemeriksaan struktur):
get_tree_leaf_counts()melaporkan jumlah daun tiap pohon, danCountermerangkum distribusinya;feature_importances_bersamafeature_names_mencetak kepentingan seluruh fitur dalam format tabel. - Baris 83–94 (pembanding):
ColumnTransformermeng-encode tiga kolom kategorikal sambil meloloskan kolom numerik (remainder="passthrough"), disusun sebagaiPipeline— arsitektur standar Chapter Encoding Variabel Kategorikal — sebelumGradientBoostingClassifierdengan anggaran pohon yang setara. - Baris 103–114 (pelanggan baru): tiga baris
DataFramedengan profil berlawanan dikirim kepredict_proba, mengambil kolom kelas 1 sebagai probabilitas berhenti berlangganan.
Membaca Hasil Eksperimen #
Bagian 2 memperlihatkan watak CatBoost pada data kecil berderau: akurasi latih berhenti di 0,913 — model menolak menghafal sisa 8,7% label yang memang diundi secara acak — sementara akurasi uji mencatat 0,741. Baris distribusi daun menegaskan klaim pohon simetris: 189 dari 200 pohon tumbuh penuh 64 daun (depth=6), sisanya berhenti di pangkat dua yang lebih kecil (32, 16, …) — tidak ada satu pun pohon dengan jumlah daun ganjil, karena tingkat yang sama selalu berpecah serentak. Kepentingan fitur menempatkan tiga fitur numerik di atas (umur akun 25,5; transaksi 19,1; kepuasan 16,8), dengan dua fitur kategorikal menyusul — masuk akal karena kontribusi aslinya memang lebih landai.
Bagian 3 adalah pembanding yang jujur terhadap kedua dunia. Kolom fitur membengkak dari 6 menjadi 12 akibat one-hot, dan GradientBoostingClassifier memboroskan kapasitasnya menghafal label acak: akurasi latih 1,000, akurasi uji 0,704 — gap latih-uji 0,296 berbanding 0,172 milik CatBoost. Selisih akurasi uji 0,037 pada 54 sampel memang masih dalam wilayah kebetulan sampling — perlakuan serius perlu cross-validation — tetapi arah ceritanya konsisten dengan desain kedua pustaka: regularisasi struktur menahan boosting dari penghafalan.
Bagian 4 menghadirkan inti kegunaan bisnisnya. Budi (premium, desktop, akun 30 bulan, kepuasan 5) diprediksi berpeluang berhenti 0,004; Siti (reguler, mobile, akun 4 bulan, kepuasan 2) melonjak ke 0,942; Dewi yang profilnya di tengah jalan mendarat di 0,383. Ketiganya diproses tanpa satu baris translasi kategori menjadi angka — DataFrame mentah masuk, probabilitas keluar.
Keluarga boosting kini lengkap kita kenal — dari mesin manual scikit-learn, XGBoost yang teregularisasi, LightGBM yang berbasis histogram, hingga CatBoost dengan jalur kategorikalnya. Seluruhnya masih satu algoritme dasar dengan keragaman yang lahir dari konfigurasi. Section ini ditutup dengan jenis keragaman yang paling berani: menggabungkan model yang sepenuhnya berbeda algoritme — topik Chapter Voting dan Stacking.
Referensi Resmi #
- CatBoost — Python Usage Examples
— contoh resmi pemakaian antarmuka Python, termasuk
cat_features. - CatBoost — Input Data — spesifikasi kolom kategorikal dan format data yang diterima pelatih.
- CatBoost — Algorithm Main Stages — penjelasan resmi statistik target terurut dan ordered boosting.
- CatBoost — Parameter Tuning
— panduan menyetel
depth,iterations, danlearning_rate.