Ana içeriğe geç

Orta14 dkA — Sinir Ağı Temelleri

Geri yayılım

Önkoşul:İleri yayılım

Kanca

  1. derste bir ağın nasıl TAHMİN ürettiğini gördük (ileri yayılım). Ama bir ağ nasıl ÖĞRENİYOR? Yanlış bir tahmin yaptığında, hangi ağırlığın ne kadar “suçlu” olduğunu nasıl buluyor? Cevap, modern derin öğrenmenin kalbi olan tek bir fikirde saklı: zincir kuralı.

Sezgi

Geri yayılım, kayıptan başlayıp ağın İÇİNDEN GERİYE doğru, her ağırlığın kayba ne kadar katkıda bulunduğunu (gradyanını) hesaplar. Bu, matematikteki ZİNCİR KURALININ (bir sonraki “ek matematik” bölümünde detaylandıracağız) doğrudan bir uygulamasıdır: kayıp çıktıya bağlı, çıktı gizli katmana bağlı, gizli katman ağırlıklara bağlı — bu zincirin her halkasını tek tek çarparak, en baştaki ağırlığa kadar etkiyi taşıyabiliriz.

Gizli nöron 1: z₁ = 0.5·1 + 0.3·1 + 0.1 = 0.900 → h₁ = ReLU(z₁) = 0.900

Gizli nöron 2: z₂ = -0.4·1 + 0.6·1 + -0.15 = 0.050 → h₂ = ReLU(z₂) = 0.050

Çıktı nöronu: ŷ = 0.7·0.900 + (-0.5)·0.050 + 0.2 = 0.805

Kayıp: L = ½(ŷ − y)² = ½(0.805 − 1)² = 0.0190

Girdiler ağdan SIRAYLA (soldan sağa) geçiyor: önce gizli katman, sonra çıktı, sonra kayıp.

“Geri yayılım” sekmesine geç — her satır, bir öncekinin sonucunu KULLANIYOR, ama bu sefer SAĞDAN SOLA. Kayıptan başlayıp önce çıktı katmanına, sonra gizli katmana, en sonunda her bir ağırlığa kadar geri gidiyoruz.

Mekanizma

Aynı 2-2-1 ağla, 3. dersteki ileri yayılımı hatırlayalım:

İleri yayılım hatırlatması

z1 = x @ W1 + b1
h1 = relu(z1)
z_cikti = h1 @ W2 + b2
y_tahmin = z_cikti
kayip = 0.5 * (y_tahmin - y_hedef) ** 2
print(f"İleri yayılım (3. dersten): ŷ={y_tahmin:.3f}, kayıp={kayip:.4f}")
İleri yayılım (3. dersten): ŷ=0.805, kayıp=0.0190

Şimdi geri yayılımı elle, zincir kuralıyla adım adım hesaplayalım:

Zincir kuralıyla elle geri yayılım

# Geri yayılım: ZİNCİR KURALINI kullanarak, kayıptan başlayıp AĞIRLIKLARA doğru geriye gidiyoruz.
dL_dyhat = y_tahmin - y_hedef                    # dL/dŷ
dL_dh1 = dL_dyhat * W2[0]                         # dL/dh1 = dL/dŷ * dŷ/dh1
dL_dh2 = dL_dyhat * W2[1]
# z1 aslında [z_norn1, z_norn2] -- gizli katmanın İKİ nöronunun ön-aktivasyonu (3. dersten).
dL_dz1 = dL_dh1 * relu_turev(z1[0])               # dL/dz1 = dL/dh1 * dh1/dz1 (ReLU türevi)
dL_dz2 = dL_dh2 * relu_turev(z1[1])

dL_dW2 = dL_dyhat * h1                            # çıktı katmanı ağırlık gradyanları
dL_db2 = dL_dyhat
dL_dW1 = np.outer(x, [dL_dz1, dL_dz2])            # gizli katman ağırlık gradyanları
dL_db1 = np.array([dL_dz1, dL_dz2])

print(f"\ndL/dŷ = {dL_dyhat:.4f}")
print(f"dL/dh = [{dL_dh1:.4f}, {dL_dh2:.4f}]")
print(f"dL/dz (ReLU sonrası) = [{dL_dz1:.4f}, {dL_dz2:.4f}]")
print(f"dL/dW2 (çıktı ağırlıkları) = {dL_dW2.round(4)}")
print(f"dL/dW1 (gizli katman ağırlıkları) =\n{dL_dW1.round(4)}")

dL/dŷ = -0.1950
dL/dh = [-0.1365, 0.0975]
dL/dz (ReLU sonrası) = [-0.1365, 0.0975]
dL/dW2 (çıktı ağırlıkları) = [-0.1755 -0.0097]
dL/dW1 (gizli katman ağırlıkları) =
[[-0.1365  0.0975]
 [-0.1365  0.0975]]

Bu sayılar interaktif bileşendeki değerlerle BİREBİR eşleşiyor. Ama bu gradyanların gerçekten DOĞRU olduğunu nasıl bilebiliriz?

Matematik

Zincir kuralı
Lw11=Ly^y^h1h1z1z1w11\frac{\partial L}{\partial w_{11}} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial h_1} \cdot \frac{\partial h_1}{\partial z_1} \cdot \frac{\partial z_1}{\partial w_{11}}
SembolAnlamı
L/y^\partial L / \partial \hat{y}Kaybın, ÇIKTIYA göre değişimi — zincirin ilk halkası
y^/h1\partial \hat{y} / \partial h_1Çıktının, gizli nörona göre değişimi (bu örnekte wc¸ıktı1w_{\text{çıktı1}})
h1/z1\partial h_1 / \partial z_1Aktivasyonun türevi (ReLU için 0 ya da 1)
z1/w11\partial z_1 / \partial w_{11}Ön-aktivasyonun, ağırlığa göre değişimi (bu örnekte x1x_1)

Her halka, KENDİ yerel türevini hesaplar; zincir kuralı bunları çarparak, uzak bir ağırlığın nihai kayba olan TOPLAM etkisini bulur. Ağ ne kadar derin olursa olsun, bu zincir sadece uzar — mantık aynı kalır.

Matematik tazelemeZincir kuralı (backprop'un kalbi)

Kod

Elle türettiğimiz gradyanları, BAĞIMSIZ bir yöntemle doğrulayalım — sayısal gradyan kontrolü:

Sayısal gradyan kontrolü

# Elle türettiğimiz gradyanların DOĞRU olduğunu nasıl bilebiliriz? "Sayısal gradyan kontrolü":
# bir ağırlığı ÇOK KÜÇÜK oynat, kaybın ne kadar değiştiğini ölç, analitik gradyanla karşılaştır.
def kayip_hesapla(W1_, b1_, W2_, b2_):
    z1_ = x @ W1_ + b1_
    h1_ = relu(z1_)
    yhat_ = h1_ @ W2_ + b2_
    return 0.5 * (yhat_ - y_hedef) ** 2

epsilon = 1e-5
W2_test_idx = 0
W2_arti = W2.copy(); W2_arti[W2_test_idx] += epsilon
W2_eksi = W2.copy(); W2_eksi[W2_test_idx] -= epsilon
sayisal_gradyan = (kayip_hesapla(W1, b1, W2_arti, b2) - kayip_hesapla(W1, b1, W2_eksi, b2)) / (2 * epsilon)

print(f"\nAnalitik (elle türetilmiş) dL/dW2[0]: {dL_dW2[0]:.6f}")
print(f"Sayısal (ε kadar oynatıp ölçülen) dL/dW2[0]: {sayisal_gradyan:.6f}")
print(f"Fark: {abs(dL_dW2[0] - sayisal_gradyan):.8f}  -- neredeyse SIFIR, zincir kuralımız DOĞRU.")

Analitik (elle türetilmiş) dL/dW2[0]: -0.175500
Sayısal (ε kadar oynatıp ölçülen) dL/dW2[0]: -0.175500
Fark: 0.00000000  -- neredeyse SIFIR, zincir kuralımız DOĞRU.

Analitik (zincir kuralıyla elle türetilmiş) ve sayısal (ağırlığı gerçekten oynatıp ölçülen) gradyanlar BİREBİR aynı — fark tam olarak sıfır. Çoğu kişi “zincir kuralı sadece teorik bir formül, gerçekte optimizer’ın ‘sihirli’ bir şekilde çalıştığını varsaymalıyız” sanır. Değil, çünkü bu, PyTorch gibi kütüphanelerin arka planda yaptığı TAM OLARAK budur — otomatik türev alma (autograd), zincir kuralını senin yerine, ama aynı matematiksel ilkeyle uygular.

Şimdi bu gradyanları kullanarak GERÇEKTEN bir ağırlık güncelleme adımı atalım:

Gradyan inişiyle ağırlık güncelleme

# Gradyanları bulduktan sonra, ağırlıkları GRADYANIN TERSİ yönünde küçük bir adım kaydırıyoruz (4. ML dersini hatırla).
ogrenme_orani = 0.3
W1_yeni = W1 - ogrenme_orani * dL_dW1
b1_yeni = b1 - ogrenme_orani * dL_db1
W2_yeni = W2 - ogrenme_orani * dL_dW2
b2_yeni = b2 - ogrenme_orani * dL_db2

kayip_once = kayip_hesapla(W1, b1, W2, b2)
kayip_sonra = kayip_hesapla(W1_yeni, b1_yeni, W2_yeni, b2_yeni)
print(f"\nBir gradyan inişi adımından ÖNCE kayıp: {kayip_once:.4f}")
print(f"Bir gradyan inişi adımından SONRA kayıp:  {kayip_sonra:.4f}")
print(f"Tek bir geri yayılım + güncelleme adımı, kaybı {(1 - kayip_sonra / kayip_once) * 100:.1f}% azalttı.")

Bir gradyan inişi adımından ÖNCE kayıp: 0.0190
Bir gradyan inişi adımından SONRA kayıp:  0.0004
Tek bir geri yayılım + güncelleme adımı, kaybı 97.9% azalttı.

Tek bir geri yayılım + güncelleme adımı, kaybı %97.9 azalttı!

Geri yayılım turlarına karşı kaybın hızla sıfıra yaklaştığını gösteren düşen bir eğri grafiği.
Her turda geri yayılım + güncelleme tekrarlandığında, kayıp hızla sıfıra yaklaşıyor -- bu, bir sinir ağının 'öğrenmesinin' tam olarak ne demek olduğu.

Nerede işe yarar

Geri yayılım, EVERY modern sinir ağının eğitiminin temelini oluşturur:

  • PyTorch/TensorFlow’un backward() fonksiyonu. Bu kütüphaneler, senin yazdığın ileri yayılım kodundan otomatik olarak zincir kuralını çıkarır (autograd) — elle türetmene gerek kalmaz.
  • Gradyan kontrolü, gerçek hata ayıklamada kullanılır. Özel bir katman veya kayıp fonksiyonu yazarken, geri yayılım kodunun doğruluğunu bu yöntemle test edebilirsin.
  • “Neden derinlik bazen zorlaşıyor” sorusunun temeli. Bir sonraki derste (kaybolan gradyan), bu zincirin çok UZADIĞINDA neler ters gidebileceğini göreceğiz.

Bu 3 hatayı yaparsın:

  1. Geri yayılımı “sihirli bir kara kutu” sanıp hiç anlamaya çalışmamak — bu derste gördüğümüz gibi, aslında basit bir zincir kuralı uygulamasından ibaret.
  2. Kendi özel katmanını yazarken gradyan kontrolü yapmayı atlamak — bu derste gördüğümüz teknik, gerçek hata ayıklamada hayat kurtarır.
  3. Öğrenme oranını (bu derste 0.3) düşünmeden çok büyük seçmek — 4. ML dersini hatırla, çok büyük adımlar kaybı azaltmak yerine ıraksatabilir.

Kendini test et

1. Geri yayılım, matematiksel olarak hangi ilkeye dayanır?
  1. Rastgele arama
  2. Zincir kuralı -- kayıptan çıktıya, çıktıdan gizli katmana, gizli katmandan ağırlıklara doğru yerel türevleri çarparak etkiyi taşımak (doğru cevap)
  3. En küçük kareler yöntemi
  4. Bayes teoremi

Neden: Geri yayılım, kalkülüsteki zincir kuralını kullanarak, kayıptan en baştaki ağırlıklara kadar her 'halkanın' yerel türevini çarpıp nihai gradyanı hesaplar.

2. Notebook'ta analitik ve sayısal gradyanın BİREBİR aynı çıkması neyi kanıtlıyor?
  1. Hiçbir şey, tesadüf
  2. Zincir kuralıyla elle türetilen gradyan hesaplamasının DOĞRU olduğunu -- iki bağımsız yöntem aynı sonuca ulaştı (doğru cevap)
  3. Kod hatalı çalıştığını
  4. Öğrenme oranının yanlış olduğunu

Neden: Sayısal gradyan kontrolü, ağırlığı gerçekten küçük oynatıp kaybın ne kadar değiştiğini ÖLÇEREK gradyanı bağımsız bir şekilde tahmin eder; bu, zincir kuralıyla türetilen analitik gradyanla eşleştiği için hesaplamanın doğruluğunu kanıtlar.

3. Tek bir geri yayılım + güncelleme adımının kaybı %97.9 azaltması neyi gösteriyor?
  1. Bu her zaman böyle olur, her ağırlık güncellemesi kaybı bu kadar azaltır
  2. Gradyanların doğru yönü gösterdiğini ve öğrenme oranının (bu örnekte) bu adımda etkili bir adım büyüklüğü sağladığını (doğru cevap)
  3. Model artık hiç eğitime ihtiyaç duymuyor
  4. Kod hatalı çalıştı

Neden: Bu dramatik iyileşme, doğru hesaplanan gradyanların kaybı azaltan yönü doğru gösterdiğinin somut bir kanıtı -- ama her problemde bu kadar büyük bir tek-adım iyileşmesi garanti değildir, öğrenme oranına ve probleme bağlıdır.

Özet

Özet

  • Geri yayılım, zincir kuralını kullanarak kayıptan her ağırlığa kadar gradyanı geriye doğru taşır.
  • Her "halka", sadece kendi yerel türevini hesaplar; zincir kuralı bunları çarparak toplam etkiyi bulur.
  • Sayısal gradyan kontrolü (ağırlığı ε kadar oynatıp kaybı ölçmek), analitik gradyanların doğruluğunu bağımsız olarak doğrular.
  • Gradyanlar bulunduktan sonra, ağırlıklar gradyanın TERSİ yönünde küçük adımlarla güncellenir.
  • PyTorch gibi kütüphaneler, bu süreci "autograd" ile otomatikleştirir -- ama arka planda olan matematik tam olarak budur.
Sonraki adım: Kaybolan gradyan problemi →