06 β Model Evaluation
Estimasi: 4 jam Tujuan: Pilih metric tepat, evaluate dengan benar, tune hyperparameter dengan rigor.
Kenapa Materi Ini Penting?
Banyak orang training model tapi tidak tahu bagaimana caranya yakin model mereka bagus. Mereka lihat accuracy 95% lalu deploy, tapi ternyata di production model gagal total karena imbalanced data atau distribution shift. Evaluasi yang baik adalah jaring pengaman antara development dan production.
Untuk bootcamp Dicoding, kemampuan memilih metric yang sesuai dengan bisnis adalah skill yang membedakan engineer matang vs pemula. Spam filter butuh precision tinggi (jangan buang email penting). Kanker test butuh recall tinggi (jangan miss diagnosis). Pemilihan metric salah = kerja keras tuning model jadi sia-sia. Kamu juga akan banyak mendiskusikan ini dengan reviewer expert dan stakeholder.
flowchart TD
A["π Model Evaluation"] --> B["π’ Pick Metric"]
A --> C["βοΈ Split Strategy"]
A --> D["π― Hyperparameter<br/>Tuning"]
A --> E["π Diagnose<br/>Over/Underfit"]
B --> B1["Classification:<br/>Acc, Precision, Recall,<br/>F1, AUC"]
B --> B2["Regression:<br/>MSE, MAE, RΒ²"]
C --> C1["Train/Val/Test"]
C --> C2["Cross-Validation"]
D --> D1["Grid Search"]
D --> D2["Random Search"]
D --> D3["Optuna (Bayesian)"]
E --> E1["Learning Curve"]
E --> E2["Validation Curve"]
Bagian 1 β Confusion Matrix Lengkap
Analogi: Tabel Kebenaran Diagnosa
Bayangkan dokter mendiagnosa pasien kanker. Ada 4 kemungkinan: benar diagnosa kanker (TP), benar diagnosa sehat (TN), salah bilang kanker padahal sehat (FP β pasien stress sia-sia), salah bilang sehat padahal kanker (FN β paling fatal). Confusion matrix adalah tabel 2x2 yang merangkum 4 kasus ini.
Cara Membaca Diagram:
- Header: "Predict Positive/Negative" (kolom)
- Sisi: "Actual Positive/Negative" (baris)
- 4 cell isi: TP, FN, FP, TN
- Hijau = benar, kuning/merah = salah
Walkthrough Step-by-Step:
- TP (kiri-atas): predict positif, actual positif β benar
- FN (kanan-atas): predict negatif, actual positif β MISS (Type II) β sangat costly di kanker
- FP (kiri-bawah): predict positif, actual negatif β false alarm (Type I) β costly di spam filter
- TN (kanan-bawah): predict negatif, actual negatif β benar
Analogi Sehari-hari: Tes COVID. TP = positif & sakit (benar). FN = negatif tapi sakit (orang menyebar tanpa tahu β bahaya!). FP = positif tapi sehat (isolasi sia-sia). TN = negatif & sehat (benar).
Diagram statis Mermaid sebagai fallback:
flowchart TD
A["π Confusion Matrix"] --> B["π’ TP<br/>Predict Pos<br/>Actual Pos<br/>β
Benar"]
A --> C["π’ TN<br/>Predict Neg<br/>Actual Neg<br/>β
Benar"]
A --> D["π‘ FP<br/>Predict Pos<br/>Actual Neg<br/>β Type I Error"]
A --> E["π΄ FN<br/>Predict Neg<br/>Actual Pos<br/>β Type II Error"]
style B fill:#d4f4dd
style C fill:#d4f4dd
style D fill:#fff4d4
style E fill:#ffd4d4
Predicted
Pos Neg
Actual Pos TP FN
Neg FP TN
- TP (True Positive) β predict pos, actual pos β
- TN (True Negative) β predict neg, actual neg β
- FP (False Positive) β predict pos, actual neg β (Type I error)
- FN (False Negative) β predict neg, actual pos β (Type II error)
from sklearn.metrics import confusion_matrix
import seaborn as sns
cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues")
Bagian 2 β Metrics Klasifikasi
Analogi: Pilih Metric Berdasarkan Konsekuensi
Cara Membaca Diagram:
- Atas: pertanyaan utama (class balanced?)
- Branch ke metric sesuai biaya kesalahan
- Tiap leaf = metric yang cocok
Walkthrough Step-by-Step:
- Class balanced? β Accuracy OK
- Imbalanced + FP costly (spam filter) β Precision
- Imbalanced + FN costly (kanker test) β Recall
- Imbalanced + keduanya penting β F1 Score
- Threshold-independent β ROC-AUC
Analogi Sehari-hari: Pemilihan KPI di bisnis. Restoran fokus repeat customer (recall pelanggan). Bank fokus tidak pinjam ke fraud (precision). Manajer balance keduanya (F1).
Diagram statis Mermaid sebagai fallback:
flowchart TD
A["π― Pilih Metric"] --> B{"Class<br/>balanced?"}
B -->|Ya| C["β
Accuracy OK"]
B -->|Tidak| D{"Mana yang<br/>lebih costly?"}
D -->|False Positive<br/>misal spam filter| E["π Precision"]
D -->|False Negative<br/>misal kanker| F["π Recall"]
D -->|Keduanya| G["π F1 Score"]
D -->|Threshold-independent| H["π ROC-AUC"]
style E fill:#d4f4dd
style F fill:#d4f4dd
style G fill:#d4f4dd
Accuracy
Accuracy = (TP + TN) / Total
β Tidak bagus untuk imbalanced data. Kalau 95% class A, predict semua A β 95% accuracy.
Precision
Precision = TP / (TP + FP)
"Dari yang predict positive, berapa benar?"
Penting saat FP costly (e.g., spam filter β jangan buang email penting).
Recall (Sensitivity)
Recall = TP / (TP + FN)
"Dari yang aktual positive, berapa terdeteksi?"
Penting saat FN costly (e.g., kanker test β jangan miss diagnosis).
F1 Score
Harmonic mean precision-recall:
F1 = 2 Γ (P Γ R) / (P + R)
Balance keduanya. Default metric kalau bingung.
F-beta
Bias ke salah satu:
- F2 β recall lebih penting
- F0.5 β precision lebih penting
Specificity
Specificity = TN / (TN + FP)
"Dari yang aktual negative, berapa benar?"
Bagian 3 β ROC & AUC
Analogi: Trade-off Threshold
Model klasifikasi probabilistik output skor 0-1. Default threshold = 0.5: > 0.5 = positive. Tapi threshold bisa diubah. Kalau kamu naikkan ke 0.7, model jadi lebih "ketat" (predict positive cuma kalau yakin) β precision naik tapi recall turun. Kalau kamu turunkan ke 0.3, model lebih "longgar" β recall naik tapi precision turun.
ROC curve plot trade-off ini di semua threshold sekaligus. AUC (Area Under Curve) = ringkasan seberapa bagus model membedakan dua kelas, terlepas dari threshold.
Cara Membaca Diagram:
- Kiri ke kanan: pipeline dari probabilitas β ROC β AUC
- Kanan: interpretasi nilai AUC
- Makin tinggi AUC = makin baik model membedakan kelas
Walkthrough Step-by-Step:
- Model output probabilitas 0-1 per sample
- Sweep threshold dari 0.1 sampai 0.9
- Tiap threshold hitung TPR (recall) dan FPR (1 - specificity)
- Plot TPR vs FPR di tiap threshold β ROC curve
- Area di bawah curve = AUC
- AUC: 0.5 random, 0.7-0.8 decent, 0.9+ excellent, 1.0 curiga leakage
Analogi Sehari-hari: Detektor logam di bandara. Threshold ketat = banyak false alarm tapi tidak miss senjata (high recall, low precision). Threshold longgar = sedikit false alarm tapi miss senjata (high precision, low recall). ROC menunjukkan trade-off di semua tingkat sensitivitas.
Diagram statis Mermaid sebagai fallback:
flowchart LR
A["π Probabilitas<br/>0 - 1"] --> B["ποΈ Threshold:<br/>0.1 β 0.9"]
B --> C["π Tiap threshold<br/>hitung TPR, FPR"]
C --> D["π Plot ROC curve"]
D --> E["π Hitung AUC<br/>(area under curve)"]
E --> F{"AUC?"}
F -->|0.5| G["π² Random"]
F -->|0.7-0.8| H["β
Decent"]
F -->|0.9+| I["π Excellent"]
F -->|1.0| J["β οΈ Curiga leakage"]
ROC Curve
Plot TPR (Recall) vs FPR (1 - Specificity) di berbagai threshold.
from sklearn.metrics import roc_curve, roc_auc_score
import matplotlib.pyplot as plt
y_proba = model.predict_proba(X_test)[:, 1]
fpr, tpr, thresholds = roc_curve(y_test, y_proba)
plt.plot(fpr, tpr, label=f"AUC = {roc_auc_score(y_test, y_proba):.3f}")
plt.plot([0, 1], [0, 1], 'k--')
plt.xlabel("FPR")
plt.ylabel("TPR")
plt.legend()
AUC (Area Under ROC)
- 0.5 = random
- 0.7-0.8 = decent
- 0.8-0.9 = good
- 0.9+ = excellent
- 1.0 = perfect (curiga overfit/leak)
Pros AUC: threshold-independent, robust ke imbalance.
Precision-Recall Curve
Lebih informatif untuk highly imbalanced data:
from sklearn.metrics import precision_recall_curve
precision, recall, _ = precision_recall_curve(y_test, y_proba)
plt.plot(recall, precision)
Bagian 4 β Multiclass Metrics
from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred))
# Output:
# precision recall f1-score support
# class_0 0.85 0.90 0.87 100
# class_1 0.78 0.72 0.75 80
# class_2 0.92 0.88 0.90 50
#
# accuracy 0.84 230
# macro avg 0.85 0.83 0.84 230
# weighted avg 0.84 0.84 0.84 230
Macro vs Weighted
- Macro β average tiap class equal (cocok untuk imbalanced β class kecil dapat suara)
- Weighted β average tiap class proporsional (mirip accuracy)
- Micro β global TP/FP/FN (= accuracy untuk multiclass)
Bagian 5 β Metrics Regression
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
import numpy as np
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
mape = np.mean(np.abs((y_test - y_pred) / y_test)) * 100
| Metric | Skala | Robust ke outlier? | Mudah interpret? |
|---|---|---|---|
| MSE | Squared | β | β |
| RMSE | Same as y | β | β |
| MAE | Same as y | β | β |
| RΒ² | 0-1 | β | β |
| MAPE | % | β | β |
Bagian 6 β Cross-Validation
Analogi: Ujian Beberapa Kali, Soal Berbeda
Tunggu, ini sudah dijelaskan di file 01? Yes, tapi penting diulang dari sudut evaluasi. Kalau cuma single train/test split, score yang kamu dapat bisa lucky/unlucky tergantung random_state. Cross-validation memberi distribusi score (mean Β± std) yang lebih reliable.
flowchart TD
A["π¦ Dataset"] --> B{"Tipe data?"}
B -->|Tabular biasa| C["π KFold (5-10)"]
B -->|Imbalanced classification| D["βοΈ StratifiedKFold"]
B -->|Time series| E["β° TimeSeriesSplit<br/>(no shuffle!)"]
B -->|Group data| F["π₯ GroupKFold"]
style C fill:#d4f4dd
style D fill:#d4f4dd
style E fill:#fff4d4
k-Fold
from sklearn.model_selection import cross_val_score, KFold
scores = cross_val_score(model, X, y, cv=5, scoring="f1")
print(f"CV: {scores.mean():.4f} Β± {scores.std():.4f}")
Stratified K-Fold (untuk classification)
from sklearn.model_selection import StratifiedKFold
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=cv)
Cross-Val Predict (untuk inspect)
from sklearn.model_selection import cross_val_predict
y_pred_cv = cross_val_predict(model, X, y, cv=5)
# Setiap titik di-predict pakai fold yang dia bukan training-nya
Time Series Split
Untuk data temporal β TIDAK BOLEH random shuffle:
from sklearn.model_selection import TimeSeriesSplit
tss = TimeSeriesSplit(n_splits=5)
for train_idx, test_idx in tss.split(X):
# train ada 1, 2, 3, ..., test ada n+1
pass
Bagian 7 β Hyperparameter Tuning
Grid Search
from sklearn.model_selection import GridSearchCV
param_grid = {
"n_estimators": [100, 200, 500],
"max_depth": [10, 20, None],
"min_samples_split": [2, 5],
}
grid = GridSearchCV(
RandomForestClassifier(random_state=42),
param_grid,
cv=5,
scoring="f1",
n_jobs=-1,
verbose=2
)
grid.fit(X_train, y_train)
print(f"Best: {grid.best_params_}")
print(f"Score: {grid.best_score_:.4f}")
Random Search
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform
param_dist = {
"n_estimators": randint(100, 1000),
"max_depth": randint(5, 50),
"min_samples_split": randint(2, 20),
}
random = RandomizedSearchCV(
RandomForestClassifier(random_state=42),
param_dist,
n_iter=50,
cv=5,
n_jobs=-1
)
random.fit(X_train, y_train)
Bayesian Optimization (Optuna)
# pip install optuna
import optuna
def objective(trial):
params = {
"n_estimators": trial.suggest_int("n_estimators", 50, 500),
"max_depth": trial.suggest_int("max_depth", 3, 30),
"learning_rate": trial.suggest_float("lr", 0.01, 0.3),
}
model = xgb.XGBClassifier(**params)
score = cross_val_score(model, X_train, y_train, cv=5).mean()
return score
study = optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=100)
print(study.best_params)
Optuna >> Grid/Random untuk dataset besar atau banyak param.
Bagian 8 β Learning Curve
Analogi: Murid Hafal vs Murid Paham
Plot learning curve = lihat bagaimana model belajar saat data ditambah.
- Murid yang menghafal (overfit): dapat 100 di latihan, tapi 60 di ujian. Gap besar.
- Murid yang tidak belajar (underfit): dapat 60 di latihan, 60 di ujian. Sama-sama jelek.
- Murid yang paham (good fit): dapat 90 di latihan, 88 di ujian. Dekat dan tinggi.
flowchart TD
A["π Learning Curve"] --> B{"Train vs<br/>Val score?"}
B -->|Train tinggi<br/>Val rendah<br/>Gap besar| C["β οΈ Overfit<br/>(murid menghafal)"]
B -->|Train rendah<br/>Val rendah| D["β οΈ Underfit<br/>(murid tidak belajar)"]
B -->|Train tinggi<br/>Val tinggi<br/>Dekat| E["β
Good Fit<br/>(murid paham)"]
C --> F["π‘ Solusi: regularize,<br/>reduce complexity,<br/>more data"]
D --> G["π‘ Solusi: model lebih kompleks,<br/>more features,<br/>longer training"]
style C fill:#ffd4d4
style D fill:#fff4d4
style E fill:#d4f4dd
Plot training vs validation score saat dataset besar bertambah:
from sklearn.model_selection import learning_curve
train_sizes, train_scores, val_scores = learning_curve(
model, X, y, cv=5, train_sizes=np.linspace(0.1, 1.0, 10)
)
plt.plot(train_sizes, train_scores.mean(axis=1), label="Train")
plt.plot(train_sizes, val_scores.mean(axis=1), label="Val")
plt.xlabel("Training samples")
plt.ylabel("Score")
plt.legend()
Diagnose
| Pattern | Diagnosis |
|---|---|
| Train high, val rendah, gap besar | Overfit β kurangi complexity, regularize, more data |
| Train rendah, val rendah | Underfit β model lebih kompleks, more features |
| Train & val tinggi & dekat | Good fit |
| Val masih naik di akhir | Bisa lebih banyak data |
Bagian 9 β Validation Curve (Per Hyperparameter)
from sklearn.model_selection import validation_curve
train_scores, val_scores = validation_curve(
RandomForestClassifier(),
X, y,
param_name="max_depth",
param_range=[1, 5, 10, 20, 50, None],
cv=5
)
Plot show optimal value tiap hyperparameter.
Tabel Komparasi Hyperparameter Tuning
| Method | Speed | Accuracy | Best For |
|---|---|---|---|
| GridSearchCV | π Slow | ββββ | Sedikit hyperparameter, exhaustive |
| RandomizedSearchCV | β‘β‘ | βββ | Banyak hyperparameter, budget terbatas |
| Optuna (Bayesian) | β‘β‘β‘ | βββββ | Banyak hyperparameter, smart search |
| Manual Tuning | β‘β‘β‘β‘ | ββ | Eksplorasi awal, debugging |
Common Mistakes & FAQ
Common Mistakes
flowchart TD
A["β οΈ Common Mistakes"] --> B["π Cuma lihat accuracy<br/>untuk imbalanced"]
A --> C["π― Tune di test set"]
A --> D["π§ Data leakage di CV"]
A --> E["π² Tidak shuffle<br/>untuk classification"]
A --> F["β° Time series<br/>random shuffle"]
A --> G["π Tidak bandingkan<br/>dengan baseline"]
style A fill:#ffe0e0
1. Cuma Accuracy untuk Imbalanced
Dataset 99% kelas A, 1% kelas B. Predict semua A β 99% accuracy. Useless.
β Pakai F1, AUC, atau precision/recall sesuai konteks bisnis.
2. Tune di Test Set
Iterasi GridSearchCV pakai test set β kamu fit hyperparameter ke test β leak.
β Tune pakai validation set atau CV. Test set sentuh sekali untuk evaluasi final.
3. CV tanpa Stratify untuk Classification
Imbalanced data + KFold biasa β fold bisa skip kelas minor.
β Pakai StratifiedKFold.
4. Time Series Random Shuffle
β Time series dengan KFold biasa β train pakai data masa depan, test pakai data masa lalu. Mustahil di production.
β Pakai TimeSeriesSplit yang menjaga urutan temporal.
5. Tidak Bandingkan dengan Baseline
"Model saya accuracy 80%". Bagus? Tergantung. Kalau baseline (predict majority) sudah 78%, model kamu cuma improve 2%. Selalu compare dengan DummyClassifier.
FAQ
Q: Saya harus pakai metric apa? A: Tergantung bisnis. Diskusi dengan stakeholder dulu. Kalau bingung, F1 atau AUC default yang aman untuk klasifikasi.
Q: Berapa CV fold yang ideal? A: 5-10 standar. Dataset kecil β 10. Dataset besar β 5 (lebih cepat).
Q: Grid Search atau Random Search? A: Random Search hampir selalu lebih efisien. Grid hanya kalau hyperparameter sedikit (β€ 3 dengan opsi sedikit).
Q: Optuna selalu lebih bagus? A: Untuk banyak hyperparameter dan dataset besar, ya. Untuk masalah kecil, overhead-nya tidak terbayar.
Q: Train accuracy 100%, test accuracy 90%. Bagus? A: Curiga overfit. Coba regularize atau cross-validate. Gap kecil bisa OK, gap besar (>10%) suspicious.
Q: ROC-AUC 0.95 tapi PR-AUC 0.4. Kenapa? A: Klasik tanda imbalanced data. ROC-AUC over-optimistic untuk imbalanced. Trust PR-AUC.
Cek Pemahaman
- Bisa baca confusion matrix?
- Tahu kapan pakai precision vs recall?
- Tahu apa itu AUC dan kapan dipakai?
- Bisa pakai cross-validation?
- Bisa Grid/Random Search?
- Bisa diagnose overfit/underfit dari learning curve?
Challenge 5.6
Challenge β Full Evaluation Pipeline
Pilih dataset, train model, lakukan:
- Confusion matrix + classification report
- ROC curve + AUC
- PR curve
- Learning curve (diagnose fit)
- Hyperparameter tuning (Grid + Random)
- Final model dengan best params
- Test set evaluation
Notebook lengkap, push ke GitHub.
Selanjutnya: 07-kaggle-project.md