İçeriğe geç
Educora
Üniversite22 dk38 / 42

PyTorch: autograd ve otomatik türev

PyTorch türevleri senin yerine nasıl hesaplar: hesaplama grafı, requires_grad, backward() ve .grad, zincir kuralı, gradyan birikimi, torch.no_grad() ve autograd ile gradyan inişi.

Kendini test et
Bu derste öğreneceklerin
  • Hesaplama grafının ve geri geçişin (backpropagation) nasıl çalıştığını açıklamak
  • requires_grad, .backward() ve .grad ile gradyanları hesaplamak ve zincir kuralıyla elle doğrulamak
  • Gradyanları sıfırlamak ve torch.no_grad()'u doğru kullanmak
  • Autograd ile gradyan inişini sıfırdan yazmak

İlk derste doğrusal regresyonun gradyanlarını elle çıkardık, hem de yalnızca iki parametre için. Modern sinir ağlarında milyonlarca, büyük dil modellerinde milyarlarca parametre vardır; her birinin türevini kâğıt üzerinde çıkarmak imkânsızdır. PyTorch'un autograd mekanizması bu işi otomatik yapar: tensörlerle yaptığın her işlemi kaydeder, sonra tek bir komutla tüm parametrelere göre kesin türevleri hesaplar. Bu ne yaklaşık sayısal farklardır ne de sembolik cebir; bu otomatik türevdir (automatic differentiation).

Hesaplama grafı

requires_grad=True olan bir tensörle yapılan her işlem, yönlü ve döngüsüz bir grafa (DAG) yeni bir düğüm ekler. Grafın yaprakları girdiler ve parametreler, kökü ise kayıptır. İleri geçişte (forward pass) graf kurulur ve değerler hesaplanır; geri geçişte (backward pass) autograd kökten yapraklara doğru ilerler ve her düğümde zincir kuralını uygular. Sinir ağlarında bu sürece geri yayılım (backpropagation) denir.

Düğümİleri geçişYerel türevgrad_fn
uu = w · x∂u/∂w = xMulBackward0
ŷŷ = u + b∂ŷ/∂u = 1, ∂ŷ/∂b = 1AddBackward0
ee = ŷ − y∂e/∂ŷ = 1SubBackward0
LL = e²∂L/∂e = 2ePowBackward0
L = (w · x + b − y)² kaybının grafı. Geri geçişte yerel türevler aşağıdan yukarıya çarpılır: ∂L/∂w = 2e · 1 · 1 · x.

PyTorch'ta graf dinamiktir (define-by-run): her ileri geçişte yeniden kurulur. Bu yüzden bir modelin içinde sıradan Python if ve for ifadelerini özgürce kullanabilirsin; graf, o anda gerçekten çalışan yola uyar.

YöntemNasıl çalışırDezavantajı
Sayısal türev(f(θ + h) − f(θ − h)) / 2hyaklaşıktır ve her parametre için iki hesaplama ister
Sembolik türevformülü cebir kurallarıyla dönüştürür (SymPy gibi)büyük modellerde ifadeler aşırı şişer; döngüleri ve dallanmaları işlemek zordur
Otomatik türev (autograd)çalıştırılan işlemlerin yerel türevlerini zincir kuralıyla birleştirirara değerleri saklamak için ek bellek gerekir

requires_grad, backward() ve .grad

Python
import torch

x = torch.tensor(3.0, requires_grad=True)
y = x ** 2 + 2 * x + 1
print(y)
y.backward()
print(x.grad)
Beklenen çıktı
tensor(16., grad_fn=<AddBackward0>)
tensor(8.)
  • requires_grad=True, PyTorch'a “bu tensöre göre türev gerekecek, işlemleri kaydet” der. Parametreler (nn.Linear ağırlıkları vb.) bunu otomatik yapar.
  • grad_fn, tensörü hangi işlemin oluşturduğunu gösterir; bu onun graftaki düğümüdür.
  • y.backward(), dy/dx'i hesaplar ve sonucu yaprağın .grad özniteliğine yazar: y = x² + 2x + 1 için x = 3'te dy/dx = 2x + 2 = 8.

Parametre bir vektör olduğunda .grad da aynı şekilde bir vektördür: her eleman için bir kısmi türev. Örneğin f = ∑ vᵢ² için ∂f/∂vᵢ = 2vᵢ olur:

Python
import torch

v = torch.tensor([1.0, -2.0, 3.0], requires_grad=True)
f = (v ** 2).sum()
f.backward()
print(v.grad)
Beklenen çıktı
tensor([ 2., -4.,  6.])

requires_grad bayrağı sonradan da değiştirilebilir: p.requires_grad_(False) bir parametreyi “dondurur”; gradyanı hesaplanmaz ve eğitim sırasında değişmez. Bu, transfer öğrenmede kullanılır: önceden eğitilmiş bir ağın katmanlarının çoğu dondurulur, yalnızca son katman yeni göreve uyarlanır. Bunu evrişimli ağlar dersinde göreceksin.

Zincir kuralı

dL/dx = dL/dy · dy/dx ∂L/∂w = ∂L/∂ŷ · ∂ŷ/∂wdL/dx = dL/dy · dy/dx ∂L/∂w = ∂L/∂ŷ · ∂ŷ/∂w
burada:
  • Lkayıp (grafın kökü)
  • y, ŷara değerler (grafın iç düğümleri)
  • x, wyapraklar: girdiler ve parametreler

Bileşik bir fonksiyonun türevi, yerel türevlerin çarpımına eşittir. Bir değişken kaybı birkaç yol üzerinden etkiliyorsa tüm yollardaki çarpımlar toplanır: ∂L/∂x = ∑ₖ ∂L/∂yₖ · ∂yₖ/∂x.

Örnek 1: doğrusal bir modelin gradyanı

ŷ = w · x + b, L = (ŷ − y)². w = 2, b = 1, x = 3, y = 10 için ∂L/∂w ve ∂L/∂b'yi zincir kuralıyla bul.

Çözümü göster
İleri geçiş: ŷ = 2 · 3 + 1 = 7, L = (7 − 10)² = 9.
∂L/∂ŷ = 2(ŷ − y) = −6.
∂ŷ/∂w = x = 3 ⇒ ∂L/∂w = −6 · 3 = −18.
∂ŷ/∂b = 1 ⇒ ∂L/∂b = −6.
İki gradyan da negatiftir: w ve b'yi artırmak kaybı azaltır; gerçekten de ŷ = 7 hedefin (10) altındadır.
Python
import torch

w = torch.tensor(2.0, requires_grad=True)
b = torch.tensor(1.0, requires_grad=True)
x, y = torch.tensor(3.0), torch.tensor(10.0)
y_hat = w * x + b
loss = (y_hat - y) ** 2
print(type(loss.grad_fn).__name__, type(y_hat.grad_fn).__name__)
loss.backward()
print(loss.item(), w.grad.item(), b.grad.item())
Beklenen çıktı
PowBackward0 AddBackward0
9.0 -18.0 -6.0
Autograd, elle bulduğumuz −18 ve −6 değerlerini doğrular. x ve y için requires_grad verilmedi; onlar parametre değil, veridir.
σ(z) = 1 / (1 + e⁻ᶻ) σ′(z) = σ(z) · (1 − σ(z))σ(z) = 1 / (1 + e⁻ᶻ) σ′(z) = σ(z) · (1 − σ(z))
burada:
  • σ(z)sigmoid fonksiyonu, değerleri (0; 1) aralığında
  • σ′(z)türevi; en büyük değeri z = 0'da 0,25'tir

İspat: σ′(z) = e⁻ᶻ / (1 + e⁻ᶻ)² = σ(z) · e⁻ᶻ / (1 + e⁻ᶻ); son kesir 1 − σ(z)'ye eşittir.

Örnek 2: sigmoid bir nöronun gradyanı

y = σ(w · x), w = 0, x = 2. dy/dw'yi bul.

Çözümü göster
z = w · x = 0, y = σ(0) = 0,5.
dy/dw = σ′(z) · dz/dw = σ(z)(1 − σ(z)) · x = 0,5 · 0,5 · 2 = 0,5.
Sonuç: σ′ hiçbir zaman 0,25'ten büyük olmadığı için gradyan geçtiği her sigmoid katmanında en az 4 kat küçülür. 10 katmandan sonra bu 4¹⁰ ≈ 10⁶ kat demektir: kaybolan gradyan sorunu. Derin ağlarda ReLU'nun baskın olmasının nedeni budur.
Python
import torch

w = torch.tensor(0.0, requires_grad=True)
x = torch.tensor(2.0)
y = torch.sigmoid(w * x)
y.backward()
print(y.item(), w.grad.item())
Beklenen çıktı
0.5 0.5
Örnek 3: iki yol üzerinden etki eden bir değişken

f = x · y + x², x = 2, y = 3. ∂f/∂x ve ∂f/∂y'yi bul. Autograd burada ne yapar?

Çözümü göster
x grafta iki düğüme girer: u = x · y ve v = x².
Birinci yol: ∂u/∂x = y = 3. İkinci yol: ∂v/∂x = 2x = 4.
Yollar toplanır: ∂f/∂x = 3 + 4 = 7; ∂f/∂y = x = 2.
Autograd da aynısını yapar: x'e ulaşan her gradyan akışını .grad içinde toplar. Gradyan birikiminin kaynağı tam olarak budur.

Geri geçişin gücü verimliliğindedir: tek bir backward() çağrısı kaybın tüm parametrelere göre gradyanını hesaplar ve maliyeti ileri geçişle aynı mertebededir (küçük bir sabit çarpan kadar). Her parametreyi ayrı ayrı hesaplasaydık, bir milyon parametreli bir ağ için bir milyon ileri geçiş gerekirdi.

Gradyanlar birikir: sıfırlama ve torch.no_grad()

.backward() eski gradyanın yerine yazmaz, ona ekler. Aşağıda y = x³'ün x = 2'deki türevi 3x² = 12'dir, ama üç çağrıdan sonra .grad 36 olur:

Python
import torch

x = torch.tensor(2.0, requires_grad=True)
for i in range(3):
    y = x ** 3
    y.backward()
    print(x.grad)
x.grad.zero_()
print(x.grad)
with torch.no_grad():
    z = x * 2
print(z.requires_grad, x.detach().requires_grad)
Beklenen çıktı
tensor(12.)
tensor(24.)
tensor(36.)
tensor(0.)
False False
  • x.grad.zero_() gradyanı yerinde sıfırlar (sondaki alt çizgi _ yerinde işlem demektir). Sonraki derslerde bunu optimizer.zero_grad() yapacak.
  • with torch.no_grad(): bloğunda graf kurulmaz: parametreleri güncellerken ve modeli değerlendirirken (çıkarım) kullanılır, bellek ve zaman kazandırır.
  • .detach(), aynı veriyi paylaşan ama graftan koparılmış bir tensör döndürür; örneğin bir değeri yazdırmak ya da NumPy'a çevirmek için.

Autograd ile gradyan inişi

Şimdi hepsini birleştirip y = 2x + 1 doğrusundan gürültüyle üretilmiş 20 noktaya doğrusal bir model uyduralım. Bu kez türev formülü yazmıyoruz; autograd onları her adımda hesaplıyor. Bu döngünün beş adımı tüm derin öğrenmenin temelidir: ileri geçiş → kayıp → backward → no_grad içinde güncelleme → gradyanları sıfırlama.

Python
import torch

torch.manual_seed(42)
X = torch.linspace(0, 1, 20)
Y = 2 * X + 1 + 0.1 * torch.randn(20)
w = torch.zeros(1, requires_grad=True)
b = torch.zeros(1, requires_grad=True)
lr = 0.5
for step in range(201):
    loss = ((w * X + b - Y) ** 2).mean()
    loss.backward()
    with torch.no_grad():
        w -= lr * w.grad
        b -= lr * b.grad
    w.grad.zero_()
    b.grad.zero_()
    if step % 50 == 0:
        print(f'step {step:3d}  loss {loss.item():.4f}')
print(f'w = {w.item():.3f}, b = {b.item():.3f}')
Beklenen çıktı
step   0  loss 4.4302
step  50  loss 0.0085
step 100  loss 0.0084
step 150  loss 0.0084
step 200  loss 0.0084
w = 1.907, b = 1.068
Yalnızca 50 adımda kayıp 4,43'ten 0,0085'e düştü. Son kayıp gürültünün varyansına (0,1² = 0,01) yakındır; daha aşağı inmek aşırı öğrenme olurdu. w ve b tam olarak 2 ve 1 değildir, çünkü 20 gürültülü nokta için en iyi doğru budur.

Bu döngüyü iyi hatırla: sonraki derslerde onu kısaltacağız ama özü değişmeyecek. torch.optim.SGD optimizasyon aracının step() metodu her parametre için no_grad içinde tam olarak p -= lr * p.grad işlemini yapar, zero_grad() ise gradyanları temizler. nn.Linear gibi katmanlar w ve b'yi kendileri oluşturur ve requires_grad'ı senin yerine açar. Yani “sihirli” görünen üst düzey kod, bu beş satırın kullanışlı bir sarmalayıcısıdır.

Önemli noktalar

  • Autograd tensör işlemlerinden bir hesaplama grafı kurar; backward() zincir kuralını kökten yapraklara uygular.
  • requires_grad=True olan yaprakların gradyanları .grad'da saklanır; backward() bir skaler üzerinde çağrılır.
  • Zincir kuralı: dL/dx = dL/dy · dy/dx; sigmoid için σ′ = σ(1 − σ).
  • Gradyanlar birikir; her adımdan sonra sıfırla. Güncellemeler ve değerlendirme torch.no_grad() içinde yapılır.
  • Eğitim döngüsü: ileri geçiş → kayıp → backward → güncelleme → sıfırlama.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
x = 1, y = x³ + 4x. y.backward() sonrasında x.grad kaçtır?