Akurasi yang dihitung pada data latih bukan bukti model berhasil belajar — model dapat sekadar menghafal sampel yang sama berulang kali. Karena itu dataset harus dibagi sebelum training dimulai: sebagian menggerakkan bobot, sebagian memandu keputusan, dan sebagian lagi disimpan terkunci sebagai ujian akhir. Chapter ini membahas peran ketiga subset, teknik stratified split, lalu mengeksekusi pembagian dua tahap pada dataset 600 sampel tiga kelas berproporsi 50/30/20 — lengkap dengan pelatihan mini dan evaluasi final pada test set.
Tiga Subset dan Perannya #
| Subset | Peran | Seberapa sering disentuh |
|---|---|---|
| Training | mengubah bobot model lewat gradien | setiap iterasi |
| Validasi | memantau kemajuan, memilih hyperparameter, early stopping | setiap epoch atau setiap eksperimen |
| Testing | estimasi performa final pada data yang belum pernah dilihat | tepat satu kali, di akhir proyek |
Mengapa validasi dan test tidak digabung? Setiap keputusan yang kita ambil berdasarkan sebuah subset — memilih learning rate, jumlah epoch, arsitektur — membuat informasi dari subset itu ikut membentuk model secara tidak langsung. Validasi memang boleh dipakai untuk keputusan seperti itu. Test set berbeda: begitu angkanya dipakai memilih model, test set turut “terlatih” dan hasil evaluasinya berhenti menjadi perkiraan yang jujur untuk data baru.
Stratified Split #
Pembagian acak pada data tidak seimbang dapat menggeser proporsi kelas antar subset — sampel kelas minoritas bisa menumpuk di satu subset. Argumen stratify=y pada train_test_split menjaga proporsi tiap kelas di tiap subset mengikuti proporsi dataset asli. Semakin kecil dataset, semakin terasa efeknya: pada 120 sampel test, selisih beberapa persen proporsi kelas sudah menggeser akurasi.
Pembagian Dua Tahap #
train_test_split membagi data menjadi dua bagian sekali panggil, sedangkan kita butuh tiga subset. Solusinya adalah dua panggilan berurutan: pisahkan test lebih dulu (20% dari total), lalu pisahkan validasi dari sisa (25% dari 80% = 20% dari total). Hasilnya rasio 60/20/20.
Berkas bagi_data.py menerapkan diagram tersebut: melatih MLP kecil pada data latih, memantau akurasi validasi, lalu menyentuh test set tepat sekali di baris paling akhir:
"""Pembagian data training/validasi/testing dengan stratified split.
Dataset sintetis 3 kelas berproporsi 50/30/20 dibagi dua tahap memakai
train_test_split: tahap pertama memisahkan test (20%), tahap kedua
memisahkan validasi dari sisa (25% dari 80% = 20% total). Stratify
menjaga proporsi kelas di ketiga subset. Model kecil lalu dilatih pada
train, dipantau lewat validasi, dan diuji SEKALI di akhir pada test.
"""
import torch
import torch.nn as nn
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from torch.utils.data import DataLoader, TensorDataset
torch.manual_seed(42)
# ---------- 1. Dataset sintetis 3 kelas, proporsi tidak seimbang ----------
X, y = make_classification(
n_samples=600,
n_features=8,
n_informative=6,
n_redundant=2,
n_classes=3,
weights=[0.5, 0.3, 0.2],
flip_y=0.03,
class_sep=1.3,
random_state=42,
)
# ---------- 2. Dua tahap pembagian: test dulu, lalu validasi ----------
X_sisa, X_test, y_sisa, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
X_latih, X_val, y_latih, y_val = train_test_split(
X_sisa, y_sisa, test_size=0.25, stratify=y_sisa, random_state=42
)
def proporsi(label):
"""Proporsi tiap kelas dalam persen, dipisah tanda '/'. """
total = len(label)
return " / ".join(f"{(label == k).mean() * 100:4.1f}%" for k in (0, 1, 2))
print("===== PEMBAGIAN DUA TAHAP (stratified) =====")
print(f"total : {len(y):3d} sampel | proporsi kelas {proporsi(y)}")
print(f"latih : {len(y_latih):3d} sampel | proporsi kelas {proporsi(y_latih)}")
print(f"validasi : {len(y_val):3d} sampel | proporsi kelas {proporsi(y_val)}")
print(f"test : {len(y_test):3d} sampel | proporsi kelas {proporsi(y_test)}")
print("rasio akhir 60/20/20; proporsi kelas terjaga di ketiga subset")
# ---------- 3. Latih model kecil, pantau lewat validasi ----------
def buat_model():
"""Model dengan inisialisasi bobot identik untuk tiap pelatihan."""
torch.manual_seed(7)
return nn.Sequential(
nn.Linear(8, 16),
nn.ReLU(),
nn.Linear(16, 3),
)
model = buat_model()
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.5)
dataset_latih = TensorDataset(
torch.tensor(X_latih, dtype=torch.float32),
torch.tensor(y_latih, dtype=torch.long),
)
loader = DataLoader(
dataset_latih,
batch_size=32,
shuffle=True,
generator=torch.Generator().manual_seed(42),
)
X_val_t = torch.tensor(X_val, dtype=torch.float32)
y_val_t = torch.tensor(y_val, dtype=torch.long)
print()
print("===== PELATIHAN (30 epoch, dipantau lewat validasi) =====")
JUMLAH_EPOCH = 30
for epoch in range(1, JUMLAH_EPOCH + 1):
model.train()
for X_batch, y_batch in loader:
optimizer.zero_grad()
loss = loss_fn(model(X_batch), y_batch)
loss.backward()
optimizer.step()
if epoch % 10 == 0:
model.eval()
with torch.no_grad():
akurasi_val = (model(X_val_t).argmax(dim=1) == y_val_t).float().mean().item()
print(f"epoch {epoch:2d} | akurasi validasi {akurasi_val:.3f}")
# ---------- 4. Sentuh test tepat satu kali, di paling akhir ----------
model.eval()
def akurasi(X_np, y_np):
with torch.no_grad():
pred = model(torch.tensor(X_np, dtype=torch.float32)).argmax(dim=1)
return (pred == torch.tensor(y_np)).float().mean().item()
print()
print("===== EVALUASI AKHIR (test dievaluasi sekali saja) =====")
print(f"akurasi latih : {akurasi(X_latih, y_latih):.3f}")
print(f"akurasi validasi : {akurasi(X_val, y_val):.3f}")
print(f"akurasi test : {akurasi(X_test, y_test):.3f}")Output yang Dihasilkan:
===== PEMBAGIAN DUA TAHAP (stratified) =====
total : 600 sampel | proporsi kelas 49.2% / 30.5% / 20.3%
latih : 360 sampel | proporsi kelas 49.2% / 30.3% / 20.6%
validasi : 120 sampel | proporsi kelas 49.2% / 30.8% / 20.0%
test : 120 sampel | proporsi kelas 49.2% / 30.8% / 20.0%
rasio akhir 60/20/20; proporsi kelas terjaga di ketiga subset
===== PELATIHAN (30 epoch, dipantau lewat validasi) =====
epoch 10 | akurasi validasi 0.800
epoch 20 | akurasi validasi 0.833
epoch 30 | akurasi validasi 0.883
===== EVALUASI AKHIR (test dievaluasi sekali saja) =====
akurasi latih : 0.850
akurasi validasi : 0.883
akurasi test : 0.842Contoh di atas diuji nyata dengan scikit-learn 1.5.2 dan PyTorch 2.14.1+cpu. Mari kita telaah kode di atas baris per baris:
- Baris 19-29 (dataset):
make_classificationmembuat 600 sampel 8 fitur, 3 kelas berproporsi 50/30/20, plusflip_y=0.03yang membalik 3% label supaya dataset tidak steril dan akurasi tidak pernah 100%. - Baris 32-37 (dua tahap): panggilan pertama memisahkan 120 sampel test dengan
stratify=y; panggilan kedua membelah 480 sampel sisa menjadi 360 latih dan 120 validasi denganstratify=y_sisa— stratifikasi tahap kedua memakai label sisa, bukan label total. - Baris 40-51 (verifikasi proporsi): fungsi
proporsi()mencetak persentase tiap kelas; keluaran menunjukkan 49,2% / 30,5% / 20,3% yang nyaris identik di keempat baris — bukti stratifikasi bekerja. - Baris 54-66 (komponen):
buat_model()memanggiltorch.manual_seed(7)setiap berjalan sehingga model selalu memulai dari bobot yang sama; SGDlr=0.5dipilih karena fiturmake_classificationsudah berskala seragam. - Baris 68-79 (DataLoader validasi): data latih di-batch 32, sedangkan validasi dievaluasi sekaligus dalam mode
eval— pola standar chapter Training Loop . - Baris 83-96 (pelatihan): akurasi validasi dicetak setiap 10 epoch; angka ini memandu keputusan seperti “apakah 30 epoch cukup?”, jadi boleh dilihat berulang kali.
- Baris 99-109 (evaluasi final): test set dievaluasi sekali setelah pelatihan selesai — 0,842 pada test, berbanding 0,850 latih dan 0,883 validasi.
Ketiga angka tidak perlu sama: selisih kecil antar subset wajar karena isi sampelnya berbeda. Yang justru perlu dicurigai adalah akurasi latih jauh di atas validasi (indikasi overfitting) atau ketiganya seragam tinggi tetapi gagal di data baru (indikasi kebocoran).
Rasio yang Umum Dipakai #
| Ukuran dataset | Rasio lazim | Catatan |
|---|---|---|
| kecil (ratusan–ribuan sampel) | 60/20/20 atau 70/15/15 | validasi dan test perlu cukup besar agar metriknya stabil |
| menengah (puluhan ribu) | 80/10/10 | kompromi umum untuk data tabular |
| besar (jutaan sampel) | 98/1/1 | 1% saja sudah berarti puluhan ribu sampel |
Dataset sangat kecil membuat validasi terlalu sedikit untuk dipercaya; alternatifnya cross-validation yang memutar peran tiap lipatan, dibahas di modules/model_evaluation . Rujukan API pembaginya: sklearn.model_selection.train_test_split .
Data kini terbagi rapi, tetapi kedua fitur belum boleh langsung dilatih: kolom bernilai jutaan bersebelahan dengan kolom pecahan membuat permukaan loss memanjang dan gradient descent berjalan timpang. Chapter berikutnya menyamakan skala fitur — dan membuktikan efeknya pada kecepatan konvergensi.