ML Temelleri · Modül 3 · 21 dakika

Geri yayılım

Geri yayılım (backpropagation)geri yayılım (backpropagation): Zincir kuralıyla, kaybın her parametreye göre gradyanını çıkıştan girişe doğru hesaplama yöntemi., kaybın (loss) her parametreye göre gradyanını (gradient)gradyan (gradient): Bir fonksiyonun en hızlı arttığı yönü ve bu artışın dikliğini gösteren türev vektörü. tek bir geçişte verir. Hesabı bir hesaplama grafiğine (computational graph)hesaplama grafiği (computational graph): Bir hesabı adımlarına bölüp düğümler ve kenarlarla gösteren yapı: her düğüm bir değerdir, her kenar (ok) bir değerin hangi işlemde kullanıldığını söyler. döker ve zincir kuralını (chain rule)zincir kuralı (chain rule): İç içe fonksiyonların türevini veren kural: dış fonksiyonun iç fonksiyonun değerindeki türevi, iç fonksiyonun türeviyle çarpılır; geri yayılımın dayanağıdır. loss’tan başlayarak adım adım uygular.

Gradyan inişiyle (gradient descent)gradyan inişi (gradient descent): Kaybı azaltmak için parametreleri gradyanın tersine küçük adımlarla güncelleyen yöntem. bir sinir ağını eğitmek için bu gradyanların hepsi gerekir. Parametre sayısı binlerden milyarlara çıkabilir. Geri yayılım olmadan gradyanı iki yolla bulursun:

  • Elle çıkarmak: her ağ için pratik değildir.
  • Sayısal gradyan (numerical gradient): her parametre için loss’u iki kez hesaplamayı gerektirir.

Bu derste şunları yapacaksın:

  • Küçük bir computational graph’ta gradyanların geriye nasıl aktığını göreceksin.
  • Bir değer birden çok yerde kullanıldığında ne olduğunu öğreneceksin.
  • Zincir kuralını kendi başına uygulayan, tek bir sayı tutan bir nesnenin mantığını kuracaksın.

Bir sonraki ders, Yoğun katmanın geri geçişi ve gradyan kontrolü, aynı kuralı matrislere taşır.

Hesabı bir graph’a dök

Hesabı tek işlemlik adımlara bölersen, bir computational graph elde edersin.

Düğümler ve kenarlar

Tek bir örnekte (sample) doğrusal bir modelin loss’uyla başlayalım.

Girdi xx, ağırlık (weight)ağırlık (weight): Bir girdiyle çarpılarak onun çıktıya ne kadar katkıda bulunacağını belirleyen parametre. Doğrusal modelde her özniteliğin kendi ağırlığı vardır. ww, sapma (bias)sapma (bias): Girdilerle çarpılmadan çıktıya eklenen parametre. Doğrusal modelde bütün öznitelikler 0 iken modelin verdiği tahmindir. bb ve hedef değer (target) yy olsun. Tahmin (prediction) wx+bwx + b‘dir. Loss, tahminle target’ın farkının karesidir:

L=(wx+b−y)2L = (wx + b - y)^2

Bu, ortalama kare hatanın (mean squared error, MSE)ortalama kare hata (mean squared error, MSE): Tahminlerle hedef değerler arasındaki farkların karelerinin ortalaması. Büyük hataları küçüklerden daha ağır cezalandırır. tek örnek için, yani N=1N = 1 iken aldığı biçimdir. Değerleri x=3x = 3, w=2w = 2, b=1b = 1 ve y=10y = 10 alalım. Hesap, her biri tek bir işlem olan dört adımdır:

  1. Girdiyi ağırlıkla çarp: p=w x=2⋅3=6p = w\,x = 2 \cdot 3 = 6.
  2. Sapmayı ekle: z=p+b=6+1=7z = p + b = 6 + 1 = 7.
  3. Hedef değeri çıkar: e=z−y=7−10=−3e = z - y = 7 - 10 = -3.
  4. Hatanın karesini al: L=e2=9L = e^2 = 9.

Aynı dört adım NumPy’siz, düz Python’da şöyle yazılır:

x, w, b, y = 3.0, 2.0, 1.0, 10.0   # girdi, weight, bias, targetp = w * x     # çarpımz = p + b     # tahmine = z - y     # hataL = e ** 2    # lossprint(p, z, e, L)   # → 6.0 7.0 -3.0 9.0

Her adım yeni bir sayı üretir: pp çarpımdır, zz modelin tahminidir, ee hatadır. Bu sayıların her biri bir düğümdür (node).

Bir düğümden ötekine çizilen ok, ikincisinin hesabında birincinin kullanıldığını gösterir. Bu ok kenar (edge) adını taşır.

İleri geçiş (forward pass)

xx, ww, bb ve yy hiçbir şeyden hesaplanmaz; bunlar graph’ın başlangıç düğümleridir. LL ise son düğümdür. Düğümlerin değerlerini başlangıç düğümlerinden LL‘ye doğru hesaplamaya ileri geçiş (forward pass) denir.

Bir fonksiyona dökersen, başlangıç değerlerini değiştirip bütün düğümleri yeniden hesaplayabilirsin:

def forward(x, w, b, y):    p = w * x    z = p + b    e = z - y    return p, z, e, e ** 2print(forward(3.0, 2.0, 1.0, 10.0))   # → (6.0, 7.0, -3.0, 9.0)print(forward(4.0, 2.0, 1.0, 10.0))   # → (8.0, 9.0, -1.0, 1.0)

Aşağıdaki grafik bu hesabı aynı değerlerle çiziyor. Şunları dene:

backprop_graph

Hesaplama grafiği · computational graph · L = (w·x + b − y)²

w2x3p = w·x6b1z = p+b7y10e = z−y-3L = e²9
x = 3
w = 2

ileri geçiş · p = 6 · z = 7 · e = -3 · L = 9

  • İleri’ye bas: düğümler, girdileri hazır olur olmaz sırayla dolar. Değerler yukarıdaki dört adımdakilerle aynıdır.
  • xx‘i ya da ww‘yi değiştir ve değerlerin yeniden hesaplandığına bak.

Yerel türevler

Computational graph’ın işe yarayan yanı şudur: her düğüm, yalnızca kendi girdilerine bakan tek bir basit işlemdir. Bu yüzden her birinin türevi (derivative) bir bakışta yazılır.

Bir işlemin sonucunun her girdisine göre kısmi türevine (partial derivative)kısmi türev (partial derivative): Birden çok değişkenli bir fonksiyonun, öbür değişkenler sabit tutulup yalnızca bir değişkene göre alınan türevi. yerel türev (local derivative) diyelim. Yerel türev yalnızca o işleme bağlıdır; graph’ın geri kalanından habersizdir. Dört işlemimizin yerel türevleri, forward pass’te bulunan değerlerle şöyledir:

p=wx∂p∂w=x=3,∂p∂x=w=2z=p+b∂z∂p=1,∂z∂b=1e=z−y∂e∂z=1,∂e∂y=−1L=e2∂L∂e=2e=−6\begin{aligned} p = wx &\qquad \frac{\partial p}{\partial w} = x = 3, \quad \frac{\partial p}{\partial x} = w = 2 \\ z = p + b &\qquad \frac{\partial z}{\partial p} = 1, \quad \frac{\partial z}{\partial b} = 1 \\ e = z - y &\qquad \frac{\partial e}{\partial z} = 1, \quad \frac{\partial e}{\partial y} = -1 \\ L = e^2 &\qquad \frac{\partial L}{\partial e} = 2e = -6 \end{aligned}

Her yerel türev, bir girdi biraz artınca sonucun ne kadar değiştiğini söyler:

  • Toplama değişikliği olduğu gibi geçirir.
  • Çıkarma, ikinci girdide işareti çevirir.
  • Çarpma, değişikliği diğer girdinin değeriyle büyütür.
  • Kare, hatanın iki katını verir.

Çarpma ve kare, forward pass’te hesaplanan xx, ww ve ee değerlerini kullanır. Geriye doğru hesap bu değerlere ihtiyaç duyar. Bu yüzden forward pass’in değerlerini saklarsın.

Koddaki karşılığı şudur:

x, w, b, y = 3.0, 2.0, 1.0, 10.0e = (w * x + b) - y            # forward pass'ten saklanan değer: -3.0dp_dw, dp_dx = x, w            # p = w * xdz_dp, dz_db = 1.0, 1.0        # z = p + bde_dz, de_dy = 1.0, -1.0       # e = z - ydL_de = 2 * e                  # L = e ** 2print(dp_dw, dp_dx)   # → 3.0 2.0print(dL_de)          # → -6.0
Kısa sorux = 3 ve w = 2 iken p = w·x çarpımının w'ye göre yerel türevi kaçtır?

Cevabı göster

3. Çarpmada bir girdiye göre yerel türev, öbür girdinin değeridir. w'ye göre türev x = 3 olur.

Gradyanı geriye taşı

Bir düğümün gradyanı, loss’un o düğümün değerine göre türevidir. Değer biraz artarsa LL‘nin ne kadar değişeceğini söyler. Yukarıdaki grafikte önce İleri’ye, sonra Geri’ye bas.

Kenarlar LL‘den başlayarak girdilere doğru sırayla yanar ve her düğümün gradyanı yazılır. Bu sayıları birlikte çıkaralım.

Chain rule

Önce tek bir yola bakalım. LL‘yi yalnızca ww‘nin fonksiyonu olarak yazarsak iç içe iki fonksiyon elde ederiz:

  • iç fonksiyon: g(w)=xw+b−yg(w) = xw + b - y;
  • dış fonksiyon: f(g)=g2f(g) = g^2.

İç içe fonksiyonların türevini chain rule verir:

∂L∂w=f′(g(w)) g′(w)\frac{\partial L}{\partial w} = f'(g(w))\,g'(w)

Dış fonksiyonun türevini, iç fonksiyonun değerinde hesapla ve iç fonksiyonun türeviyle çarp. w=2w = 2 için üç adım yeter:

  1. İç fonksiyonun değerini bul: g=3⋅2+1−10=−3g = 3 \cdot 2 + 1 - 10 = -3.
  2. Dış fonksiyonun türevini bu değerde hesapla: f′(g)=2g=−6f'(g) = 2g = -6.
  3. İç fonksiyonun türevini hesapla: g′(w)=x=3g'(w) = x = 3.

Son iki sayının çarpımı gradyanı verir:

∂L∂w=f′(g) g′(w)=−6⋅3=−18\frac{\partial L}{\partial w} = f'(g)\,g'(w) = -6 \cdot 3 = -18
x, w, b, y = 3.0, 2.0, 1.0, 10.0g = x * w + b - y     # iç fonksiyonun değeridf = 2 * g            # dış türev, g'de hesaplanırdg = x                # iç türevprint(g, df, dg)      # → -3.0 -6.0 3.0print(df * dg)        # → -18.0wrong = 2 * w * dg    # yanlış: dış türev g'de değil w'de hesaplandıprint(wrong)          # → 12.0

Dikkat

En sık yapılan hata, dış fonksiyonun türevini iç fonksiyonun değerinde değil, ww‘nin kendisinde hesaplamaktır. f′(g)f'(g) için g=−3g = -3 gerekir; w=2w = 2‘yi yerine koyarsan yanlış bir sayı çıkar.

Yol boyunca çarpma

Yol uzarsa çarpan sayısı artar. LL‘den ww‘ye giden yol L←e←z←p←wL \leftarrow e \leftarrow z \leftarrow p \leftarrow w‘dir ve gradyan, yol boyunca yerel türevlerin çarpımıdır:

∂L∂w=∂L∂e⋅∂e∂z⋅∂z∂p⋅∂p∂w\frac{\partial L}{\partial w} = \frac{\partial L}{\partial e}\cdot\frac{\partial e}{\partial z}\cdot\frac{\partial z}{\partial p}\cdot\frac{\partial p}{\partial w}

Değerleri koyunca yukarıdaki −18-18 çıkar. xx‘e giden yol da aynı −6⋅1⋅1-6 \cdot 1 \cdot 1 ile başlar. Ortak parçayı her yol için yeniden hesaplamak gereksizdir.

import numpy as np# L <- e <- z <- p <- w yolundaki yerel türevlerpath_w = [-6.0, 1.0, 1.0, 3.0]    # dL/de, de/dz, dz/dp, dp/dwpath_x = [-6.0, 1.0, 1.0, 2.0]    # son çarpan dp/dx = wprint(np.prod(path_w), np.prod(path_x))   # → -18.0 -12.0

Geri geçiş (backward pass)

Yolları ayrı ayrı çarpmak yerine LL‘den başlar ve her düğümde bir önceki sonucu taşırız. Bu işleme geri geçiş (backward pass) denir.

Kural şudur: bir düğümün gradyanı, onu kullanan düğümün gradyanı ile aradaki yerel türevin çarpımıdır. Kuralın ilk çarpanına, yani onu kullanan düğümün getirdiği gradyana, gelen gradyan (upstream gradient) denir.

Her düğümde gelen gradyanı yerel türevle çarp. LL‘nin kendisine göre türevi 1’dir ve hesap buradan başlar:

  1. LL‘nin gradyanını 1 al.
  2. ee‘nin gradyanını bul: L=e2L = e^2 işleminin yerel türevi 2e=−62e = -6 olduğundan 1⋅(−6)=−61 \cdot (-6) = -6.
  3. zz‘nin gradyanını bul: e=z−ye = z - y işleminde yerel türev 11 olduğundan −6⋅1=−6-6 \cdot 1 = -6.
  4. yy‘nin gradyanını bul: e=z−ye = z - y işleminde yerel türev −1-1 olduğundan −6⋅(−1)=6-6 \cdot (-1) = 6.
  5. pp‘nin gradyanını bul: z=p+bz = p + b işleminde yerel türev 11 olduğundan −6⋅1=−6-6 \cdot 1 = -6.
  6. bb‘nin gradyanını bul: z=p+bz = p + b işleminde yerel türev 11 olduğundan −6⋅1=−6-6 \cdot 1 = -6.
  7. ww‘nin gradyanını bul: p=wxp = wx işleminde yerel türev x=3x = 3 olduğundan −6⋅3=−18-6 \cdot 3 = -18.
  8. xx‘in gradyanını bul: p=wxp = wx işleminde yerel türev w=2w = 2 olduğundan −6⋅2=−12-6 \cdot 2 = -12.
x, w, b, y = 3.0, 2.0, 1.0, 10.0e = (w * x + b) - y        # forward pass: -3.0dL = 1.0                   # L'nin gradyanıde = dL * (2 * e)          # kare: yerel türev 2edz = de * 1.0              # çıkarma, z tarafıdy = de * -1.0             # çıkarma, y tarafıdp = dz * 1.0              # toplama, p tarafıdb = dz * 1.0              # toplama, b tarafıdw = dp * x                # çarpma: öbür girdinin değeridx = dp * wprint(de, dz, dy)          # → -6.0 -6.0 6.0print(dp, db)              # → -6.0 -6.0print(dw, dx)              # → -18.0 -12.0

Toplama ve çarpmada gradyan

Toplama ve çarpma gradyanı şöyle dağıtır:

  • Toplama, gelen gradyanı iki girdisine olduğu gibi geçirir.
  • Çarpma, her girdiye öbür girdinin değeriyle çarpılmış hâlini verir: ww‘nin gradyanı xx‘i, xx‘inki ww‘yi taşır.

Gradyanı okumak

ww ve bb parametredir; gradyanları, gradient descent’in ihtiyaç duyduğu sayılardır. xx ile yy veridir ve değişmez.

ww‘nin gradyanı −18-18‘dir ve negatiftir. ww‘yi artırırsan loss azalır. Gradient descent ww‘yi tam bu yönde iter.

Grafiğe dön ve ww‘yi değiştir:

  • w=3w = 3 yap: tahmin 3⋅3+1=103 \cdot 3 + 1 = 10 olur ve target’a eşitlenir. LL dışındaki her gradyan 2e2e çarpanını taşır. e=0e = 0 olunca hepsi sıfırlanır ve ayarlanacak bir şey kalmaz.
  • w=1w = 1 yap: e=−6e = -6 olur ve ww‘nin gradyanı −36-36 çıkar.
def dL_dw(w, x=3.0, b=1.0, y=10.0):    return 2 * (w * x + b - y) * xprint(dL_dw(3.0), dL_dw(1.0))   # → 0.0 -36.0

Tek bir backward pass bütün gradyanları forward pass’e yakın bir maliyetle verir. Sayısal gradyan her parametre için loss’u iki kez hesaplardı.

Uzun yollarda gradyan küçülür

Yolun uzunluğunun da bir bedeli var: yol uzadıkça çarpan sayısı artar ve çarpanlar 1’den küçükse çarpım hızla küçülür. Önceki derste gördüğün kaybolan gradyanın (vanishing gradient)kaybolan gradyan (vanishing gradient): Gradyanın, türevi küçük aktivasyonlardan geçerek girişe yakın katmanlara ulaşana kadar neredeyse sıfıra inmesi; o katmanlar bu yüzden çok yavaş öğrenir ya da hiç öğrenemez. nedeni budur.

factors = [0.5] * 10              # 10 yerel türev, her biri 0.5print(np.prod(factors))           # → 0.0009765625
Kısa sorup = w·x çarpımında w = 2, x = 3 ve p'nin gradyanı -6 iken w'nin gradyanı kaçtır?

Cevabı göster

-18. w'nin gradyanı, gelen gradyan ile öbür girdinin değerinin çarpımıdır: -6 · 3 = -18.

Bir değer birden çok yerde kullanılınca

Buraya kadar her düğümü tek bir düğüm kullanıyordu. Bir değer birden çok yerde kullanılınca graph dallanır.

Dallanan bir graph

Küçük bir örnekte f=u v+uf = u\,v + u, u=2u = 2 ve v=3v = 3 olsun. Forward pass’te q=uv=6q = uv = 6 ve f=q+u=8f = q + u = 8 çıkar.

uu iki yerde kullanılır, çarpımda ve toplamda. Bu yüzden ff‘den uu‘ya iki yol gider:

  • f←q←uf \leftarrow q \leftarrow u: yerel türevlerin çarpımı 1⋅v=31 \cdot v = 3.
  • f←uf \leftarrow u: yerel türev 11.

İki katkının toplamı gradyanı verir:

∂f∂u=3+1=4\frac{\partial f}{\partial u} = 3 + 1 = 4

Sonucu başka bir yoldan doğrula. f=u (v+1)f = u\,(v + 1) olarak yazarsan türevi v+1=4v + 1 = 4 çıkar. uu biraz arttığında hem çarpım hem toplam değişir ve ff ikisinin etkisini birlikte taşır.

u, v = 2.0, 3.0q = u * v                  # çarpım yoluf = q + u                  # toplam yoluprint(q, f)                # → 6.0 8.0via_q = 1.0 * v            # f <- q <- u: 1 * vdirect = 1.0               # f <- uprint(via_q + direct)      # → 4.0def func(u):               # sayısal kontrol: aynı f    return u * v + uh = 1e-5print(round((func(u + h) - func(u - h)) / (2 * h), 4))   # → 4.0

Yollar arasında topla

Yol boyunca çarp, yollar arasında topla. Bir değerin gradyanı, ona giden bütün yolların katkılarının toplamıdır. Gerçek ağlarda dallanma her yerdedir:

  • Yığındaki (batch) her örnek, aynı weight’i yeniden kullanır.
  • Bir katmandaki (layer) her nöron, aynı girdiyi alır.

İkisiyle de bir sonraki derste, yoğun katmanın geri geçişinde karşılaşacaksın.

Birincisine sayılarla bakalım. İki örnek aynı w=2w = 2‘yi kullanır ve her örnek ww‘ye ayrı bir yol açar:

import numpy as npw = 2.0xs = np.array([3.0, 1.0])       # iki örnek, aynı wys = np.array([10.0, 4.0])# her örneğin loss'u (w * x - y) ** 2; dL/dw = 2 * (w * x - y) * xper_sample = 2 * (w * xs - ys) * xsprint(per_sample)               # → [-24.  -4.]print(per_sample.sum())         # → -28.0
Kısa soruf = u·v + u·u fonksiyonunda u = 2 ve v = 3 iken f'nin u'ya göre gradyanı kaçtır?

Cevabı göster

7. u·v yolundan v = 3 gelir; u·u içinde u iki kez geçtiği için iki yoldan u + u = 4 gelir. Yollardan gelen gradyanlar toplanır: 3 + 4 = 7.

Skaler bir autograd

Binlerce düğümlü bir ağda backward pass’i bilgisayara bırakırsın. Hesap sırasında graph’ı kaydeden ve gradyanları kendiliğinden hesaplayan sisteme otomatik türev alma (automatic differentiation), kısaca autograd denir. PyTorch’ta loss.backward() çağrısı bunu yapar.

Burada onun en küçük hâlini düşüneceksin: yalnızca bir sayı tutan skaler bir nesne. Mini autograd problemindeki Value sınıfı budur.

Value nesnesi

Bir Value şunları taşır:

  • hesabın değerini;
  • gradyanını, başlangıçta 0;
  • girdilerini, yani kendisini hesaplamakta kullanılan düğümleri;
  • gradyanını girdilerine nasıl geçireceğini bilen bir kuralı.

u * v yazdığında yeni bir Value doğar:

  • değeri çarpımdır;
  • girdileri u ile v’dir;
  • kuralı çarpmanın yerel türevleridir.

Aşağıdaki bloklar sırayla aynı Value sınıfını kurar. Her blok öncekinin üstüne metot ekler: class Value(Value): önceki sınıfın bütün metotlarını alır ve yenilerini ekler.

Problemde hepsini tek bir sınıfa yazarsın. Orada __init__ yalnızca data alır; girdileri saklamak için ona bir alan eklersin.

İlk blok nesnenin dört parçasını tanımlar:

class Value:    def __init__(self, data, prev=()):        self.data = float(data)         # hesabın değeri        self.grad = 0.0                 # gradyan, başta 0        self._prev = prev               # girdiler        self._backward = lambda: None   # gradyanı girdilere geçiren kuralu = Value(2.0)print(u.data, u.grad)   # → 2.0 0.0

Üç işlem ve yerel türevleri

Üç işlem yetiyor:

İşlem∂c/∂u\partial c / \partial u∂c/∂v\partial c / \partial v
c=u+vc = u + v1111
c=u vc = u\,vvvuu
c=tanh⁡uc = \tanh u1−c21 - c^2

Her kural yalnızca düğümün kendi girdilerini ve sonucunu kullanır. tanh⁡\tanh‘ın kuralı sonuç cc‘yi kullanır, bu yüzden forward pass’te bulunan değer yeter. Bu türevi önceki dersten biliyorsun.

Her işlem yeni bir Value döndürür ve kuralını _backward içine koyar. Gradyanı girdilere += ile eklediğine dikkat et:

import mathclass Value(Value):                 # önceki Value'ya metot ekler    def __add__(self, other):        out = Value(self.data + other.data, (self, other))        def _backward():            self.grad += out.grad                # yerel türev 1            other.grad += out.grad        out._backward = _backward        return out    def __mul__(self, other):        out = Value(self.data * other.data, (self, other))        def _backward():            self.grad += other.data * out.grad   # yerel türev: öbür girdi            other.grad += self.data * out.grad        out._backward = _backward        return out    def tanh(self):        out = Value(math.tanh(self.data), (self,))        def _backward():            self.grad += (1 - out.data ** 2) * out.grad   # 1 - c ** 2        out._backward = _backward        return out

Öbür işlemler bu üçünden kurulur:

  • u−vu - v, vv‘yi −1-1 ile çarpıp uu‘ya eklemektir.
  • Kare almak, bir değeri kendisiyle çarpmaktır (e⋅ee \cdot e).
y = Value(10.0)e = Value(7.0) + y * Value(-1.0)   # çıkarma: (-1) ile çarp, ekleL = e * e                          # kare: kendisiyle çarpprint(e.data, L.data)              # → -3.0 9.0

Topolojik sıra (topological order)

Backward pass’te sıra önemlidir. Bir düğümün gradyanı, onu kullanan bütün düğümler gradyan katkılarını ilettikten sonra tamamlanır. Daha erken davranırsan eksik bir toplamı girdilerine geçirirsin.

Doğru sıra, her düğümün girdilerinden sonra geldiği bir listedir. Buna topolojik sıra denir. Bu dersin graph’ı için bir tanesi x,w,p,b,z,y,e,Lx, w, p, b, z, y, e, L sırasıdır.

Topological order tek değildir (xx ile ww yer değiştirebilir), ama pp hiçbir zaman xx‘ten ya da ww‘dan önce gelemez. Forward pass listeyi soldan sağa yürür. Backward pass sağdan sola yürür.

Listeyi LL‘den başlayıp girdilere inerek kurarsın:

  • Bir düğümü ancak girdilerinin hepsini listeye ekledikten sonra ekle.
  • Zaten eklediğin düğümü atla.
def topological_order(root):    order, visited = [], set()    def visit(v):        if v not in visited:            visited.add(v)            for child in v._prev:                visit(child)            order.append(v)          # girdilerinden sonra gelir    visit(root)    return orderu, v = Value(2.0), Value(3.0)f = u * v + uprint([n.data for n in topological_order(f)])   # → [2.0, 3.0, 6.0, 8.0]

Backward pass’in üç adımı

Backward pass üç adımdır:

  1. Çıkıştan başlayarak topological order’ı kur.
  2. Çıkışın gradyanını 1 yap; öbür düğümlerin gradyanı 0 olarak başlar.
  3. Listeyi sondan başa dolaş: her düğüm, kendi gradyanını yerel türevlerle çarpıp girdilerinin gradyanına ekler.

Üç adım Value’ya tek bir metot olarak girer. Aynı f = u·v + u ifadesini deneyelim:

class Value(Value):                 # önceki Value'ya metot ekler    def backward(self):        self.grad = 1.0                           # çıkışın gradyanı 1        for v in reversed(topological_order(self)):            v._backward()                         # gradyanı girdilere ekleu, v = Value(2.0), Value(3.0)f = u * v + uf.backward()print(f.data, u.grad, v.grad)   # → 8.0 4.0 2.0

Dikkat

Gradyanı girdiye eklemekle onun üstüne yazmak aynı şey değildir. c=u⋅uc = u \cdot u ve u=3u = 3 olsun.

Çarpmanın iki girdisi de aynı Value’dur ve her biri 33 getirir. Doğru gradyan 3+3=6=2u3 + 3 = 6 = 2u‘dur. Gelen her sayıyı öncekinin üstüne yazarsan yalnızca 33 kalır.

u = Value(3.0)c = u * uc.backward()# += yerine = kullansaydık 3.0 kalırdıprint(u.grad)   # → 6.0

Bir nöronda uçtan uca

Bir nöronla sayılara bakalım: L=(tanh⁡(wx+b)−y)2L = (\tanh(wx + b) - y)^2, x=2x = 2, w=0,5w = 0{,}5, b=0b = 0 ve y=1y = 1. Önce forward pass’i yürüt:

  1. Girdiyi ağırlıkla çarpıp sapmayı ekle: z=wx+b=1z = wx + b = 1.
  2. tanh⁡\tanh uygula: a=tanh⁡z≈0,7616a = \tanh z \approx 0{,}7616.
  3. Hedef değeri çıkar: e=a−y≈−0,2384e = a - y \approx -0{,}2384.
  4. Hatanın karesini al: L=e2≈0,0568L = e^2 \approx 0{,}0568.

Sonra backward pass’i yürüt:

  1. Kare kuralıyla aa‘nın gradyanını bul: 2e≈−0,47682e \approx -0{,}4768.
  2. zz‘nin gradyanını bul: tanh⁡\tanh‘ın yerel türevi 1−a2≈0,42001 - a^2 \approx 0{,}4200 olduğundan sonuç ≈−0,2002\approx -0{,}2002 olur.
  3. Toplama, zz‘nin gradyanını bb‘ye ve çarpıma olduğu gibi geçirir: ikisi de ≈−0,2002\approx -0{,}2002 alır.
  4. Çarpmada gradyanı ww‘ye xx ile, xx‘e ww ile çarparak ver.

Üç gradyan şöyle çıkar:

∂L∂w≈−0,4005,∂L∂x≈−0,1001,∂L∂b≈−0,2002\frac{\partial L}{\partial w} \approx -0{,}4005, \qquad \frac{\partial L}{\partial x} \approx -0{,}1001, \qquad \frac{\partial L}{\partial b} \approx -0{,}2002

Sayılar dört basamağa yuvarlandı. Yuvarlanmış değerleri elle çarparsan son basamakta bir fark çıkabilir. Kendi Value’nu bu sayılarla sınayabilirsin:

x, w = Value(2.0), Value(0.5)b, y = Value(0.0), Value(1.0)z = w * x + ba = z.tanh()e = a + y * Value(-1.0)        # a - yL = e * eL.backward()print(round(L.data, 4))                                    # → 0.0568print(round(w.grad, 4), round(x.grad, 4), round(b.grad, 4))# → -0.4005 -0.1001 -0.2002
Kısa soruBackward pass'te bir düğüm, gradyanını girdilerine onu kullanan bütün düğümlerden önce iletirse ne olur?

Cevabı göster

Girdilerine eksik bir gradyan geçer; sonradan gelen katkılar girdilere hiç ulaşmaz. Her düğüm gradyanını girdilerine bir kez iletir. O anda katkıların hepsi gelmemişse eksik toplamı iletir. Topolojik sıra bu yüzden her düğümü, onu kullanan bütün düğümlerden sonra işler.

Özet

  • Computational graph, hesabı tek işlemlik düğümlere böler. Forward pass değerleri girdilerden loss’a doğru hesaplar. Backward pass gradyanları loss’tan girdilere doğru hesaplar.
  • Bir yolda yerel türevleri çarparsın. Bir değer birden çok yerde kullanılıyorsa yollardan gelen gradyanları toplarsın.
  • Skaler bir autograd, her düğümde değeri, gradyanı, girdileri ve yerel türev kuralını tutar.
  • Autograd gradyanları topological order’ın tersinde, üstüne yazmadan ekleyerek hesaplar.

Terimler

TürkçeEnglish
gradyan inişigradient descent
kayıploss
gradyangradient
sayısal gradyannumerical gradient
geri yayılımbackpropagation
hesaplama grafiğicomputational graph
zincir kuralıchain rule
örneksample
ağırlıkweight
sapmabias
hedef değertarget
tahminprediction
ortalama kare hatamean squared error, MSE
düğümnode
kenaredge
ileri geçişforward pass
türevderivative
kısmi türevpartial derivative
yerel türevlocal derivative
geri geçişbackward pass
gelen gradyanupstream gradient
kaybolan gradyanvanishing gradient
yığınbatch
katmanlayer
otomatik türev almaautomatic differentiation
autogradautograd
topolojik sıratopological order

Şimdi sıra sende

  1. Zincir kuralıZorluk: KolayBaşla →
  2. Mini autogradZorluk: ZorBaşla →
İlk problem: Zincir kuralı →

Notların

Metinden bir bölüm seçip Not al'a basarak da ekleyebilirsin.

Geri bildirim

Tür