DataLens

Pembagian train/validation/test dan cross-validation

Pakai set validasi untuk memilih model tanpa menyentuh set test, dapatkan perkiraan yang lebih andal dengan k-fold cross-validation, dan setel pengaturan dengan grid search.

Level: Menengah · 8 menit baca

Latihan modul ini

Soal-soal untuk pelajaran ini berjalan di browser-mu dan langsung mengecek jawabanmu.

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.

SetDipakai untukSeberapa sering
trainingmencocokkan parameter setiap modelsetiap model
validasimembandingkan model dan pengaturanberkali-kali
testperkiraan akhir yang jujursekali, 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:

  1. Bagi data (yang bukan test) menjadi 5 fold yang sama besar.
  2. Latih pada 4 fold, validasi pada fold ke-5.
  3. Ulangi 5 kali, setiap fold bergiliran menjadi validasi.
  4. 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.

Ide utama

Pakai cross-validation pada data training untuk membandingkan dan menyetel model. Simpan set test untuk satu pengecekan terakhir dari model yang dipilih.

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

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

Latihan modul ini

Soal-soal untuk pelajaran ini berjalan di browser-mu dan langsung mengecek jawabanmu.

Berikutnya: Metrik klasifikasi: accuracy, precision, recall, F1 →