backward()

Memanggil backward() untuk menghitung gradien dan membaca hasilnya di atribut grad.

Daftar isi

Topik ini membahas method backward(), yaitu perintah yang menggerakkan penghitungan gradien pada graf komputasi yang sudah terbentuk. Pembahasan meliputi pemanggilan pada keluaran skalar, keharusan argumen gradient untuk keluaran non-skalar, sifat akumulasi atribut grad antara panggilan, serta error yang muncul apabila graf yang sama diproses dua kali. Setelah topik ini, pembacaan gradien pada atribut grad dapat dilakukan dengan tepat, lengkap dengan pemahaman atas pesan error yang paling sering muncul.

Menghitung gradien pada keluaran skalar#

Method backward() menjalankan aturan rantai mundur mulai dari tensor yang dipanggilnya sampai ke setiap tensor leaf yang terlacak, lalu menuliskan hasilnya pada atribut grad tensor tersebut. Pemanggilan tanpa argumen hanya sah untuk tensor yang memiliki tepat satu elemen, dan bentuk inilah yang digunakan sehari-hari karena fungsi loss pada pelatihan mengembalikan satu angka:

python backward-dasar.py
import torch

x = torch.tensor(3.0, requires_grad=True)
y = x ** 2            # y = x^2, dy/dx = 2x

y.backward()

print("y       =", y.item())
print("x.grad  =", x.grad.item())
print("grad_fn =", type(y.grad_fn).__name__)
text Keluaran
y       = 9.0
x.grad  = 6.0
grad_fn = PowBackward0

Nilai x.grad sebesar 6.0 cocok dengan turunan analitik ddxx2=2x \frac{d}{dx} x^2 = 2x di titik x=3 x = 3 . Keluaran ketiga menunjukkan grad_fn milik y, yaitu PowBackward0, yang menandakan y merupakan ujung dari graf yang direkam. Gradien hanya diisi pada tensor leaf seperti x, bukan pada hasil operasi menengah, karena besaran yang dicari memang berada pada leaf.

Keluaran non-skalar menuntut argumen gradient#

Apabila backward() dipanggil pada tensor yang memiliki lebih dari satu elemen, autograd tidak dapat menebak bobot untuk menggabungkan tiap komponen menjadi satu angka. Ia menuntut argumen gradient berupa tensor dengan bentuk yang sama, yang berperan sebagai bobot penyumbang setiap komponen. Tanpa argumen itu, pemanggilan gagal dengan pesan berikut:

python backward-vektor.py
import torch

x = torch.tensor([1.0, 2.0], requires_grad=True)
y = x * 2            # y berupa vektor, bukan skalar

try:
    y.backward()
except RuntimeError as e:
    print("RuntimeError:", e)
text Keluaran
RuntimeError: grad can be implicitly created only for scalar outputs

Pesan tersebut bukan tanda kerusakan, melainkan permintaan kejelasan. Penyelesaiannya adalah memberikan argumen, misalnya y.backward(gradient=torch.ones(2)) agar turunan setiap komponen dihitung terpisah lalu dijumlahkan, atau mengganti y dengan y.sum() terlebih dahulu. Kasus ini jarang dihadapi pada pelatihan karena fungsi loss lazim mengembalikan skalar, tetapi pesan errornya muncul cukup sering sehingga layak dikenali sejak awal.

Gradien terakumulasi antara panggilan#

Atribut grad tidak pernah direset secara otomatis. Setiap pemanggilan backward() menambahkan kontribusi baru ke nilai yang sudah ada, dan peristiwa ini terjadi bahkan ketika kedua pemanggilan berasal dari graf yang berbeda:

python akumulasi-grad.py
import torch

x = torch.tensor(3.0, requires_grad=True)

(x * 2).backward()
print("setelah graf pertama :", x.grad.item())

(x * 2).backward()
print("setelah graf kedua   :", x.grad.item())

x.grad = None
(x * 2).backward()
print("setelah dinolkan     :", x.grad.item())
text Keluaran
setelah graf pertama : 2.0
setelah graf kedua   : 4.0
setelah dinolkan     : 2.0

Kedua graf pada program di atas sama sekali terpisah, tetapi x.grad naik dari 2.0 menjadi 4.0 karena kontribusi kedua graf dijumlahkan pada atribut yang sama. Sifat ini memang dirancang, misalnya untuk menghimpun gradien dari beberapa batch sebelum satu kali pembaruan. Pada pelatihan lazim, gradien lama justru tidak diinginkan, sehingga perulangan pelatihan selalu mengosongkannya lebih dahulu. Method optimizer.zero_grad() yang digunakan pada perulangan pelatihan melakukan persis pekerjaan baris x.grad = None di atas untuk seluruh parameter sekaligus.

Graf yang sama hanya dapat diproses sekali#

Setelah backward() selesai, tensor-tensor antara yang disimpan selama tahap forward dibebaskan untuk menghemat memori. Graf itu dianggap selesai digunakan, sehingga pemanggilan backward() kedua pada objek hasil yang sama gagal dengan pesan berikut:

python graf-sama-dua-kali.py
import torch

x = torch.tensor(3.0, requires_grad=True)
y = x * 2

y.backward()
print("backward pertama selesai, x.grad =", x.grad.item())

try:
    y.backward()
except RuntimeError as e:
    print("RuntimeError:", e)
text Keluaran
backward pertama selesai, x.grad = 2.0
RuntimeError: Trying to backward through the graph a second time (or directly access saved tensors after they have already been freed). Saved intermediate values of the graph are freed when you call .backward() or autograd.grad(). Specify retain_graph=True if you need to backward through the graph a second time or if you need to access saved tensors after calling backward.

Pesan tersebut panjang, tetapi isinya spesifik, yaitu tensor antara sudah dibebaskan sehingga pemanggilan kedua tidak memiliki bahan untuk bekerja. Apabila graf yang sama memang perlu dihitung mundur lebih dari sekali, misalnya ketika beberapa fungsi loss dijumlahkan pada tahap yang berbeda, argumen retain_graph=True memerintahkan PyTorch menyimpan tensor antaranya. Pada pola pelatihan lazim, satu panggilan backward() per graf sudah cukup, sehingga pelepasan ini menjadi penghematan yang berlangsung otomatis.

Kesalahan umum#

Kesalahan pertama adalah membaca grad sebelum backward() dipanggil. Atribut itu berisi None sampai perhitungan dijalankan, dan None bukan angka, sehingga baris berikutnya yang menggunakannya biasanya gagal dengan pesan tentang tipe data. Perbaikannya adalah menata urutan kode, yaitu tahap forward, pemanggilan backward(), lalu pembacaan grad.

Kesalahan kedua adalah memanggil backward() ketika tidak ada tensor yang terlacak. Pesan “element 0 of tensors does not require grad and does not have a grad_fn” muncul dan menunjuk pada tensor paling depan. Penyebabnya hampir selalu sama, yaitu tensor yang seharusnya menjadi variabel pencarian lupa disetel requires_grad=True, atau seluruh blok berjalan di dalam torch.no_grad() tanpa disadari. Kedua penyebab itu diperiksa lebih dahulu sebelum mencurigai bagian kode lainnya.

Ringkasan#

  • backward() menghitung gradien seluruh tensor leaf yang terlacak dan mengisi atribut grad masing-masing.
  • Pemanggilan tanpa argumen hanya sah untuk keluaran skalar, sedangkan keluaran non-skalar menuntut argumen gradient.
  • Atribut grad terakumulasi antara panggilan, sehingga pelatihan selalu mengosongkannya di awal setiap iterasi.
  • Graf yang sama hanya dapat diproses sekali karena tensor antara dibebaskan setelah backward(), kecuali retain_graph=True digunakan.

Referensi#