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:

python
"""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:

text
== 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       180

Mari kita telaah kode di atas baris per baris:

  1. Baris 25-35 (data): stratify=y menjaga proporsi tiga kelas tetap sama di data latih dan uji; tanpa stratifikasi, keberuntungan pembagian bisa membuat data uji kekurangan satu kelas.
  2. 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.
  3. Baris 71-73 (loader_uji): batch_size=64 dan shuffle=False — urutan prediksi harus mengikuti urutan data uji agar bisa dibandingkan langsung dengan y_uji.
  4. Baris 75-83 (evaluasi batch-wise): daftar pred_batch dan label_batch menampung hasil tiap batch, lalu torch.cat meratakan menjadi satu tensor sebelum diubah ke array NumPy agar terbaca scikit-learn.
  5. 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.
  6. Baris 90-94 (verifikasi): kedua jalur menghasilkan prediksi identik — bukti bahwa pemilihan jalur murni soal memori, bukan soal hasil.
  7. Baris 97-104 (metrik): accuracy_score, f1_score, confusion_matrix, dan classification_report menerima 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 #

MetrikRumusSituasi tepat
Akurasibenar ÷ total sampelkelas seimbang, biaya tiap jenis kesalahan setara
PrecisionTP ÷ (TP + FP)kesalahan alarm palsu mahal, mis. saring email spam
RecallTP ÷ (TP + FN)kasus terlewat berakibat buruk, mis. deteksi penyakit
F12PR ÷ (P + R)keseimbangan precision-recall pada kelas tak seimbang
F1 makrorata-rata F1 seluruh kelassetiap kelas dihitung setara, kelas kecil tetap terlihat
F1 tertimbangF1 per kelas dibobot supportringkasan 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.