Seluruh komponen kini tersedia: model dari nn.Module, fungsi loss dan optimizer, serta pasokan batch dari DataLoader. Yang belum ada adalah mesin yang memutar semuanya secara bergilir — training loop. Bentuknya nyaris sama di setiap proyek PyTorch, dari model mainan sampai riset skala besar, sehingga memahami setiap barisnya bernilai jangka panjang: sekali kuasai, kita bisa membaca kode pelatihan proyek orang lain dalam hitungan detik. Chapter ini membedah cetakan tersebut sampai ke detail urutan zero_grad, lalu menjalankannya pada dataset klasifikasi 512 sampel dengan validasi tiap epoch.

Anatomi Satu Putaran Training #

Satu epoch berarti satu putaran penuh menelusuri seluruh data latih. Di dalamnya, setiap batch melewati empat langkah yang sudah kita kenal secara matematis pada section Dasar Neural Network — kini dikerjakan oleh autograd dan optimizer:

python
for X_batch, y_batch in loader:
    optimizer.zero_grad()
    loss = criterion(model(X_batch), y_batch)
    loss.backward()
    optimizer.step()

Urutannya ketat: gradien lama dibersihkan lebih dulu, forward pass menghasilkan prediksi, loss mengukur kesalahannya, backward pass mengisi atribut .grad tiap parameter, dan step() menggeser parameter melawan arah gradien. Diagram berikut menempatkan siklus batch tersebut di dalam kerangka epoch yang lebih besar:

Cetakan Utuh dengan Validasi #

Berkas training_loop.py menerapkan cetakan tersebut pada make_moons — 512 sampel dua fitur, dibagi 384 sampel latih dan 128 sampel validasi. Modelnya MLP [2, 16, 2] dengan CrossEntropyLoss dan SGD, dilatih 30 epoch:

python
"""Cetakan training loop PyTorch dengan validasi tiap epoch.

Alur satu epoch: model.train() -> iterasi batch (zero_grad -> forward ->
loss -> backward -> step) -> model.eval() + torch.no_grad() untuk
validasi -> log. Dataset make_moons 512 sampel, MLP [2, 16, 2],
SGD lr=0.5, batch 32, 30 epoch.
"""

import torch
import torch.nn as nn
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
from torch.utils.data import DataLoader, TensorDataset

torch.manual_seed(42)

# ---------- 1. Data: make_moons, pembagian latih/validasi ----------
X, y = make_moons(n_samples=512, noise=0.20, random_state=42)
X_latih, X_val, y_latih, y_val = train_test_split(
    X, y, test_size=0.25, random_state=42
)

# Standardisasi dihitung dari data latih saja, lalu diterapkan keduanya
rata2 = X_latih.mean(axis=0)
simpangan = X_latih.std(axis=0)
X_latih = (X_latih - rata2) / simpangan
X_val = (X_val - rata2) / simpangan

dataset_latih = TensorDataset(
    torch.tensor(X_latih, dtype=torch.float32),
    torch.tensor(y_latih, dtype=torch.long),
)
X_val_t = torch.tensor(X_val, dtype=torch.float32)
y_val_t = torch.tensor(y_val, dtype=torch.long)

loader = DataLoader(
    dataset_latih,
    batch_size=32,
    shuffle=True,
    num_workers=0,
    generator=torch.Generator().manual_seed(42),
)

# ---------- 2. Model, fungsi loss, optimizer ----------
model = nn.Sequential(
    nn.Linear(2, 16),
    nn.ReLU(),
    nn.Linear(16, 2),
)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.5)

print("dataset : make_moons 512 sampel | latih", len(dataset_latih),
      "| validasi", len(y_val_t))
print("model   : [2, 16, 2] | CrossEntropyLoss | SGD lr=0.5 | batch 32")
print()

# ---------- 3. Training loop: latih + validasi tiap epoch ----------
JUMLAH_EPOCH = 30
for epoch in range(1, JUMLAH_EPOCH + 1):
    model.train()                         # aktifkan mode latih
    total_loss = 0.0
    for X_batch, y_batch in loader:
        optimizer.zero_grad()             # hapus gradien epoch sebelumnya
        logits = model(X_batch)           # 1) forward pass
        loss = criterion(logits, y_batch)  # 2) hitung loss
        loss.backward()                   # 3) backward pass
        optimizer.step()                  # 4) update parameter
        total_loss += loss.item()
    loss_latih = total_loss / len(loader)

    model.eval()                          # nonaktifkan perilaku khusus latih
    with torch.no_grad():                 # validasi tanpa graf gradien
        logits_val = model(X_val_t)
        loss_val = criterion(logits_val, y_val_t).item()
        benar = (logits_val.argmax(dim=1) == y_val_t).sum().item()
        akurasi_val = benar / len(y_val_t)

    if epoch == 1 or epoch % 5 == 0:
        print(f"epoch {epoch:2d} | loss latih {loss_latih:.4f} | "
              f"loss val {loss_val:.4f} | akurasi val {akurasi_val:.3f}")

# ---------- 4. Akurasi akhir pada kedua subset ----------
model.eval()
with torch.no_grad():
    pred_latih = model(dataset_latih.tensors[0]).argmax(dim=1)
    akurasi_latih = (pred_latih == dataset_latih.tensors[1]).float().mean().item()
    akurasi_val_akhir = akurasi_val

print(f"\nakurasi akhir: latih {akurasi_latih:.3f} | "
      f"validasi {akurasi_val_akhir:.3f}")

Output yang Dihasilkan:

text
dataset : make_moons 512 sampel | latih 384 | validasi 128
model   : [2, 16, 2] | CrossEntropyLoss | SGD lr=0.5 | batch 32

epoch  1 | loss latih 0.3984 | loss val 0.3247 | akurasi val 0.883
epoch  5 | loss latih 0.2869 | loss val 0.2903 | akurasi val 0.875
epoch 10 | loss latih 0.2395 | loss val 0.2416 | akurasi val 0.883
epoch 15 | loss latih 0.1739 | loss val 0.1779 | akurasi val 0.914
epoch 20 | loss latih 0.1283 | loss val 0.1356 | akurasi val 0.938
epoch 25 | loss latih 0.1021 | loss val 0.1081 | akurasi val 0.945
epoch 30 | loss latih 0.0859 | loss val 0.0958 | akurasi val 0.961

akurasi akhir: latih 0.977 | validasi 0.961

Mari kita telaah kode di atas baris per baris:

  1. Baris 15 (torch.manual_seed(42)): mengunci inisialisasi bobot dan urutan shuffle ke satu nilai seed, sehingga keluaran di atas muncul identik pada setiap eksekusi — nanti dibahas tuntas di chapter Reproducibility .
  2. Baris 18-27 (data): pembagian latih/validasi memakai random_state=42, dan standardisasi dihitung dari data latih saja — rata-rata dan simpangan data validasi tidak boleh menyusup ke preprocessing agar evaluasi tetap jujur.
  3. Baris 36-42 (DataLoader): pola dari chapter Dataset dan DataLoader : batch 32, shuffle=True, generator ber-seed — 384 sampel menghasilkan 12 batch per epoch.
  4. Baris 45-51 (komponen): nn.Sequential menyusun model; CrossEntropyLoss menerima logits mentah dan mengembalikan loss rata-rata batch; SGD(lr=0.5) menggeser tiap parameter. Pemilihan loss dan optimizer dibahas pada chapter Loss dan Optimizer .
  5. Baris 61 (model.train()): mengaktifkan mode latih — untuk MLP Linear+ReLU ini tidak mengubah apa pun, tetapi kebiasaannya dipasang sejak awal karena dropout dan batchnorm berperilaku berbeda antar mode.
  6. Baris 64 (optimizer.zero_grad()): gradien tidak terhapus sendiri. Autograd menambahkan gradien baru ke .grad yang lama, sehingga tanpa pembersihan ini gradien terakumulasi lintas batch dan update melenceng.
  7. Baris 65-68 (empat langkah inti): forward menghasilkan logits (32, 2); loss skalar; backward() mengisi .grad; step() memindahkan parameter.
  8. Baris 69-70 (log loss latih): loss.item() menyalin angka skalar keluar dari tensor agar bisa dijumlahkan tanpa menyimpan graf komputasi.
  9. Baris 72-77 (validasi): model.eval() lalu torch.no_grad() — prediksi validasi tidak perlu gradien, jadi pencatatan graf dimatikan agar cepat dan hemat memori; akurasi dihitung dari argmax(dim=1) dibanding label.
  10. Baris 79-81 (log berkala): mencetak tiap epoch ke-5 mencegah terminal kebanjiran teks pada training panjang.
  11. Baris 83-91 (akurasi akhir): model dievaluasi sekali lagi dalam mode eval pada kedua subset untuk perbandingan akhir.

Membaca Hasil Training #

Dua sinyal pada keluaran patut dicermati. Pertama, loss latih turun 0,3984 → 0,0859 dan loss validasi menurun seiring — model benar-benar belajar, bukan sekadar menghafal, karena data validasi tidak pernah dipakai mengubah bobot. Kedua, akurasi akhir latih 0,977 berbanding validasi 0,961 — selisih kecil yang menandakan model belum terjebak overfitting; selisih yang melebar justru menjadi alarm, topik yang dibahas mendalam di section Regularisasi dan Generalisasi .

Rangkaian langkah pada tabel berikut merangkum akibat tiap baris kalau sampai terlewat:

Baris kunciPeranJika terlewat
optimizer.zero_grad()kosongkan .grad lamagradien terakumulasi antar batch, update melenceng
loss.backward()isi .grad lewat autogradparameter tak pernah berubah, loss diam
optimizer.step()geser parametermodel tidak belajar meski gradien tersedia
model.train() / model.eval()atur mode layerdropout dan batchnorm salah perilaku
torch.no_grad()matikan pencatatan grafvalidasi lambat, memori membengkak

Rujukan resmi untuk siklus optimisasi — zero_grad, step, dan scheduler learning rate — tersedia di docs.pytorch.org/docs/stable/optim.html . Model yang telah terlatih masih menyimpan satu pertanyaan: seberapa baik sebenarnya angka 0,961 itu? Chapter berikutnya membedah evaluasi secara menyeluruh — dari mode eval, akurasi, F1, hingga confusion matrix.