Random forest pada chapter sebelumnya melatih ratusan pohon secara serentak, saling lepas, lalu meringkas suara mereka lewat voting. Gradient boosting memakai strategi yang tampak serupa tetapi bekerja secara berlawanan: pohon-pohon dibangun satu demi satu, dan setiap pohon baru dididik khusus untuk memperbaiki kesalahan gabungan pohon-pohon sebelumnya. Model keluarga ini adalah penghuni tetap papan atas kompetisi data tabular selama satu dekade terakhir. Pada chapter ini kita membedah mesin koreksi berantainya, mencobanya pada data pemeriksaan pasien yang sama dengan chapter random forest supaya hasilnya bisa dibandingkan langsung, lalu berkenalan dengan pustaka XGBoost yang menjadi standar industri.

Boosting: Belajar dari Galat secara Berurutan #

Ide dasar boosting bisa dirumuskan sebagai model aditif — prediksi akhir adalah penjumlahan banyak model kecil:

text
F_m(x) = F_(m-1)(x) + v * h_m(x)

F_m     : gabungan model setelah m tahap
F_(m-1) : gabungan model tahap sebelumnya
h_m     : pohon kecil baru, dilatih pada sisa galat
v       : learning_rate, seberapa besar koreksi diterapkan

Setiap tahap, algoritma mengukur residual — selisih antara nilai sebenarnya dan prediksi gabungan saat ini — lalu melatih sebuah pohon kecil (sering kali hanya kedalaman 2-4) yang dipasangkan pada residual tersebut, bukan pada target asli. Kata gradient berasal dari cara ini dinyatakan secara formal: menurunkan nilai loss function dengan menambahkan model yang bergerak searah dengan gradien negatifnya; untuk klasifikasi, arah itu setara dengan versi terukur dari residual. Pohon baru tidak perlu pintar sendirian — cukup sedikit lebih baik daripada tebakan acak pada bagian data yang masih sering meleset.

Perbedaan arah ini pula yang memisahkan boosting dari bagging:

AspekRandom Forest (bagging)Gradient Boosting (boosting)
Cara anggota dilatihSerentak, saling lepasBerurutan, tiap pohon bergantung pada koreksi sebelumnya
Sasaran perbaikanMenurunkan variansi (meredam pohon yang terlalu menghafal)Menurunkan bias (merakit pohon lemah menjadi kuat)
Ukuran tiap anggotaPohon besar-dalamPohon kecil-dangkal
Menambah jumlah anggotaNyaris tidak pernah memperburukBisa memperburuk bila berlebihan
Sensitivitas pada derauRendah — voting meredam pencilanLebih tinggi — koreksi berantai bisa mengejar derau

Konsekuensi praktisnya: bagging adalah mesin yang stabil tetapi mentok di tingkat tertentu, sedangkan boosting berpotensi mencapai akurasi lebih tinggi dengan harga perhatian ekstra pada proses latihnya.

Melatih Gradient Boosting pada Data Pemeriksaan #

Kita pakai kembali data 34 pasien puskesmas dari chapter random forest — umur, bmi, olahraga_per_pekan, makan_manis_per_pekan, dengan target diabetes — supaya perbandingan bagging lawan boosting berada pada data yang persis sama:

python
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingClassifier

pasien = pd.DataFrame({
    "umur": [33, 38, 28, 39, 40, 26, 35, 61, 58, 24, 31, 55, 42, 47, 59, 63, 29,
             65, 57, 53, 45, 36, 52, 51, 49, 27, 48, 44, 41, 46, 37, 50, 43, 34],
    "bmi": [22, 25, 22, 27, 23, 20, 24, 29, 34, 19, 21, 36, 22, 32, 35, 30, 23,
            33, 37, 26, 24, 21, 31, 28, 33, 20, 23, 31, 26, 28, 26, 27, 25, 25],
    "olahraga_per_pekan": [6, 4, 5, 1, 3, 5, 4, 0, 1, 6, 6, 0, 3, 2, 0, 1, 4,
                           1, 2, 5, 5, 7, 0, 3, 2, 5, 4, 0, 2, 2, 3, 4, 1, 2],
    "makan_manis_per_pekan": [1, 2, 2, 5, 2, 3, 3, 5, 7, 2, 1, 6, 2, 7, 6, 5, 2,
                              4, 6, 2, 3, 1, 6, 5, 8, 1, 3, 8, 4, 5, 3, 3, 4, 3],
    "diabetes": [0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0,
                 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0],
})

fitur = ["umur", "bmi", "olahraga_per_pekan", "makan_manis_per_pekan"]
X = pasien[fitur]
y = pasien["diabetes"]
X_latih, X_uji, y_latih, y_uji = train_test_split(X, y, test_size=0.3,
                                                   stratify=y, random_state=42)

model = GradientBoostingClassifier(n_estimators=100, learning_rate=0.1,
                                   random_state=42)
model.fit(X_latih, y_latih)

print("Akurasi data latih : %.2f" % model.score(X_latih, y_latih))
print("Akurasi data uji   : %.2f" % model.score(X_uji, y_uji))
print()
print("Perkembangan akurasi uji selama pelatihan:")
akurasi_tahap = [np.mean(prediksi == y_uji.to_numpy())
                 for prediksi in model.staged_predict(X_uji)]
for tahap in [1, 5, 10, 25, 50, 100]:
    print("  setelah %3d pohon: %.2f" % (tahap, akurasi_tahap[tahap - 1]))

Output yang Dihasilkan:

text
Akurasi data latih : 1.00
Akurasi data uji   : 0.91

Perkembangan akurasi uji selama pelatihan:
  setelah   1 pohon: 1.00
  setelah   5 pohon: 1.00
  setelah  10 pohon: 1.00
  setelah  25 pohon: 1.00
  setelah  50 pohon: 0.91
  setelah 100 pohon: 0.91

Mari kita telaah baris-baris kode di atas:

  1. GradientBoostingClassifier(n_estimators=100, learning_rate=0.1, random_state=42): n_estimators adalah jumlah tahap koreksi (pohon kecil) yang dirakit, learning_rate seberapa besar pengaruh tiap koreksi diterapkan, dan random_state tetap dikunci karena pemilihan titik belahan pohon mengandung unsur acak.
  2. model.staged_predict(X_uji) adalah generator yang mengembalikan prediksi gabungan setelah tiap tahap — cara resmi mengintip proses belajar berurutan. Kita mengubah seluruh tahap menjadi daftar akurasi, lalu menyadap beberapa tahap saja.
  3. Pola akurasinya mengungkap watak boosting: pohon pertama saja sudah cukup memisahkan data uji ini (1,00), karena makan_manis_per_pekan memang pemisah yang kuat. Setelah pohon ke-50, akurasi uji justru turun ke 0,91 — koreksi berikutnya tidak lagi memperbaiki pola sejati, melainkan mengejar derau pada data latih. Ingat ukuran sampelnya: satu dari sebelas pasien uji bernilai 9 poin persen, jadi baca angka ini sebagai perilaku, bukan angka final.

Justru di sinilah letak perbedaan fundamentalnya dengan random forest: pada bagging, menambah pohon hampir tidak pernah memperburuk; pada boosting, tambahan tahap bisa merugikan dan perlu diawasi — praktik umumnya dengan menghentikan pelatihan begitu akurasi pada data validasi berhenti menanjak (early stopping).

learning_rate: Ukuran Langkah Setiap Koreksi #

Parameter learning_rate (disebut juga shrinkage) mengalikan kontribusi tiap pohon baru sebelum ditambahkan ke gabungan. Nilai kecil berarti langkah hati-hati: tiap pohon hanya menggeser prediksi sedikit, sehingga dibutuhkan lebih banyak tahap untuk sampai pada model yang baik, tetapi jalannya lebih terkendali. Nilai besar berarti koreksi diterapkan penuh: model cepat menurunkan galat latih, tetapi mudah melompati pola yang halus dan menempel pada derau. Kita ukur langsung dampaknya dengan mengunci n_estimators=100 dan mengubah laju belajarnya:

python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingClassifier

pasien = pd.DataFrame({
    "umur": [33, 38, 28, 39, 40, 26, 35, 61, 58, 24, 31, 55, 42, 47, 59, 63, 29,
             65, 57, 53, 45, 36, 52, 51, 49, 27, 48, 44, 41, 46, 37, 50, 43, 34],
    "bmi": [22, 25, 22, 27, 23, 20, 24, 29, 34, 19, 21, 36, 22, 32, 35, 30, 23,
            33, 37, 26, 24, 21, 31, 28, 33, 20, 23, 31, 26, 28, 26, 27, 25, 25],
    "olahraga_per_pekan": [6, 4, 5, 1, 3, 5, 4, 0, 1, 6, 6, 0, 3, 2, 0, 1, 4,
                           1, 2, 5, 5, 7, 0, 3, 2, 5, 4, 0, 2, 2, 3, 4, 1, 2],
    "makan_manis_per_pekan": [1, 2, 2, 5, 2, 3, 3, 5, 7, 2, 1, 6, 2, 7, 6, 5, 2,
                              4, 6, 2, 3, 1, 6, 5, 8, 1, 3, 8, 4, 5, 3, 3, 4, 3],
    "diabetes": [0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0,
                 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0],
})
X = pasien[["umur", "bmi", "olahraga_per_pekan", "makan_manis_per_pekan"]]
y = pasien["diabetes"]
X_latih, X_uji, y_latih, y_uji = train_test_split(X, y, test_size=0.3,
                                                   stratify=y, random_state=42)

print("learning_rate | akurasi latih | akurasi uji")
for laju in [0.02, 0.1, 0.3, 1.0]:
    model = GradientBoostingClassifier(n_estimators=100, learning_rate=laju,
                                       random_state=42)
    model.fit(X_latih, y_latih)
    print("          %.2f |          %.2f |        %.2f"
          % (laju, model.score(X_latih, y_latih), model.score(X_uji, y_uji)))

Output yang Dihasilkan:

text
learning_rate | akurasi latih | akurasi uji
          0.02 |          1.00 |        1.00
          0.10 |          1.00 |        0.91
          0.30 |          1.00 |        0.91
          1.00 |          1.00 |        0.91

Tabel ini menampilkan pertukarannya dengan jujur pada data kecil kita:

  1. learning_rate=0.02 menang: dengan langkah kecil, 100 tahap koreksi baru setara dengan sedikit pembelajaran efektif — model belum sempat mengejar derau, sehingga akurasi ujinya masih 1,00.
  2. Laju 0,1 hingga 1,0 sama-sama 0,91: semakin agresif langkahnya, semakin cepat galat latih habis (semuanya 1,00 pada data latih), dan semakin cepat pula sisa energi pelatihan terserap ke derau.
  3. Akurasi latih 1,00 di semua laju menegaskan bahwa galat latih bukan lagi sumber informasi — yang membedakan hanyalah seberapa dalam model menempel pada kekhasan data latih.

XGBoost: Boosting dengan Rem Bawaan #

GradientBoostingClassifier milik scikit-learn adalah implementasi rapi untuk belajar, tetapi dunia industri dan kompetisi lebih sering memakai XGBoost — pustaka terpisah yang mengemas boosting dengan rem bawaan: regularisasi L1/L2 pada bobot daun, penalti kedalaman, dan penanganan missing values. Antarmukanya sengaja dibuat serasi dengan scikit-learn sehingga pola fit-score kita tetap berlaku. Kita adu ketiganya pada pemisahan data yang sama:

python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingClassifier, RandomForestClassifier
from xgboost import XGBClassifier

pasien = pd.DataFrame({
    "umur": [33, 38, 28, 39, 40, 26, 35, 61, 58, 24, 31, 55, 42, 47, 59, 63, 29,
             65, 57, 53, 45, 36, 52, 51, 49, 27, 48, 44, 41, 46, 37, 50, 43, 34],
    "bmi": [22, 25, 22, 27, 23, 20, 24, 29, 34, 19, 21, 36, 22, 32, 35, 30, 23,
            33, 37, 26, 24, 21, 31, 28, 33, 20, 23, 31, 26, 28, 26, 27, 25, 25],
    "olahraga_per_pekan": [6, 4, 5, 1, 3, 5, 4, 0, 1, 6, 6, 0, 3, 2, 0, 1, 4,
                           1, 2, 5, 5, 7, 0, 3, 2, 5, 4, 0, 2, 2, 3, 4, 1, 2],
    "makan_manis_per_pekan": [1, 2, 2, 5, 2, 3, 3, 5, 7, 2, 1, 6, 2, 7, 6, 5, 2,
                              4, 6, 2, 3, 1, 6, 5, 8, 1, 3, 8, 4, 5, 3, 3, 4, 3],
    "diabetes": [0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0,
                 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0],
})
X = pasien[["umur", "bmi", "olahraga_per_pekan", "makan_manis_per_pekan"]]
y = pasien["diabetes"]
X_latih, X_uji, y_latih, y_uji = train_test_split(X, y, test_size=0.3,
                                                   stratify=y, random_state=42)

hasil = []

hutan = RandomForestClassifier(n_estimators=100, random_state=42)
hutan.fit(X_latih, y_latih)
hasil.append(("Random Forest (bagging)", hutan.score(X_uji, y_uji)))

gb = GradientBoostingClassifier(n_estimators=100, learning_rate=0.1,
                                random_state=42)
gb.fit(X_latih, y_latih)
hasil.append(("GradientBoosting (sklearn)", gb.score(X_uji, y_uji)))

xgb = XGBClassifier(n_estimators=100, learning_rate=0.1, random_state=42,
                    eval_metric="logloss")
xgb.fit(X_latih, y_latih)
hasil.append(("XGBoost (boosting)", xgb.score(X_uji, y_uji)))

print("Perbandingan tiga model pada data uji:")
for nama, skor in hasil:
    print("  %-26s: akurasi %.2f" % (nama, skor))

Output yang Dihasilkan:

text
Perbandingan tiga model pada data uji:
  Random Forest (bagging)   : akurasi 1.00
  GradientBoosting (sklearn): akurasi 0.91
  XGBoost (boosting)        : akurasi 1.00

Mari kita telaah bagian pentingnya:

  1. XGBClassifier(..., eval_metric="logloss"): kita memberi tahu XGBoost metrik pemantauan yang dipakai — logloss, ukuran kualitas probabilitas untuk klasifikasi — supaya proses latih dan penghentian dini punya tolok ukur yang jelas. Parameter lainnya kita kenal dari GradientBoostingClassifier.
  2. XGBoost mencapai 1,00 pada pengaturan yang sama yang membuat GradientBoosting sklearn jatuh ke 0,91. Pembedanya bukan kebetulan: rem bawaan XGBoost (regularisasi daun dan min_child_weight, yaitu jumlah contoh minimum pada tiap daun) mencegah tahap-tahap akhir menempel pada satu-dua pasien perbatasan.
  3. Daftar hasil sengaja dibangun bertahap agar ketiga model berlatih pada pemisahan data yang persis sama — syarat wajib agar perbandingannya adil.

Kekuatan dan Kelemahan Gradient Boosting #

AspekKeterangan
Akurasi pada data tabularSering menjadi yang tertinggi di antara model klasik saat disetel serius
Butuh penyetelanlearning_rate, n_estimators, dan kedalaman saling terkait; hasil bawaan belum tentu terbaik
Tambahan tahap bisa merugikanBerbeda dari bagging; wajib diawasi dengan validasi atau early stopping
Tanpa penskalaan fiturWarisan pohon: bmi dan umur boleh beda satuan
Pelatihan berurutanTahap m bergantung pada tahap m-1, sulit diparalelkan seutuh hutan
Ukuran model besarRatusan pohon tersimpan; kalibrasi dan pemangkasan diperlukan untuk produksi

Kapan Kita Memakainya #

Gradient boosting adalah pilihan saat data tabular berukuran ratusan baris ke atas, ketika kita mengejar akurasi setinggi mungkin, dan ketika kita bersedia mengelola proses penyetelannya. Untuk dataset mungil seperti contoh kita, random forest yang santai sering sudah cukup — bahkan lebih aman. Sejauh ini seluruh model kita, dari regresi hingga ensemble, adalah model parametrik yang melatih parameter dari data. Chapter berikutnya memperkenalkan pendekatan yang sama sekali berbeda: model yang tidak melatih apa pun, cukup menghafal data lalu bertanya kepada tetangga terdekat.

Rangkuman #

  • Gradient boosting membangun model aditif secara berurutan: tiap pohon kecil dilatih pada residual (sisa galat) gabungan sebelumnya, dikalikan learning_rate sebelum ditambahkan.
  • Berlawanan dengan bagging yang menurunkan variansi, boosting menurunkan bias dengan merakit pohon lemah menjadi kuat — tetapi tambahan tahap bisa merugikan, seperti akurasi uji kita yang turun dari 1,00 ke 0,91 setelah pohon ke-50.
  • learning_rate mengatur ukuran langkah: laju 0,02 dengan 100 tahap mempertahankan 1,00 pada data kita, sementara laju 0,1 ke atas tergesa mengejar derau.
  • XGBoost menambahkan regularisasi bawaan sehingga pada pengaturan yang sama mencapai 1,00 — alasan pustaka ini menjadi standar industri untuk data tabular.
  • Pembandingan model selalu memakai pemisahan data yang sama; angka 0,91 lawan 1,00 pada 11 pasien uji adalah petunjuk perilaku, bukan putusan akhir.

Keluarga ensemble berbasis pohon kita telah lengkap. Mari kita berpindah ke model dengan filosofi paling sederhana sekaligus paling berbeda: menghafal seluruh data dan membiarkan tetangga terdekat yang bicarakan — chapter K-Nearest Neighbors .

Referensi #

  1. XGBoost — Dokumentasi resmi
  2. scikit-learn — FAQ (memilih dan menyetel model)