Lima baris inti perulangan pelatihan sering disalin tanpa dipertanyakan, dan kebiasaan menyalin itulah yang membuat salah satu barisnya mudah terlewat tanpa jejak yang terlihat. Topik ini menjelaskan alasan setiap baris ada, satu per satu, lalu membuktikannya dengan eksperimen kecil yang membuang zero_grad dan mengamati akibatnya. Setelah membacanya, setiap baris dapat dipertanggungjawabkan, bukan sekadar dihafal.
Mengapa zero_grad ada#
zero_grad() ada karena satu perilaku autograd yang jarang ditebak: gradien tidak ditimpa ketika backward() dipanggil lagi, melainkan ditambahkan ke nilai yang sudah ada. Autograd, mekanisme diferensiasi otomatis PyTorch, memang sengaja merancang akumulasi ini untuk kasus yang memerlukan penjumlahan gradien dari beberapa loss. Akan tetapi, pada perulangan pelatihan biasa, akumulasi itu harus dikosongkan di awal setiap iterasi agar pembaruan bobot hanya berdasar pada gradien batch yang sedang diproses.
Perilaku akumulasi berikut diperagakan pada satu model kecil tanpa optimizer sama sekali. backward() dipanggil dua kali pada loss yang sama, dan gradien bobot dibaca setelahnya.
import torch
import torch.nn as nn
torch.manual_seed(0)
model = nn.Linear(1, 1)
x = torch.tensor([[0.5]])
y = torch.tensor([[2.0]])
loss_fn = nn.MSELoss()
loss = loss_fn(model(x), y)
loss.backward()
print("gradien setelah backward pertama:", model.weight.grad.item())
loss = loss_fn(model(x), y)
loss.backward()
print("gradien setelah backward kedua :", model.weight.grad.item())gradien setelah backward pertama: -1.4672998189926147
gradien setelah backward kedua : -2.9345996379852295Angka kedua tepat dua kali angka pertama, yaitu gradien baru ditambahkan pada gradien lama yang belum dibersihkan. Bobot model tidak berubah di antara kedua panggilan karena step() tidak dipanggil, sehingga kedua loss identik dan gradiennya identik. Inilah pekerjaan zero_grad(): mengembalikan atribut .grad setiap parameter menjadi nol sebelum perhitungan gradien berikutnya, sehingga setiap iterasi memulai perhitungan dari kertas bersih.
Mengapa backward ada#
Baris loss.backward() ada karena PyTorch tidak menghitung gradien secara diam-diam pada saat forward berjalan. Sepanjang operasi forward, autograd merekam setiap operasi aritmetika ke dalam graf komputasi, yakni struktur yang mencatat operasi mana menghasilkan nilai mana. Pemanggilan backward() pada loss kemudian menelusuri graf itu dari belakang, menerapkan aturan rantai, dan mengisi atribut .grad pada setiap parameter yang terlibat. Tanpa baris ini, atribut .grad tetap None dan tidak ada informasi apa pun tentang arah perbaikan bobot.
Fungsi loss menjadi titik panggilan backward karena seluruh pertimbangan pelatihan diringkas di situ. Loss mean squared error yang membandingkan prediksi dengan target, misalnya, meringkas selisih keduanya menjadi satu angka tunggal,
sehingga gradien yang dihitung backward merupakan arah penurunan angka itu terhadap setiap bobot. Gradien belum menjadi langkah pembaruan, melainkan hanya informasi, dan informasi itu menunggu dibaca oleh optimizer.
Mengapa step ada#
Baris optimizer.step() ada karena autograd sengaja tidak mengubah bobot. PyTorch memisahkan perhitungan gradien dari pembaruan bobot, sehingga strategi pembaruan dapat ditukar tanpa menyentuh kode lain. Ketika step() dipanggil, optimizer membaca .grad setiap parameter dan menerapkan aturan pembaruannya. Untuk gradient descent dengan learning rate , aturannya adalah
Contoh berikut membuat hubungan itu terlihat secara numerik. Loss yang digunakan sengaja sederhana, yaitu jumlah keluaran model untuk satu masukan bernilai 1.0, sehingga gradiennya dapat dihitung dengan tangan.
import torch
import torch.nn as nn
torch.manual_seed(0)
model = nn.Linear(1, 1)
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
print("bobot sebelum step :", model.weight.item())
loss = model(torch.tensor([[1.0]])).sum()
loss.backward()
print("gradien terisi :", model.weight.grad.item())
optimizer.step()
print("bobot sesudah step :", model.weight.item())bobot sebelum step : -0.007486820220947266
gradien terisi : 1.0
bobot sesudah step : -0.10748682171106339Bobot bergeser tepat sebesar dikalikan gradien, yaitu ke arah negatif, dan sisanya pada kedua nilai sama persis. Perpindahan ini membuktikan bahwa step() tidak melakukan keajaiban apa pun, melainkan membaca gradien yang sudah terisi oleh backward() dan menjalankan rumus pembaruan. Learning rate menentukan panjang langkahnya, sedangkan gradien menentukan arahnya.
Eksperimen membuang zero_grad#
Dampak zero_grad() yang hilang paling mudah diamati pada eksperimen kecil berikut. Pelatihannya sengaja menggunakan satu batch penuh berisi 64 sampel tanpa pengacakan, sehingga satu-satunya perbedaan antara kedua jalur adalah ada atau tidaknya zero_grad(). Loss dicatat pada lima titik langkah.
import torch
import torch.nn as nn
torch.manual_seed(0)
x = torch.linspace(0.0, 1.0, 64).unsqueeze(1)
y = 2.0 * x + 1.0
def latih(sertakan_zero_grad):
torch.manual_seed(0)
model = nn.Linear(1, 1)
loss_fn = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
riwayat = []
for langkah in range(60):
if sertakan_zero_grad:
optimizer.zero_grad()
loss = loss_fn(model(x), y)
loss.backward()
optimizer.step()
riwayat.append(loss.item())
return riwayat
a = latih(True)
b = latih(False)
print("langkah | dengan zero_grad | tanpa zero_grad")
for i in (0, 9, 19, 39, 59):
print(f"{i + 1:>7} | {a[i]:>15.4f} | {b[i]:>14.4f}")langkah | dengan zero_grad | tanpa zero_grad
1 | 2.4995 | 2.4995
10 | 0.1395 | 0.0709
20 | 0.0970 | 1.9836
40 | 0.0562 | 0.2353
60 | 0.0325 | 0.5689Jalur tanpa zero_grad() bahkan sempat unggul pada langkah ke-10, karena gradien yang terakumulasi memperbesar langkah pembaruan seperti learning rate yang tiba-tiba naik. Keunggulan itu tidak bertahan. Mulai langkah ke-20, lossnya melonjak ke 1.9836, lalu berayun antara 0.2353 dan 0.5689 tanpa pernah menetap, sedangkan jalur yang benar terus menurun hingga 0.0325. Penyebabnya persis seperti demonstrasi akumulasi di atas: gradien yang digunakan step() adalah gabungan gradien langkah saat ini dan seluruh langkah sebelumnya, sehingga arah pembaruan selalu basi dan berlebihan. Pelatihan tetap berjalan tanpa pesan error apa pun, dan hanya kurva loss yang menunjukkan ada yang salah.
Urutan baris tidak boleh ditukar#
Kelima baris membentuk rantai sebab akibat: zero_grad() menyiapkan kertas bersih, forward menghasilkan prediksi, criterion mengubah selisih menjadi loss, backward() mengisi gradien dari loss itu, dan step() mengonsumsinya. Setiap pertukaran memutus rantai pada tempat yang berbeda, seperti dirangkum pada tabel berikut.
| Baris yang ditukar | Akibatnya |
|---|---|
step() sebelum backward() | Gradien masih kosong, sehingga SGD melewati parameter tersebut tanpa pembaruan apa pun. |
backward() sebelum forward | Graf komputasi belum terbentuk, sehingga backward() pada tensor tanpa riwayat operasi gagal dengan pesan error. |
zero_grad() setelah backward() | Gradien yang baru dihapus sebelum sempat digunakan step(), sehingga bobot tidak bergerak. |
step() dua kali untuk satu backward() | Pembaruan kedua menggunakan gradien yang sama, sehingga langkah pada arah lama menjadi dobel. |
Dua konsekuensi teratas bising, karena berhenti dengan pesan error. Dua konsekuensi terbawah senyap, karena pelatihan berjalan dengan hasil yang salah. Kebiasaan yang menjaga urutan ini adalah menuliskan kelima baris sekali di dalam perulangan batch dan tidak menyusunnya ulang tanpa alasan yang terukur.
Ringkasan#
zero_grad()ada karena autograd mengakumulasi gradien pada atribut.gradalih-alih menimpanya, dan gradien basi harus dibersihkan sebelum setiap iterasi.backward()mengisi.graddengan menelusuri graf komputasi dari loss, sedangkanstep()membaca gradien itu dan memperbarui bobot sebesar learning rate dikalikan gradien.- Membuang
zero_grad()tidak menghasilkan error, melainkan loss yang berayun dan gagal menetap, sehingga urutan kelima baris wajib dijaga.
Referensi#
- PyTorch documentation — PyTorch
— Rujukan perilaku autograd, atribut
.grad, dan optimizer yang menjadi dasar penjelasan topik ini. - Deep Learning with PyTorch: A 60 Minute Blitz — PyTorch
— Tutorial resmi tentang autograd dan perulangan pelatihan dengan
torch.autograd.