Idenya
Gradient descent (track ML) membutuhkan gradient dari loss terhadap setiap parameter. Untuk regresi linear kamu menurunkannya secara manual. Untuk network dengan sejuta parameter, tidak ada yang sanggup. Autograd melakukannya secara otomatis: PyTorch merekam setiap operasi pada tensor yang "membutuhkan gradient", dan saat kamu memanggil .backward() pada loss, ia menelusuri rekaman itu ke belakang sambil menerapkan aturan rantai (chain rule), mengisi .grad untuk setiap parameter.
Itulah seluruh trik di balik training neural network.
Gradient pertama
Di track ML kamu menghitung gradient secara manual, dengan kalkulus. Autograd melakukannya untukmu. Ini kasus paling sederhana: satu bobot, w, dan loss yang paling kecil saat w bernilai 5. Tanyakan ke PyTorch ke arah mana w harus digeser:
import torch
w = torch.tensor(3.0, requires_grad=True)
loss = (w - 5) ** 2 # minimum at w = 5
loss.backward()
print(loss.item(), w.grad)
4.0 tensor(-4.)
Turunan dari (w − 5)² adalah 2(w − 5), yang pada w = 3 bernilai −4. PyTorch menghitung persis itu. Tanda negatif berarti "naikkan w untuk menurunkan loss", dan itulah yang akan dilakukan gradient descent.
Graph yang direkam
Setiap hasil mengingat operasi yang membuatnya:
x = torch.tensor([1.0, 2.0, 3.0])
y = torch.tensor([3.0, 5.0, 7.0])
w = torch.tensor(0.0, requires_grad=True)
b = torch.tensor(0.0, requires_grad=True)
pred = w * x + b
loss = ((pred - y) ** 2).mean()
print(type(pred.grad_fn).__name__, type(loss.grad_fn).__name__)
loss.backward()
print(w.grad, b.grad)
AddBackward0 MeanBackward0
tensor(-22.6667) tensor(-10.)
Ini persis gradient MSE yang kamu turunkan di modul gradient descent: mean(2·error·x) dan mean(2·error). Entri grad_fn adalah langkah-langkah yang direkam (sebuah penjumlahan, sebuah mean) yang ditelusuri oleh backward().
Training loop dengan autograd
Ganti gradient yang ditulis manual dari track ML dengan autograd:
w = torch.tensor(0.0, requires_grad=True)
b = torch.tensor(0.0, requires_grad=True)
learning_rate = 0.05
for step in range(500):
loss = ((w * x + b - y) ** 2).mean()
loss.backward() # compute w.grad and b.grad
with torch.no_grad(): # update without recording these operations
w -= learning_rate * w.grad
b -= learning_rate * b.grad
w.grad.zero_() # reset for the next step
b.grad.zero_()
print(round(w.item(), 3), round(b.item(), 3), round(loss.item(), 6))
2.0 1.0 0.0
Hasilnya w = 2, b = 1 (datanya y = 2x + 1). Tiga detail penting:
torch.no_grad()di sekitar update: update itu sendiri tidak boleh menjadi bagian dari graph yang direkam.- Mengenolkan gradient: PyTorch menambahkan gradient baru ke
.gradalih-alih menggantinya. Tanpazero_(), setiap langkah akan memakai jumlah semua gradient sebelumnya, dan training jadi kacau. - Loop-nya adalah prediksi → loss →
backward()→ update → nolkan. Di modul-modul berikutnya,optimizer.step()danoptimizer.zero_grad()mengerjakan langkah 4 dan 5 untukmu.
Gradient terakumulasi: lihat sendiri
Detail ketiga itu, mengosongkan gradient, adalah yang pasti pernah dilupakan semua orang. Ini yang terjadi kalau kamu memanggil backward() berulang kali tanpa mengosongkan w.grad di antaranya:
w = torch.tensor(3.0, requires_grad=True)
for _ in range(3):
loss = (w - 5) ** 2
loss.backward()
print(w.grad)
tensor(-4.)
tensor(-8.)
tensor(-12.)
Loss yang sama, dipanggil tiga kali: −4, lalu −8, lalu −12. Itulah akumulasi. Kadang berguna (untuk menyimulasikan batch yang lebih besar), tetapi hampir selalu ini bug karena lupa zero_grad().
loss.backward() mengisi .grad untuk setiap tensor dengan requires_grad=True dengan menerapkan chain rule pada operasi yang direkam. Gradient terus bertambah sampai kamu mengenolkannya, dan update parameter harus berada di dalam torch.no_grad().
Mematikan pelacakan
Merekam butuh memori dan waktu. Saat evaluasi dan prediksi kamu tidak butuh gradient:
with torch.no_grad():
predictions = w * x + b
print(predictions.requires_grad)
print((w * x).detach().requires_grad)
False
False
torch.no_grad() mematikan perekaman untuk satu blok; .detach() mengembalikan tensor yang terputus dari graph (dibutuhkan sebelum .numpy(), seperti yang ditunjukkan modul sebelumnya).
Mengecek gradient secara numerik
Bagaimana kamu tahu sebuah gradient sudah benar? Bandingkan dengan finite difference: geser parameternya sedikit sekali dan lihat seberapa besar loss-nya berubah. Ini bisa dilakukan di NumPy biasa:
import numpy as np
x = np.array([1.0, 2.0, 3.0])
y = np.array([3.0, 5.0, 7.0])
def loss(w, b):
return np.mean((w * x + b - y) ** 2)
w, b, eps = 0.5, 0.2, 1e-6
numeric_w = (loss(w + eps, b) - loss(w - eps, b)) / (2 * eps)
analytic_w = np.mean(2 * (w * x + b - y) * x)
print(round(numeric_w, 4), round(analytic_w, 4))
-17.2 -17.2
Hasilnya cocok. Developer library mengetes autograd persis dengan cara ini (torch.autograd.gradcheck), dan ini cara yang bagus untuk meyakinkan dirimu bahwa "gradient adalah kemiringan" bukan sekadar slogan.
Mengapa ini penting untuk kode yang bersih
Autograd menghilangkan bagian kode ML yang paling rawan kesalahan: gradient yang diturunkan manual. Sebagai gantinya, ia menuntut disiplin: nolkan gradient di setiap langkah, bungkus update dan evaluasi dengan no_grad, dan lakukan detach sebelum keluar dari PyTorch. Tiga kebiasaan ini mencegah sebagian besar bug "training-ku melakukan hal aneh".
Rangkuman
requires_grad=Truemembuat PyTorch merekam operasi pada sebuah tensor.loss.backward()menghitung gradient lewat chain rule ke.gradmilik setiap parameter.- Gradient terakumulasi: nolkan di setiap langkah (
.grad.zero_()atauoptimizer.zero_grad()). - Update parameter di dalam
torch.no_grad(); gunakan juga (atau.detach()) untuk evaluasi dan prediksi. - Finite difference mengecek gradient secara numerik:
(f(w+ε) − f(w−ε)) / 2ε.