Anatomi perulangan

Lima baris inti perulangan pelatihan: zero_grad, forward, loss, backward, step.

Daftar isi

Setiap proyek deep learning dengan PyTorch berputar di sekitar satu struktur yang sama, yaitu perulangan pelatihan (training loop), kode yang menggerakkan model menelusuri data berulang kali sampai prediksinya membaik. Topik ini membedah anatomi struktur tersebut, mulai dari lima baris inti yang membentuk satu iterasi (iteration), yakni satu kali pemrosesan satu kelompok data, sampai skrip pelatihan pertama yang lengkap. Setelah membaca topik ini, bentuk dasar perulangan pelatihan dapat ditulis dari ingatan, dan fungsi setiap baris dapat dijelaskan.

Lima baris inti satu iterasi#

Satu iterasi perulangan pelatihan memproses satu batch, yaitu kelompok kecil sampel yang diproses bersama dalam satu langkah, dan selalu tersusun dari lima baris yang sama dalam urutan yang sama. Urutan ini bersifat kanonik, sehingga begitu dikenali pada satu proyek, struktur tersebut dapat ditemukan kembali pada hampir semua kode PyTorch lainnya. Kelima baris tersebut ditulis sebagai berikut.

python Lima baris inti satu iterasi
# tidak-dijalankan: fragmen merujuk variabel yang didefinisikan pada skrip lengkap di bawah
optimizer.zero_grad()              # (1) kosongkan gradien lama
y_pred = model(x_batch)            # (2) forward, model menghitung prediksi
loss = criterion(y_pred, y_batch)  # (3) hitung loss
loss.backward()                    # (4) backward, gradien dihitung
optimizer.step()                   # (5) perbarui bobot

Baris (1) mengosongkan nilai gradien (gradient), yaitu arah dan besar perubahan bobot yang dihitung PyTorch, sebelum perhitungan baru dimulai. Baris (2) menjalankan operasi maju (forward pass), yakni memasukkan batch masukan ke model untuk memperoleh prediksi. Baris (3) menghitung loss, angka tunggal yang mengukur seberapa jauh prediksi menyimpang dari target, menggunakan fungsi loss yang disimpan pada variabel criterion. Baris (4) menjalankan operasi mundur (backward pass) yang mengisi gradien setiap bobot model dari loss tersebut. Baris (5) meminta optimizer menggunakan gradien itu untuk memperbarui bobot model.

Diagram berikut menunjukkan siklus tersebut beserta jalan baliknya ke batch berikutnya.

Siklus satu iterasi perulangan pelatihan12345zero_grad()kosongkan gradienmodel(x)prediksi, forwardcriterion()hitung lossbackward()isi .gradstep()perbarui bobotbatch berikutnya
Siklus satu iterasi perulangan pelatihan

Kelima baris dijalankan untuk setiap batch dalam dataset. Setelah batch terakhir diproses, satu epoch, yaitu satu penelusuran penuh atas seluruh data pelatihan, dianggap selesai, dan perulangan luar kemudian memulai epoch berikutnya dari baris (1) lagi.

Skrip perulangan pelatihan pertama#

Skrip berikut menerapkan kelima baris itu secara utuh. Datanya sintetis: 64 titik diambil dari hubungan linear y=2x+1y = 2x + 1 yang diberi sedikit derau, sehingga jawaban yang benar sudah diketahui dan hasil pelatihan dapat dinilai secara langsung. TensorDataset membungkus pasangan masukan dan target menjadi satu dataset, sedangkan DataLoader memotongnya menjadi batch berisi delapan sampel dan mengacak urutannya pada setiap epoch. Model yang digunakan adalah nn.Linear(1, 1), fungsi lossnya nn.MSELoss(), dan optimizernya torch.optim.SGD dengan learning rate 0.5.

python latih_manual.py
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader

torch.manual_seed(0)

# Data sintetis: regresi linear y = 2x + 1 dengan sedikit derau
n = 64
x = torch.linspace(0.0, 1.0, n).unsqueeze(1)
y = 2.0 * x + 1.0 + 0.05 * torch.randn(n, 1)

dataset = TensorDataset(x, y)
loader = DataLoader(dataset, batch_size=8, shuffle=True)

model = nn.Linear(1, 1)                  # model yang akan dilatih
criterion = nn.MSELoss()                 # fungsi loss
optimizer = torch.optim.SGD(model.parameters(), lr=0.5)

for epoch in range(5):
    total = 0.0
    for x_batch, y_batch in loader:
        optimizer.zero_grad()              # (1) kosongkan gradien lama
        y_pred = model(x_batch)            # (2) forward
        loss = criterion(y_pred, y_batch)  # (3) hitung loss
        loss.backward()                    # (4) backward
        optimizer.step()                   # (5) perbarui bobot
        total += loss.item()
    print(f"epoch {epoch + 1}: loss rata-rata {total / len(loader):.4f}")

print("bobot akhir:", model.weight.item())
print("bias akhir :", model.bias.item())
text Keluaran
epoch 1: loss rata-rata 0.5150
epoch 2: loss rata-rata 0.0187
epoch 3: loss rata-rata 0.0050
epoch 4: loss rata-rata 0.0041
epoch 5: loss rata-rata 0.0044
bobot akhir: 1.9852105379104614
bias akhir : 0.9896457195281982

Perulangan luarnya berjalan lima epoch, sedangkan perulangan dalamnya menelusuri loader batch demi batch. Di dalam perulangan dalam itulah kelima baris inti dieksekusi, satu kali untuk setiap batch. Angka loss pada setiap epoch diakumulasi dari seluruh batch, lalu dibagi len(loader), yaitu jumlah batch dalam satu epoch, sehingga nilai yang dicetak merupakan rata-rata loss epoch tersebut. torch.manual_seed(0) di awal skrip membuat pengacakan data dan inisialisasi bobot tetap sama, sehingga keluarannya dapat direproduksi.

Membaca keluaran skrip#

Loss turun dari 0.5150 pada epoch pertama menjadi sekitar 0.004 pada epoch keempat dan kelima. Penurunan besar terjadi pada epoch kedua, kemudian kurvanya mendatar dekat nilai 0.004. Perbandingan ini penting: derau yang ditanamkan pada data sebesar 0.05 menetapkan lantai teoretis untuk fungsi loss mean squared error (MSE) yang menghitung rata-rata kuadrat selisih prediksi dan target,

L=1n∑i=1n(y^i−yi)2. L = \frac{1}{n}\sum_{i=1}^{n}\left(\hat{y}_i - y_i\right)^2 .

Kuadrat derau tersebut, 0.052=0.00250.05^2 = 0.0025, adalah nilai loss terbaik yang masih dapat dicapai model linear mana pun pada data ini, sehingga kurva yang mendatar dekat angka itu justru menandakan pelatihan sehat. Angka epoch terakhir yang sedikit naik dari 0.0041 menjadi 0.0044 bukan gejala gangguan, melainkan fluktuasi wajar karena urutan batch diacak dan rata-rata dihitung dari sampel yang berbeda di setiap epoch.

Dua baris terakhir keluaran menegaskan hasilnya. Bobot model berakhir di 1.985 dan biasnya di 0.990, dua angka yang menghampiri dua dan satu, yaitu koefisien yang ditanamkan saat data dibuat. Model memang tidak pernah menemukan nilai tersebut secara persis karena sampel pelatihan berau, tetapi arah pembaruan tetap benar dan selisihnya terus menyempit.

Kesalahan umum#

Tiga kesalahan berikut paling sering muncul saat perulangan pelatihan pertama ditulis.

  1. Lupa membagi akumulasi loss dengan len(loader). Angka yang dicetak menjadi total loss seluruh batch, bukan rata-ratanya, sehingga angka itu tidak sebanding apabila jumlah batch berubah. Perbaikannya: bagi total dengan len(loader) sebelum dicetak.
  2. Menambahkan loss.item() tanpa alasan yang jelas atau justru mencetak objek loss mentah. Mencetak loss mentah memuat pembungkus tensor beserta graf komputasinya, sedangkan .item() mengambil angka Python biasa yang ringan untuk disimpan ke dalam daftar.
  3. Meletakkan optimizer.zero_grad() di luar perulangan batch. Gradien kemudian terakumulasi lintas batch, dan baris kelima menerima informasi yang tercampur dari banyak batch sekaligus. Gejalanya tidak berupa pesan error, melainkan loss yang sulit menetap.

Ringkasan#

  • Satu iterasi perulangan pelatihan tersusun dari lima baris berurutan: optimizer.zero_grad(), forward, penghitungan loss, loss.backward(), dan optimizer.step().
  • Perulangan dalam menelusuri batch demi batch, sedangkan perulangan luar menghitung epoch. Rata-rata loss per epoch diperoleh dengan membagi akumulasi loss oleh len(loader).
  • Loss yang mendatar di dekat kuadrat derau data menandakan pelatihan sudah mendekati batas terbaik yang mungkin, bukan berarti pelatihan gagal.

Referensi#