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:

python
"""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:

text
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.0

Mari kita telaah kode di atas baris per baris:

  1. Baris 5-7 (tiga daun): x, w, dan b dibuat dengan requires_grad=True — dalam model nyata, hanya w dan b (parameter) yang diberi tanda ini; x berperan sebagai data masukan.
  2. Baris 9-15 (forward + pencatatan): tiga operasi membentuk u, s, y; setiap hasil membawa grad_fn sesuai diagram — MulBackward0, AddBackward0, PowBackward0. Nilainya sendiri masih dihitung maju seperti biasa.
  3. Baris 17-21 (y.backward()): penelusuran balik mengisi .grad tiap daun. Turunan analitiknya bisa diikuti manual: dy/ds = 2s = 14, lalu dy/dw = 2s · x = 28, dy/dx = 2s · w = 42, dan dy/db = 2s = 14 — ketiganya cocok dengan keluaran.
  4. Baris 22-30 (finite difference): sebagai bukti independen, gradien diaproksimasi dengan (f(w+ε) − f(w−ε)) / 2ε pada ε = 1e-4; hasil 28.0 sama 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:

python
"""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:

text
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: True

Mari kita telaah kode di atas baris per baris:

  1. Baris 7-12 (akumulasi): turunan y = 2w adalah 2; setelah dua backward(), w.grad berisi 4.0 — keduanya dijumlahkan. Perilaku ini disengaja untuk mendukung gradient accumulation pada batch yang dipecah, tetapi menjadi sumber bug bila lupa mengosongkan.
  2. Baris 14-15 (zero_grad): pengosongan eksplisit sebelum backward berikutnya mengembalikan w.grad ke nol — dalam model nyata tugas ini diambil alih oleh optimizer.zero_grad().
  3. Baris 17-20 (no_grad): di dalam blok with torch.no_grad():, operasi tetap berjalan tetapi tidak direkam — y.grad_fn bernilai None dan requires_grad False. Gunakan untuk inferensi dan untuk pembaruan bobot manual.
  4. 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 #

SituasiLakukanHindari
Inferensi / evaluasibungkus 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 trainingoptimizer.zero_grad() tiap iterasimembiarkan gradien terakumulasi antar batch
Data masukan / labeltensor biasa tanpa requires_gradmenandai data dengan requires_grad=True
Memanggil backward()pada skalar losspada tensor multi-elemen tanpa argumen gradient
Debug nilai antaraprint(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.