Evaluasi model tidak dimulai dari memilih metrik, melainkan dari menyiapkan data yang dinilai. Model yang diukur pada data yang sama dengan data latihannya akan tampak hebat padahal hanya menghafal, dan seluruh angka pada Section ini menjadi tidak berarti jika protokol pembagiannya keliru. Chapter Pemisahan Dataset
pada Section Dasar Machine Learning sudah membahas mekanisme dasar train_test_split; pada chapter ini kita membedah sisi protokol evaluasinya — tiga peran subset data, pembagian dua tahap, dan batas tegas antara eksperimen yang boleh diulang dan pengukuran final yang hanya boleh sekali.
Tiga Peran: Latihan, Validasi, dan Pengujian #
Evaluasi yang jujur membutuhkan tiga subset dengan tiga tugas yang berbeda. Data latih (training set) adalah bahan baku: model membaca baris-barisnya dan menyesuaikan parameter darinya. Data validasi (validation set) adalah arena eksperimen: kita memakainya untuk membandingkan kandidat model, memilih max_depth, atau mencoba kombinasi fitur. Data uji (test set) adalah wasit final: model terpilih diukur padanya satu kali saja, lalu hasilnya dilaporkan.
| Subset | Peran | Seberapa sering dipakai |
|---|---|---|
| Data latih (train) | Melatih model, menyesuaikan parameter | Setiap pelatihan |
| Data validasi (validation) | Memilih model dan menyetel hyperparameter | Berulang kali selama eksperimen |
| Data uji (test) | Pengukuran final, simulasi data baru | Satu kali, di akhir |
Mengapa validasi tidak bisa dirangkap dengan data uji? Karena setiap keputusan yang kita ambil berdasarkan sebuah subset adalah kebocoran informasi dari subset itu ke model. Jika kita memilih max_depth berdasarkan skor pada data uji, data uji sudah ikut melatih keputusan kita — ia bukan lagi perwakilan data baru. Skor final menjadi terlalu optimis, dan kejutan baru muncul ketika model beroperasi di produksi. Pemisahan validasi dan uji menjaga amplop data uji tetap tertutup sampai detik terakhir.
Membagi Tiga Subset dengan Dua Tahap train_test_split #
scikit-learn tidak menyediakan fungsi khusus pembagi tiga subset, tetapi kita bisa merangkai dua panggilan train_test_split. Tahap pertama memisahkan data latih dari sisa data; tahap kedua membelah sisa itu menjadi validasi dan uji. Kita pakai data 40 member Toko Kopi Kita dengan dua fitur perilaku dan label berlangganan:
import pandas as pd
from sklearn.model_selection import train_test_split
pelanggan = pd.DataFrame({
"kunjungan_bulan": [1, 8, 7, 5, 5, 9, 1, 7, 3, 1, 6, 10, 8, 8, 8, 8,
6, 2, 9, 5, 6, 4, 2, 10, 8, 7, 5, 9, 6, 5,
5, 3, 1, 6, 9, 1, 9, 9, 3, 7],
"total_belanja": [170, 610, 570, 310, 100, 770, 380, 710, 550, 630, 610, 190,
320, 400, 420, 80, 450, 160, 600, 560, 740, 600, 320, 770,
350, 290, 720, 320, 100, 400, 640, 190, 390, 140, 560, 400,
290, 220, 470, 550],
"berlangganan": [0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0,
1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
})
X = pelanggan[["kunjungan_bulan", "total_belanja"]]
y = pelanggan["berlangganan"]
print("Total data : %d pelanggan, %d berlangganan (%.0f%%)"
% (len(y), y.sum(), 100 * y.mean()))
X_train, X_sisa, y_train, y_sisa = train_test_split(
X, y, test_size=0.30, random_state=42, stratify=y
)
X_val, X_test, y_val, y_test = train_test_split(
X_sisa, y_sisa, test_size=0.50, random_state=42, stratify=y_sisa
)
print("Data latih : %2d baris, %d berlangganan (%.0f%%)"
% (len(y_train), y_train.sum(), 100 * y_train.mean()))
print("Data validasi : %2d baris, %d berlangganan (%.0f%%)"
% (len(y_val), y_val.sum(), 100 * y_val.mean()))
print("Data uji : %2d baris, %d berlangganan (%.0f%%)"
% (len(y_test), y_test.sum(), 100 * y_test.mean()))Output yang Dihasilkan:
Total data : 40 pelanggan, 19 berlangganan (48%)
Data latih : 28 baris, 13 berlangganan (46%)
Data validasi : 6 baris, 3 berlangganan (50%)
Data uji : 6 baris, 3 berlangganan (50%)Mari kita telaah baris-baris kode di atas:
Xdany:Xmemuat dua kolom fitur,ymemuat label. Pemisahan ini mengikuti konvensi scikit-learn yang sudah kita kenal sejak chapter Fitur dan Target .- Panggilan pertama
train_test_splitdengantest_size=0.30: 30% data (12 baris) dialokasikan sebagaiX_sisa, sisanya 28 baris menjadi data latih. Parameterstratify=ymenjaga proporsi kelas tetap terwakili. - Panggilan kedua dengan
test_size=0.50: 12 baris sisahan dibelah sama besar menjadi 6 baris validasi dan 6 baris uji. Stratifikasi pada tahap ini memakaiy_sisa— label yang benar-benar ada di sisihan, bukan label seluruh data. random_state=42pada kedua panggilan: pembagian identik setiap dijalankan ulang, sehingga angka-angka pada chapter ini dapat direproduksi pembaca.
Hasil cetakan menunjukkan proporsi kelas yang nyaris seragam: 48% berlangganan di data asli, 46% di data latih, dan 50% di validasi maupun uji. Gabungan 70/15/15 seperti ini adalah komposisi awal yang wajar untuk data berukuran kecil sampai menengah; ketiga subset cukup besar untuk perannya masing-masing.
Data Validasi sebagai Arena Pemilihan Model #
Sekarang kita lihat peran ketiga subset bekerja dalam satu alur utuh. Kita melatih DecisionTreeClassifier dengan beberapa kedalaman berbeda, membandingkannya pada data validasi, lalu mengukur model terpilih pada data uji tepat satu kali:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
pelanggan = pd.DataFrame({
"kunjungan_bulan": [1, 8, 7, 5, 5, 9, 1, 7, 3, 1, 6, 10, 8, 8, 8, 8,
6, 2, 9, 5, 6, 4, 2, 10, 8, 7, 5, 9, 6, 5,
5, 3, 1, 6, 9, 1, 9, 9, 3, 7],
"total_belanja": [170, 610, 570, 310, 100, 770, 380, 710, 550, 630, 610, 190,
320, 400, 420, 80, 450, 160, 600, 560, 740, 600, 320, 770,
350, 290, 720, 320, 100, 400, 640, 190, 390, 140, 560, 400,
290, 220, 470, 550],
"berlangganan": [0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0,
1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
})
X = pelanggan[["kunjungan_bulan", "total_belanja"]]
y = pelanggan["berlangganan"]
X_train, X_sisa, y_train, y_sisa = train_test_split(
X, y, test_size=0.30, random_state=42, stratify=y
)
X_val, X_test, y_val, y_test = train_test_split(
X_sisa, y_sisa, test_size=0.50, random_state=42, stratify=y_sisa
)
print("Pencarian max_depth memakai data validasi:")
skor_validasi = {}
for kedalaman in [1, 2, 3, 5]:
model = DecisionTreeClassifier(max_depth=kedalaman, random_state=42)
model.fit(X_train, y_train)
akurasi_train = accuracy_score(y_train, model.predict(X_train))
akurasi_val = accuracy_score(y_val, model.predict(X_val))
skor_validasi[kedalaman] = akurasi_val
print("max_depth=%d -> akurasi train %.3f, akurasi validasi %.3f"
% (kedalaman, akurasi_train, akurasi_val))
terbaik = max(skor_validasi, key=skor_validasi.get)
print("Max depth terpilih:", terbaik)
model_akhir = DecisionTreeClassifier(max_depth=terbaik, random_state=42)
model_akhir.fit(X_train, y_train)
print("Akurasi final pada data uji: %.3f"
% accuracy_score(y_test, model_akhir.predict(X_test)))Output yang Dihasilkan:
Pencarian max_depth memakai data validasi:
max_depth=1 -> akurasi train 0.714, akurasi validasi 0.833
max_depth=2 -> akurasi train 0.786, akurasi validasi 1.000
max_depth=3 -> akurasi train 0.857, akurasi validasi 1.000
max_depth=5 -> akurasi train 1.000, akurasi validasi 0.667
Max depth terpilih: 2
Akurasi final pada data uji: 0.833Mari kita baca hasilnya sebagai cerita evaluasi:
max_depth=1terlalu dangkal: pohon hanya bisa bertanya satu kali, akurasi data latih 0.714 — model terlalu sederhana untuk pola data.max_depth=5menghafal: akurasi data latih mencapai 1.000, tetapi akurasi validasi anjlok ke 0.667. Inilah overfitting yang tertangkap basah oleh data validasi — persis peran yang tidak bisa dimainkan data latih.max_depth=2dan3imbang di validasi: keduanya mencapai 1.000.max()memilih nilai pertama yang tertinggi, yaitu 2; memilih model paling sederhana di antara kandidat yang imbang adalah kebiasaan yang baik.- Baris terakhir adalah satu-satunya kontak dengan data uji: model final dilatih ulang dengan
max_depth=2, lalu diukur sekali. Akurasi 0.833 sedikit di bawah skor validasi 1.000 — wajar, karena validasi ikut memilih model sedangkan data uji sama sekali belum pernah tersentuh.
Perhatikan bahwa data uji tidak pernah muncul di dalam loop pencarian. Seluruh keputusan diambil dari data validasi; data uji hanya hadir di baris terakhir sebagai hakim. Itulah perbedaan mendasar antara dua angka yang tampak serupa: skor validasi adalah alat memilih, skor uji adalah laporan akhir.
Sekali Split versus Evaluasi Berulang #
Protokol dua tahap di atas memakai satu kali pembagian — praktik yang disebut hold-out. Kelebihannya sederhana dan cepat; kekurangannya, hasil validasi bergantung pada keberuntungan pembagian. Dengan enam baris validasi seperti contoh kita, satu-dua baris yang kebetulan sulit bisa menggeser akurasi jauh; satu model bisa tampak unggul padahal hanya diuntungkan pembagian. Solusinya bukan mengintip data uji, melainkan memutar peran data: cross-validation membagi data latih menjadi beberapa lipatan dan bergilir menjadikan tiap lipatan data validasi, sehingga setiap model dinilai dari beberapa pembagian sekaligus. Kita akan membedahnya tuntas pada chapter Cross-Validation di Section ini.
Rangkuman #
- Tiga subset punya tiga peran: data latih melatih model, data validasi menjadi arena pemilihan model dan hyperparameter, dan data uji menjadi pengukuran final yang hanya boleh dijalani satu kali.
- Pembagian tiga subset dirangkai dari dua panggilan
train_test_split: pisahkan data latih dari sisa, lalu belah sisa menjadi validasi dan uji — keduanya denganrandom_statedanstratify. - Demo 40 member memperlihatkan data validasi menangkap overfitting:
max_depth=5mencetak akurasi latih 1.000 tetapi validasi hanya 0.667, sehinggamax_depth=2yang dipilih. - Skor validasi adalah alat memilih; skor uji adalah laporan akhir — mencampur keduanya membuat skor final terlalu optimis.
- Satu kali pembagian membuat hasil validasi bergantung keberuntungan; perbaikannya dengan cross-validation yang memutar peran data, bukan dengan memakai data uji berulang kali.
Dengan protokol pembagian yang tegas, pertanyaan berikutnya adalah apa yang seharusnya diukur pada subset-subset itu. Untuk masalah regresi, jawabannya sekeluarga metrik galat — MAE, MSE, RMSE, dan R-squared — yang kita bahas pada chapter Metrik Evaluasi Regresi .