y, test_size=0.2, random_state=42)
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)
# Permutation importance pada TEST set (lebih jujur)
perm_test = permutation_importance(rf, X_test, y_test, n_repeats=30, random_state=42)
perm_df = pd.DataFrame({
'feature': X.columns,
'importance_mean': perm_test.importances_mean,
'importance_std': perm_test.importances_std
}).sort_values('importance_mean', ascending=False)
fig, ax = plt.subplots(figsize=(10, 8))
ax.barh(
perm_df['feature'].head(15),
perm_df['importance_mean'].head(15),
xerr=perm_df['importance_std'].head(15),
color='steelblue', capsize=4
)
ax.invert_yaxis()
ax.set_xlabel('Penurunan Accuracy saat Fitur Diacak')
ax.set_title('Permutation Importance (Test Set) dengan Confidence Interval')
plt.tight_layout()
plt.show()
# Implementasi manual untuk pemahaman mendalamdefpermutation_importance_manual(model, X, y, metric, n_repeats=10):
baseline = metric(y, model.predict(X))
importances = {}
for col in X.columns:
scores = []
for _ inrange(n_repeats):
X_permuted = X.copy()
X_permuted[col] = np.random.permutation(X_permuted[col].values)
scores.append(baseline - metric(y, model.predict(X_permuted)))
importances[col] = np.mean(scores)
return pd.Series(importances).sort_values(ascending=False)
from sklearn.metrics import accuracy_score
manual_imp = permutation_importance_manual(rf, X_test, y_test, accuracy_score)
print(manual_imp.head(10))
Hari 4
Ethical AI: Bias dalam Model dan Cara Mendeteksinya
Model ML dilatih pada data historis yang seringkali mengandung ketidakadilan sistemik. Tanpa pemeriksaan aktif, model akan mereplikasi dan bahkan memperkuat bias tersebut. Ini bukan masalah teknis semata, ini masalah etika dengan konsekuensi nyata pada kehidupan orang.
Aktivitas 1 (30 menit): Studi kasus nyata bias dalam AI. Abbas membaca dan mendiskusikan tiga kasus nyata: (1) COMPAS recidivism algorithm yang dua kali lebih sering salah label orang kulit hitam sebagai high-risk, (2) Amazon's hiring AI yang diskriminatif terhadap perempuan karena ditraining pada data historis yang didominasi laki-laki, (3) Facial recognition yang error rate jauh lebih tinggi untuk wajah perempuan berkulit gelap. Untuk setiap kasus, Abbas mengidentifikasi: sumber bias, dampak nyata, dan apa yang seharusnya dilakukan.
Aktivitas 2 (40 menit): Deteksi bias menggunakan dataset Credit. Abbas menggunakan German Credit Dataset yang mengandung fitur demografis. Latih model credit scoring, lalu hitung fairness metrics: (a) demographic parity (apakah approval rate sama untuk semua kelompok demografis?), (b) equal opportunity (apakah true positive rate sama untuk semua kelompok?), (c) predictive parity (apakah precision sama untuk semua kelompok?).
Aktivitas 3 (25 menit): Tiga jenis bias yang perlu dipahami. (1) Historical bias: data historis mencerminkan ketidakadilan masa lalu. (2) Representation bias: kelompok tertentu underrepresented dalam training data. (3) Measurement bias: cara data dikumpulkan berbeda untuk kelompok berbeda. Abbas membuat mind map yang menghubungkan jenis bias ke contoh nyata dan cara mitigasinya.
Aktivitas 4 (20 menit): Fairness-accuracy tradeoff. Dalam banyak kasus, meningkatkan fairness akan menurunkan accuracy keseluruhan. Abbas mengeksplorasi ini: apply fairness constraint (misalnya force equal FPR untuk semua kelompok) dan ukur dampaknya pada accuracy. Diskusikan: siapa yang berhak membuat keputusan trade-off ini?
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
# Load German Credit Data
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/statlog/german/german.data"
cols = ['checking', 'duration', 'credit_history', 'purpose', 'amount',
'savings', 'employment', 'installment_rate', 'personal_status', 'other_debtors',
'residence_since', 'property', 'age', 'other_installment', 'housing',
'existing_credits', 'job', 'num_dependents', 'phone', 'foreign_worker', 'target']
df = pd.read_csv(url, sep=' ', header=None, names=cols)
# Buat kelompok umur: muda (< 30) vs tua (>= 30)
df['age_group'] = (df['age'] >= 30).map({True: '>=30', False: '<30'})
df['target'] = (df['target'] == 1).astype(int) # 1=Good credit, 0=Bad# Simple model dengan fitur numerik
num_cols = ['duration', 'amount', 'installment_rate', 'age', 'existing_credits']
X = df[num_cols]
y = df['target']
age_group = df['age_group']
X_train, X_test, y_train, y_test, ag_train, ag_test = train_test_split(
X, y, age_group, test_size=0.3, random_state=42
)
model = LogisticRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
# Fairness check: TPR per kelompok umurprint("=== Fairness Analysis per Kelompok Umur ===")
for group in ['<30', '>=30']:
mask = ag_test == group
tn, fp, fn, tp = confusion_matrix(y_test[mask], y_pred[mask]).ravel()
tpr = tp / (tp + fn) if (tp + fn) > 0else0
fpr = fp / (fp + tn) if (fp + tn) > 0else0
approval = y_pred[mask].mean()
print(f"Kelompok {group}: TPR={tpr:.3f}, FPR={fpr:.3f}, Approval Rate={approval:.3f}")
Refleksi Abbas: Jika Abbas membangun sistem credit scoring untuk bank, dan model Abbas menunjukkan FPR dua kali lebih tinggi untuk kelompok umur muda, apa yang harus dilakukan? Apakah Abbas sebagai data scientist bertanggung jawab atas ini? Siapa lagi yang terlibat dalam keputusan deployment? Tulis jawaban Abbas di notebook.
Hari 5
Mini Project: Credit Scoring Model dengan Full Evaluation Report
Hari terakhir bulan 10 adalah proyek komprehensif yang mengintegrasikan semua yang dipelajari: feature engineering, evaluasi mendalam, ensemble method, interpretabilitas, dan fairness check. Hasilnya adalah laporan evaluasi profesional.
Aktivitas 1 (35 menit): Data preparation dan feature engineering. Abbas menggunakan German Credit Dataset atau dataset Home Credit Default Risk (Kaggle). Buat fitur-fitur baru: debt_to_income ratio, log-transform fitur yang skewed, interaction antara employment duration dan credit amount, target encoding untuk categorical columns. Dokumentasikan setiap keputusan feature engineering beserta reasoning-nya.
Aktivitas 2 (45 menit): Training pipeline lengkap. Bangun sklearn Pipeline yang mencakup: preprocessing (imputer, scaler, encoder), SMOTE untuk imbalanced target (default rate biasanya 10-20%), dan XGBoost classifier dengan early stopping. Gunakan cross-validation untuk semua keputusan hyperparameter.
Aktivitas 3 (30 menit): Full evaluation suite. Hitung: confusion matrix, classification report, ROC-AUC, precision-recall curve, Kolmogorov-Smirnov statistic (metrik standard industri untuk credit scoring), dan Gini coefficient. Plot semua dalam satu figure grid yang rapi.
Aktivitas 4 (30 menit): Interpretability report. Buat SHAP summary plot, pilih 3 sampel (1 true positive, 1 false positive, 1 false negative) dan buat waterfall plot untuk masing-masing, sertakan dependence plot untuk 3 fitur paling penting. Tulis narasi penjelasan untuk setiap plot.
Aktivitas 5 (20 menit): Fairness audit dan kesimpulan. Hitung fairness metrics (TPR, FPR, approval rate) per kelompok untuk setidaknya satu atribut sensitif yang tersedia (age group, jenis kelamin jika ada). Tulis executive summary 1 halaman yang mencakup: performa model, fitur paling penting, potensi bias yang ditemukan, dan rekomendasi untuk deployment yang bertanggung jawab.
Abbas's Deliverable: Simpan semua output (confusion matrix plot, ROC curve, SHAP summary, fairness table) ke dalam folder project. Buat file credit_model_report.md yang berisi: ringkasan model, metrics utama, 3 fitur paling penting beserta interpretasi bisnis, temuan fairness, dan rekomendasi deployment. Ini adalah format laporan yang digunakan di industri keuangan.
Referensi Buku Bulan 10
Empat buku ini adalah referensi standar industri untuk topik yang dipelajari bulan ini. Abbas tidak perlu membaca semua secara linier, gunakan sebagai referensi saat mengerjakan latihan.
Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow
Aurelien Geron (Edisi ke-3, 2022)
Buku paling komprehensif untuk implementasi ML dengan sklearn. Bab yang relevan untuk bulan ini: Bab 2 (End-to-End ML Project), Bab 7 (Ensemble Learning), dan Bab 9 (Unsupervised Learning). Penjelasan bias-variance tradeoff dan ensemble methods di buku ini adalah yang terbaik tersedia dalam bahasa Inggris yang mudah dipahami. Tersedia di O'Reilly Learning (akses gratis dengan email universitas).
EnsemblesklearnEvaluasi
Interpretable Machine Learning
Christoph Molnar (2023, tersedia gratis di christophm.github.io)
Buku open-source yang menjadi referensi utama untuk semua teknik interpretabilitas model. Mencakup SHAP, LIME, permutation importance, partial dependence plots, dan banyak lagi dengan penjelasan matematis yang dibuat sangat accessible. Bab 5 (Model-Agnostic Methods) dan Bab 6 (Example-Based Explanations) sangat relevan. Dapat dibaca langsung di browser, gratis, tanpa perlu download.
SHAPLIMEInterpretabilitas
Feature Engineering for Machine Learning
Alice Zheng dan Amanda Casari (O'Reilly, 2018)
Satu-satunya buku yang didedikasikan sepenuhnya untuk feature engineering dengan contoh praktis. Bab 3 (Text Feature Engineering) dan Bab 6 (Dimensionality Reduction) mungkin melampaui scope bulan ini, tapi Bab 1 (The Machine Learning Pipeline), Bab 2 (Fancy Tricks with Simple Numbers), dan Bab 5 (Categorical Variables) sangat relevan langsung. Gaya penulisannya sangat practical, tidak terlalu teoritis.
Feature EngineeringEncodingPractical
The Elements of Statistical Learning
Hastie, Tibshirani, Friedman (Edisi ke-2, tersedia gratis di web.stanford.edu)
Ini adalah "bible" machine learning, berat secara matematis tapi gratis dan sangat lengkap. Untuk bulan ini, Abbas cukup membaca Bab 10 (Boosting and Additive Trees) untuk memahami gradient boosting dari first principles, dan Bab 15 (Random Forests) untuk bagging. Tidak perlu memahami semua derivasi, fokus pada intuisi dan gambaran besar dari setiap bab.
TeoriGradient BoostingGratis PDF
Tools dan Library Bulan 10
Berikut adalah semua tools yang dibutuhkan Abbas untuk menyelesaikan bulan ini, lengkap dengan cara instalasi, use case utama, dan tips penggunaan di MacBook Air M5.
SK
scikit-learn
versi 1.4+
Fondasi semua pekerjaan bulan ini. Abbas akan menggunakan: sklearn.inspection.permutation_importance, sklearn.model_selection.learning_curve, sklearn.metrics.roc_curve, sklearn.ensemble.StackingClassifier, sklearn.ensemble.VotingClassifier, sklearn.preprocessing.PolynomialFeatures.
pip install -U scikit-learn
Di MacBook M5, scikit-learn sudah dioptimasi untuk Apple Silicon via conda-forge. Jika menggunakan conda: conda install -c conda-forge scikit-learn untuk performa terbaik.
XG
XGBoost
versi 2.0+
Library gradient boosting yang paling banyak digunakan di kompetisi ML dan industri. XGBoost 2.0+ mendukung Apple Silicon secara native dengan Metal acceleration. Abbas akan menggunakan: XGBClassifier, XGBRegressor, early stopping, dan SHAP TreeExplainer (XGBoost punya built-in SHAP support).
pip install xgboost
# Verifikasi XGBoost berjalan dengan benar di M5import xgboost as xgb
print(xgb.__version__) # Harus 2.0+
SH
SHAP
versi 0.44+
Library utama untuk interpretabilitas model. SHAP mendukung hampir semua jenis model: Tree models (TreeExplainer, cepat), Linear models (LinearExplainer), Neural networks (DeepExplainer), dan model apapun (KernelExplainer, lambat). Abbas akan menggunakan plots: summary_plot, waterfall, dependence_plot, bar, beeswarm.
pip install shap
# Test instalasiimport shap
shap.initjs() # Untuk visualisasi di Jupyter# Verifikasi TreeExplainer berjalanfrom sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
X, y = load_iris(return_X_y=True)
rf = RandomForestClassifier().fit(X, y)
exp = shap.TreeExplainer(rf)
vals = exp.shap_values(X[:5])
print("SHAP OK. Shape:", [v.shape for v in vals])
IB
imbalanced-learn
versi 0.11+
Library khusus untuk menangani imbalanced datasets. Menyediakan: over-sampling (SMOTE, ADASYN, BorderlineSMOTE), under-sampling (RandomUnderSampler, TomekLinks, EditedNearestNeighbours), combined methods (SMOTETomek, SMOTEENN), dan Pipeline khusus yang integrasi dengan sklearn tapi handle resampling dengan benar (hanya pada training fold).
pip install imbalanced-learn
from imblearn.over_sampling import SMOTE, ADASYN, BorderlineSMOTE
from imblearn.under_sampling import RandomUnderSampler, TomekLinks
from imblearn.combine import SMOTETomek
from imblearn.pipeline import Pipeline
# Selalu gunakan imblearn Pipeline, bukan sklearn Pipeline# ketika ada step resampling di dalamnya
OP
Optuna
versi 3.5+
Framework hyperparameter optimization yang lebih powerful dan fleksibel dibanding GridSearchCV atau RandomizedSearchCV. Optuna menggunakan Bayesian optimization (TPE sampler) untuk menemukan hyperparameter optimal dengan lebih sedikit evaluasi. Mendukung pruning (stop promising trial early jika tidak menjanjikan). Sangat useful untuk tuning XGBoost yang punya banyak hyperparameter.
Platform open-source untuk lifecycle management machine learning: tracking experiments, packaging code, dan deploying models. Di bulan 10, Abbas menggunakan MLflow untuk mencatat setiap experiment (hyperparameters, metrics, artifacts) sehingga bisa membandingkan ratusan run dengan mudah. Ini adalah kebiasaan profesional yang sangat penting untuk dipelajari sejak awal.
Tips MLflow untuk Abbas: Biasakan log setiap experiment dari sekarang, bahkan yang gagal. Experiment yang gagal memberikan informasi berharga. Di industri, tidak ada data scientist yang mengingat semua percobaan yang pernah dilakukan tanpa tracking system.
Setup Lengkap untuk MacBook Air M5:
# Satu command untuk install semua dependencies bulan 10
pip install scikit-learn xgboost shap imbalanced-learn optuna mlflow \
lime category_encoders matplotlib seaborn pandas numpy scipy
# Verifikasi semua terinstall dengan benar
python -c "
import sklearn, xgboost, shap, imblearn, optuna, mlflow, lime
import category_encoders as ce
print('scikit-learn:', sklearn.__version__)
print('xgboost:', xgboost.__version__)
print('shap:', shap.__version__)
print('imbalanced-learn:', imblearn.__version__)
print('optuna:', optuna.__version__)
print('mlflow:', mlflow.__version__)
print('Semua library siap. Abbas bisa mulai belajar.')
"
Target Akhir Bulan 10: Ketika bulan ini selesai, Abbas harus bisa menjawab pertanyaan berikut tanpa membuka catatan: (1) Kapan Abbott harus menggunakan AUC-ROC vs precision-recall curve? (2) Apa perbedaan antara bias dan variance, dan bagaimana cara mendiagnosanya? (3) Apa yang dilakukan SMOTE, dan mengapa ia tidak boleh diterapkan pada validation data? (4) Jelaskan perbedaan bagging dan boosting dalam satu paragraf. (5) Apa yang dimaksud dengan SHAP value untuk sebuah fitur pada satu prediksi? (6) Berikan satu contoh di mana model ML yang "akurat" bisa tidak adil.
Tips Praktis dan Jebakan Pemula
Delapan tips berikut dikumpulkan dari kesalahan yang paling sering dilakukan data scientist junior saat pertama kali bekerja dengan evaluasi model lanjutan, XGBoost, dan SHAP. Pelajari ini sekarang agar Abbas tidak mengulangi kesalahan yang sama di kemudian hari.
Tips 1: SHAP value bisa negatif dan itu normal
SHAP value untuk satu fitur pada satu prediksi menunjukkan kontribusi fitur tersebut terhadap prediksi, relatif terhadap nilai baseline (rata-rata semua prediksi). Nilai positif berarti fitur ini mendorong prediksi ke arah kelas positif, nilai negatif berarti mendorongnya ke arah kelas negatif. Keduanya adalah informasi yang berguna. Jika Abbas melihat semua SHAP value positif, itu justru patut dicurigai karena setiap prediksi memiliki trade-off antar fitur.
Peringatan 1: SMOTE HARUS dilakukan setelah train-test split, bukan sebelum
Jika Abbas men-apply SMOTE sebelum split, data sintetis akan "bocor" ke test set. Model dievaluasi pada data sintetis yang berasal dari training distribution, sehingga performa terlihat jauh lebih baik dari kenyataan. Ini adalah salah satu bentuk data leakage yang paling sering tidak disadari pemula. Solusinya: selalu gunakan imblearn.Pipeline yang secara otomatis menangani ini dengan benar, hanya meng-oversample pada training fold saat cross-validation.
Tips 2: Turunkan learning_rate, naikkan n_estimators secara bersamaan di XGBoost
Dalam XGBoost, learning_rate (alias eta) dan n_estimators punya hubungan terbalik: learning rate kecil butuh lebih banyak pohon untuk konvergen, tapi biasanya menghasilkan model yang lebih robust terhadap overfitting. Pola yang umum di industri: mulai dengan learning_rate=0.01 dan n_estimators=1000, lalu aktifkan early_stopping_rounds=50 untuk menemukan jumlah pohon optimal secara otomatis tanpa harus melakukan grid search.
Peringatan 2: Feature importance bawaan tree model bisa menyesatkan feature_importances_ dari RandomForest atau XGBoost menggunakan impurity-based importance yang punya dua kelemahan serius. Pertama, secara sistematis memberikan skor lebih tinggi pada fitur dengan banyak nilai unik (fitur kontinu vs kategorikal dengan sedikit kategori). Kedua, tidak bisa membedakan antara fitur yang benar-benar penting dan fitur yang "kebetulan" berkorelasi dengan target di training set. Selalu validasi dengan permutation importance pada test set atau SHAP values sebelum melaporkan fitur penting ke stakeholder.
Tips 3: Pilih AUC-ROC atau Precision-Recall berdasarkan tingkat imbalance data
Ketika dataset sangat imbalanced (contoh: 95% negatif, 5% positif seperti fraud detection), AUC-ROC bisa terlihat tinggi meskipun model hampir tidak pernah mendeteksi kelas positif. Precision-Recall curve jauh lebih informatif untuk kasus ini karena fokus pada kelas minoritas saja. Aturan praktis: jika ratio imbalance lebih dari 1:10, prioritaskan PR-AUC daripada ROC-AUC sebagai metrik utama untuk pengambilan keputusan deployment.
Tips 4: Early stopping di XGBoost membutuhkan eval_set yang terpisah dari training data
Early stopping hanya bisa digunakan jika Abbas memberikan eval_set saat memanggil .fit(). XGBoost memantau performa pada eval_set setiap iterasi dan berhenti jika tidak ada perbaikan selama early_stopping_rounds iterasi berturut-turut. Eval_set ini HARUS berupa validation set yang tidak digunakan untuk training. Pola yang direkomendasikan: model.fit(X_train, y_train, eval_set=[(X_val, y_val)], verbose=100) dengan early_stopping_rounds=50.
Peringatan 3: Jangan pakai accuracy sebagai metrik utama untuk dataset imbalanced
Pada dataset dengan 95% kelas negatif, model yang selalu memprediksi negatif akan punya accuracy 95%, tapi recall untuk kelas positif adalah 0. Ini adalah model yang sama sekali tidak berguna dari perspektif bisnis. Untuk imbalanced data, gunakan kombinasi ini: balanced accuracy (rata-rata recall per kelas), F1-score khusus untuk kelas minoritas, atau PR-AUC. Pilihan metrik harus selalu disesuaikan dengan konsekuensi bisnis dari setiap jenis kesalahan.
Tips 5: SHAP pada Pipeline sklearn butuh satu langkah ekstra sebelum bisa dipakai
Jika Abbas menggunakan sklearn Pipeline dengan scaler di dalamnya, SHAP values dihitung pada data yang sudah di-scale. Artinya, nilai di axis plot adalah nilai scaled, bukan nilai asli yang mudah diinterpretasi. Solusinya: extract model dari pipeline dulu (pipeline.named_steps['clf']), lalu scale data secara manual (scaler.transform(X_test)) sebelum memberikannya ke shap.TreeExplainer. Ini memudahkan interpretasi tanpa mengubah hasil.
Latihan Tambahan: Tantangan Lanjutan
Tiga latihan berikut dirancang untuk Abbas yang sudah menyelesaikan semua aktivitas minggu 1 hingga 4. Tingkat kesulitannya lebih tinggi dan membutuhkan kombinasi konsep dari berbagai topik. Kerjakan secara berurutan karena setiap latihan membangun fondasi untuk latihan berikutnya.
Latihan A
Perbandingan Model Side-by-Side: Laporan Evaluasi Multi-Model
Latihan ini melatih Abbas untuk membandingkan empat model berbeda secara sistematis pada dataset yang sama, menggunakan semua metrics yang dipelajari bulan ini. Ini adalah skill yang selalu dibutuhkan saat membuat keputusan model mana yang sebaiknya di-deploy ke production.
Bangun pipeline evaluasi yang membandingkan: Logistic Regression (baseline), Random Forest, XGBoost, dan LightGBM. Untuk setiap model, hitung: AUC-ROC, PR-AUC, F1 (kelas minoritas), Gini coefficient, KS statistic, dan waktu training. Tampilkan hasilnya dalam tabel perbandingan lengkap.
import pandas as pd
import numpy as np
import time
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import StratifiedKFold, cross_validate
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (average_precision_score,
f1_score, make_scorer)
import xgboost as xgb
import lightgbm as lgb
# Dataset sintetis imbalanced 1:10
X, y = make_classification(
n_samples=5000, n_features=20, n_informative=10,
n_redundant=5, weights=[0.9, 0.1], random_state=42
)
print(f"Class balance: {np.bincount(y)}")
# Definisi empat model yang akan dibandingkan
models = {
"Logistic Regression": Pipeline([
("scaler", StandardScaler()),
("clf", LogisticRegression(class_weight="balanced", max_iter=1000))
]),
"Random Forest": RandomForestClassifier(
n_estimators=100, class_weight="balanced", random_state=42, n_jobs=-1
),
"XGBoost": xgb.XGBClassifier(
n_estimators=200, scale_pos_weight=9,
learning_rate=0.05, random_state=42, verbosity=0
),
"LightGBM": lgb.LGBMClassifier(
n_estimators=200, class_weight="balanced",
learning_rate=0.05, random_state=42, verbose=-1
)
}
# Scorer custom untuk kelas minoritas
f1_minority = make_scorer(f1_score, pos_label=1)
pr_auc_scorer = make_scorer(average_precision_score, needs_proba=True)
results = []
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
for name, model in models.items():
t0 = time.time()
scores = cross_validate(
model, X, y, cv=cv,
scoring={
"roc_auc": "roc_auc",
"pr_auc": pr_auc_scorer,
"f1": f1_minority
},
return_train_score=False, n_jobs=-1
)
elapsed = time.time() - t0
roc = scores["test_roc_auc"]
pr = scores["test_pr_auc"]
f1 = scores["test_f1"]
results.append({
"Model": name,
"ROC-AUC": f"{roc.mean():.4f} ยฑ {roc.std():.4f}",
"PR-AUC": f"{pr.mean():.4f} ยฑ {pr.std():.4f}",
"F1 (pos)": f"{f1.mean():.4f} ยฑ {f1.std():.4f}",
"Gini": f"{(2*roc.mean()-1):.4f}",
"Waktu (s)": f"{elapsed:.1f}"
})
print(f"Selesai: {name} ({elapsed:.1f}s)")
df_results = pd.DataFrame(results)
print("
=== TABEL PERBANDINGAN MODEL ===")
print(df_results.to_string(index=False))
# Tugas Abbas: Identifikasi model terbaik untuk setiap metrik.# Apakah model terbaik di ROC-AUC juga terbaik di PR-AUC?# Kapan perbedaan antara keduanya menjadi penting untuk keputusan bisnis?# Hint: coba ubah weights=[0.99, 0.01] dan amati perubahan ranking model.
Tantangan bonus Latihan A: Plot learning curve untuk dua model terbaik secara bersamaan dalam satu figure. Identifikasi mana yang mengalami overfitting dan mana yang underfitting berdasarkan gap antara train score dan validation score. Tambahkan kolom "KS Statistic" ke tabel dengan menghitung ks_2samp dari scipy pada setiap fold.
Latihan B
SHAP Deep Dive: Dari Global ke Individual Explanation
Latihan ini melatih Abbas memahami tiga level interpretasi SHAP: global (semua data), kelompok (segmen tertentu), dan individual (satu prediksi spesifik). Pola tiga level ini adalah format yang dipakai di laporan model credit scoring dan fraud detection profesional.
Gunakan model XGBoost dari Latihan A. Abbas akan membuat tiga jenis plot berbeda dan menuliskan narasi penjelasan untuk masing-masing, seolah menjelaskan kepada kepala divisi yang bukan data scientist.
import shap
import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import train_test_split
# Gunakan data dari Latihan A; latih ulang XGBoost pada train set
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
xgb_model = xgb.XGBClassifier(
n_estimators=200, scale_pos_weight=9,
learning_rate=0.05, random_state=42, verbosity=0
)
xgb_model.fit(X_train, y_train)
feature_names = [f"fitur_{i:02d}"for i inrange(X.shape[1])]
y_pred = xgb_model.predict(X_test)
y_prob = xgb_model.predict_proba(X_test)[:, 1]
# ---- LEVEL 1: Global Summary Plot ----
explainer = shap.TreeExplainer(xgb_model)
shap_values = explainer.shap_values(X_test)
plt.figure()
shap.summary_plot(shap_values, X_test, feature_names=feature_names, show=False)
plt.title("Global: Kontribusi Rata-rata Tiap Fitur (semua test samples)")
plt.tight_layout()
plt.savefig("shap_global_summary.png", dpi=150)
plt.show()
# ---- LEVEL 2: Waterfall untuk True Positive, False Positive, False Negative ----
idx_tp = np.where((y_test == 1) & (y_pred == 1))[0][0]
idx_fp = np.where((y_test == 0) & (y_pred == 1))[0][0]
idx_fn = np.where((y_test == 1) & (y_pred == 0))[0][0]
fig, axes = plt.subplots(1, 3, figsize=(21, 6))
for ax, idx, label inzip(
axes,
[idx_tp, idx_fp, idx_fn],
["True Positive", "False Positive", "False Negative"]):
plt.sca(ax)
shap.waterfall_plot(
shap.Explanation(
values = shap_values[idx],
base_values = explainer.expected_value,
data = X_test[idx],
feature_names = feature_names
),
show=False
)
ax.set_title(f"{label} (prob={y_prob[idx]:.3f})")
plt.suptitle("Individual Explanations: TP vs FP vs FN", y=1.02, fontsize=13)
plt.tight_layout()
plt.savefig("shap_waterfall_trio.png", dpi=150, bbox_inches="tight")
plt.show()
# ---- LEVEL 3: Dependence plot untuk fitur paling penting ----
top_idx = np.abs(shap_values).mean(axis=0).argmax()
shap.dependence_plot(
top_idx, shap_values, X_test,
feature_names=feature_names,
interaction_index="auto",
show=False
)
plt.title(f"Dependence Plot: {feature_names[top_idx]}")
plt.tight_layout()
plt.savefig("shap_dependence.png", dpi=150)
plt.show()
# Tugas Abbas: Tulis narasi untuk setiap plot dalam bahasa Indonesia.# Narasi global: "Fitur apa yang paling mempengaruhi model secara keseluruhan?"# Narasi waterfall FP: "Mengapa model salah pada kasus False Positive ini?"# Narasi dependence: "Bagaimana nilai fitur mempengaruhi arah prediksi model?"
Catatan SHAP pada data array vs DataFrame: Jika Abbas menggunakan numpy array (bukan DataFrame), feature_names harus diisi secara manual saat membuat shap.Explanation. Jika dibiarkan kosong, plot akan menampilkan kolom bernama "Feature 0", "Feature 1", dan seterusnya, yang tidak informatif untuk laporan. Selalu buat nama fitur yang deskriptif sejak awal agar setiap output langsung bisa dibaca tanpa legenda tambahan.
Latihan C
Threshold Optimization: Menyesuaikan Decision Boundary dengan Kebutuhan Bisnis
Default threshold 0.5 jarang optimal di dunia nyata. Di credit scoring, biaya meminjamkan ke debitur yang akan gagal bayar (False Negative) bisa jauh lebih mahal daripada menolak debitur yang sebenarnya baik (False Positive). Latihan ini mengajarkan Abbas cara menentukan threshold yang optimal berdasarkan business cost secara kuantitatif.
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import precision_recall_curve, f1_score, confusion_matrix
# Anggap xgb_model, X_test, y_test, y_prob sudah ada dari Latihan B# ---- 1. Cari threshold F1 optimal ----
prec_arr, rec_arr, thresholds = precision_recall_curve(y_test, y_prob)
f1_scores = 2 * (prec_arr * rec_arr) / (prec_arr + rec_arr + 1e-9)
best_t_f1 = thresholds[f1_scores[:-1].argmax()]
print(f"Threshold F1 optimal : {best_t_f1:.4f}")
# ---- 2. Threshold berdasarkan business cost ----# Asumsi: FN (gagal deteksi bad credit) cost = 5x lebih mahal dari FP
cost_fn = 5
cost_fp = 1
total_costs = []
for t in thresholds:
yp = (y_prob >= t).astype(int)
tn, fp, fn, tp = confusion_matrix(y_test, yp).ravel()
total_costs.append(fn * cost_fn + fp * cost_fp)
best_t_cost = thresholds[np.argmin(total_costs)]
print(f"Threshold cost-optimal: {best_t_cost:.4f}")
# ---- 3. Visualisasi dua threshold di satu figure ----
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[0].plot(thresholds, f1_scores[:-1], color="steelblue", label="F1 Score")
axes[0].axvline(best_t_f1, color="red", linestyle="--",
label=f"Best F1 t={best_t_f1:.3f}")
axes[0].axvline(0.5, color="gray", linestyle=":", label="Default 0.5")
axes[0].set_xlabel("Threshold")
axes[0].set_title("F1 Score vs Threshold")
axes[0].legend()
axes[1].plot(thresholds, total_costs, color="coral", label="Total Business Cost")
axes[1].axvline(best_t_cost, color="red", linestyle="--",
label=f"Min cost t={best_t_cost:.3f}")
axes[1].set_xlabel("Threshold")
axes[1].set_title("Business Cost vs Threshold (FN cost = 5x FP)")
axes[1].legend()
plt.tight_layout()
plt.savefig("threshold_optimization.png", dpi=150)
plt.show()
# ---- 4. Laporan perbandingan tiga threshold ----print("
{'Threshold':<14} {'Precision':<12} {'Recall':<10} {'F1':<8} {'Cost'}")
for t_name, t_val in [
("Default 0.5", 0.5),
("F1-optimal", best_t_f1),
("Cost-optimal", best_t_cost)]:
yp = (y_prob >= t_val).astype(int)
tn, fp, fn, tp = confusion_matrix(y_test, yp).ravel()
p = tp / (tp + fp + 1e-9)
r = tp / (tp + fn + 1e-9)
f = 2 * p * r / (p + r + 1e-9)
c = fn * cost_fn + fp * cost_fp
print(f"{t_name:<14} {p:.4f} {r:.4f} {f:.4f} {c}")
Diskusi untuk Abbas: Perhatikan bahwa threshold F1-optimal dan threshold cost-optimal akan menghasilkan angka yang berbeda, dan keduanya berbeda dari default 0.5. Siapa yang seharusnya menentukan angka cost_fn dan cost_fp? Apakah ini keputusan data scientist, kepala kredit, atau keduanya? Tulis pendapat Abbas di notebook proyek. Pertanyaan ini sering muncul di wawancara data scientist di perusahaan finansial, karena menyentuh batas antara tanggung jawab teknis dan bisnis.
Studi Kasus: Credit Scoring dengan Evaluasi Lengkap
Konteks nyata: Credit scoring adalah ML paling banyak dipakai di Indonesia. Bank BRI, BCA, Akulaku, Kredivo semuanya punya model seperti ini. Abbas akan membangun versi sederhana dari nol.
Studi KasusCredit Scoring End-to-End▼
Step 1
Buat dan Eksplorasi Dataset
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, roc_auc_score
np.random.seed(42)
n = 1000
df = pd.DataFrame({
"usia": np.random.randint(22, 65, n),
"pendapatan": np.random.normal(5e6, 2e6, n).clip(1e6),
"pinjaman": np.random.normal(20e6, 10e6, n).clip(1e6),
"lama_kerja": np.random.randint(0, 30, n),
"riwayat": np.random.choice([2,1,0], n, p=[0.6,0.3,0.1]),
})
df["rasio"] = df["pinjaman"] / df["pendapatan"]
df["gagal"] = ((df["rasio"] > 5) | (df["riwayat"] == 0)).astype(int)
print(df["gagal"].value_counts())
Imbalanced dataset! Kelas 0 jauh lebih banyak dari kelas 1. Accuracy tidak berguna di sini. Pakai ROC-AUC dan F1-score.
Challenge: Tambahkan fitur baru: "cicilan_per_bulan = pinjaman / 36". Apakah ROC-AUC naik? Apakah fitur baru ini masuk top-3 importance?
Trigger: Model ini menentukan siapa yang dapat kredit. Kalau model bias terhadap kelompok umur tertentu, siapa yang dirugikan? Bagaimana cara mendeteksi bias ini secara kuantitatif?
Cheatsheet: Kapan Pakai Metrik Apa
Accuracy: Hanya untuk dataset balanced. Jangan pakai untuk fraud, penyakit langka, credit default.