Perulangan evaluasi adalah prosedur baku untuk mengukur kinerja model terhadap data, dan topik ini membangun versi kanoniknya di PyTorch dari awal hingga akhir. Pola yang dihasilkan terdiri atas tiga bagian yang hampir selalu muncul bersama, yaitu model.eval(), konteks torch.no_grad(), serta penghitungan akurasi melalui akumulasi jumlah prediksi benar dan jumlah sampel. Skrip contoh melatih model mini pada data sintetis terlebih dahulu, sehingga angka akurasi yang dihasilkan benar-benar bermakna.
Pola kanonik perulangan evaluasi#
Satu perulangan evaluasi selalu memiliki struktur yang sama. Model dipindahkan ke mode evaluasi dengan model.eval(), seluruh perhitungan dibungkus dengan konteks torch.no_grad(), kemudian data diiterasi batch demi batch untuk membandingkan prediksi dengan label. Skrip berikut berdiri sendiri, yaitu membuat data sintetis tiga kelas, melatih model mini selama lima epoch, lalu menjalankan perulangan evaluasi pada data yang sama:
"""Perulangan evaluasi: melatih model mini lalu mengukur akurasinya.
Skrip berdiri sendiri: data sintetis dibuat, model dilatih 5 epoch, kemudian
akurasi dihitung dengan pola kanonik model.eval() + torch.no_grad().
"""
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
torch.manual_seed(0)
# data sintetis: tiga gugus dengan dua fitur
n_per_kelas = 100
pusat = torch.tensor([[0.0, 0.0], [3.0, 3.0], [-3.0, 3.0]])
fitur = torch.cat([pusat[k] + 0.6 * torch.randn(n_per_kelas, 2) for k in range(3)])
label = torch.cat([torch.full((n_per_kelas,), k) for k in range(3)])
dataset = TensorDataset(fitur, label)
loader_latih = DataLoader(dataset, batch_size=32, shuffle=True,
generator=torch.Generator().manual_seed(0))
loader_eval = DataLoader(dataset, batch_size=32, shuffle=False)
model = nn.Sequential(nn.Linear(2, 16), nn.ReLU(), nn.Linear(16, 3))
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
criterion = nn.CrossEntropyLoss()
# pelatihan singkat, 5 epoch, supaya akurasi evaluasi bermakna
model.train()
for epoch in range(5):
for xb, yb in loader_latih:
optimizer.zero_grad()
loss = criterion(model(xb), yb)
loss.backward()
optimizer.step()
# forward yang sama, dengan dan tanpa no_grad
with torch.no_grad():
a = model(fitur)
b = model(fitur)
print("di dalam no_grad : requires_grad =", a.requires_grad,
"| grad_fn =", a.grad_fn)
print("di luar no_grad : requires_grad =", b.requires_grad,
"| grad_fn =", type(b.grad_fn).__name__)
# perulangan evaluasi: pola kanonik
model.eval()
benar = 0
total = 0
pertama = True
with torch.no_grad():
for xb, yb in loader_eval:
logits = model(xb) # (N, 3)
preds = logits.argmax(dim=1) # (N,)
if pertama:
print("bentuk logits :", tuple(logits.shape))
print("bentuk preds :", tuple(preds.shape))
print("bentuk label batch :", tuple(yb.shape))
print("preds.requires_grad :", preds.requires_grad)
pertama = False
benar += (preds == yb).sum().item()
total += yb.numel()
print("benar :", benar)
print("total :", total)
print("akurasi :", benar / total)di dalam no_grad : requires_grad = False | grad_fn = None
di luar no_grad : requires_grad = True | grad_fn = AddmmBackward0
bentuk logits : (32, 3)
bentuk preds : (32,)
bentuk label batch : (32,)
preds.requires_grad : False
benar : 298
total : 300
akurasi : 0.9933333333333333Keluaran memperlihatkan dua hal yang patut dicermati. Pertama, preds.requires_grad bernilai False karena forward berlangsung di dalam konteks no_grad(), sedangkan forward yang sama di luar konteks menghasilkan grad_fn = AddmmBackward0 sebagaimana tercetak pada baris awal. Kedua, akurasi 298 dari 300 berasal dari pembagian sederhana benar dibagi total, dan nilainya tinggi karena model sudah dilatih lima epoch pada gugus data yang mudah dipisahkan.
Alasan di balik eval() dan no_grad()#
model.eval() mengatur perilaku layer yang berbeda antara pelatihan dan inferensi, terutama dropout dan batch normalization. Pada model contoh yang hanya berisi Linear dan ReLU, pemanggilan model.eval() tidak mengubah angka apa pun. Kebiasaan memanggilnya tetap dipertahankan karena pola yang sama harus tetap benar ketika model bertambah kompleks, misalnya ketika layer dropout ditambahkan.
Konteks torch.no_grad() bekerja pada sisi pencatatan graf. Di dalam konteks, operasi tidak membentuk graf komputasi, sehingga memori tidak menumpuk dan perhitungan menjadi lebih ringan. Tiga alasan membuat konteks ini wajib pada tahap evaluasi:
- Gradien tidak akan dihitung, karena tidak ada pemanggilan
backward()pada evaluasi. - Memori lebih hemat, karena riwayat operasi untuk autograd tidak disimpan.
- Eksekusi lebih cepat, karena pembukuan graf tidak ikut dijalankan.
Tabel berikut merangkum pembagian peran keduanya:
| Bagian | Yang diatur | Akibat bila dilupakan |
|---|---|---|
model.eval() | perilaku layer seperti dropout dan batch normalization | prediksi berfluktuasi atau menyimpang pada model tertentu |
torch.no_grad() | pencatatan graf autograd | memori membengkak dan evaluasi melambat tanpa pesan error |
Urutan lengkapnya, dari mode evaluasi hingga satu angka akurasi, digambarkan sebagai berikut:
Penghitungan akurasi per batch#
Inti penghitungan akurasi terletak pada tiga baris di dalam perulangan. Method argmax(dim=1) mengubah logits menjadi indeks kelas, yaitu logits berbentuk (N, 3) yang berisi skor mentah untuk tiga kelas menjadi preds berbentuk (N,) yang berisi indeks dengan skor tertinggi. Label yb juga berbentuk (N,) dan bertipe int64, sehingga perbandingan preds == yb menghasilkan tensor boolean sepanjang N.
Perbandingan boolean itu dijumlahkan dengan sum() lalu dikonversi menjadi bilangan Python dengan .item(), sehingga variabel benar bertambah satu untuk setiap prediksi yang tepat. Variabel total dihitung dengan yb.numel(), yaitu jumlah elemen pada batch yang sedang diproses, sehingga batch yang tidak penuh tetap terhitung benar. Akurasi akhir adalah pembagian benar / total setelah seluruh batch selesai. Label yang dibuat dengan torch.full((n,), k) secara default bertipe int64, sama seperti keluaran argmax, sehingga tidak diperlukan konversi apa pun.
Keluaran skrip menampilkan bentuk batch pertama, yaitu (32, 3) dan (32,), sedangkan batch terakhir hanya memuat 12 sampel karena 300 tidak habis dibagi 32. Penjumlahan total tetap mencapai 300 karena numel() menghitung isi batch apa adanya. Cara penghitungan ini menghasilkan angka yang persis sama meskipun ukuran batch diubah, selama seluruh sampel ikut diiterasi.
Kesalahan umum#
Kesalahan pertama adalah lupa membungkus perulangan dengan torch.no_grad(). Tidak ada pesan error yang muncul, tetapi graf autograd menumpuk untuk setiap batch, sehingga memori membengkak dan evaluasi melambat, dan gejalanya sering baru terasa pada dataset besar. Perbaikannya adalah menuliskan with torch.no_grad(): sebagai pembungkus seluruh perulangan sejak pola pertama dibuat.
Kesalahan kedua adalah lupa memanggil model.eval() sebelum perulangan. Pada model dengan dropout, keluaran acak membuat angka akurasi berubah antara eksekusi, sedangkan pada model dengan batch normalization, statistik batch ikut memengaruhi prediksi. Perbaikannya adalah memanggil model.eval() sebelum evaluasi dan model.train() saat kembali melatih.
Kesalahan ketiga adalah mengakumulasi tensor, bukan bilangan Python. Baris benar += (preds == yb).sum() tanpa .item() membuat benar menjadi tensor, sehingga pencetakannya tampil dalam bungkus tensor(...) dan operasi lanjutan ikut membawa beban tensor. Perbaikannya adalah selalu memanggil .item() ketika nilai satu skalar akan digunakan di luar perhitungan torch.
Ringkasan#
- Perulangan evaluasi kanonik memanggil
model.eval(), membungkus seluruh perhitungan dengantorch.no_grad(), lalu mengiterasi data batch demi batch. argmax(dim=1)mengubah logits(N, 3)menjadi prediksi(N,)yang sejajar dengan label(N,).- Akurasi dihitung dari akumulasi
(preds == labels).sum().item()dannumel(), lalu dibagi setelah seluruh batch selesai. no_grad()menghemat memori dan mempercepat evaluasi karena graf autograd tidak dibentuk.
Referensi#
- PyTorch documentation — PyTorch
— Rujukan API resmi untuk
torch.no_grad,Tensor.argmax, dan methodeval. - Datasets & DataLoaders — PyTorch
— Dokumentasi
DataLoaderyang membagi data menjadi batch pada perulangan pelatihan maupun evaluasi.