- Hesaplama grafının ve geri geçişin (backpropagation) nasıl çalıştığını açıklamak
requires_grad,.backward()ve.gradile 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ürev | grad_fn |
|---|---|---|---|
| u | u = w · x | ∂u/∂w = x | MulBackward0 |
| ŷ | ŷ = u + b | ∂ŷ/∂u = 1, ∂ŷ/∂b = 1 | AddBackward0 |
| e | e = ŷ − y | ∂e/∂ŷ = 1 | SubBackward0 |
| L | L = e² | ∂L/∂e = 2e | PowBackward0 |
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öntem | Nasıl çalışır | Dezavantajı |
|---|---|---|
| Sayısal türev | (f(θ + h) − f(θ − h)) / 2h | yaklaşıktır ve her parametre için iki hesaplama ister |
| Sembolik türev | formü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ştirir | ara değerleri saklamak için ek bellek gerekir |
requires_grad, backward() ve .grad
import torch
x = torch.tensor(3.0, requires_grad=True)
y = x ** 2 + 2 * x + 1
print(y)
y.backward()
print(x.grad)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.Linearağı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:
import torch
v = torch.tensor([1.0, -2.0, 3.0], requires_grad=True)
f = (v ** 2).sum()
f.backward()
print(v.grad)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ı
- 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.
ŷ = 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Çözümü gizle
∂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.
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())PowBackward0 AddBackward0 9.0 -18.0 -6.0
x ve y için requires_grad verilmedi; onlar parametre değil, veridir.- σ(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.
y = σ(w · x), w = 0, x = 2. dy/dw'yi bul.
Çözümü gösterÇözümü gizle
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.
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())0.5 0.5
f = x · y + x², x = 2, y = 3. ∂f/∂x ve ∂f/∂y'yi bul. Autograd burada ne yapar?
Çözümü gösterÇözümü gizle
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:
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)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 bunuoptimizer.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.
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}')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
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=Trueolan 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.
y.backward() sonrasında x.grad kaçtır?