RNN
Önkoşul:Word embedding (Word2Vec, GloVe)
Kanca
- derste kelimeleri anlamlı vektörlere çevirmeyi öğrendik. Ama bir CÜMLE, kelimelerin sadece bir KÜMESİ değil, bir DİZİSİDİR — sıra önemlidir. RNN (Recurrent Neural Network), bir diziyi kelime kelime işleyip bir “hafıza” taşıyan ilk mimari.
Sezgi
RNN, bir diziyi TEK TEK işler ve her adımda bir “gizli durum” (hidden state) günceller — bu gizli durum, o ana kadar görülen HER ŞEYİN bir özeti gibi davranır. Her yeni kelime, hem kendisini hem de ÖNCEKİ gizli durumu kullanarak yeni bir gizli durum üretir.
Mekanizma
Küçük bir RNN’i 3 adımlık bir dizide çalıştıralım:
RNN'in temel akışı
# RNN (Recurrent Neural Network), bir diziyi TEK TEK, bir "gizli durum" (hafıza)
# taşıyarak işler. Her adımda: yeni gizli durum = f(şu anki girdi, ÖNCEKİ gizli durum).
rnn = nn.RNN(input_size=4, hidden_size=3, batch_first=True)
dizi = torch.randn(1, 3, 4) # (batch=1, 3 adımlık dizi, her adım 4 boyutlu)
cikis, son_gizli = rnn(dizi)
print("Girdi şekli:", tuple(dizi.shape), "(batch, sekans_uzunluğu, embedding_boyutu)")
print("Her adımdaki gizli durum şekli:", tuple(cikis.shape))
print("\nHer adımdaki gizli durum (h_t):")
for t in range(3):
print(f" h_{t}: {[round(v,3) for v in cikis[0,t].tolist()]}")Girdi şekli: (1, 3, 4) (batch, sekans_uzunluğu, embedding_boyutu)
Her adımdaki gizli durum şekli: (1, 3, 3)
Her adımdaki gizli durum (h_t):
h_0: [0.901, 0.556, 0.907]
h_1: [-0.712, 0.128, 0.483]
h_2: [0.827, 0.86, 0.933]Formülü elle uygulayıp PyTorch’un sonucuyla doğrulayalım:
Elle hesaplama vs PyTorch
# RNN'nin formülünü elle uygulayıp PyTorch'un sonucuyla karşılaştıralım.
W_ih, W_hh = rnn.weight_ih_l0, rnn.weight_hh_l0
b_ih, b_hh = rnn.bias_ih_l0, rnn.bias_hh_l0
h = torch.zeros(3) # başlangıç gizli durumu -- SIFIR
elle_gizli = []
for t in range(3):
h = torch.tanh(W_ih @ dizi[0, t] + b_ih + W_hh @ h + b_hh)
elle_gizli.append([round(v, 3) for v in h.tolist()])
print("\nElle hesaplanan (h_t = tanh(W_ih·x_t + b_ih + W_hh·h_{t-1} + b_hh)):")
for t, hv in enumerate(elle_gizli):
print(f" h_{t}: {hv}")
print(f"\nPyTorch ile BİREBİR aynı mı? {all(abs(a-b) < 1e-4 for t in range(3) for a,b in zip(elle_gizli[t], [round(v,3) for v in cikis[0,t].tolist()]))}")
Elle hesaplanan (h_t = tanh(W_ih·x_t + b_ih + W_hh·h_{t-1} + b_hh)):
h_0: [0.901, 0.556, 0.907]
h_1: [-0.712, 0.128, 0.483]
h_2: [0.827, 0.86, 0.933]
PyTorch ile BİREBİR aynı mı? TrueŞimdi asıl önemli soru: sıra GERÇEKTEN önemli mi?
Aynı kelimeler, ters sıra
# RNN'nin KRİTİK özelliği: her adımdaki gizli durum, ÖNCEKİ TÜM dizinin sırasına bağlıdır.
dizi_ters = dizi[:, [2, 1, 0], :] # AYNI 3 "kelime", TERS sırada
_, son_gizli_ters = rnn(dizi_ters)
print(f"\nOrijinal sıra, son gizli durum: {[round(v,3) for v in son_gizli[0,0].tolist()]}")
print(f"Ters sıra, son gizli durum: {[round(v,3) for v in son_gizli_ters[0,0].tolist()]}")
print(f"Aynı mı? {torch.allclose(son_gizli, son_gizli_ters)}")
print("AYNI 3 vektör, SADECE sırası değişince, TAMAMEN farklı bir son gizli durum üretiyor.")
Orijinal sıra, son gizli durum: [0.827, 0.86, 0.933]
Ters sıra, son gizli durum: [0.934, 0.672, 0.951]
Aynı mı? False
AYNI 3 vektör, SADECE sırası değişince, TAMAMEN farklı bir son gizli durum üretiyor.AYNI 3 vektör, SADECE sırası değişince, TAMAMEN farklı bir son gizli durum üretiyor. Çoğu kişi “sinir ağları girdiyi bir küme gibi işler” sanır (4. kategorideki CNN’ler gibi konum-hassas olsa da, bag-of-words BÖYLE değildi). RNN’de yanlış, çünkü her adım, ÖNCEKİ adımların gizli durumuna bağlı — sıra, matematiğin İÇİNE gömülü.
Matematik
RNN'in tekrarlayan (recurrent) formülü
| Sembol | Anlamı |
|---|---|
| . adımdaki girdi (örn. . kelimenin embedding’i) | |
| Bir ÖNCEKİ adımın gizli durumu — “hafıza” | |
| Bu adımın YENİ gizli durumu — hem ‘yi hem ‘i “özetler” | |
| Öğrenilebilir ağırlık matrisleri — TÜM adımlarda PAYLAŞILIR |
ve ‘nin HER adımda aynı kalması (paylaşılması), 27. DL Vision dersindeki “aynı çekirdeği her konuma uygula” fikrinin dizilerdeki KARŞILIĞIDIR.
Kod
Şimdi 1. dersin o çarpıcı örneğine dönelim — ama bu kez RNN ile:
1. dersin sorununu RNN çözüyor mu?
# 1. dersteki o çarpıcı örneğe dönelim: bag-of-words'ün AYIRT EDEMEDİĞİ iki cümle.
kelimeler_p = ["bu", "film", "çok", "iyi", "hiç", "kötü", "değil"] # OLUMLU
kelimeler_n = ["bu", "film", "hiç", "iyi", "değil", "çok", "kötü"] # OLUMSUZ
dagarcik = sorted(set(kelimeler_p) | set(kelimeler_n))
EMBED_BOYUT = 8
embedding_tablosu = {k: torch.randn(EMBED_BOYUT) for k in dagarcik} # rastgele (henüz EĞİTİLMEMİŞ) embedding'ler
def diziye_cevir(kelimeler):
return torch.stack([embedding_tablosu[k] for k in kelimeler]).unsqueeze(0)
rnn2 = nn.RNN(input_size=EMBED_BOYUT, hidden_size=4, batch_first=True)
_, h_p = rnn2(diziye_cevir(kelimeler_p))
_, h_n = rnn2(diziye_cevir(kelimeler_n))
def bow(kelimeler, dagarcik):
sayim = Counter(kelimeler)
return [sayim.get(k, 0) for k in dagarcik]
print(f"\n1. dersteki bag-of-words vektörleri AYNI mı? {bow(kelimeler_p, dagarcik) == bow(kelimeler_n, dagarcik)}")
print(f"RNN'nin son gizli durumları AYNI mı? {torch.allclose(h_p, h_n)}")
print(f"\nPozitif cümle son gizli durum: {[round(v,3) for v in h_p[0,0].tolist()]}")
print(f"Negatif cümle son gizli durum: {[round(v,3) for v in h_n[0,0].tolist()]}")
print("\nRNN henüz EĞİTİLMEMİŞ olsa bile (rastgele ağırlıklarla), SIRAYA duyarlı olduğu için")
print("bu iki cümleyi FARKLI temsillere çeviriyor -- 1. dersteki sorunun doğrudan çözümü.")
1. dersteki bag-of-words vektörleri AYNI mı? True
RNN'nin son gizli durumları AYNI mı? False
Pozitif cümle son gizli durum: [-0.47, -0.932, -0.88, -0.601]
Negatif cümle son gizli durum: [-0.085, -0.911, -0.76, -0.304]
RNN henüz EĞİTİLMEMİŞ olsa bile (rastgele ağırlıklarla), SIRAYA duyarlı olduğu için
bu iki cümleyi FARKLI temsillere çeviriyor -- 1. dersteki sorunun doğrudan çözümü.- dersteki bag-of-words vektörleri AYNI; RNN’in son gizli durumları FARKLI. Ve bu RNN henüz hiç EĞİTİLMEDİ — sadece rastgele ağırlıklarla bile, mimarinin KENDİSİ sıraya duyarlı.
Nerede işe yarar
RNN, sıralı veri (metin, ses, zaman serisi) işlemenin İLK derin öğrenme çözümüydü:
nn.RNN, PyTorch’ta hazır bir katman olarak gelir — ama 8. derste göreceğimiz gibi, PRATİKTE saf RNN nadiren kullanılır.- Gizli durum boyutu, ne kadar “hafıza” taşınabileceğini sınırlar — küçük boyut, az bilgi; büyük boyut, daha fazla hesaplama.
- RNN’ler, diziyi SOLDAN SAĞA (veya çift yönlü) işler — bu, 9-10. derste göreceğimiz attention’ın DOĞRUDAN taşıma yaklaşımından temel farkıdır.
Bu 3 hatayı yaparsın:
- RNN’i “hafızasız” bir katman sanmak — asıl gücü TAM OLARAK bu hafıza (gizli durum) mekanizması.
- Ağırlıkların her adımda FARKLI olduğunu sanmak — ve TÜM adımlarda paylaşılır.
- RNN’in mükemmel bir çözüm olduğunu düşünmek — 8. derste, uzun dizilerde bu mimarinin CİDDİ sınırlarını göreceğiz.
Kendini test et
1. Notebook'ta aynı 3 vektör, ters sırada verildiğinde RNN'in son gizli durumu neden değişti?
- Kod hatalıydı
- Her adımdaki gizli durum, ÖNCEKİ TÜM adımların sırasına bağlıdır -- h_t hesaplanırken h_{t-1} kullanılır, bu da sırayı formülün İÇİNE gömer (doğru cevap)
- RNN girdiyi rastgele karıştırır
- Ağırlıklar her çalıştırmada değişir
Neden: RNN'in formülü h_t = tanh(...+ W_hh·h_{t-1} + ...) şeklindedir -- her adım, bir önceki adımın SONUCUNU girdi olarak kullanır, bu yüzden aynı öğeler farklı sırada verildiğinde farklı bir hesaplama zinciri oluşur.
2. Notebook'ta 1. dersteki bag-of-words vektörleri AYNI iken RNN'in son gizli durumları neden FARKLI çıktı?
- RNN yanlış çalışıyordu
- Bag-of-words sadece kelime SAYIMINI tutar (sıradan bağımsız); RNN ise her kelimeyi SIRAYLA işleyip önceki durumu hatırladığı için sıraya duyarlıdır (doğru cevap)
- İki yöntem de aynı sonucu vermeliydi
- RNN kelimeleri anlamıyor
Neden: Bag-of-words, "kaç tane" sorusuna cevap verir ve sırayı tamamen atar; RNN ise dizinin İÇİNDEN geçerken her adımda bir önceki duruma bağlı yeni bir durum ürettiği için, aynı kelimelerin farklı sırası farklı bir son duruma yol açar.
3. RNN'in W_ih ve W_hh ağırlıklarının HER zaman adımında PAYLAŞILMASI ne anlama gelir?
- Her adımda farklı ağırlıklar kullanılır
- Dizinin 1. kelimesi ile 10. kelimesi, AYNI ağırlık matrisleriyle işlenir -- bu da RNN'in değişken uzunluktaki dizilere uygulanabilmesini sağlar (doğru cevap)
- Ağırlıklar eğitim sırasında hiç değişmez
- Bu, RNN'in bir zayıflığıdır
Neden: Ağırlık paylaşımı, 27. DL Vision dersindeki "aynı konvolüsyon çekirdeğini her konuma uygulama" fikrinin dizilerdeki karşılığıdır -- bu sayede RNN, eğitimde görmediği uzunluktaki dizilere bile uygulanabilir.
Özet
Özet
- RNN, bir diziyi tek tek işleyip her adımda bir gizli durumu (hafıza) günceller.
- h_t = tanh(W_ih·x_t + b_ih + W_hh·h_{t-1} + b_hh) formülü, her adımda AYNI ağırlıklarla uygulanır.
- Bu tekrarlayan yapı, RNN'i doğal olarak SIRAYA duyarlı yapar -- bag-of-words'ün çözemediği bir sorunu çözer.
- Ağırlıkların adımlar arası paylaşılması, RNN'i değişken uzunluktaki dizilere uygulanabilir kılar.
- RNN, sıralı veri işlemenin ilk derin öğrenme çözümüydü -- ama 8. derste ciddi sınırlarını göreceğiz.