Yoğun katmanın geri geçişi ve gradyan kontrolü
Bir ağın her katmanı (layer), kaybın (loss) kendi parametrelerine 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ü. bilmek zorundadır. Yoğun katmanın (dense layer)yoğun katman (dense layer): Her girdinin her nörona bağlandığı katman: girdiler bir ağırlık matrisiyle çarpılır, sapma eklenir ve sonuç bir aktivasyon fonksiyonundan geçer. geri geçişi (backward pass) bu gradyanları üç kısa formülle verir. Yanlış yazılmış bir geri geçiş hata vermez; bu yüzden gradyanı bağımsız bir yolla da sınarsın.
Bu ders, Geri yayılım dersinin devamıdır. Orada kurduğun kural, yani yol boyunca çarp ve yollar arasında topla, burada matrislere uygulanır. Bu dersin kodu Value sınıfına dayanmaz; yalnızca NumPy kullanır.
Bu derste şunları yapacaksın:
- Yoğun katmanın ileri geçişini (forward pass) ve şekillerini (shape) inceleyeceksin.
- , ve formüllerini türeteceksin.
- Gradyanı sayısal gradyanla (numerical gradient) kontrol edeceksin.
- Göreli hatayı (relative error) yorumlayacaksın.
Dense layer’ın backward pass’i
Bir dense layer’ın backward pass’i üç gradyan çıkarır ve üçü de aynı kuraldan gelir: yol boyunca çarp, yollar arasında topla.
Layer ve shape’ler
Layer, örneklik bir yığın (batch) için hesaplar. Parametreleri 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. matrisi ile sapma (bias)sapma (bias): Girdilerle çarpılmadan çıktıya eklenen parametre. Doğrusal modelde bütün öznitelikler 0 iken modelin verdiği tahmindir. vektörü ‘dir.
‘in her satırı bir örneği, her sütunu bir özniteliği (feature)öznitelik (feature): Bir örneği tanımlayan ve modele girdi olarak verilen ölçülebilir değerlerden biri. temsil eder. Matrislerin shape’leri şöyledir:
- : ;
- : ;
- : elemanlı;
- : .
Elemanlara inersek ‘nin her elemanı şöyle olur:
Küçük bir örnek: , ve , yani tek nöronlu bir layer. olsun:
Forward pass verir.
import numpy as npX = np.array([[1., 2., 0.], [3., 1., 2.]]) # (2, 3): 2 örnek, 3 featureW = np.array([[1.], [-1.], [2.]]) # (3, 1): d_in x d_outb = np.array([1.]) # (1,): d_out elemanZ = X @ W + bprint(Z.ravel(), Z.shape) # → [0. 7.] (2, 1)
dZ: layer’a ulaşan gradyan
Backward pass’te layer’a, loss’un layer’ın çıktısına göre gradyanı gelir. Buna gelen gradyan (upstream gradient) denir. Bu gradyan ‘dir ve ile aynı shape’tedir:
Layer’ın işi üç gradyan çıkarmaktır:
- : ‘nin gradyanı, yani bir parametrenin gradyanı;
- : ‘nin gradyanı, yani bir parametrenin gradyanı;
- : bir önceki layer’a gidecek gradyan.
Örnekte hedef değerler (target) ve loss ortalama kare hata (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. ise layer’a gelen gradyan şudur:
y = np.array([[1.], [4.]]) # target'larn = len(X)dZ = 2 / n * (Z - y) # MSE'nin gradyanıprint(dZ.ravel()) # → [-1. 3.]
dW: weight gradyanı
‘nin . sütunundaki her örnek, weight’ini kullanır. Bu yüzden yoldan gradyan gelir. Yollar arasında toplama kuralını Geri yayılım dersinden biliyorsun. Her yoldaki yerel türev olduğundan katkılar toplanır:
Örnekte:
dW = X.T @ dZ # matris çarpımıprint(dW.ravel()) # → [8. 1. 6.]dW_loop = np.zeros_like(W) # aynı sayı, N yolun toplamıylafor i in range(n): for k in range(3): dW_loop[k, 0] += X[i, k] * dZ[i, 0]print(dW_loop.ravel()) # → [8. 1. 6.]
iken , Doğrusal regresyon dersindeki gradyanın aynısıdır:
Orada yerine, hata vektörü ‘nin katı vardı.
y_hat = Z.ravel() # d_out = 1: tahmin vektörüerror = y_hat - y.ravel()print(2 / n * X.T @ error) # → [8. 1. 6.]
db: bias gradyanı
Her örnek bias’ını da kullanır ve yerel türevi 1’dir:
Burada , ‘nin . satırıdır. Yani , ‘nin sütun toplamlarıdır. Örnekte .
db = dZ.sum(axis=0) # axis=0: örnekler boyunca toplaprint(db) # → [2.]
dX: girdi gradyanı
ise . örneğin bütün çıktılarına, yani nörona gider. içindeki yerel türevi ‘dir:
Örnekte:
dX = dZ @ W.Tprint(dX)# → [[-1. 1. -2.]# [ 3. -3. 6.]]
Üç formül de aynı kuralın matris çarpımı (matrix product) diliyle yazılışıdır. Toplamlar, bir değerin birden çok yerde kullanılmasından gelir.
, ve sürgülerini oynat ve her gradyanın shape’ine bak.
vurgulu kenarlar: çarpımda birbirini götüren iç boyutlar
N = 4 · d_in = 3 · d_out = 2 · dZ, Z ile aynı şekilde: (4, 2) dW (3, 2) = W (3, 2) ✓ · db (2,) = b (2,) ✓ · dX (4, 3) = X (4, 3) ✓
Shape kuralı
Her gradyanın shape’i, gradyanı alınan değişkeninkiyle aynıdır. Bu, formülleri hatırlamanın ve kontrol etmenin en kolay yoludur. Şöyle kontrol edersin:
- : olduğundan çıkar, tıpkı gibi.
- : olduğundan verir, tıpkı gibi.
() ile, () ile aynı shape’te. Shape’ler uyuşuyor.
print(dW.shape, W.shape) # → (3, 1) (3, 1)print(dX.shape, X.shape) # → (2, 3) (2, 3)print(db.shape, b.shape) # → (1,) (1,)
dense_backward fonksiyonu
Üç formül tek bir fonksiyona sığar. Yoğun katman geri geçişi probleminin istediği fonksiyon budur:
def dense_backward(X, W, dZ): dX = dZ @ W.T # (N, d_out) @ (d_out, d_in) dW = X.T @ dZ # (d_in, N) @ (N, d_out) db = dZ.sum(axis=0) # örnekler boyunca topla return dX, dW, dbdX, dW, db = dense_backward(X, W, dZ)print(dW.ravel(), db) # → [8. 1. 6.] [2.]print(dX.shape) # → (2, 3)
Forward pass’ten saklananlar
Backward pass’in forward pass’ten neye ihtiyaç duyduğuna dikkat et:
- için gerekir.
- için gerekir.
- gerekmez, çünkü toplamanın yerel türevi 1’dir.
Layer bu yüzden forward pass’te ‘i saklar:
class Dense: def __init__(self, W, b): self.W, self.b = W, b def forward(self, X): self.X = X # backward için sakla return X @ self.W + self.b def backward(self, dZ): return dense_backward(self.X, self.W, dZ)layer = Dense(W, b)layer.forward(X)dX, dW, db = layer.backward(dZ)print(dW.ravel()) # → [8. 1. 6.]
Aktivasyon fonksiyonu ve zincir
Layer’ın ardında bir aktivasyon fonksiyonu (activation function)aktivasyon fonksiyonu (activation function): Bir nöronun ağırlıklı toplamını çıktıya çeviren ve ağın doğrusal olmayan fonksiyonlar öğrenebilmesini sağlayan fonksiyon; en bilinenleri sigmoid, tanh, ReLU ve GELU'dur. varsa, eleman bazında (element-wise) bir işlemdir. Her elemanın yerel türevi ‘dir. Böylece olur (, element-wise çarpımdır).
Z = np.array([[0.0, 1.0]])A = np.tanh(Z)dA = np.array([[2.0, -1.0]])dZ = dA * (1 - A ** 2) # dA ⊙ f'(Z), f = tanhprint(np.round(dZ, 4).tolist()) # → [[2.0, -0.42]]
Bir layer’ın ‘i, bir önceki layer’ın ‘sı olur. Backward pass layer’ları sondan başa böyle dolaşır:
rng = np.random.default_rng(0)layer1 = Dense(rng.normal(size=(3, 5)), np.zeros(5))layer2 = Dense(rng.normal(size=(5, 2)), np.zeros(2))X = rng.normal(size=(4, 3))A1 = np.tanh(layer1.forward(X)) # layer + tanhZ2 = layer2.forward(A1)dZ2 = np.ones_like(Z2) # gelen gradyan (örnek: L = Z2'nin toplamı)dA1, dW2, db2 = layer2.backward(dZ2) # dX, bir önceki layer'ın dA'sı olurdZ1 = dA1 * (1 - A1 ** 2) # tanh'ın yerel türevidX, dW1, db1 = layer1.backward(dZ1)print(dW2.shape, dW1.shape, dX.shape) # → (5, 2) (3, 5) (4, 3)
Bu zinciri ileride tam bir ağda döngüye koyacaksın.
Gradyan doğru mu?
Backpropagation’ın sinsi yanı, yanlış yazılmış bir backward pass’in hata vermemesidir. Loss yine bir sayı olarak çıkar ve model yine bir şeyler öğrenir. Ama kötü öğrenir ve nedenini bulmak zordur.
Bu yüzden gradyanı bağımsız bir yolla sınarsın. Buna gradyan kontrolü (gradient check) denir.
Merkezi fark
Bağımsız yol, Gradyan inişi dersinden tanıdığın merkezi farktır (central difference). Şöyle uygularsın:
- Bir parametreyi kadar artır ve loss’u hesapla.
- Aynı parametreyi kadar azalt ve loss’u yine hesapla.
- İki loss’un farkını ‘ye böl.
Bütün parametreler için aynı hesap, Sayısal gradyan probleminin fonksiyonudur. Kontrol, bu sayısal gradyanı backward pass’in verdiği analitik gradyanla (analytic gradient) karşılaştırır.
Geri yayılım dersinin sonundaki nöronda ‘nin gradyanı çıkmıştı. ile merkezi fark da verir. ‘ın yerel türevini unutup ‘nin gradyanını bulsaydık, iki sayı belirgin biçimde ayrışırdı.
def loss(w): # nöron: x = 2, b = 0, y = 1 return (np.tanh(w * 2.0) - 1.0) ** 2w, h = 0.5, 1e-5numeric = (loss(w + h) - loss(w - h)) / (2 * h)print(round(numeric, 4)) # → -0.4005e = np.tanh(w * 2.0) - 1.0# tanh'ın türevi unutulmuş: dL/dw = 2e * xprint(round(2 * e * 2.0, 4)) # → -0.9536
Göreli hata
Farkı tek bir sayıya indirmek için göreli hata (relative error) kullanırsın. Analitik gradyan , sayısal gradyan olsun:
, ‘nin uzunluğudur: bütün elemanlarının karelerinin toplamının karekökü. NumPy’da np.linalg.norm(g) hesaplar; gibi bir matriste de bütün elemanları tek bir liste gibi sayar.
Pay, iki gradyanın ne kadar ayrıldığını verir. Payda, gradyanların büyüklüğünü verir. Göreli hata, farkı gradyanların kendi ölçeğine göre ölçer.
Mutlak fark yanıltır. Aşağıdaki iki durumda mutlak fark aynıdır, , ama göreli hata çok farklıdır:
| Gradyanlar | Mutlak fark | Göreli hata |
|---|---|---|
| ve | yaklaşık | |
| ve | yaklaşık |
def relative_error(g_a, g_n): g_a, g_n = np.asarray(g_a, float), np.asarray(g_n, float) return np.linalg.norm(g_a - g_n) / (np.linalg.norm(g_a) + np.linalg.norm(g_n))print(f"{relative_error([1e4], [1e4 + 1e-4]):.0e}") # → 5e-09print(round(relative_error([1e-4], [2e-4]), 2)) # → 0.33
gradient_check fonksiyonu
Sayısal gradyanı ve göreli hatayı tek fonksiyonda birleştirirsin. Gradyan kontrolü probleminin istediği fonksiyon budur:
def gradient_check(f, grad_f, x, h=1e-5): x = np.array(x, dtype=float) analytic = np.asarray(grad_f(x), dtype=float) numeric = np.zeros_like(x) for i in np.ndindex(x.shape): old = x[i] x[i] = old + h f_plus = f(x) x[i] = old - h f_minus = f(x) x[i] = old # değeri geri koy numeric[i] = (f_plus - f_minus) / (2 * h) denominator = np.linalg.norm(analytic) + np.linalg.norm(numeric) if denominator == 0: return 0.0 # iki gradyan da sıfır return float(np.linalg.norm(analytic - numeric) / denominator)
Nöronumuzda göreli hata yaklaşık çıkar. ‘ın türevini unutmuş olsaydık yaklaşık çıkardı.
w, x, b, y = 0.5, 2.0, 0.0, 1.0a = np.tanh(w * x + b)e = a - ydz = 2 * e * (1 - a ** 2) # tanh'ın yerel türeviyleg_ok = np.array([dz * x, dz * w, dz]) # dL/dw, dL/dx, dL/dbg_bug = 2 * e * np.array([x, w, 1.0]) # tanh'ın türevi unutulmuşdef neuron_loss(p): # p = [w, x, b] return (np.tanh(p[0] * p[1] + p[2]) - y) ** 2p = np.array([w, x, b])print(f"{gradient_check(neuron_loss, lambda _: g_ok, p):.0e}") # → 3e-10print(round(gradient_check(neuron_loss, lambda _: g_bug, p), 2)) # → 0.41
Göreli hatayı yorumlamak
Kabaca bir kural olarak, float64’te ve yaklaşık iken şunlar geçerlidir:
- Doğru bir gradyanın göreli hatası ‘nin çok altında kalır.
- ‘ün üstü büyük olasılıkla bir hatadır.
- İkisinin arası şüphelidir.
def verdict(rel): if rel < 1e-7: return "doğru" if rel > 1e-3: return "büyük olasılıkla hata" return "şüpheli"print(verdict(3e-10)) # → doğruprint(verdict(2e-5)) # → şüpheliprint(verdict(0.41)) # → büyük olasılıkla hata
Dense layer’ı kontrol et
Aşağıda dense_backward’ın üç çıktısı ve bir de yanlış yazılmış sınanıyor. Yanlış sürüm ‘yi bir kez daha ‘e böler:
X = np.array([[1., 2., 0.], [3., 1., 2.]])W = np.array([[1.], [-1.], [2.]])b, y = np.array([1.]), np.array([[1.], [4.]])dZ = 2 / len(X) * (X @ W + b - y)dX, dW, db = dense_backward(X, W, dZ)loss_W = lambda W_: np.mean((X @ W_ + b - y) ** 2)loss_X = lambda X_: np.mean((X_ @ W + b - y) ** 2)loss_b = lambda b_: np.mean((X @ W + b_ - y) ** 2)print(gradient_check(loss_W, lambda _: dW, W) < 1e-7) # → Trueprint(gradient_check(loss_X, lambda _: dX, X) < 1e-7) # → Trueprint(gradient_check(loss_b, lambda _: db, b) < 1e-7) # → Trueprint(round(gradient_check(loss_W, lambda _: dW / 2, W), 4)) # → 0.3333
Kontrol ederken dikkat
Kontrolü yaparken şunlara dikkat et:
- Küçük bir girdide ve float64’te çalıştır. Float32’nin yuvarlama hatası ölçüyü bozar.
- Her parametreyi kontrol et: doğru çıkarken yanlış olabilir.
- ReLU gibi köşesi olan fonksiyonlarda girdi köşeye çok yakınsa merkezi fark yanlış çıkabilir. Bu bir backward pass hatası değildir.
- İki gradyan da sıfırsa payda sıfır olur. Bu durumu ayrıca ele almak gerekir.
Kontrol her parametre için iki forward pass ister. Bu yüzden onu eğitimde değil, kodu yazarken bir kez çalıştırırsın.
Özet
- Dense layer’ın backward pass’i üç formüldür: , ve , ‘nin sütun toplamıdır.
- Her gradyanın shape’i, gradyanı alınan değişkeninkiyle aynıdır.
- Backward pass, forward pass’ten ve ‘yi ister; layer ‘i forward pass’te saklar.
- Gradient check, analitik gradyanı merkezi farkla bulunan sayısal gradyanla karşılaştırır ve farkı göreli hatayla ölçer.
- Göreli hata ‘nin altındaysa gradyan doğrudur; ‘ün üstündeyse büyük olasılıkla bir hata vardır.
Terimler
| Türkçe | English |
|---|---|
| katman | layer |
| kayıp | loss |
| gradyan | gradient |
| yoğun katman | dense layer |
| geri geçiş | backward pass |
| ileri geçiş | forward pass |
| şekil | shape |
| sayısal gradyan | numerical gradient |
| göreli hata | relative error |
| yığın | batch |
| parametre | parameter |
| ağırlık | weight |
| sapma | bias |
| öznitelik | feature |
| gelen gradyan | upstream gradient |
| hedef değer | target |
| ortalama kare hata | mean squared error, MSE |
| matris çarpımı | matrix product |
| aktivasyon fonksiyonu | activation function |
| eleman bazında | element-wise |
| gradyan kontrolü | gradient check |
| merkezi fark | central difference |
| analitik gradyan | analytic gradient |
Şimdi sıra sende
Ders tamamlandı. Sıradaki: ilk problem.
İlk problem: Yoğun katman geri geçişi →Notların
Metinden bir bölüm seçip Not al'a basarak da ekleyebilirsin.
Bu derse dair söylemek istediğin bir şey var mı?