Training loop pada chapter sebelumnya menutup laporan dengan akurasi validasi 0,961 — tetapi satu angka itu menyembunyikan banyak hal. Model salah pada sampel mana? Kelas mana yang paling sering tertukar? Dan apakah akurasi memang metrik yang tepat untuk masalah kita? Evaluasi yang baik adalah ritual berlapis: menyiapkan model ke mode evaluasi, memilih jalur komputasi yang sesuai ukuran data, lalu membaca metrik yang menjawab pertanyaan bisnis sebenarnya. Chapter ini melatih model klasifikasi tiga kelas secara singkat, lalu membedah evaluasinya dari confusion matrix hingga classification_report.
Ritual Sebelum Menghitung Metrik #
Dua pengaman dari training loop wajib dipasang ulang setiap kali mengevaluasi. Pertama, model.eval() menempatkan layer ke perilaku inferensi — dropout berhenti menghapus neuron dan batchnorm memakai statistik akumulasi, bukan statistik batch yang sedang lewat. Kedua, torch.no_grad() mematikan pencatatan graf komputasi: prediksi tidak perlu gradien, sehingga evaluasi berjalan lebih cepat dan tidak menumpuk memori. Kombinasi keduanya memastikan angka yang kita hitung menggambarkan model sebagaimana adanya saat dipakai di produksi.
Untuk komputasinya sendiri ada dua jalur. Dataset uji yang muat sekaligus di RAM cukup dimasukkan utuh ke model(X_uji) — satu forward pass besar. Dataset yang lebih besar dari memori, atau model dengan input besar seperti gambar beresolusi tinggi, dievaluasi batch demi batch lewat DataLoader dengan shuffle=False, lalu hasil tiap batch digabungkan dengan torch.cat. Kedua jalur harus menghasilkan prediksi yang identik — dan contoh di bawah membuktikannya.
Evaluasi Menyeluruh pada Model Tiga Kelas #
Berkas evaluasi_model.py melatih MLP [8, 32, 3] pada data sintetis tiga kelas dari make_classification
— versi ringkas dari training loop sebelumnya karena fokus kita bergeser ke evaluasi:
"""Evaluasi model klasifikasi PyTorch secara menyeluruh.
Alur: latih model mini 3 kelas (bentuk ringkas dari chapter Training
Loop) -> evaluasi batch-wise lewat DataLoader -> evaluasi seluruh
dataset sekaligus -> bandingkan hasilnya -> hitung akurasi, F1,
confusion matrix, dan classification_report dengan scikit-learn.
"""
import numpy as np
import torch
import torch.nn as nn
from sklearn.datasets import make_classification
from sklearn.metrics import (
accuracy_score,
classification_report,
confusion_matrix,
f1_score,
)
from sklearn.model_selection import train_test_split
from torch.utils.data import DataLoader, TensorDataset
torch.manual_seed(42)
# ---------- 1. Data: 3 kelas, 8 fitur, 600 sampel ----------
X, y = make_classification(
n_samples=600,
n_features=8,
n_informative=5,
n_redundant=2,
n_classes=3,
random_state=42,
)
X_latih, X_uji, y_latih, y_uji = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
rata2 = X_latih.mean(axis=0)
simpangan = X_latih.std(axis=0)
X_latih = (X_latih - rata2) / simpangan
X_uji = (X_uji - rata2) / simpangan
dataset_latih = TensorDataset(
torch.tensor(X_latih, dtype=torch.float32),
torch.tensor(y_latih, dtype=torch.long),
)
loader_latih = DataLoader(
dataset_latih,
batch_size=32,
shuffle=True,
num_workers=0,
generator=torch.Generator().manual_seed(42),
)
# ---------- 2. Latih model mini (versi ringkas, tanpa log) ----------
model = nn.Sequential(
nn.Linear(8, 32),
nn.ReLU(),
nn.Linear(32, 3),
)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
for epoch in range(40):
model.train()
for X_batch, y_batch in loader_latih:
optimizer.zero_grad()
loss = criterion(model(X_batch), y_batch)
loss.backward()
optimizer.step()
# ---------- 3. Evaluasi batch-wise melalui DataLoader ----------
X_uji_t = torch.tensor(X_uji, dtype=torch.float32)
dataset_uji = TensorDataset(X_uji_t, torch.tensor(y_uji, dtype=torch.long))
loader_uji = DataLoader(dataset_uji, batch_size=64, shuffle=False)
model.eval()
pred_batch, label_batch = [], []
with torch.no_grad():
for X_batch, y_batch in loader_uji:
logits = model(X_batch)
pred_batch.append(logits.argmax(dim=1))
label_batch.append(y_batch)
pred_batch = torch.cat(pred_batch).numpy()
label_batch = torch.cat(label_batch).numpy()
# ---------- 4. Evaluasi seluruh dataset sekaligus ----------
with torch.no_grad():
logits_penuh = model(X_uji_t)
pred_penuh = logits_penuh.argmax(dim=1).numpy()
print("== dua jalur evaluasi ==")
print("prediksi batch-wise identik dengan seluruh dataset:",
bool((pred_batch == pred_penuh).all()))
print("label terkumpul identik dengan y_uji :",
bool((label_batch == y_uji).all()))
# ---------- 5. Metrik lengkap dengan scikit-learn ----------
print("\n== metrik ==")
print("akurasi :", round(accuracy_score(y_uji, pred_penuh), 3))
print("F1 makro :", round(f1_score(y_uji, pred_penuh, average="macro"), 3))
print("F1 mikro :", round(f1_score(y_uji, pred_penuh, average="micro"), 3))
print("\nconfusion matrix (baris = label benar, kolom = prediksi):")
print(confusion_matrix(y_uji, pred_penuh))
print("\nclassification_report:")
print(classification_report(y_uji, pred_penuh, digits=3))Output yang Dihasilkan:
== dua jalur evaluasi ==
prediksi batch-wise identik dengan seluruh dataset: True
label terkumpul identik dengan y_uji : True
== metrik ==
akurasi : 0.744
F1 makro : 0.744
F1 mikro : 0.744
confusion matrix (baris = label benar, kolom = prediksi):
[[41 7 13]
[ 5 48 6]
[ 4 11 45]]
classification_report:
precision recall f1-score support
0 0.820 0.672 0.739 61
1 0.727 0.814 0.768 59
2 0.703 0.750 0.726 60
accuracy 0.744 180
macro avg 0.750 0.745 0.744 180
weighted avg 0.751 0.744 0.744 180Mari kita telaah kode di atas baris per baris:
- Baris 25-35 (data):
stratify=ymenjaga proporsi tiga kelas tetap sama di data latih dan uji; tanpa stratifikasi, keberuntungan pembagian bisa membuat data uji kekurangan satu kelas. - Baris 62-68 (training ringkas): empat langkah inti dari chapter Training Loop tanpa logging — 40 epoch pada 420 sampel cukup bagi model kecil ini untuk konvergen.
- Baris 71-73 (
loader_uji):batch_size=64danshuffle=False— urutan prediksi harus mengikuti urutan data uji agar bisa dibandingkan langsung dengany_uji. - Baris 75-83 (evaluasi batch-wise): daftar
pred_batchdanlabel_batchmenampung hasil tiap batch, lalutorch.catmeratakan menjadi satu tensor sebelum diubah ke array NumPy agar terbaca scikit-learn. - Baris 86-88 (evaluasi serentak): 180 sampel dimuat sekaligus — jalur yang sama sekali tidak boleh dipakai begitu data uji besar, karena seluruh logits
(180, 3)menempati memori sekaligus. - Baris 90-94 (verifikasi): kedua jalur menghasilkan prediksi identik — bukti bahwa pemilihan jalur murni soal memori, bukan soal hasil.
- Baris 97-104 (metrik):
accuracy_score,f1_score,confusion_matrix, danclassification_reportmenerima label benar dan prediksi dalam bentuk array NumPy berisi indeks kelas.
Membaca Confusion Matrix #
Matriks [[41, 7, 13], [5, 48, 6], [4, 11, 45]] dibaca per baris. Kelas 0: 41 prediksi benar, 7 sampel salah diprediksi kelas 1, dan 13 sampel salah diprediksi kelas 2 — pasangan kelas 0 dan kelas 2 adalah sumber kesalahan terbesar model ini. Kelas 1 paling akurat dengan recall 0,814. Pola seperti ini menjadi bahan perbaikan berikutnya: menambah fitur pembeda untuk pasangan kelas yang tertukar, atau menaikkan bobot kelas yang lemah. Pembahasan confusion matrix yang lebih mendalam beserta metrik per kelas tersedia di section Evaluasi dan Debugging Model
.
Peta Metrik dan Kapan Dipakainya #
| Metrik | Rumus | Situasi tepat |
|---|---|---|
| Akurasi | benar ÷ total sampel | kelas seimbang, biaya tiap jenis kesalahan setara |
| Precision | TP ÷ (TP + FP) | kesalahan alarm palsu mahal, mis. saring email spam |
| Recall | TP ÷ (TP + FN) | kasus terlewat berakibat buruk, mis. deteksi penyakit |
| F1 | 2PR ÷ (P + R) | keseimbangan precision-recall pada kelas tak seimbang |
| F1 makro | rata-rata F1 seluruh kelas | setiap kelas dihitung setara, kelas kecil tetap terlihat |
| F1 tertimbang | F1 per kelas dibobot support | ringkasan satu angka untuk dataset tak seimbang |
Rumus-rumus precision, recall, dan F1 beserta perilakunya pada berbagai distribusi kelas dirinci di dokumentasi resmi scikit-learn.org/stable/modules/model_evaluation.html . Model yang telah terukur layak disimpan agar training panjang tidak perlu diulang dari nol — persis materi chapter berikutnya.