Idenya
Kamu jarang melatih hanya satu model. Kamu akan membandingkan model, mencoba kumpulan fitur dan menyetel pengaturan seperti "seberapa dalam sebuah pohon boleh tumbuh?" Setiap perbandingan adalah keputusan berdasarkan data. Kalau kamu membuat keputusan-keputusan itu memakai set test, pilihanmu perlahan-lahan menyesuaikan diri dengannya, dan skornya berhenti menjadi perkiraan yang jujur. Set test harus tetap menjadi kejutan.
Perbaikannya: set ketiga untuk keputusan.
| Set | Dipakai untuk | Seberapa sering |
|---|---|---|
| training | mencocokkan parameter setiap model | setiap model |
| validasi | membandingkan model dan pengaturan | berkali-kali |
| test | perkiraan akhir yang jujur | sekali, di akhir |
Pembagian tiga arah
Kalau kamu mencoba beberapa model lalu menyimpan yang terbaik di test set, test set itu diam-diam sudah menjadi bagian dari pelatihan: kamu memilih model yang kebetulan cocok dengannya. Solusinya adalah potongan data ketiga, validation set, untuk membandingkan model, sementara test set disimpan untuk satu pengecekan terakhir. Caranya dengan membagi dua kali:
import pandas as pd
from sklearn.model_selection import train_test_split
churn = pd.read_csv("churn.csv")
FEATURES = ["tenure_months", "orders_90d", "days_since_last_order", "support_tickets", "app_user"]
X, y = churn[FEATURES], churn["churned"]
X_temp, X_test, y_temp, y_test = train_test_split(X, y, test_size=0.2, random_state=0, stratify=y)
X_train, X_val, y_train, y_val = train_test_split(X_temp, y_temp, test_size=0.25, random_state=0, stratify=y_temp)
print(len(X_train), len(X_val), len(X_test))
240 80 80
60% training, 20% validasi, 20% test. Sekarang bandingkan model-model kandidat pada set validasi:
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
candidates = {
"logistic": LogisticRegression(max_iter=1000),
"tree depth 2": DecisionTreeClassifier(max_depth=2, random_state=0),
"tree depth 10": DecisionTreeClassifier(max_depth=10, random_state=0),
}
for name, model in candidates.items():
model.fit(X_train, y_train)
print(f"{name:14} train {model.score(X_train, y_train):.3f} validation {model.score(X_val, y_val):.3f}")
logistic train 0.833 validation 0.812
tree depth 2 train 0.783 validation 0.713
tree depth 10 train 0.992 validation 0.775
Pohon yang dalam menghafal data training (akurasi training hampir sempurna) tapi skornya lebih rendah di validasi: overfitting, tertangkap tanpa menyentuh set test. Pilih berdasarkan skor validasi, lalu evaluasi hanya pemenangnya pada set test, sekali.
Masalah dengan satu pembagian
Dengan 80 customer validasi, satu pembagian yang sial bisa membuat model terlihat 5 poin lebih baik atau lebih buruk dari kenyataannya. Coba beberapa pembagian acak yang berbeda dan perhatikan skornya bergerak:
for seed in [0, 1, 2, 3, 4]:
Xa, Xb, ya, yb = train_test_split(X_temp, y_temp, test_size=0.25, random_state=seed, stratify=y_temp)
print(seed, round(LogisticRegression(max_iter=1000).fit(Xa, ya).score(Xb, yb), 3))
0 0.812
1 0.775
2 0.812
3 0.887
4 0.812
Model yang sama, data yang sama, skornya tersebar dalam beberapa poin. Dataset kecil membuat satu pembagian penuh noise.
k-fold cross-validation
Cross-validation memakai setiap contoh untuk validasi tepat sekali. Dengan k = 5:
- Bagi data (yang bukan test) menjadi 5 fold yang sama besar.
- Latih pada 4 fold, validasi pada fold ke-5.
- Ulangi 5 kali, setiap fold bergiliran menjadi validasi.
- Rata-ratakan kelima skornya.
from sklearn.model_selection import cross_val_score
scores = cross_val_score(LogisticRegression(max_iter=1000), X_temp, y_temp, cv=5)
print(scores.round(3))
print(f"mean {scores.mean():.3f} ± {scores.std():.3f}")
[0.828 0.891 0.812 0.828 0.812]
mean 0.834 ± 0.029
Kamu mendapat rata-rata dan gambaran sebarannya. Untuk klasifikasi, scikit-learn otomatis menstratifikasi fold-nya. Harganya adalah k kali waktu training, yang tidak berarti apa-apa untuk model seperti ini.
Pakai cross-validation pada data training untuk membandingkan dan menyetel model. Simpan set test untuk satu pengecekan terakhir dari model yang dipilih.
Menyetel dengan grid search
Pengaturan yang kamu pilih sebelum training (kedalaman pohon, kekuatan regularisasi) disebut hyperparameter, berbeda dengan parameter yang dipelajari model. GridSearchCV mencoba setiap kombinasi dengan cross-validation dan menyimpan yang terbaik:
from sklearn.model_selection import GridSearchCV
search = GridSearchCV(
DecisionTreeClassifier(random_state=0),
param_grid={"max_depth": [1, 2, 3, 4, 6, 8], "min_samples_leaf": [1, 5, 20]},
cv=5,
)
search.fit(X_temp, y_temp)
print(search.best_params_, round(search.best_score_, 3))
print("test:", round(search.score(X_test, y_test), 3))
{'max_depth': 3, 'min_samples_leaf': 1} 0.791
test: 0.738
best_score_ adalah skor hasil cross-validation; setelah pencarian, search melatih ulang pengaturan terbaik pada seluruh X_temp, dan baru kemudian kita melihat set test. Di sini skor test-nya beberapa poin di bawah skor CV. Dengan hanya 80 customer test, itu masih dalam noise yang kamu lihat di eksperimen pembagian di atas; penurunan yang jauh lebih besar akan berarti pencariannya overfit pada fold validasi.
Kasus-kasus khusus
- Time series: jangan pernah diacak. Validasi pada periode yang lebih belakangan daripada yang dipakai training (
TimeSeriesSplit), karena dalam pemakaian sungguhan kamu selalu memprediksi masa depan dari masa lalu. - Kelompok: kalau satu customer punya banyak baris, simpan semua barisnya di fold yang sama (
GroupKFold), atau model akan diuji pada orang yang sudah pernah dilihatnya.
Mengapa ini penting untuk kode yang bersih
Proses validasi adalah bagian dari kodemu, bukan renungan belakangan: pembagian atau skema CV yang tetap dengan seed, satu fungsi yang menilai model, dan set test yang dievaluasi tepat sekali di langkah akhir yang ditandai dengan jelas. Struktur itulah yang membuatmu (dan reviewer) bisa memercayai angka yang dilaporkan.
Rangkuman
- Set training untuk mencocokkan; set validasi (atau CV) untuk memilih; set test untuk melaporkan, sekali.
- Satu pembagian bisa penuh noise; k-fold cross-validation merata-ratakan k skor validasi.
cross_val_score(model, X, y, cv=5)mengembalikan satu skor per fold.- Hyperparameter (seperti
max_depth) disetel denganGridSearchCV(model, param_grid, cv=5). - Time series: validasi pada data yang lebih belakangan; data berkelompok: simpan kelompoknya bersama.