Pozisyonel kodlama
Önkoşul:Multi-head attention
Kanca
“Kedi köpeği kovaladı” ile “Köpeği kedi kovaladı” — self-attention için bu iki cümle, konum bilgisi olmadan, AYNI “kelime kümesi”. Bir mekanizma bu kadar güçlüyken, kelime SIRASINI nasıl bu kadar kolay “unutabilir”?
Sezgi
Self-attention, HER token’ı diğer HER token’la karşılaştırır — ama bu karşılaştırma, token’ların dizideki SIRASINA değil, SADECE içeriklerine bakar. RNN’de sıra otomatikti (7-8. ders): bilgi soldan sağa AKARDI. Self-attention’da böyle bir akış YOK — bu yüzden konum bilgisini AYRICA eklemek gerekiyor. Aşağıda sinüzoidal kodlamanın her konum için nasıl benzersiz bir “parmak izi” ürettiğini incele:
Soldaki bir konuma (satıra) tıkla -- diğer TÜM konumlarla ne kadar "benzer" olduğunu gör:
Bir konuma tıkla, göreli benzerliği gör.
İpucu: yakın konumların çubukları uzun, uzak konumlarınki kısa olacak.
Mekanizma
Önce sorunu SAYILARLA kanıtlayalım: token’ları karıştırıp geri sıralarsak, çıktı DEĞİŞMELİ MİYDİ?
Self-attention, sırayı görmüyor
# Self-attention'ın GİZLİ bir sorunu var: KONUM (position) kavramı YOK. Bir kelimenin
# cümledeki 1. mi, yoksa 5. mi olduğunu BİLMİYOR -- sadece kelimelerin BİRBİRİYLE
# benzerliğine bakıyor. Bunu KANITLAYALIM: token'ları KARIŞTIRIP (permute) AYNI
# hesaplamayı tekrarlayalım.
N, D = 5, 8
X = torch.randn(N, D)
Wq, Wk, Wv = torch.randn(D, D) * 0.5, torch.randn(D, D) * 0.5, torch.randn(D, D) * 0.5
def calistir(X_girdi):
Q, K, V = X_girdi @ Wq, X_girdi @ Wk, X_girdi @ Wv
return dikkat(Q, K, V)
cikti_orijinal, _ = calistir(X)
permutasyon = torch.tensor([2, 0, 4, 1, 3])
X_karisik = X[permutasyon]
cikti_karisik, _ = calistir(X_karisik)
# Karışık çıktıyı AYNI permütasyonla geri "düzeltirsek", orijinalle AYNI mı olmalı?
cikti_karisik_geri = torch.zeros_like(cikti_karisik)
cikti_karisik_geri[permutasyon] = cikti_karisik
fark = (cikti_orijinal - cikti_karisik_geri).abs().max().item()
print(f"Token sırası KARIŞTIRILDI (permütasyon: {permutasyon.tolist()}), sonra çıktı GERİ sıralandı.")
print(f"Orijinal çıktı ile karıştırılıp-geri-sıralanan çıktı arasındaki fark: {fark:.2e}")
print(f"torch.allclose: {torch.allclose(cikti_orijinal, cikti_karisik_geri, atol=1e-6)}")
print("\nSonuç: self-attention, token'ların SIRASINI 'görmüyor' -- SADECE hangi token'ların")
print("VAR olduğunu görüyor. 'Kedi köpeği kovaladı' ile 'Köpeği kedi kovaladı', konum bilgisi")
print("OLMADAN, attention için AYNI 'küme'dir.")Token sırası KARIŞTIRILDI (permütasyon: [2, 0, 4, 1, 3]), sonra çıktı GERİ sıralandı.
Orijinal çıktı ile karıştırılıp-geri-sıralanan çıktı arasındaki fark: 2.38e-07
torch.allclose: True
Sonuç: self-attention, token'ların SIRASINI 'görmüyor' -- SADECE hangi token'ların
VAR olduğunu görüyor. 'Kedi köpeği kovaladı' ile 'Köpeği kedi kovaladı', konum bilgisi
OLMADAN, attention için AYNI 'küme'dir.Fark 2.38e-07 — pratikte SIFIR. Token’ları karıştırıp geri sıralamak, hesaplamayı HİÇ etkilemiyor. Şimdi çözümü kuralım:
Sinüzoidal pozisyonel kodlama
# Çözüm: her konuma (position) ÖZEL, SABİT bir vektör ekle -- "Attention is All You
# Need" makalesinin sinüzoidal kodlaması:
def pozisyonel_kodlama(uzunluk, boyut):
pe = torch.zeros(uzunluk, boyut)
pozisyon = torch.arange(uzunluk).unsqueeze(1).float()
bolen = torch.exp(torch.arange(0, boyut, 2).float() * (-math.log(10000.0) / boyut))
pe[:, 0::2] = torch.sin(pozisyon * bolen)
pe[:, 1::2] = torch.cos(pozisyon * bolen)
return pe
PE = pozisyonel_kodlama(N, D)
print(f"\nPozisyonel kodlama şekli: {tuple(PE.shape)}")
print(f"Konum 0'ın kodlaması: {[round(v,3) for v in PE[0].tolist()]}")
print(f"Konum 1'in kodlaması: {[round(v,3) for v in PE[1].tolist()]}")
print("Konum 0'ın kodlaması hep [0, 1, 0, 1, ...] ile başlar (sin(0)=0, cos(0)=1) -- SABİT bir başlangıç noktası.")
Pozisyonel kodlama şekli: (5, 8)
Konum 0'ın kodlaması: [0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0]
Konum 1'in kodlaması: [0.841, 0.54, 0.1, 0.995, 0.01, 1.0, 0.001, 1.0]
Konum 0'ın kodlaması hep [0, 1, 0, 1, ...] ile başlar (sin(0)=0, cos(0)=1) -- SABİT bir başlangıç noktası.PE eklenince, sıra artık önemli
# Şimdi X'e PE'yi EKLEYİP AYNI karıştırma testini tekrarlayalım.
X_pozisyonlu = X + PE
def calistir_pozisyonlu(X_girdi, PE_girdi):
X_toplam = X_girdi + PE_girdi
Q, K, V = X_toplam @ Wq, X_toplam @ Wk, X_toplam @ Wv
return dikkat(Q, K, V)
cikti_poz_orijinal, _ = calistir_pozisyonlu(X, PE)
# DİKKAT: X karıştırılıyor ama PE karıştırılmıyor -- pozisyonlar SABİT kalıyor,
# çünkü PE 'bu token 3. sırada' der, token'ın KENDİSİYLE değil.
cikti_poz_karisik, _ = calistir_pozisyonlu(X_karisik, PE)
cikti_poz_karisik_geri = torch.zeros_like(cikti_poz_karisik)
cikti_poz_karisik_geri[permutasyon] = cikti_poz_karisik
fark_poz = (cikti_poz_orijinal - cikti_poz_karisik_geri).abs().max().item()
print(f"\nPE EKLENDİKTEN sonra, AYNI karıştır-geri-sırala testi -- fark: {fark_poz:.4f}")
print(f"torch.allclose: {torch.allclose(cikti_poz_orijinal, cikti_poz_karisik_geri, atol=1e-6)}")
print("ARTIK eşleşmiyor! PE eklenince, HANGİ token'ın HANGİ konumda olduğu SONUCU değiştiriyor --")
print("self-attention artık 'sırasız bir küme' değil, 'sıralı bir dizi' işliyor.")
PE EKLENDİKTEN sonra, AYNI karıştır-geri-sırala testi -- fark: 1.4039
torch.allclose: False
ARTIK eşleşmiyor! PE eklenince, HANGİ token'ın HANGİ konumda olduğu SONUCU değiştiriyor --
self-attention artık 'sırasız bir küme' değil, 'sıralı bir dizi' işliyor.Fark 1.40’a çıktı — artık token’ları karıştırıp geri sıralamak SONUCU DEĞİŞTİRİYOR, çünkü her token’a “ben kaçıncı sıradayım” bilgisi eklendi.
Matematik
Sinüzoidal pozisyonel kodlama
| Sembol | Anlamı |
|---|---|
| Token’ın dizideki konumu (0, 1, 2, …) | |
| Boyut çiftinin indeksi (her için bir sin, bir cos boyutu) | |
| Gömme boyutu |
Bu formül İKİ nedenle seçildi: (1) HER konum için BENZERSİZ bir vektör üretir, (2) YAKIN konumların vektörleri birbirine BENZER, UZAK konumlarınki FARKLI — yani model göreli mesafeyi “hissedebilir”. ÖĞRENİLEN bir kodlama yerine SABİT bir formül kullanılması, modelin eğitimde GÖRMEDİĞİ uzunluktaki dizilere de (sınırlı ölçüde) genelleyebilmesini sağlar.
Kod
Bu “yakın = benzer, uzak = farklı” özelliğini doğrudan ölçelim:
Kosinüs benzerliği, mesafeyle azalıyor
# PE vektörlerinin bir başka özelliği: YAKIN konumların kodlamaları birbirine BENZER,
# UZAK konumların kodlamaları FARKLI -- göreli konum bilgisini TAŞIYOR.
# Gerçekçi bir boyutla (gerçek Transformer'lar d_model=512-768 kullanır, biz 64 ile
# gösteriyoruz) bu ilişki çok daha PÜRÜZSÜZ görünür:
D_BUYUK = 64
PE_uzun = pozisyonel_kodlama(30, D_BUYUK)
referans = PE_uzun[5]
print(f"\nKonum 5'in kodlamasıyla diğer konumların kosinüs benzerliği (boyut={D_BUYUK}):")
for hedef in [4, 6, 10, 15, 20]:
benzerlik = F.cosine_similarity(referans, PE_uzun[hedef], dim=0).item()
uzaklik = abs(hedef - 5)
print(f" konum {hedef:<3} (uzaklık={uzaklik:<2}) benzerlik={benzerlik:.3f}")
print("\nUzaklık arttıkça benzerlik DÜZENLİ biçimde azalıyor -- model, 'bu iki kelime YAKIN' ile")
print("'bu iki kelime UZAK' arasındaki farkı, kodlamaların GEOMETRİSİNDEN çıkarabilir.")
print("(Not: sinüzoidal kodlama PERİYODİK olduğu için ÇOK büyük uzaklıklarda bu azalış")
print("tekrar dalgalanabilir -- ama modelin göreceği tipik dizi uzunluklarında sorun değildir.)")
Konum 5'in kodlamasıyla diğer konumların kosinüs benzerliği (boyut=64):
konum 4 (uzaklık=1 ) benzerlik=0.966
konum 6 (uzaklık=1 ) benzerlik=0.966
konum 10 (uzaklık=5 ) benzerlik=0.734
konum 15 (uzaklık=10) benzerlik=0.658
konum 20 (uzaklık=15) benzerlik=0.608
Uzaklık arttıkça benzerlik DÜZENLİ biçimde azalıyor -- model, 'bu iki kelime YAKIN' ile
'bu iki kelime UZAK' arasındaki farkı, kodlamaların GEOMETRİSİNDEN çıkarabilir.
(Not: sinüzoidal kodlama PERİYODİK olduğu için ÇOK büyük uzaklıklarda bu azalış
tekrar dalgalanabilir -- ama modelin göreceği tipik dizi uzunluklarında sorun değildir.)Nerede işe yarar
Pozisyonel kodlama, “Attention is All You Need” makalesinin (18. ders) ele aldığı SON parça:
- Bu kodlama, gömmeye TOPLANIR (concat değil, +) — yani ; model, ikisinin KARIŞIMINDAN hem “ne” hem “nerede” bilgisini ÇIKARMAYI öğrenir.
- GPT gibi modern modeller genelde ÖĞRENİLEN pozisyonel kodlama kullanır (sabit formül yerine, her konum için bir embedding vektörü EĞİTİLİR) — daha esnek ama eğitimde görülmeyen uzunluklara genellemez.
- RoPE (Rotary Positional Embedding) gibi daha yeni yöntemler (LLaMA, çoğu modern LLM), konumu Q ve K’ya TOPLAMA yerine DÖNDÜRME (rotation) ile ekler — bu dersin kapsamı dışında ama AYNI temel sorunu (sıra bilgisi) çözer.
Bu 2 hatayı yaparsın:
- Pozisyonel kodlamayı “isteğe bağlı bir iyileştirme” sanmak — HAYIR, ONSUZ self-attention kelime SIRASINI hiç göremez; bu OPSİYONEL değil, ZORUNLUDUR.
- Sinüzoidal kodlamanın MUTLAK konumu (kelime tam olarak kaçıncı sırada) kodladığını, göreli konumu (iki kelime birbirine ne kadar YAKIN) kodlamadığını düşünmek — aslında HER İKİSİNİ de, farklı derecelerde taşır (bu dersteki benzerlik-mesafe deneyi göreli tarafı gösteriyor).
Kendini test et
1. Notebook'ta token'ları karıştırıp geri sıralamak, PE EKLENMEDEN önce çıktıyı DEĞİŞTİRMEZKEN (fark 2.38e-07), PE EKLENDİKTEN sonra DEĞİŞTİRİYOR (fark 1.40). Bu NEYİ kanıtlıyor?
- PE eklenmesi bir hata kaynağıdır
- Self-attention'ın kendisi token SIRASINA duyarsızdır (permütasyon değişmez); PE, her token'a 'ben kaçıncı sıradayım' bilgisini ekleyerek modelin SIRAYI ayırt etmesini SAĞLAR (doğru cevap)
- İki test de aynı sonucu vermeliydi
- Bu sadece küçük modellerde görülen bir durumdur
Neden: sirasizlik-kanit bloğunda fark pratikte sıfırken (2.38e-07), kodlama-eklenince bloğunda fark 1.40'a çıkıyor -- bu, PE'nin TAM OLARAK çözmesi gereken sorunu (konum körlüğü) çözdüğünü kanıtlıyor.
2. Sinüzoidal pozisyonel kodlama formülünde neden SIN ve COS'un İKİSİ BİRDEN kullanılır, tek bir fonksiyon değil?
- Rastgele bir tercih, önemi yok
- Her boyut çiftinde (sin, cos) farklı frekanslarla, her konum için BENZERSİZ bir vektör üretmek VE yakın konumların vektörlerinin birbirine benzer kalmasını sağlamak için (doğru cevap)
- Hesaplama hızını artırmak için
- Sadece negatif değerlerden kaçınmak için
Neden: MathBox'ta görüldüğü gibi her i için bir sin bir cos boyutu üretiliyor; bu, uzaklik-ile-benzerlik bloğunda gösterilen 'yakın konumlar benzer, uzak konumlar farklı' özelliğini SAĞLAYAN matematiksel yapı.
3. 'Kedi köpeği kovaladı' ile 'Köpeği kedi kovaladı' örneği, pozisyonel kodlama OLMADAN self-attention için NEDEN aynı 'küme'?
- Bu iki cümle Türkçede aynı anlama gelir
- Self-attention token'ların HANGİ SIRADA olduğunu değil, SADECE hangi token'ların VAR olduğunu ve birbirleriyle ne kadar BENZER olduğunu görür -- konum bilgisi olmadan iki farklı sıralama aynı hesaplamayı üretir (doğru cevap)
- Attention mekanizması Türkçe dilbilgisini anlamaz
- Bu örnekte kelimeler yanlış seçilmiştir
Neden: sirasizlik-kanit bloğu TAM OLARAK bunu gösteriyor: aynı token kümesinin FARKLI sıralamaları, permütasyonu geri aldığınızda İDENTİK çıktı üretiyor -- self-attention'ın sıraya duyarsız (permütasyon değişmez) olduğunun kanıtı.
Özet
Özet
- Self-attention, token'ların SIRASINA değil, SADECE içeriklerine bakar -- permütasyon DEĞİŞMEZDİR (kanıt: karıştır-geri-sırala testi, fark ~0).
- Sinüzoidal pozisyonel kodlama, her konuma SABİT, BENZERSİZ bir vektör atar ve gömmeye TOPLANIR.
- PE eklendikten sonra, token sırası SONUCU DEĞİŞTİRİR (kanıt: aynı test, fark 1.40).
- PE vektörleri arasındaki kosinüs benzerliği, konumlar arasındaki UZAKLIKLA (genelde) AZALIR -- göreli konum bilgisi taşır.
- Gerçek modern modeller (GPT, LLaMA) genelde ÖĞRENİLEN veya döndürme-tabanlı (RoPE) kodlama kullanır -- AYNI temel sorunu farklı şekilde çözerler.