Tiga chapter sebelumnya mengajarkan cara mencari: grid menyapu semua kombinasi, random search menyampel distribusi, halving memangkas kandidat lewat turnamen anggaran. Semuanya mengjawab pertanyaan “konfigurasi mana yang menang” — tetapi tidak satu pun memberitahu kita bentuk medannya: hyperparameter mana yang benar-benar mengubah kinerja, di wilayah nilai mana akurasinya berbalik arah, dan kapan tambahan kompleksitas berhenti membayar. validation_curve menjawab pertanyaan-pertanyaan itu dengan eksperimen yang jauh lebih murah: pilih satu hyperparameter, sapu beberapa nilainya, lalu baca sepasang kurva skor latih dan validasi. Mesinnya persis mesin Chapter Learning Curve pada Section Evaluasi dan Validasi Model — hanya sumbu-x nya berganti dari jumlah sampel menjadi nilai hyperparameter. Chapter ini membedah mekanismenya, tiga pola bentuk kurva yang layak dihafal, lalu membaca dua kurva nyata pada data kampanye pemasaran.
Mekanisme validation_curve #
Fungsi validation_curve dari sklearn.model_selection menjalankan eksperimen bersarang: untuk tiap nilai pada param_range, fungsi ini melatih estimator yang benar-benar baru pada tiap lipatan cross-validation, lalu mencatat skor pada data latih dan data validasi lipatan tersebut. Hasilnya dua matriks berbentuk (jumlah nilai × jumlah lipatan) — rata-rata per baris (.mean(axis=1)) menghasilkan dua kurva yang siap dibaca, dan .std(axis=1) mengukur seberapa banyak kurva itu bergoyang antar lipatan. Tidak ada best_params_, tidak ada model final yang di-refit — validation_curve adalah alat diagnosis, bukan alat pencarian.
Karena estimator dilatih dari nol untuk tiap nilai, perubahan skor murni berasal dari hyperparameter — bukan sisa pelatihan sebelumnya. Estimator boleh berupa Pipeline penuh: tulis nama parameternya dengan sintaks dua garis bawah, misalnya "kneighborsclassifier__n_neighbors"; seluruh pipeline — termasuk StandardScaler di dalamnya — di-refit pada tiap lipatan sehingga scaling tidak bocor ke fold validasi.
Tiga Pola yang Layak Dikenali #
Hampir semua kurva validasi yang sehat jatuh ke salah satu dari tiga pola berikut. Membacanya lebih penting daripada mencari angka puncaknya:
| Pola pada kurva | Diagnosis | Tindakan |
|---|---|---|
| Latih menanjak terus, validasi memuncak lalu menurun; gap melebar | Overfitting — kapasitas melampaui data | Pilih nilai di sekitar puncak; tolak ujung kapasitas tinggi |
| Kedua kurva rendah dan berdempet di seluruh rentang | Underfitting, atau hyperparameter tidak berpengaruh | Perbaiki model/fitur; jangan membakar anggaran tuning di sini |
| Kurva berplateau lebar dengan gap sempit | Wilayah stabil — hyperparameter tidak sensitif | Pilih nilai sederhana/murah di dalam plateau |
Pola pertama adalah wajah klasik kapasitas: max_depth, n_neighbors kecil, num_leaves besar. Pola kedua sering dijumpai pada model yang salah spesifikasi — kinerjanya tidak bergerak karena sumber masalahnya bukan tombol yang sedang diputar. Pola ketiga adalah kabar baik: plateau berarti pilihan Anda tidak rapuh, dan anggaran pencarian lebih baik dialihkan ke hyperparameter lain yang kurvanya benar-benar berbentuk.
Eksperimen: Dua Kurva pada Data Kampanye #
Skrip berikut menjalankan dua sapuan pada data kampanye pemasaran yang sama — 400 sampel, 12 fitur, 10% label terbalik. Bagian 1 menyapu max_depth pada decision tree; Bagian 2 menyapu n_neighbors pada k-NN yang dibungkus pipeline bersama StandardScaler.
# Kurva validasi: efek satu hyperparameter terhadap skor latih dan validasi.
# Bagian 1: max_depth pada DecisionTreeClassifier (pola overfitting klasik).
# Bagian 2: n_neighbors pada KNeighborsClassifier dalam pipeline (pola U).
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import StratifiedKFold, validation_curve
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier
# Data kampanye pemasaran: 400 sampel, 12 fitur, 10% label terbalik.
X, y = make_classification(n_samples=400, n_features=12, n_informative=6,
n_redundant=3, flip_y=0.10, class_sep=1.0,
random_state=42)
cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)
def tabel_kurva(rentang, skor_latih, skor_validasi):
"""Cetak tabel kurva validasi: nilai, rata-rata latih/validasi, gap."""
print(f" {'nilai':<12} {'latih':>6} {'validasi':>10} {'+- std':>7} {'gap':>7}")
for nilai, a, b, s in zip(rentang, skor_latih.mean(axis=1),
skor_validasi.mean(axis=1),
skor_validasi.std(axis=1)):
print(f" {str(nilai):<12} {a:>6.3f} {b:>10.3f} {s:>7.3f} {a - b:>7.3f}")
# --- Bagian 1: max_depth pada decision tree ----------------------------
rentang_kedalaman = [1, 2, 3, 4, 6, None]
latih, validasi = validation_curve(
DecisionTreeClassifier(random_state=42), X, y,
param_name="max_depth", param_range=rentang_kedalaman,
cv=cv, scoring="accuracy", n_jobs=1)
print("Bagian 1 - max_depth pada DecisionTreeClassifier:")
tabel_kurva(rentang_kedalaman, latih, validasi)
i_terbaik = int(np.argmax(validasi.mean(axis=1)))
print(f" validasi tertinggi : max_depth = "
f"{rentang_kedalaman[i_terbaik]} "
f"({validasi.mean(axis=1)[i_terbaik]:.3f})")
# --- Bagian 2: n_neighbors pada k-NN dalam pipeline --------------------
rentang_tetangga = [1, 3, 5, 10, 20, 50]
pipeline = make_pipeline(StandardScaler(), KNeighborsClassifier())
latih, validasi = validation_curve(
pipeline, X, y,
param_name="kneighborsclassifier__n_neighbors",
param_range=rentang_tetangga, cv=cv, scoring="accuracy", n_jobs=1)
print("\nBagian 2 - n_neighbors pada KNeighborsClassifier (dalam pipeline):")
tabel_kurva(rentang_tetangga, latih, validasi)
i_terbaik = int(np.argmax(validasi.mean(axis=1)))
print(f" validasi tertinggi : n_neighbors = "
f"{rentang_tetangga[i_terbaik]} "
f"({validasi.mean(axis=1)[i_terbaik]:.3f})")Output yang Dihasilkan:
Bagian 1 - max_depth pada DecisionTreeClassifier:
nilai latih validasi +- std gap
1 0.759 0.752 0.027 0.006
2 0.815 0.790 0.027 0.025
3 0.850 0.817 0.019 0.033
4 0.896 0.770 0.025 0.126
6 0.960 0.767 0.006 0.193
None 1.000 0.777 0.025 0.223
validasi tertinggi : max_depth = 3 (0.817)
Bagian 2 - n_neighbors pada KNeighborsClassifier (dalam pipeline):
nilai latih validasi +- std gap
1 1.000 0.783 0.047 0.217
3 0.881 0.840 0.027 0.041
5 0.871 0.845 0.020 0.026
10 0.871 0.850 0.012 0.021
20 0.859 0.840 0.020 0.019
50 0.817 0.805 0.027 0.012
validasi tertinggi : n_neighbors = 10 (0.850)Mari kita telaah bagian-bagian kunci skrip tersebut:
- Baris 14–17 (data dan lipatan tetap):
make_classification(random_state=42)menyediakan masalah dengan 10% label terbalik;StratifiedKFold(n_splits=3, shuffle=True, random_state=42)menjaga proporsi kelas di tiap lipatan sekaligus menjamin tabel yang sama pada setiap eksekusi. - Baris 20–26 (pencetak tabel): fungsi
tabel_kurvamerata-ratakan tiap baris matriks skor (.mean(axis=1)), menghitung deviasi standar antar lipatan, dan menambahkan kolomgap= skor latih dikurangi skor validasi — tiga angka yang menjadi bahan baku diagnosis. - Baris 30–34 (sapuan pertama): satu pemanggilan
validation_curvemenggantikan enam loop pelatihan manual;param_name="max_depth"danparam_rangeberisi enam nilai termasukNone(pohon tumbuh penuh),scoring="accuracy"eksplisit,n_jobs=1. - Baris 38–41 (puncak otomatis):
np.argmaxpada rata-rata validasi menandai nilai terbaik — dilakukan untuk kenyamanan pembacaan, bukan untuk dijadikan keputusan final. - Baris 44–49 (sapuan kedua): estimator berupa
PipelineStandardScaler+ k-NN;param_name="kneighborsclassifier__n_neighbors"menembus pipeline hingga hyperparameter k-NN, dan seluruh pipeline di-refit per nilai per lipatan.
Membaca Hasil Eksperimen #
Bagian 1 menampilkan pola overfitting paling klasik. Skor latih menanjak monoton dari 0,759 pada kedalaman 1 sampai 1,000 pada pohon tumbuh penuh — makin dalam pohon, makin luas ruang hafalannya. Skor validasi mengikuti hanya sampai kedalaman 3 (0,817), lalu berbalik turun ke kisaran 0,77: kapasitas tambahan setelah titik itu dipakai menghafal derau label, buangannya terlihat langsung pada gap yang membengkak dari 0,006 menjadi 0,223. Perhatikan pula kolom deviasi standar yang kecil (0,006–0,027) — puncak 0,817 itu berdiri di atas tiga lipatan yang cukup jujur menyepakati kisaran 0,79–0,82, sehingga pola “puncak di 3” layak dipercaya sebagai wilayah, bukan kebetulan.
Bagian 2 melengkapi dengan pola lengkap dari kiri ke kanan. Pada n_neighbors=1, k-NN menghafal tiap titik latih (skor latih 1,000) dan langsung dihukum di validasi (0,783, gap 0,217) — overfitting versi instance-based. Ke arah kanan, gap menutup perlahan dan validasi memuncak 0,850 pada k=10, sebelum menurun ke 0,805 pada k=50 — tetangga terlalu banyak mulai menghaluskan batas kelas hingga informasi lokal tercuci (underfitting). Pelajaran terpenting ada di wilayah tengah: k=3 sampai k=20 semuanya beradu dalam rentang 0,840–0,850, selisihnya lebih kecil daripada deviasi standar antar lipatan. Kurva ini tidak menunjuk satu angka juara tunggal; kurva ini menunjuk plateau — dan pilihan mana pun di dalamnya akan berperilaku nyaris sama di lapangan.
Kurva validasi menjawab “hyperparameter mana dan di wilayah mana”. Satu tingkat lagi tetap terbuka: bagaimana bila satu evaluasi saja sudah mahal — model besar, data besar, anggaran terbatas? Di titik itu pencarian perlu belajar dari evaluasi yang sudah selesai dan berhenti lebih dini pada kandidat yang jelas tertinggal. Dua gagasan itu — optimasi Bayesian dan multi-fidelity — menutup Section ini pada Chapter Strategi Optimasi Lanjutan.
Referensi Resmi #
- Validation curves: plotting scores to evaluate models — user guide resmi yang membahas validation curve berdampingan dengan learning curve.
- sklearn.model_selection.validation_curve — API Reference
— kontrak
param_name,param_range, dan bentuk matriks skor yang dikembalikan. - Plotting Validation Curves — contoh resmi — contoh lengkap sapuan parameter SVM beserta plotnya.