Chapter Konsep Ensemble Learning menutup dengan satu pertanyaan praktis: bagaimana menciptakan keragaman secara sistematis, bukan sekadar mengubah max_features tiga pohon secara manual? Keluarga bagging menjawabnya dengan trik yang elegan — cukup satu algoritme dasar (decision tree yang tumbuh bebas) dan satu mekanisme penggeser data (bootstrap). Random forest menambahkan lapisan kedua: setiap pemecahan simpul hanya boleh mempertimbangkan subset fitur yang dipilih acak. Kombinasi dua penggeser tersebut mengubah decision tree — model paling mudah overfitting — menjadi salah satu algoritme paling andal untuk data tabular. Chapter ini membedah mekanismenya, lalu membuktikan efek penurunan variansnya dengan angka latih-uji yang bisa direproduksi.
Cara Kerja Bootstrap Sampling #
Bootstrap berarti menarik n sampel dari data latih berukuran n dengan pengembalian (with replacement). Satu sampel boleh tercabut berkali-kali, sampel lain bisa tidak tercabut sama sekali. Peluang satu data tertentu tidak terpilih pada satu cabutan adalah 1 - 1/n; setelah n cabutan, peluang data tersebut tidak pernah terpilih menjadi (1 - 1/n)^n, yang konvergen ke e^-1 ≈ 0,368. Dengan demikian satu salinan bootstrap rata-rata hanya memuat sekitar 63,2% data unik, dan sisanya berupa duplikat.
Angka 63,2% ini bukan trivia — konsekuensinya langsung terasa pada desain ensemble. Setiap pohon di dalam hutan melihat mayoritas data yang sama tetapi dengan penekanan berbeda: sampel yang tercabut ganda mendapat bobot lebih besar, sampel yang terlewat tidak dilihat sama sekali. Perbedaan penekanan inilah yang membuat prediksi dua pohon melebar — persis keragaman yang syaratkan ensemble yang baik.
Out-of-Bag: Validasi Gratis #
Sisi lain kalkulasi 63,2% justru menguntungkan. Rata-rata 36,8% data tidak dilihat oleh satu pohon tertentu — sampel-sampel itu disebut out-of-bag (OOB) bagi pohon tersebut. Untuk menilai satu sampel latih, kita cukup mengumpulkan prediksi dari pohon-pohon yang tidak pernah melihat sampel itu, lalu mengambil suara mayoritasnya. Hasilnya adalah skor OOB: estimasi akurasi generalisasi yang dihitung murni dari data latih, tanpa menyentuh data uji sama sekali.
Di scikit-learn, mekanisme ini aktif lewat oob_score=True; setelah fit(), atribut oob_score_ berisi skornya. Skor OOB sangat berguna saat data terbatas dan menyisihkan validation set terpisah terasa boros — satu set data latih sekaligus berfungsi sebagai bahan latih dan bahan validasi.
Dari Bagging ke Random Forest #
Bagging murni (BaggingClassifier) hanya menggeser baris data. Jika satu fitur sangat dominan, fitur tersebut tetap menempati simpul atas di hampir semua pohon — akibatnya pohon-pohon berkorelasi kuat meski datanya digeser. Random forest memutus korelasi ini dengan menggeser kolom juga: pada setiap simpul, hanya max_features fitur terpilih acak yang boleh dipertimbangkan sebagai kandidat pemecahan. Fitur dominan kadang tidak ikut terpilih, sehingga pohon lain dipaksa menemukan pemecahan alternatif yang juga informatif.
Perbedaan posisi penggeser ini penting: pada BaggingClassifier, parameter max_features membatasi fitur per pohon; pada RandomForestClassifier, pembatasan terjadi per simpul — jauh lebih halus. Untuk klasifikasi, nilai bawaan scikit-learn adalah max_features="sqrt": dari 16 fitur pada contoh kita, tiap simpul hanya menimbang 4 kandidat fitur.
| Model | Mekanisme keragaman | Efek khas |
|---|---|---|
| Pohon tunggal | Tidak ada | varians tinggi; prediksi goyah antar dataset |
| Bagging | Bootstrap pada baris data | varians turun; pohon masih bisa berkorelasi lewat fitur dominan |
| Random Forest | Bootstrap baris + subset fitur per simpul | varians turun lebih jauh; pohon nyaris bebas korelasi |
Dalam bahasa dekomposisi galat pada Chapter Bias-Variance Tradeoff: bagging menekan varians tanpa menaikkan bias secara berarti. Decision tree yang tumbuh bebas memang berbias rendah (fleksibel) tetapi ber-varians tinggi (mudah berubah total jika data bergeser sedikit); merata-ratakan ratusan pohon seperti itu menahan goyangan. Model dasar yang kaku — misalnya decision stump — justru bukan kandidat bagging yang baik karena variansnya memang kecil sejak awal.
Hyperparameter yang Perlu Diperhatikan #
| Hyperparameter | Peran | Panduan singkat |
|---|---|---|
n_estimators | Jumlah pohon dalam hutan | Lebih banyak = prediksi makin halus; imbal hasil makin mengecil. 100–500 lazim untuk memulai |
max_features | Jumlah fitur kandidat per simpul | "sqrt" (bawaan klasifikasi) titik awal aman; makin kecil makin beragam pohonnya |
max_depth | Kedalaman tiap pohon | None umum dipakai — varians ditangani oleh averaging, bukan pemangkasan |
Menaikkan n_estimators hampir tidak pernah memperburuk generalisasi; efeknya konvergen menuju rata-rata yang stabil, dengan biaya waktu pelatihan dan memori yang tumbuh linear. Karena itu pedoman praktisnya sederhana: pilih angka yang masuk anggaran komputasi, lalu serahkan kualitas pada max_features dan max_depth.
Eksperimen: Satu Pohon vs Dua Ensemble #
Saatnya mengukur semua klaim di atas. Skrip berikut menjalankan tiga pemeriksaan: anatomi satu sampel bootstrap, perbandingan pohon tunggal melawan dua ensemble pada data yang sama, lalu skor OOB berdampingan dengan akurasi uji.
# Bagging dan Random Forest: menstabilkan pohon dalam lewat rata-rata
# atas banyak salinan data yang digeser acak (bootstrap).
# Bagian 1: anatomi satu sampel bootstrap (berapa persen isinya unik?).
# Bagian 2: pohon tunggal yang dalam vs bagging vs random forest.
# Bagian 3: skor OOB sebagai validasi gratis dari data yang tidak terpakai.
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier, RandomForestClassifier
# --- Bagian 1: anatomi satu sampel bootstrap ---------------------------
# Bootstrap = menarik n data DENGAN pengembalian dari kumpulan aslinya.
rng = np.random.default_rng(42)
n_data = 100
n_cabutan = rng.integers(0, n_data, size=n_data)
n_unik = len(np.unique(n_cabutan))
fraksi_teoritis = 1 - (1 - 1 / n_data) ** n_data
print("Bagian 1 - satu sampel bootstrap dari 100 data:")
print(f" {'jumlah indeks unik':<36} : {n_unik} dari {n_data}")
print(f" {'fraksi unik (empiris)':<36} : {n_unik / n_data:.3f}")
print(f" {'fraksi unik (teoritis 1-(1-1/n)^n)':<36} : {fraksi_teoritis:.3f}")
# --- Bagian 2: pohon tunggal vs dua ensemble --------------------------
X, y = make_classification(n_samples=200, n_features=16, n_informative=6,
n_redundant=3, flip_y=0.0, random_state=42)
X_latih, X_uji, y_latih, y_uji = train_test_split(
X, y, test_size=60, random_state=42)
pohon_tunggal = DecisionTreeClassifier(random_state=42)
bagging = BaggingClassifier(n_estimators=300, oob_score=True,
random_state=42)
hutan_acak = RandomForestClassifier(n_estimators=300, oob_score=True,
random_state=42)
kandidat = [
("pohon tunggal (dalam)", pohon_tunggal),
("bagging (300 pohon)", bagging),
("random forest (300 pohon)", hutan_acak),
]
print("\nBagian 2 - akurasi latih vs uji (200 sampel, 16 fitur):")
print(f" {'model':<26} {'latih':>6} {'uji':>6} {'selisih':>8}")
for nama, model in kandidat:
model.fit(X_latih, y_latih)
a_latih = model.score(X_latih, y_latih)
a_uji = model.score(X_uji, y_uji)
print(f" {nama:<26} {a_latih:>6.3f} {a_uji:>6.3f} "
f"{a_latih - a_uji:>8.3f}")
# --- Bagian 3: skor OOB, validasi tanpa menyentuh data uji -------------
# Tiap pohon hanya melihat sekitar 63% data latih; sampel sisanya
# (out-of-bag) dipakai gratis untuk menguji pohon yang tidak pernah
# melihat sampel tersebut.
print("\nBagian 3 - skor OOB vs akurasi uji:")
print(f" {'model':<26} {'OOB':>6} {'uji':>6}")
for nama, model in kandidat[1:]:
print(f" {nama:<26} {model.oob_score_:>6.3f} "
f"{model.score(X_uji, y_uji):>6.3f}")Output yang Dihasilkan:
Bagian 1 - satu sampel bootstrap dari 100 data:
jumlah indeks unik : 66 dari 100
fraksi unik (empiris) : 0.660
fraksi unik (teoritis 1-(1-1/n)^n) : 0.634
Bagian 2 - akurasi latih vs uji (200 sampel, 16 fitur):
model latih uji selisih
pohon tunggal (dalam) 1.000 0.683 0.317
bagging (300 pohon) 1.000 0.817 0.183
random forest (300 pohon) 1.000 0.850 0.150
Bagian 3 - skor OOB vs akurasi uji:
model OOB uji
bagging (300 pohon) 0.764 0.817
random forest (300 pohon) 0.800 0.850Mari kita telaah bagian-bagian kunci skrip tersebut:
- Baris 15–19 (membangun satu bootstrap):
rng.integers(0, n_data, size=n_data)menarik 100 indeks dengan pengembalian;np.uniquemenghitung indeks berbeda yang muncul, dan baris 19 menghitung fraksi teoritis1 - (1-1/n)^nsebagai pembanding analitis. - Baris 26–29 (dataset tetap):
make_classification(..., random_state=42)denganflip_y=0.0menghasilkan 200 sampel 16 fitur tanpa label terbalik;train_test_split(..., random_state=42)menyisihkan 60 sampel uji yang sama pada setiap eksekusi. - Baris 31–35 (tiga model): pohon tunggal tumbuh bebas (
max_depth=Nonebawaan), sedangkan kedua ensemble memakai 300 pohon dan mengaktifkanoob_score=Trueagar skor OOB tercatat saat pelatihan. - Baris 37–41 (daftar kandidat): ketiga model disimpan sebagai pasangan nama-objek agar loop evaluasi memperlakukan semuanya identik.
- Baris 45–50 (evaluasi latih vs uji): kolom
selisihmenghitung jurang latih-uji — ukuran langsung dari gejala overfitting yang kita bahas pada Chapter Overfitting dan Underfitting. - Baris 56–60 (skor OOB):
model.oob_score_dibaca setelahfit()dan dicetak berdampingan dengan akurasi uji; pohon tunggal sengaja dilewati (kandidat[1:]) karena konsep OOB hanya berlaku bagi model bootstrap.
Membaca Hasil Eksperimen #
Bagian 1 mengonfirmasi teori: dari 100 cabutan, hanya 66 indeks unik yang muncul (66,0% empiris — dekat dengan 63,4% teoritis; angka empiris memang berfluktuasi sekitar nilai teoritisnya). Bagian 2 memperlihatkan cerita varians yang utuh. Pohon tunggal mencetak akurasi latih sempurna 1,000 tetapi akurasi uji hanya 0,683 — jurang 0,317, tanda klasik menghafal. Bagging memangkas jurang itu hingga 0,183 (akurasi uji 0,817), dan random forest menutupnya lagi ke 0,150 dengan akurasi uji 0,850 — tambahan subset fitur per simpul memberi keragaman ekstra yang tidak dimiliki bagging murni.
Bagian 3 menutup dengan temuan paling praktis: skor OOB bagging 0,764 dan random forest 0,800 berada satu langkah di bawah akurasi uji masing-masing (0,817 dan 0,850), tanpa satu byte data uji pun tersentuh. Pada dataset kecil semacam ini, OOB layak dipakai sebagai indikator generalisasi selama proses tuning — data uji tetap terjaga untuk penilaian akhir.
Bagging bekerja karena pohon-pohonnya saling menutupi kelebihan varians. Keluarga boosting membalik logikanya sepenuhnya: pohon-pohon kecil disusun berurutan, masing-masing belajar dari galat gabungan sebelumnya. Mari kita lanjutkan ke Chapter Gradient Boosting.
Referensi Resmi #
- Ensemble methods — scikit-learn User Guide — bagian Forest of randomized trees dan Bagging meta-estimator.
- RandomForestClassifier — scikit-learn API
— dokumentasi hyperparameter
n_estimators,max_features, danoob_score. - BaggingClassifier — scikit-learn API — pembanding bagging murni tanpa subset fitur per simpul.