Pembelajaran Ensemble
Why Single Models Fail
Sebelum memahami kekuatan Ensemble, kita perlu memahami kelemahan mendasar model tunggal — dan mengapa menggabungkan banyak model lemah bisa menghasilkan prediksi yang jauh lebih kuat.
Masalah Utama Model Tunggal
Model Machine Learning tunggal selalu berhadapan dengan dua musuh utama: Bias dan Variance. Mengurangi satu masalah biasanya memperburuk yang lain — inilah yang disebut Bias-Variance Tradeoff.
📉 High Bias (Underfitting)
Model terlalu sederhana. Tidak mampu menangkap pola dalam data. Contoh: Decision Tree dengan max_depth=1. Performa buruk di training dan test data.
📈 High Variance (Overfitting)
Model terlalu kompleks. “Hafal” data training tapi gagal pada data baru. Contoh: Decision Tree tanpa batasan kedalaman. Performa sempurna di training, jelek di test.
Tiga Sumber Kegagalan Model Tunggal
-
1Bias Tinggi — Model Terlalu Simplistis Model seperti Linear Regression berasumsi hubungan data bersifat linear. Jika data nyata lebih kompleks, model ini akan selalu salah, tidak peduli seberapa banyak data yang diberikan.
-
2Variance Tinggi — Model Terlalu Sensitif Terhadap Data Decision Tree yang dalam bisa “hafal” semua noise di data training. Sedikit perubahan pada data input akan menghasilkan prediksi yang sangat berbeda.
-
3Keterbatasan Satu Perspektif Satu model hanya melihat pola dari satu “sudut pandang” algoritma. Realita dunia nyata sering memiliki pola yang kompleks yang sulit ditangkap oleh satu jenis model.
Solusi: Wisdom of the Crowd — Ide Dasar Ensemble
Bayangkan kamu meminta 1.000 orang menebak berat sebuah sapi. Rata-rata tebakan mereka hampir selalu lebih akurat daripada tebakan satu ahli saja. Inilah prinsip “Wisdom of Crowds” yang menjadi landasan Ensemble Learning.
✅ Strategi Ensemble: Bagging
Latih banyak model pada subset data berbeda (bootstrap sampling), lalu gabungkan hasil voting/averaging. Mengurangi Variance. Contoh: Random Forest.
🚀 Strategi Ensemble: Boosting
Latih model secara berurutan — setiap model baru fokus memperbaiki kesalahan model sebelumnya. Mengurangi Bias. Contoh: XGBoost, LightGBM.
🔀 Strategi Ensemble: Stacking
Gabungkan prediksi dari beberapa model yang berbeda jenis (meta-learning) menggunakan model tingkat kedua. Paling fleksibel, tapi juga paling kompleks.
| Metode | Cara Kerja | Masalah yang Diselesaikan | Contoh Algoritma |
|---|---|---|---|
| Bagging | Parallel — model independent | High Variance (Overfitting) | Random Forest |
| Boosting | Sequential — model saling melengkapi | High Bias (Underfitting) | AdaBoost, XGBoost, LightGBM |
| Stacking | Meta-learning dari output model lain | Bias dan Variance | StackingClassifier |
| Voting | Ambil mayoritas/rata-rata prediksi | Variance & ketidakpastian | VotingClassifier |
🧠 Uji Pemahaman
Random Forests Explained
Random Forest adalah raja algoritma Bagging — ia membangun ratusan Decision Tree pada data yang berbeda-beda, lalu menggabungkan suara mereka. Hasilnya? Model yang stabil, akurat, dan tahan banting terhadap overfitting.
Cara Kerja Random Forest
Random Forest menggabungkan dua sumber keacakan: Bootstrap Sampling (data acak) dan Random Feature Selection (fitur acak). Kombinasi ini menghasilkan pohon-pohon yang beragam sehingga error mereka saling mengkompensasi.
Dua Keacakan yang Membuat Random Forest Kuat
🎲 1. Bootstrap Sampling (Bagging)
Setiap pohon dilatih pada sampel dengan pengembalian (with replacement) dari data asli. Rata-rata ~63.2% data unik per pohon, sisanya terulang. Data yang tidak terpakai disebut Out-of-Bag (OOB) dan digunakan untuk validasi gratis!
🎯 2. Random Feature Selection
Di setiap node split, hanya √(jumlah fitur) yang dipilih secara acak untuk dipertimbangkan. Ini memastikan setiap pohon berbeda — tidak semua pohon akan memilih fitur yang sama di root node.
Pengaruh Jumlah Pohon — Simulator Interaktif
🎛️ Coba: Berapa Jumlah Pohon yang Ideal?
💡 Performa meningkat pesat di awal, lalu melandai setelah ~100 pohon. Menambah pohon tidak akan membuat model overfit, tapi akan memperlambat training!
Implementasi Random Forest di Python
from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import cross_val_score from sklearn.metrics import accuracy_score, classification_report import numpy as np # ── Inisialisasi Random Forest ── rf = RandomForestClassifier( n_estimators=100, # Jumlah pohon max_depth=None, # Biarkan pohon tumbuh penuh (ensemble urus overfitting) max_features='sqrt', # √n_features di tiap split min_samples_leaf=1, # Min sampel di leaf oob_score=True, # Aktifkan OOB Score n_jobs=-1, # Gunakan semua CPU core random_state=42 ) # ── Training ── rf.fit(X_train, y_train) # ── Evaluasi ── y_pred = rf.predict(X_test) print(f"Test Accuracy : {accuracy_score(y_test, y_pred):.4f}") print(f"OOB Score : {rf.oob_score_:.4f}") # Estimasi akurasi tanpa test set! # ── Cross-Validation untuk validasi lebih robust ── cv_scores = cross_val_score(rf, X_train, y_train, cv=5, scoring='accuracy') print(f"CV Accuracy : {cv_scores.mean():.4f} ± {cv_scores.std():.4f}") print("\nClassification Report:") print(classification_report(y_test, y_pred))
from sklearn.model_selection import GridSearchCV # Grid parameter yang akan dicoba param_grid = { 'n_estimators' : [50, 100, 200], 'max_depth' : [None, 10, 20], 'max_features' : ['sqrt', 'log2'], 'min_samples_leaf': [1, 2, 5], } grid_search = GridSearchCV( RandomForestClassifier(random_state=42, oob_score=True), param_grid, cv=5, scoring='f1', n_jobs=-1, verbose=1 ) grid_search.fit(X_train, y_train) print(f"Parameter Terbaik: {grid_search.best_params_}") print(f"F1-Score Terbaik : {grid_search.best_score_:.4f}") best_rf = grid_search.best_estimator_
Kelebihan vs Kekurangan Random Forest
✅ Kelebihan
• Sangat tahan terhadap overfitting
• Bisa menangani data dengan banyak fitur
• Memberikan feature importance secara gratis
• OOB score sebagai validasi bawaan
• Hampir tidak perlu feature scaling
❌ Kekurangan
• Model “black box” — sulit diinterpretasi
• Lambat untuk prediksi real-time (ratusan pohon)
• Membutuhkan lebih banyak memori dan waktu training
• Kurang baik untuk data time series
🧠 Uji Pemahaman
Gradient Boosting Intuition
Gradient Boosting adalah teknik paling powerful dalam dunia ML kompetitif. Berbeda dengan Random Forest yang paralel, Boosting membangun model secara berurutan — setiap model baru adalah “dokter” yang mengobati kesalahan model sebelumnya.
Intuisi: Belajar dari Kesalahan
Bayangkan seorang siswa yang mengerjakan soal ujian. Setelah tiap percobaan, ia hanya fokus belajar soal-soal yang salah. Inilah inti dari Boosting — setiap model selanjutnya memberikan bobot lebih besar pada data yang sebelumnya salah diprediksi.
Perbedaan Mendasar: Bagging vs Boosting
🌳 Bagging (Random Forest)
Paralel: semua pohon dilatih secara bersamaan & independent. Mengurangi Variance. Setiap pohon bertanya: “Apa prediksi terbaikku untuk data ini?” Hasil digabung dengan voting.
🚀 Boosting (GBM)
Sequential: model dilatih satu per satu. Setiap model mempelajari residual (kesalahan) model sebelumnya. Mengurangi Bias. Lebih kuat tapi lebih mudah overfit.
Learning Rate — Pengontrol Kecepatan Belajar
Learning rate (η) mengontrol seberapa besar kontribusi setiap pohon baru. Nilai kecil = belajar lambat tapi stabil. Nilai besar = belajar cepat tapi bisa melewati solusi optimal.
🎛️ Simulator: Dampak Learning Rate
💡 Aturan praktis: Learning Rate kecil + Banyak Pohon hampir selalu lebih baik dari Learning Rate besar + Sedikit Pohon. Gunakan 0.01–0.1 untuk hasil terbaik.
Tiga Varian Gradient Boosting Populer
📦 GradientBoostingClassifier
Implementasi bawaan Scikit-learn. Mudah digunakan tapi paling lambat. Cocok untuk dataset kecil-medium.
⚡ XGBoost
Extreme Gradient Boosting. Sangat cepat dengan regularisasi L1/L2 bawaan. Pemenang banyak kompetisi Kaggle.
💡 LightGBM
Light Gradient Boosting Machine. Tercepat untuk dataset besar. Menggunakan histogram-based learning yang sangat efisien.
Implementasi XGBoost & LightGBM
# Install: pip install xgboost import xgboost as xgb from sklearn.metrics import accuracy_score, f1_score, roc_auc_score # ── Inisialisasi XGBoost ── xgb_model = xgb.XGBClassifier( n_estimators=300, # Jumlah boosting rounds learning_rate=0.05, # Step size (η) — kecil lebih baik max_depth=6, # Kedalaman tiap pohon subsample=0.8, # Fraction data per pohon (seperti bagging) colsample_bytree=0.8, # Fraction fitur per pohon reg_alpha=0.1, # Regularisasi L1 reg_lambda=1.0, # Regularisasi L2 use_label_encoder=False, eval_metric='logloss', random_state=42, n_jobs=-1 ) # ── Training dengan Early Stopping ── xgb_model.fit( X_train, y_train, eval_set=[(X_test, y_test)], early_stopping_rounds=20, # Berhenti jika 20 round tidak improve verbose=50 ) # ── Evaluasi ── y_pred = xgb_model.predict(X_test) y_proba = xgb_model.predict_proba(X_test)[:, 1] print(f"XGBoost Accuracy: {accuracy_score(y_test, y_pred):.4f}") print(f"XGBoost F1-Score: {f1_score(y_test, y_pred):.4f}") print(f"XGBoost ROC-AUC : {roc_auc_score(y_test, y_proba):.4f}")
# Install: pip install lightgbm import lightgbm as lgb # ── Inisialisasi LightGBM ── lgb_model = lgb.LGBMClassifier( n_estimators=300, learning_rate=0.05, max_depth=-1, # -1 = tidak dibatasi (LightGBM atur sendiri) num_leaves=31, # Parameter utama LightGBM subsample=0.8, colsample_bytree=0.8, reg_alpha=0.1, reg_lambda=0.1, min_child_samples=20, random_state=42, n_jobs=-1, verbose=-1 # Suppress output ) lgb_model.fit( X_train, y_train, eval_set=[(X_test, y_test)], callbacks=[lgb.early_stopping(20), lgb.log_evaluation(50)] ) y_pred_lgb = lgb_model.predict(X_test) print(f"LightGBM Accuracy: {accuracy_score(y_test, y_pred_lgb):.4f}") print(f"LightGBM F1-Score: {f1_score(y_test, y_pred_lgb):.4f}")
Perbandingan Algoritma Boosting
| Aspek | GBM (sklearn) | XGBoost | LightGBM |
|---|---|---|---|
| Kecepatan Training | 🐢 Lambat | 🐇 Cepat | 🚀 Tercepat |
| Penggunaan Memory | Tinggi | Sedang | Paling Rendah |
| Akurasi | Baik | Sangat Baik | Sangat Baik |
| Regularisasi | Dasar | L1 + L2 | L1 + L2 |
| Dataset Besar | ❌ Tidak ideal | ✅ Baik | ✅ Sangat Baik |
| Kemudahan Tuning | Mudah | Sedang | Sedang |
🧠 Uji Pemahaman
Feature Importance & Interpretability
Model Ensemble yang powerful sering dianggap “black box”. Di sub-bab ini kita akan membuka kotak hitam itu — memahami fitur mana yang paling berpengaruh dan bagaimana model membuat keputusannya.
Mengapa Interpretabilitas Penting?
Di dunia kerja, seorang Data Scientist tidak hanya perlu model yang akurat — ia juga perlu menjelaskan mengapa model membuat keputusan tertentu kepada stakeholder bisnis, tim hukum, atau regulator. Inilah mengapa interpretabilitas adalah skill yang wajib dimiliki.
🔍 Debugging Model
Menemukan fitur yang tidak seharusnya berpengaruh besar (data leakage, proxy variable).
💼 Business Insight
Memberikan insight actionable: “Apa yang harus dilakukan bisnis untuk mengurangi churn?”
⚖️ Fairness & Compliance
Memastikan model tidak diskriminatif berdasarkan ras, gender, atau atribut sensitif lainnya.
Metode 1 — Built-in Feature Importance (Gini/MDI)
Random Forest dan Gradient Boosting secara otomatis menghitung Mean Decrease in Impurity (MDI) — seberapa banyak setiap fitur mengurangi Gini Impurity di semua pohon. Metode ini cepat tapi bisa bias terhadap fitur dengan kardinalitas tinggi.
import pandas as pd import matplotlib.pyplot as plt import numpy as np # ── Feature Importance dari Random Forest ── feature_imp = pd.Series( rf.feature_importances_, index=feature_names ).sort_values(ascending=False) # Ambil Top 15 fitur top_features = feature_imp.head(15) # ── Visualisasi ── fig, ax = plt.subplots(figsize=(10, 6)) colors = plt.cm.viridis(np.linspace(0.3, 0.9, len(top_features))) bars = ax.barh(top_features.index, top_features.values, color=colors) ax.set_xlabel('Feature Importance (MDI)') ax.set_title('Top 15 Feature Importance — Random Forest') ax.invert_yaxis() # Tambahkan nilai di setiap bar for bar, val in zip(bars, top_features.values): ax.text(val + 0.001, bar.get_y() + bar.get_height()/2, f'{val:.3f}', va='center', fontsize=9) plt.tight_layout() plt.show()
Metode 2 — Permutation Feature Importance
Lebih reliable dari MDI. Cara kerjanya: acak (permutasikan) nilai satu fitur, lalu ukur seberapa besar penurunan performa model. Fitur penting = performa turun drastis saat fitur tersebut diacak.
from sklearn.inspection import permutation_importance # Hitung permutation importance pada test set perm_imp = permutation_importance( rf, X_test, y_test, n_repeats=30, # Ulangi 30x untuk hasil stabil random_state=42, scoring='f1' # Metrik yang digunakan ) # Buat DataFrame hasil perm_df = pd.DataFrame({ 'feature' : feature_names, 'importance': perm_imp.importances_mean, 'std' : perm_imp.importances_std }).sort_values('importance', ascending=False) # Plot dengan error bar fig, ax = plt.subplots(figsize=(10, 6)) ax.barh(perm_df['feature'].head(15), perm_df['importance'].head(15), xerr=perm_df['std'].head(15), color='#6c63ff', alpha=0.8) ax.set_title('Permutation Feature Importance (Test Set)') ax.invert_yaxis() plt.tight_layout() plt.show()
Metode 3 — SHAP Values (State of the Art)
SHAP (SHapley Additive exPlanations) adalah standar emas interpretabilitas ML modern. Berdasarkan teori game Shapley, SHAP menghitung kontribusi setiap fitur untuk setiap prediksi individual — bukan hanya secara global.
🌍 Global Explanation
Fitur mana yang paling penting secara keseluruhan? Gunakan Summary Plot untuk melihat distribusi SHAP values semua sampel.
🔎 Local Explanation
Mengapa model memprediksi X untuk sampel tertentu? Gunakan Waterfall Plot / Force Plot untuk satu prediksi spesifik.
# Install: pip install shap import shap import matplotlib.pyplot as plt # ── Buat SHAP Explainer untuk Tree-based model ── explainer = shap.TreeExplainer(xgb_model) # Hitung SHAP values untuk test set shap_values = explainer.shap_values(X_test) # ── 1. Summary Plot (Global) ── plt.figure() shap.summary_plot( shap_values, X_test, feature_names=feature_names, plot_type="bar", # Bar chart rata-rata |SHAP| show=True ) # ── 2. Beeswarm Plot (distribusi per fitur) ── shap.summary_plot(shap_values, X_test, feature_names=feature_names) # ── 3. Waterfall Plot — Penjelasan Satu Prediksi ── idx = 0 # Index sampel yang ingin dijelaskan shap.waterfall_plot( shap.Explanation( values=shap_values[idx], base_values=explainer.expected_value, data=X_test[idx], feature_names=feature_names ) ) # ── 4. Force Plot (interaktif) ── shap.initjs() # Untuk Jupyter Notebook shap.force_plot( explainer.expected_value, shap_values[idx], X_test[idx], feature_names=feature_names )
| Metode | Scope | Kecepatan | Akurasi Interpretasi | Kapan Digunakan |
|---|---|---|---|---|
| MDI (Built-in) | Global | ⚡ Sangat Cepat | ⚠️ Bisa bias | Eksplorasi awal, cepat |
| Permutation | Global | 🐇 Cepat | ✅ Lebih akurat | Validasi final importance |
| SHAP | Global & Local | 🐢 Lambat | 🏆 Paling akurat | Presentasi ke stakeholder |
🧠 Uji Pemahaman
Hands-On: Improving Model Performance
Saatnya praktek nyata! Kita akan mengambil model terbaik dari Bab sebelumnya (Logistic Regression dengan F1~0.60) dan meningkatkan performanya secara signifikan menggunakan Ensemble Methods dan pipeline ML profesional.
Langkah 1 — Baseline dari Bab Sebelumnya
📊 Performa Model Sebelumnya (Bab 7)
Target kita: melewati semua baseline ini dengan Ensemble Learning! 🚀
Langkah 2 — Pipeline Profesional dengan Scikit-learn
Gunakan sklearn Pipeline untuk menyatukan preprocessing dan model dalam satu objek yang bersih dan tidak rentan data leakage.
from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.ensemble import RandomForestClassifier from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder import xgboost as xgb # Pisahkan kolom numerik dan kategoris num_features = X_train.select_dtypes(include=['int64', 'float64']).columns.tolist() cat_features = X_train.select_dtypes(include=['object']).columns.tolist() # Preprocessor: Scale numerik + Encode kategoris preprocessor = ColumnTransformer([ ('num', StandardScaler(), num_features), ('cat', OneHotEncoder(drop='first', sparse_output=False), cat_features) ]) # Pipeline Random Forest rf_pipeline = Pipeline([ ('prep', preprocessor), ('model', RandomForestClassifier( n_estimators=200, oob_score=True, class_weight='balanced', # Handle imbalanced class! random_state=42, n_jobs=-1 )) ]) # Pipeline XGBoost xgb_pipeline = Pipeline([ ('prep', preprocessor), ('model', xgb.XGBClassifier( n_estimators=300, learning_rate=0.05, max_depth=6, subsample=0.8, scale_pos_weight=2.7, # Ratio neg/pos untuk class imbalance random_state=42, n_jobs=-1, eval_metric='logloss' )) ]) # Training kedua pipeline rf_pipeline.fit(X_train, y_train) xgb_pipeline.fit(X_train, y_train)
Langkah 3 — Stacking Ensemble (Model Meta-Learning)
from sklearn.ensemble import StackingClassifier, RandomForestClassifier from sklearn.linear_model import LogisticRegression import lightgbm as lgb # ── Base Models (Level 0) ── base_models = [ ('rf' , RandomForestClassifier(n_estimators=100, class_weight='balanced', random_state=42, n_jobs=-1)), ('xgb' , xgb.XGBClassifier(n_estimators=200, learning_rate=0.05, scale_pos_weight=2.7, eval_metric='logloss', random_state=42, n_jobs=-1)), ('lgbm', lgb.LGBMClassifier(n_estimators=200, learning_rate=0.05, class_weight='balanced', verbose=-1, random_state=42, n_jobs=-1)), ] # ── Meta Model (Level 1) ── meta_model = LogisticRegression(C=0.1, max_iter=1000) # ── Stacking Classifier ── stacking = StackingClassifier( estimators=base_models, final_estimator=meta_model, cv=5, # 5-fold CV untuk generate Level 0 predictions stack_method='predict_proba', # Gunakan probabilitas, bukan kelas n_jobs=-1 ) stacking.fit(X_train_sc, y_train) # ── Evaluasi ── y_pred_stack = stacking.predict(X_test_sc) y_proba_stack = stacking.predict_proba(X_test_sc)[:, 1] print(f"Stacking F1-Score: {f1_score(y_test, y_pred_stack):.4f}") print(f"Stacking ROC-AUC : {roc_auc_score(y_test, y_proba_stack):.4f}")
Langkah 4 — Perbandingan Final Semua Model
📈 Peningkatan Performa — Sebelum vs Sesudah Ensemble
Langkah 5 — Export & Deploy Model Terbaik
import joblib import json # Simpan model stacking terbaik joblib.dump(stacking, 'stacking_churn_v1.pkl') joblib.dump(scaler, 'scaler_churn_v1.pkl') # Simpan metadata model model_info = { 'model_type' : 'StackingClassifier', 'base_models' : ['RandomForest', 'XGBoost', 'LightGBM'], 'meta_model' : 'LogisticRegression', 'f1_score' : 0.749, 'roc_auc' : 0.875, 'feature_count': X_train.shape[1], 'trained_on' : '2025-01-01' } with open('model_metadata.json', 'w') as f: json.dump(model_info, f, indent=2) print("✅ Model tersimpan: stacking_churn_v1.pkl") print("✅ Metadata tersimpan: model_metadata.json") # ── Contoh API Endpoint (FastAPI) ── # from fastapi import FastAPI # app = FastAPI() # model = joblib.load('stacking_churn_v1.pkl') # # @app.post('/predict') # def predict(data: dict): # X_new = preprocess(data) # proba = model.predict_proba(X_new)[0][1] # return {'churn_probability': round(float(proba), 4)}
Checklist Praktik Terbaik Ensemble Learning
- ✓Selalu mulai dari baseline sederhanaLogistic Regression atau Decision Tree tunggal adalah benchmark awal yang harus dikalahkan sebelum kompleksitas ditambah.
- ✓Handle class imbalance sebelum ensembleGunakan
class_weight='balanced', SMOTE, atau sesuaikan threshold. Ensemble tidak memperbaiki masalah imbalance secara otomatis. - ✓Gunakan Pipeline untuk mencegah data leakageSelalu bungkus preprocessing dan model dalam
sklearn.Pipelineagar transformasi hanya “belajar” dari training data. - ✓Early stopping untuk BoostingSelalu sediakan validation set dan aktifkan early stopping pada XGBoost/LightGBM untuk mencegah overfitting.
- ✓Interpretasi dengan SHAPSebelum deploy, pastikan kamu bisa menjelaskan mengapa model membuat prediksi tertentu kepada stakeholder non-teknis.
🎉 Selamat! Bab Ensemble Learning Selesai!
Kamu telah menguasai fondasi dan implementasi Ensemble Learning secara lengkap. Dari memahami mengapa model tunggal gagal, hingga membangun Stacking Ensemble yang powerful dengan XGBoost + LightGBM + Random Forest. Bab berikutnya akan membahas Model Selection & Hyperparameter Tuning — teknik sistematis untuk menemukan konfigurasi terbaik dari model apapun!