Idenya
Tidak seperti satu panggilan fit() di scikit-learn, PyTorch menyerahkan training loop untuk kamu tulis sendiri. Kodenya memang lebih banyak, tetapi kamu juga mendapat kendali penuh: kamu yang memutuskan bagaimana batch diambil, apa yang dicatat, kapan evaluasi, dan kapan berhenti. Setiap proyek PyTorch punya loop dengan kerangka yang sama, jadi begitu kamu memahaminya, kamu bisa membaca kode training siapa pun.
for each epoch (a full pass over the training data):
model.train()
for each mini-batch:
zero gradients → forward → loss → backward → optimizer step
model.eval()
with no gradients: compute validation loss
log both losses
Datanya
Lagi-lagi permintaan kopi: 4 fitur, prediksi unit yang terjual. Standarkan input-nya (training berbasis gradient butuh skala yang sebanding), dan sisihkan 66 hari terakhir untuk validasi:
import numpy as np
import pandas as pd
import torch
from torch import nn
demand = pd.read_csv("daily_demand.csv")
FEATURES = ["temperature", "weekend", "promo", "ad_spend"]
X = demand[FEATURES].to_numpy(dtype=np.float32)
y = demand["units"].to_numpy(dtype=np.float32).reshape(-1, 1)
mean, std = X[:300].mean(axis=0), X[:300].std(axis=0) # statistics from training rows only
X = (X - mean) / std
X_train, y_train = torch.from_numpy(X[:300]), torch.from_numpy(y[:300])
X_val, y_val = torch.from_numpy(X[300:]), torch.from_numpy(y[300:])
print(X_train.shape, y_train.shape, X_val.shape)
torch.Size([300, 4]) torch.Size([300, 1]) torch.Size([66, 4])
Loop-nya
Ini training loop lengkap untuk data permintaan: jaringan kecil, optimizer, loss function, dan 200 putaran atas data latih dalam batch berisi 32. Setiap proyek PyTorch yang akan kamu lihat punya loop berbentuk seperti ini:
torch.manual_seed(0)
model = nn.Sequential(nn.Linear(4, 16), nn.ReLU(), nn.Linear(16, 1))
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
loss_fn = nn.MSELoss()
batch_size = 32
for epoch in range(1, 201):
model.train()
order = torch.randperm(len(X_train)) # shuffle every epoch
for start in range(0, len(X_train), batch_size):
idx = order[start:start + batch_size]
optimizer.zero_grad()
loss = loss_fn(model(X_train[idx]), y_train[idx])
loss.backward()
optimizer.step()
model.eval()
with torch.no_grad():
train_loss = loss_fn(model(X_train), y_train).item()
val_loss = loss_fn(model(X_val), y_val).item()
if epoch in (1, 10, 50, 100, 200):
print(f"epoch {epoch:3d} train MSE {train_loss:8.2f} val MSE {val_loss:8.2f}")
epoch 1 train MSE 3105.28 val MSE 3449.84
epoch 10 train MSE 863.41 val MSE 641.93
epoch 50 train MSE 28.19 val MSE 36.46
epoch 100 train MSE 24.25 val MSE 29.97
epoch 200 train MSE 23.10 val MSE 28.22
Baris demi baris:
- Epoch: setiap epoch adalah satu kali melewati seluruh 300 baris training.
torch.randpermmengacak urutan baris di setiap epoch, sehingga batch-nya berbeda setiap kali (kata "stochastic" dalam stochastic gradient descent).- Mini-batch berisi 32 baris: sekitar 10 update per epoch. Setiap update memakai perkiraan gradient penuh yang berisik: murah dan efektif.
model.train()/model.eval()mengganti perilaku layer yang berbeda saat training (dropout, batch normalisation; modul 12). Selalu memanggilnya adalah kebiasaan baik, bahkan saat model-mu belum punya layer seperti itu.torch.no_grad()untuk validasi: tanpa perekaman graph, memori lebih hemat.- Mencatat kedua loss adalah cara kamu menyaksikan proses belajar dan mendeteksi overfitting.
MSE turun dari ribuan (bobot acak yang memprediksi asal-asalan) ke sekitar 25, sejalan dengan regresi linear dari track ML (RMSE ≈ 5 unit). Pada data yang hampir linear ini, network-nya menyamai regresi linear; ia tidak bisa secara ajaib mengalahkan noise.
Membaca kurvanya
| Pola | Arti | Tindakan |
|---|---|---|
| kedua loss turun, lalu mendatar berdekatan | sudah mempelajari apa yang bisa dipelajari | berhenti; mungkin tambahkan fitur |
| training turun, validasi mendatar lalu naik | overfitting | berhenti lebih awal, regularisasi (modul 12) |
| keduanya tetap tinggi | underfitting atau ada bug | model lebih besar, epoch lebih banyak, cek data dan learning rate |
loss melompat-lompat atau menjadi nan | learning rate terlalu tinggi, atau input tidak diskalakan | turunkan lr, standarkan |
Membuat prediksi
Setelah training, lakukan prediksi dalam mode eval tanpa gradient, lalu konversi kembali ke NumPy:
model.eval()
with torch.no_grad():
preds = model(X_val).squeeze().numpy()
mae = np.mean(np.abs(preds - y_val.squeeze().numpy()))
print(preds[:5].round(1), round(float(mae), 2))
[76.9 46.1 67.9 47. 53.8] 4.39
MAE validasinya sebanding dengan regresi linear di track ML: sanity check yang bagus bahwa loop-nya berfungsi.
Setiap training loop adalah: untuk setiap epoch, mode train(), acak, loop atas mini-batch dengan zero_grad → forward → loss → backward → step; lalu mode eval() dan no_grad() untuk validasi; catat kedua loss.
Mengapa ini penting untuk kode yang bersih
Training loop terus bertambah fitur seiring waktu: logging, checkpoint, early stopping, metrik. Mulailah dari kerangka bersih di atas, dan saat ia membesar, ekstrak fungsi train_one_epoch(model, loader, optimizer, loss_fn) dan evaluate(model, loader, loss_fn). Masing-masing lalu muat dalam satu layar dan bisa dites dengan dataset kecil. Modul struktur proyek membangun persis itu.
Rangkuman
- Epoch = satu kali melewati data training; mini-batch memberikan beberapa update per epoch.
- Acak di setiap epoch (
torch.randpermatau DataLoader). - Per batch:
optimizer.zero_grad(), forward, loss,backward(),optimizer.step(). model.train()untuk training,model.eval()+torch.no_grad()untuk validasi dan prediksi.- Catat loss training dan validasi di setiap epoch, lalu baca kurvanya untuk mendeteksi overfitting atau bug.