Kita sudah bertemu dua ensemble tanpa menyebutnya dengan nama itu: Random Forest menggabungkan ratusan pohon hasil bagging, dan Gradient Boosting merangkai pohon koreksi berantai. Ensemble learning adalah payung besar di atas keduanya: disiplin menggabungkan beberapa model agar keputusan bersamanya lebih baik daripada model mana pun yang bekerja sendirian. Kenapa bisa begitu? Karena model-model yang berbeda kekeliruannya saling melengkapi — satu model salah di baris yang benar menurut model lain. Pada chapter ini kita petakan empat keluarga ensemble, lalu mempraktikkan dua yang belum kita coba: voting dan stacking.

Empat Keluarga Ensemble #

KeluargaCara menggabungkanContohButuh model beragam?
BaggingModel sejenis dilatih paralel di salinan acak data, digabung suara mayoritasRandom ForestDibuat otomatis lewat acakan
BoostingModel dibangun berurutan, tiap model memperbaiki galat gabungan sebelumnyaGradient Boosting, XGBoostDibentuk oleh tahapan koreksi
VotingModel berbeda jenis dilatih penuh, lalu digabung lewat suara atau peluang rata-rataVotingClassifierYa — kemusatan ada di tangan kita
StackingSeperti voting, tetapi penggabungnya adalah model yang belajar dari keluaran para modelStackingClassifierYa — sama seperti voting

Bagging dan boosting sudah kita bedah pada bab masing-masing, jadi chapter ini memusatkan perhatian pada pasangan terakhir. Pembeda utamanya: pada voting dan stacking, kita bebas memilih model-model yang jauh berbeda karakter — misalnya regresi logistik, decision tree, dan Naive Bayes — karena kekuatan ensemble justru datang dari perbedaan itu.

Voting Keras dan Voting Lunak #

VotingClassifier menyediakan dua mekanisme suara. Voting keras (hard voting) mengambil label: tiap model menyebut kelasnya, kelas dengan suara terbanyak menang. Voting lunak (soft voting) mengambil keyakinan: tiap model menyampaikan predict_proba, probabilitas tiap kelas dirata-rata, kelas dengan rata-rata tertinggi menang. Voting lunak biasanya lebih kaya informasi karena membedakan “hampir yakin 0,51” dari “yakin 0,99” — dua hal yang di voting keras tercatat sama sebagai satu suara.

Dataset kita: 40 kunjungan pelanggan Toko Kopi Kita dengan dua fitur — lama menunggu (menit) dan total belanja (ribu rupiah) — dan label apakah pelanggan menyatakan puas. Tiga kandidat model: LogisticRegression, DecisionTreeClassifier, dan GaussianNB; akurasi diukur pada 12 baris uji yang tidak dilatih para model.

python
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import VotingClassifier
from sklearn.metrics import accuracy_score

rng = np.random.default_rng(42)

# 40 kunjungan: waktu tunggu dan total belanja menentukan puas/tidak (dengan goyangan acak)
menunggu = rng.integers(1, 31, 40)
belanja = rng.integers(18, 55, 40)
skor = 6.0 - 0.18 * menunggu - 0.07 * belanja
peluang = 1 / (1 + np.exp(-skor))
puas = (rng.uniform(0, 1, 40) < peluang).astype(int)

X = pd.DataFrame({"menunggu_menit": menunggu, "belanja_ribu": belanja})
y = pd.Series(puas, name="puas")

X_latih, X_uji, y_latih, y_uji = train_test_split(
    X, y, test_size=0.3, stratify=y, random_state=42
)

kandidat = [
    ("logreg", LogisticRegression(max_iter=1000)),
    ("pohon", DecisionTreeClassifier(max_depth=3, random_state=42)),
    ("nb", GaussianNB()),
]

hasil_akhir = {}
for nama, model in kandidat:
    model.fit(X_latih, y_latih)
    hasil_akhir[nama] = accuracy_score(y_uji, model.predict(X_uji))

voting_keras = VotingClassifier(estimators=kandidat, voting="hard")
voting_keras.fit(X_latih, y_latih)
hasil_akhir["voting_keras"] = accuracy_score(y_uji, voting_keras.predict(X_uji))

voting_lunak = VotingClassifier(estimators=kandidat, voting="soft")
voting_lunak.fit(X_latih, y_latih)
hasil_akhir["voting_lunak"] = accuracy_score(y_uji, voting_lunak.predict(X_uji))

for nama, skor_akurasi in hasil_akhir.items():
    print(f"Akurasi {nama:13s}: {skor_akurasi:.3f}")

# lihat mekanisme suara pada tiga pelanggan uji yang paling tidak pasti
peluang_ensemble = voting_lunak.predict_proba(X_uji)[:, 1]
urutan_tidak_pasti = np.argsort(np.abs(peluang_ensemble - 0.5))[:3]
print()
print("Mekanisme suara pada tiga pelanggan paling tidak pasti:")
for i in urutan_tidak_pasti:
    baris = X_uji.iloc[[i]]
    suara = [int(model.predict(baris)[0]) for _, model in kandidat]
    proba = [model.predict_proba(baris)[0, 1] for _, model in kandidat]
    print(f"menunggu={baris['menunggu_menit'].iloc[0]:2d}, belanja={baris['belanja_ribu'].iloc[0]:2d} | "
          f"suara={suara}, rata-rata proba={np.mean(proba):.2f}, "
          f"putusan ensemble={voting_lunak.predict(baris)[0]}, asli={y_uji.iloc[i]}")

Output yang Dihasilkan:

text
Akurasi logreg       : 0.667
Akurasi pohon        : 0.750
Akurasi nb           : 0.750
Akurasi voting_keras : 0.667
Akurasi voting_lunak : 0.750

Mekanisme suara pada tiga pelanggan paling tidak pasti:
menunggu=13, belanja=51 | suara=[1, 0, 1], rata-rata proba=0.51, putusan ensemble=1, asli=0
menunggu=26, belanja=30 | suara=[0, 1, 0], rata-rata proba=0.53, putusan ensemble=1, asli=1
menunggu=23, belanja=31 | suara=[1, 1, 0], rata-rata proba=0.57, putusan ensemble=1, asli=0

Mari kita bedah baris-baris pentingnya:

  1. Baris 12-16 (pembuatan data): label dibangkitkan lewat fungsi peluang mulus berbasis lama tunggu dan belanja, lalu dilempar dadu — sehingga hubungan fitur-label nyata tetapi berisik, seperti data kepuasan pelanggan yang sesungguhnya.
  2. Baris 19-23 (kandidat): tiga model dari tiga keluarga berbeda — batas linear, aturan pohon, dan distribusi Gaussian. Perbedaan karakter inilah bahan bakunya ensemble.
  3. Baris 30-36 (dua ensemble): VotingClassifier menerima daftar kandidat yang sama dua kali dengan mekanisme suara berbeda; keduanya memanggil fit yang melatih ketiga model secara utuh.
  4. Bacaan akurasi: voting keras (0,667) justru di bawah dua model terbaiknya (0,750) — suara “yang paling banyak” membuang tingkat keyakinan. Voting lunak menyamai 0,750: tidak mengungguli model terbaik, tetapi kita tidak perlu tahu di muka model mana yang terbaik. Itulah nilai jaminannya.
  5. Tiga pelanggan tidak pasti: perhatikan baris kedua — pohon bersuara 1 sendirian melawan dua model, tetapi rata-rata probabilitas 0,53 membuat putusan tetap 1. Keyakinan lunak bisa membalik suara keras ketika model minoritas sangat yakin (atau sebaliknya).

Stacking: Penggabung yang Belajar Menimbang #

Voting memperlakukan semua model setara — rata-rata sederhana tanpa pandang bulu. Stacking menambahkan satu lapis: keluaran tiap model (probabilitasnya) menjadi fitur baru bagi sebuah penggabung (meta-model) yang belajar sendiri model mana yang layak dipercaya dan seberapa besar. Untuk menghindari kebocoran, keluaran para kandidat dihasilkan lewat cross-validation di dalam data latih, bukan dari hasil menghafal data itu sendiri.

python
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import StackingClassifier
from sklearn.metrics import accuracy_score

rng = np.random.default_rng(42)
menunggu = rng.integers(1, 31, 40)
belanja = rng.integers(18, 55, 40)
skor = 6.0 - 0.18 * menunggu - 0.07 * belanja
peluang = 1 / (1 + np.exp(-skor))
puas = (rng.uniform(0, 1, 40) < peluang).astype(int)

X = pd.DataFrame({"menunggu_menit": menunggu, "belanja_ribu": belanja})
y = pd.Series(puas, name="puas")
X_latih, X_uji, y_latih, y_uji = train_test_split(
    X, y, test_size=0.3, stratify=y, random_state=42
)

kandidat = [
    ("logreg", LogisticRegression(max_iter=1000)),
    ("pohon", DecisionTreeClassifier(max_depth=3, random_state=42)),
    ("nb", GaussianNB()),
]

stacking = StackingClassifier(
    estimators=kandidat,
    final_estimator=LogisticRegression(max_iter=1000),
    cv=3,
)
stacking.fit(X_latih, y_latih)
print(f"Akurasi stacking: {accuracy_score(y_uji, stacking.predict(X_uji)):.3f}")

model_final = stacking.final_estimator_
print("Bobot gabungan yang dipelajari penggabung (logistic regression):")
for nama, bobot in zip(["intercept", "logreg", "pohon", "nb"], np.r_[model_final.intercept_[0], model_final.coef_[0]]):
    print(f"  {nama:9s}: {bobot:+.3f}")

Output yang Dihasilkan:

text
Akurasi stacking: 0.667
Bobot gabungan yang dipelajari penggabung (logistic regression):
  intercept: -1.021
  logreg   : +0.422
  pohon    : +1.286
  nb       : +0.893

Bedah hasilnya:

  1. Baris 26-30 (StackingClassifier): tiga kandidat yang sama kini dilatih cv=3 kali lipat untuk menghasilkan keluaran jujur; final_estimator adalah penggabungnya — di sini regresi logistik, tapi model apa pun bisa.
  2. Empat bobot: penggabung memberi bobot terbesar pada pohon (+1,286) dan terkecil pada logreg (+0,422). Voting lunak tadi memperlakukan ketiganya setara; stacking belajar bahwa pohon lebih layak dipercaya pada data ini.
  3. Akurasi 0,667: lebih rendah daripada voting lunak — dan itu pelajaran penting. Penggabung juga butuh data untuk belajar, sementara data latih kita hanya 28 baris. Pada data sekecil ini, selisih satu-dua baris uji (12 baris) lebih merupakan derau ketimbang keunggulan. Semakin besar data, semakin layak stacking dibandingkan voting sederhana.

Rangkuman #

  • Ensemble menggabungkan model agar kekeliruan yang berbeda saling menutup; syarat utamanya para anggotanya tidak seragam.
  • Empat keluarga besar: bagging (paralel di salinan acak), boosting (berurutan memperbaiki galat), voting (suara/ probabilitas model berbeda jenis), dan stacking (penggabung belajar menimbang).
  • Voting keras membuang tingkat keyakinan; voting lunak merata-ratakan predict_proba sehingga biasanya lebih kuat — pada data kita 0,750 lawan 0,667.
  • Stacking memperlakukan keluaran model sebagai fitur penggabung; bobotnya terbukti tidak setara (pohon +1,286 vs logreg +0,422), tetapi penggabung itu sendiri menuntut data yang cukup.

Model yang digabung semakin kuat, tetapi semakin banyak model yang ikut memutuskan, semakin sulit menjelaskan mengapa keputusan itu diambil. Kebutuhan itu membawa kita ke wilayah explainable AI pada chapter berikutnya: Explainable AI .

Referensi: