Melatih model berarti menggeser setiap bobot ke arah yang menurunkan loss — dan arah itu adalah gradien, turunan loss terhadap tiap bobot. Pada section prasyarat kita menghitungnya manual dengan chain rule; itu mustahil dilakukan untuk model dengan jutaan parameter. Autograd menyelesaikannya dengan cara lain: selagi forward pass berjalan, PyTorch merekam setiap operasi ke dalam graph, lalu satu panggilan backward() menelusuri graph itu dari akhir ke awal dan menjumlahkan kontribusi tiap simpul. Chapter ini adalah jantung section — kita bedah graph-nya, hitung gradien pertama kita, buktikan kebenarannya terhadap finite difference, lalu belajar mengendalikan kapan graph boleh dibangun.
Graph yang Merekam Setiap Operasi #
Mekanismenya dimulai dari satu atribut: requires_grad=True. Mulai saat atribut itu aktif, setiap operasi yang melibatkan tensor tersebut menghasilkan tensor baru yang membawa penunjuk grad_fn ke simpul operasi pembentuknya. Rangkaian penunjuk inilah graph dinamis PyTorch — graph terbentuk mengikuti kode Python yang kita tulis, baris demi baris. Diagram berikut memetakan graph untuk fungsi yang akan kita hitung, y = (x*w + b)**2:
Ketika backward() dipanggil pada y, penelusuran berjalan dari PowBackward0 ke kiri: turunan lokal tiap simpul dikalikan dengan gradien yang sudah terkumpul — persis chain rule — dan hasil akhirnya ditumpuk ke atribut .grad milik setiap daun ber-requires_grad.
Gradien Pertama Kita #
Berkas autograd_dasar.py membangun graph pada diagram di atas dan membandingkan hasil autograd dengan turunan analitik. Angka sengaja dipilih kecil: x = 2, w = 3, b = 1, sehingga y = (2×3+1)² = 49:
"""Autograd menghitung gradien y = (x*w + b)**2 lalu diverifikasi."""
import torch
x = torch.tensor(2.0, requires_grad=True)
w = torch.tensor(3.0, requires_grad=True)
b = torch.tensor(1.0, requires_grad=True)
u = x * w
s = u + b
y = s ** 2
print("u =", u.item(), "| grad_fn:", type(u.grad_fn).__name__)
print("s =", s.item(), "| grad_fn:", type(s.grad_fn).__name__)
print("y =", y.item(), "| grad_fn:", type(y.grad_fn).__name__)
y.backward()
print("dy/dx =", x.grad.item())
print("dy/dw =", w.grad.item())
print("dy/db =", b.grad.item())
eps = 1e-4
def hitung_y(w_nilai):
return (2.0 * w_nilai + 1.0) ** 2
fd = (hitung_y(3.0 + eps) - hitung_y(3.0 - eps)) / (2 * eps)
print("finite difference dy/dw =", round(fd, 6))Output yang Dihasilkan:
u = 6.0 | grad_fn: MulBackward0
s = 7.0 | grad_fn: AddBackward0
y = 49.0 | grad_fn: PowBackward0
dy/dx = 42.0
dy/dw = 28.0
dy/db = 14.0
finite difference dy/dw = 28.0Mari kita telaah kode di atas baris per baris:
- Baris 5-7 (tiga daun):
x,w, danbdibuat denganrequires_grad=True— dalam model nyata, hanyawdanb(parameter) yang diberi tanda ini;xberperan sebagai data masukan. - Baris 9-15 (forward + pencatatan): tiga operasi membentuk
u,s,y; setiap hasil membawagrad_fnsesuai diagram —MulBackward0,AddBackward0,PowBackward0. Nilainya sendiri masih dihitung maju seperti biasa. - Baris 17-21 (
y.backward()): penelusuran balik mengisi.gradtiap daun. Turunan analitiknya bisa diikuti manual:dy/ds = 2s = 14, laludy/dw = 2s · x = 28,dy/dx = 2s · w = 42, dandy/db = 2s = 14— ketiganya cocok dengan keluaran. - Baris 22-30 (finite difference): sebagai bukti independen, gradien diaproksimasi dengan
(f(w+ε) − f(w−ε)) / 2εpadaε = 1e-4; hasil28.0sama dengan autograd. Autograd menghitung turunan pasti lewat aturan diferensiasi tiap simpul, sedangkan finite difference hanya menaksir lewat kemiringan garis potong — keduanya sepakat di sini.
no_grad, detach, dan Akumulasi .grad #
Graph punya harga: memori dan waktu. Saat inferensi kita tidak butuh gradien sama sekali, dan hasil antara yang disimpan tidak perlu membawa graph. Satu sifat lagi yang sering mengejutkan pemula: .grad bersifat akumulatif — setiap backward() menambah, bukan menimpa. Berkas no_grad_detach.py mendemonstrasikan ketiganya:
"""Mengendalikan graph autograd: akumulasi .grad, no_grad, dan detach."""
import torch
w = torch.tensor(3.0, requires_grad=True)
y1 = (w * 2.0).sum()
y1.backward()
print("backward ke-1 -> w.grad =", w.grad.item())
y2 = (w * 2.0).sum()
y2.backward()
print("backward ke-2 -> w.grad =", w.grad.item(), "(terakumulasi)")
w.grad.zero_()
print("zero_grad() -> w.grad =", w.grad.item())
with torch.no_grad():
y = w * 5.0
print("no_grad -> y.grad_fn =", y.grad_fn,
"| y.requires_grad =", y.requires_grad)
z = w * 7.0
z_lepas = z.detach()
print("detach() -> z_lepas.requires_grad =", z_lepas.requires_grad,
"| berbagi memori:", z_lepas.data_ptr() == z.data_ptr())Output yang Dihasilkan:
backward ke-1 -> w.grad = 2.0
backward ke-2 -> w.grad = 4.0 (terakumulasi)
zero_grad() -> w.grad = 0.0
no_grad -> y.grad_fn = None | y.requires_grad = False
detach() -> z_lepas.requires_grad = False | berbagi memori: TrueMari kita telaah kode di atas baris per baris:
- Baris 7-12 (akumulasi): turunan
y = 2wadalah2; setelah duabackward(),w.gradberisi4.0— keduanya dijumlahkan. Perilaku ini disengaja untuk mendukung gradient accumulation pada batch yang dipecah, tetapi menjadi sumber bug bila lupa mengosongkan. - Baris 14-15 (
zero_grad): pengosongan eksplisit sebelum backward berikutnya mengembalikanw.gradke nol — dalam model nyata tugas ini diambil alih olehoptimizer.zero_grad(). - Baris 17-20 (
no_grad): di dalam blokwith torch.no_grad():, operasi tetap berjalan tetapi tidak direkam —y.grad_fnbernilaiNonedanrequires_gradFalse. Gunakan untuk inferensi dan untuk pembaruan bobot manual. - Baris 22-26 (
detach):detach()mengembalikan tensor kembaran yang berbagi memori tetapi terlepas dari graph — pas untuk menyimpan nilai antara (misalnya prediksi untuk metrik) tanpa menahan graph tetap hidup di memori.
Kebiasaan Do dan Don’t #
| Situasi | Lakukan | Hindari |
|---|---|---|
| Inferensi / evaluasi | bungkus dengan torch.no_grad() | membangun graph yang tak pernah dipakai |
| Menyimpan prediksi untuk metrik | .detach() (lalu .cpu() bila perlu) | menyimpan tensor ber-graph — graph ikut tertahan |
| Loop training | optimizer.zero_grad() tiap iterasi | membiarkan gradien terakumulasi antar batch |
| Data masukan / label | tensor biasa tanpa requires_grad | menandai data dengan requires_grad=True |
Memanggil backward() | pada skalar loss | pada tensor multi-elemen tanpa argumen gradient |
| Debug nilai antara | print(nilai.item()) atau detach() | memodifikasi tensor yang masih tercatat di graph |
Dokumentasi resmi autograd — termasuk penjelasan graph, grad_fn, dan daftar pengecualian untuk operasi tak terdiferensiasi — tersedia di docs.pytorch.org/docs/stable/autograd.html
. Dengan gradien di tangan, satu pertanyaan tersisa sebelum masuk ke pembuatan model: di perangkat mana seluruh komputasi ini sebaiknya berjalan? Chapter berikutnya membandingkan CPU dan GPU dengan pola kode yang bekerja di keduanya.