DataLens

Autograd: cara kerja gradient

Lihat bagaimana PyTorch merekam operasi dan menghitung gradient dengan backward(), mengapa gradient terakumulasi, kapan pelacakan dimatikan, dan cek gradient secara numerik di NumPy.

Level: Dasar · 8 menit baca

Latihan modul ini

Soal-soal untuk pelajaran ini berjalan di browser-mu dan langsung mengecek jawabanmu.

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:

  1. torch.no_grad() di sekitar update: update itu sendiri tidak boleh menjadi bagian dari graph yang direkam.
  2. Mengenolkan gradient: PyTorch menambahkan gradient baru ke .grad alih-alih menggantinya. Tanpa zero_(), setiap langkah akan memakai jumlah semua gradient sebelumnya, dan training jadi kacau.
  3. Loop-nya adalah prediksi → loss → backward() → update → nolkan. Di modul-modul berikutnya, optimizer.step() dan optimizer.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().

Ide utama

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

Latihan modul ini

Soal-soal untuk pelajaran ini berjalan di browser-mu dan langsung mengecek jawabanmu.

Berikutnya: Membangun model dengan nn.Module →