Ana içeriğe geç

Orta12 dk

Self-attention mekaniği (Q, K, V)

Önkoşul:Attention fikri

Kanca

“Kedi masaya çıktı çünkü o yorulmuştu.” Bu cümledeki “o” zamiri kime işaret ediyor — “Kedi”ye mi, “masa”ya mı? İnsan için bariz. Bir modelin bunu, hiçbir dilbilgisi kuralı YAZILMADAN, SADECE kelime benzerliklerinden bulabildiğini göreceğiz.

Sezgi

9. derste TEK bir dışsal sorgu vardı. Self-attention’daki fark: HER token, DİĞER TÜM token’lar için (kendisi dahil) bir sorgu üretir — dikkat, dizinin İÇİNDE, kendi kendine kurulur. Aşağıda “o” token’ına tıkla, hangi kelimeye ne kadar “baktığını” gör:

Bir token'a tıkla -- kime, ne kadar baktığını gör:
KedimasayaçıktıçünküoyorulmuştuKediKedi → Kedi: 0.5640.56Kedi → masaya: 0.0110.01Kedi → çıktı: 0.0280.03Kedi → çünkü: 0.0190.02Kedi → o: 0.3540.35Kedi → yorulmuştu: 0.0230.02masayamasaya → Kedi: 0.1590.16masaya → masaya: 0.1690.17masaya → çıktı: 0.1920.19masaya → çünkü: 0.1460.15masaya → o: 0.1570.16masaya → yorulmuştu: 0.1770.18çıktıçıktı → Kedi: 0.1360.14çıktı → masaya: 0.1700.17çıktı → çıktı: 0.2040.20çıktı → çünkü: 0.1770.18çıktı → o: 0.1390.14çıktı → yorulmuştu: 0.1740.17çünküçünkü → Kedi: 0.0630.06çünkü → masaya: 0.1070.11çünkü → çıktı: 0.1440.14çünkü → çünkü: 0.4200.42çünkü → o: 0.0800.08çünkü → yorulmuştu: 0.1860.19oo → Kedi: 0.5170.52o → masaya: 0.0180.02o → çıktı: 0.0420.04o → çünkü: 0.0340.03o → o: 0.3530.35o → yorulmuştu: 0.0350.04yorulmuştuyorulmuştu → Kedi: 0.1280.13yorulmuştu → masaya: 0.1680.17yorulmuştu → çıktı: 0.1920.19yorulmuştu → çünkü: 0.2010.20yorulmuştu → o: 0.1340.13yorulmuştu → yorulmuştu: 0.1770.18

Satır etiketine (soldaki kelimeye) tıkla.

İpucu: "o" zamirine tıkla -- modelin onu kime bağladığını gör.

Mekanizma

Self-attention’ın kalbi: AYNI gömmeden ÜÇ FARKLI rol için ÜÇ AYRI matris öğrenilir.

Cümle ve gömmeler

# 9. derste TEK bir dışsal sorgu vardı. Self-attention'da FARK şu: HER token,
# DİĞER TÜM token'lar için (kendisi dahil) BİR sorgu üretir -- yani dikkat,
# dizinin İÇİNDE, kendi kendine kuruluyor.
TOKENLER = ["Kedi", "masaya", "çıktı", "çünkü", "o", "yorulmuştu"]
D = 4  # gömme boyutu (öğretici amaçlı küçük tutuldu)

# Öğretici amaçlı elle tasarlanmış gömmeler (gerçekte öğrenilir):
X = torch.tensor([
    [1.5, 0.0, 0.0, 0.0],   # Kedi
    [0.0, 1.5, 0.0, 0.0],   # masaya
    [0.3, 0.3, 1.2, 0.0],   # çıktı
    [0.0, 0.0, 0.0, 1.0],   # çünkü
    [1.3, 0.0, 0.0, 0.1],   # o
    [0.2, 0.0, 0.9, 0.1],   # yorulmuştu
])
print(f'Cümle: "Kedi masaya çıktı çünkü o yorulmuştu"')
print(f"Token sayısı: {len(TOKENLER)}, gömme boyutu: {D}")
print("\nBu cümlede 'o' zamiri KİME işaret ediyor? İnsan için açık: 'Kedi'ye.")
print("Self-attention'ın, bunu VERİDEN öğrenip öğrenemediğini test edeceğiz.")
Cümle: "Kedi masaya çıktı çünkü o yorulmuştu"
Token sayısı: 6, gömme boyutu: 4

Bu cümlede 'o' zamiri KİME işaret ediyor? İnsan için açık: 'Kedi'ye.
Self-attention'ın, bunu VERİDEN öğrenip öğrenemediğini test edeceğiz.

Sorgu (Q), Anahtar (K), Değer (V)

# Self-attention'ın kalbi: AYNI gömmeden ÜÇ FARKLI rol için ÜÇ AYRI projeksiyon
# öğrenilir -- Sorgu (Query: "neyi arıyorum?"), Anahtar (Key: "ben neyim?"),
# Değer (Value: "katkım ne olacak?").
Wq = torch.randn(D, D) * 0.5
Wk = torch.randn(D, D) * 0.5
Wv = torch.randn(D, D) * 0.5

Q = X @ Wq
K = X @ Wk
V = X @ Wv

print(f"\nX (gömme) şekli: {tuple(X.shape)}")
print(f"Q, K, V şekli (üçü de): {tuple(Q.shape)}")
print(f"\n'o' token'ının gömmesi:  {[round(v,3) for v in X[4].tolist()]}")
print(f"'o' token'ının sorgusu (Q): {[round(v,3) for v in Q[4].tolist()]}")
print(f"'o' token'ının anahtarı (K): {[round(v,3) for v in K[4].tolist()]}")
print("Q, K, V AYNI gömmeden geliyor ama ÜÇ FARKLI matrisle çarpıldığı için ÜÇ FARKLI vektör.")

X (gömme) şekli: (6, 4)
Q, K, V şekli (üçü de): (6, 4)

'o' token'ının gömmesi:  [1.3, 0.0, 0.0, 0.1]
'o' token'ının sorgusu (Q): [-0.192, -0.742, 0.129, -1.678]
'o' token'ının anahtarı (K): [-0.883, -1.13, 0.865, -2.746]
Q, K, V AYNI gömmeden geliyor ama ÜÇ FARKLI matrisle çarpıldığı için ÜÇ FARKLI vektör.

Şimdi HER token’ın sorgusu, HER token’ın anahtarıyla karşılaştırılıp softmax’tan geçiyor — bu, N×N’lik bir “dikkat matrisi” üretir:

N×N dikkat matrisi

# HER token'ın sorgusu (Q), HER token'ın anahtarıyla (K) karşılaştırılır --
# bu, N×N'lik BİR "dikkat matrisi" (attention matrix) üretir.
puanlar_ham = Q @ K.T
puanlar = puanlar_ham / (D ** 0.5)  # ölçekleme -- bir sonraki blokta NEDEN gerekli olduğunu göreceğiz
agirliklar = F.softmax(puanlar, dim=-1)

print(f"\nDikkat matrisi şekli: {tuple(agirliklar.shape)} (satır=sorgulayan, sütun=dikkat edilen)")
o_idx = TOKENLER.index("o")
print(f"\n'{TOKENLER[o_idx]}' token'ının dikkat ağırlıkları:")
for tok, a in zip(TOKENLER, agirliklar[o_idx].tolist()):
    print(f"  {tok:<12} ağırlık={a:.3f}")
en_yuksek = TOKENLER[agirliklar[o_idx].argmax().item()]
print(f"\n'o' en YÜKSEK ağırlığı '{en_yuksek}'e veriyor -- model, ZAMİRİN kime işaret ettiğini")
print("hiçbir dilbilgisi kuralı YAZILMADAN, SADECE gömmelerin benzerliğinden buluyor.")

Dikkat matrisi şekli: (6, 6) (satır=sorgulayan, sütun=dikkat edilen)

'o' token'ının dikkat ağırlıkları:
  Kedi         ağırlık=0.517
  masaya       ağırlık=0.018
  çıktı        ağırlık=0.042
  çünkü        ağırlık=0.034
  o            ağırlık=0.353
  yorulmuştu   ağırlık=0.035

'o' en YÜKSEK ağırlığı 'Kedi'e veriyor -- model, ZAMİRİN kime işaret ettiğini
hiçbir dilbilgisi kuralı YAZILMADAN, SADECE gömmelerin benzerliğinden buluyor.

“o”, en yüksek ağırlığı (0.517) “Kedi”ye veriyor — yukarıdaki AttentionMatrisi bileşeninde AYNI sonucu görsel olarak inceleyebilirsin.

Matematik

Ölçekli nokta çarpım dikkati (scaled dot-product attention)
Q=XWQK=XWKV=XWVQ = XW_Q \qquad K = XW_K \qquad V = XW_VAttention(Q,K,V)=softmax(QKdk)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V
SembolAnlamı
XXGirdi gömmeleri (nn token × dd boyut)
WQ,WK,WVW_Q, W_K, W_VÖĞRENİLEN projeksiyon matrisleri
Q,K,VQ, K, VSorgu, anahtar, değer matrisleri (nn × dkd_k)
dkd_kKK‘nın boyutu (ölçekleme için kullanılır)
QKQK^\topHer sorgu-anahtar çiftinin benzerliği (nn × nn matris)

Neden dk\sqrt{d_k}‘ye bölüyoruz? Boyut büyüdükçe rastgele vektörlerin nokta çarpımının VARYANSI da orantılı büyür — bu da softmax’ı satüre eder:

Ölçekleme, softmax satürasyonunu önler

# Neden [email protected]'yi sqrt(D)'ye BÖLÜYORUZ? Boyut (D) büyüdükçe, rastgele vektörlerin
# nokta çarpımının VARYANSI da D ile orantılı BÜYÜR -- bu da softmax'ı SATÜRE eder.
torch.manual_seed(7)
for boyut in [4, 16, 64, 256]:
    q_rnd = torch.randn(2000, boyut)
    k_rnd = torch.randn(2000, boyut)
    dot = (q_rnd * k_rnd).sum(-1)
    print(f"boyut={boyut:<4} var(Q·K)={dot.var().item():<10.2f} var(Q·K/√boyut)={(dot / boyut**0.5).var().item():.3f}")

torch.manual_seed(3)
BUYUK_D = 256
sorgu_b = torch.randn(BUYUK_D)
anahtar_b = torch.randn(6, BUYUK_D)
ham = anahtar_b @ sorgu_b
olcekli = ham / (BUYUK_D ** 0.5)
print(f"\nBoyut={BUYUK_D} olduğunda, ÖLÇEKSİZ softmax: {[round(v,3) for v in F.softmax(ham, dim=0).tolist()]}")
print(f"Aynı puanlar, ÖLÇEKLİ softmax: {[round(v,3) for v in F.softmax(olcekli, dim=0).tolist()]}")
print(f"Ölçeksiz en yüksek ağırlık: {F.softmax(ham, dim=0).max().item():.3f} (neredeyse tek bir token'a KİLİTLENİYOR)")
print(f"Ölçekli en yüksek ağırlık: {F.softmax(olcekli, dim=0).max().item():.3f} (daha DENGELİ, gradyan akışı daha sağlıklı)")
boyut=4    var(Q·K)=3.82       var(Q·K/√boyut)=0.955
boyut=16   var(Q·K)=15.51      var(Q·K/√boyut)=0.970
boyut=64   var(Q·K)=65.04      var(Q·K/√boyut)=1.016
boyut=256  var(Q·K)=261.33     var(Q·K/√boyut)=1.021

Boyut=256 olduğunda, ÖLÇEKSİZ softmax: [0.0, 0.001, 0.277, 0.0, 0.002, 0.72]
Aynı puanlar, ÖLÇEKLİ softmax: [0.05, 0.172, 0.247, 0.089, 0.181, 0.262]
Ölçeksiz en yüksek ağırlık: 0.720 (neredeyse tek bir token'a KİLİTLENİYOR)
Ölçekli en yüksek ağırlık: 0.262 (daha DENGELİ, gradyan akışı daha sağlıklı)

Boyut 256’da, ÖLÇEKSİZ softmax neredeyse TEK bir token’a kilitleniyor (en yüksek ağırlık 0.72); ÖLÇEKLİ softmax çok daha DENGELİ (en yüksek ağırlık 0.26) — bu da gradyan akışını sağlıklı tutar (5. DL Vision dersindeki satürasyon fikriyle AYNI mantık).

Matematik tazelemeNokta çarpım: benzerliğin matematiği

Kod

Son adım: dikkat ağırlıkları, VV vektörlerinin AĞIRLIKLI ORTALAMASINI almak için kullanılır — bu, her token’ın YENİ, bağlamsallaştırılmış temsilidir.

Bağlamsallaştırılmış çıktı

# Son adım: her token'ın dikkat ağırlıkları, V (değer) vektörlerinin AĞIRLIKLI
# ORTALAMASINI almak için kullanılır -- bu, o token'ın YENİ, BAĞLAMSALLAŞTIRILMIŞ temsili.
baglam = agirliklar @ V
print(f"\n'{TOKENLER[o_idx]}' token'ının YENİ (bağlamsallaştırılmış) temsili:")
print(f"  {[round(v,3) for v in baglam[o_idx].tolist()]}")
print(f"'{TOKENLER[0]}' token'ının V vektörü:")
print(f"  {[round(v,3) for v in V[0].tolist()]}")
print("\n'o'nun yeni temsili, 'Kedi'nin DEĞER vektörüne (V) yakın -- çünkü dikkatinin")
print("büyük kısmını (0.52) ona verdi. 'o' artık SADECE 'o' değil, BAĞLAMIYLA zenginleşmiş bir temsil.")

'o' token'ının YENİ (bağlamsallaştırılmış) temsili:
  [0.399, -0.072, -0.15, -0.419]
'Kedi' token'ının V vektörü:
  [0.488, -0.016, -0.186, -0.628]

'o'nun yeni temsili, 'Kedi'nin DEĞER vektörüne (V) yakın -- çünkü dikkatinin
büyük kısmını (0.52) ona verdi. 'o' artık SADECE 'o' değil, BAĞLAMIYLA zenginleşmiş bir temsil.
6 token için self-attention dikkat matrisini gösteren ısı haritası; 'o' satırı 'Kedi' sütununda en koyu.
Self-attention matrisi: satır sorgulayan token, sütun dikkat edilen token. 'o' satırında en koyu hücre 'Kedi' sütununda.

Nerede işe yarar

Bu mekanizma, bugünün TÜM büyük dil modellerinin (GPT, BERT, Claude…) temel yapı taşı:

  • Q, K, V burada AYRI matrislerle hesaplandı çünkü ÜÇ FARKLI SORU soruyorlar: Q = “ben neyi arıyorum?”, K = “ben neyim, beni nasıl bulabilirsin?”, V = “bulunursam, katkım ne olacak?” — AYNI vektör olsalardı, bu üç rol birbirine KARIŞIRDI.
  • Burada TEK bir “dikkat başlığı” (head) var — gerçek Transformer’lar BİRDEN FAZLA başlığı PARALEL çalıştırır (12. ders), her biri FARKLI bir ilişki türünü (dilbilgisi, anlam, konum…) öğrenir.
  • Bu örnekte WQ,WK,WVW_Q, W_K, W_V rastgele başlatıldı, EĞİTİLMEDİ — yine de “o → Kedi” ilişkisi ortaya çıktı çünkü gömmeler bunu destekleyecek şekilde tasarlandı; gerçek bir modelde hem gömmeler HEM ağırlıklar EĞİTİMLE öğrenilir.

Bu 3 hatayı yaparsın:

  1. Q, K, V’nin farklı vektörler olduğunu unutup, “dikkat = kelimelerin kendisini karşılaştırmak” sanmak — HAYIR, üç AYRI öğrenilmiş projeksiyon var.
  2. Ölçeklemeyi (dk\sqrt{d_k}) gereksiz bir detay sanmak — büyük boyutlarda ATLANIRSA, softmax satüre olur ve gradyanlar neredeyse SIFIRLANIR.
  3. Dikkat matrisinin SİMETRİK olduğunu düşünmek — “o”nun “Kedi”ye bakma ağırlığı (0.517), “Kedi”nin “o”ya bakma ağırlığıyla (0.354) AYNI DEĞİL, çünkü Q ve K farklı matrisler.

Kendini test et

1. Self-attention'da Q (sorgu), K (anahtar) ve V (değer) NEDEN AYRI matrislerle hesaplanır, AYNI gömme kullanılmaz?
  1. Hesaplama hızını artırmak için
  2. Üçü FARKLI bir soruyu temsil eder (Q: 'neyi arıyorum', K: 'ben neyim', V: 'katkım ne olacak') -- aynı vektör olsalardı bu roller birbirine karışırdı (doğru cevap)
  3. Bellek tasarrufu için
  4. Sadece gelenek olduğu için, işlevsel bir fark yok

Neden: uc-projeksiyon bloğunda görüldüğü gibi, AYNI X gömmesinden Wq/Wk/Wv ile ÜÇ FARKLI vektör üretiliyor; bu ayrım olmasaydı 'benzer olmak' ile 'aranan şey olmak' aynı anlama gelirdi.

2. Notebook'ta boyut 256'da ÖLÇEKSİZ softmax'ın en yüksek ağırlığı 0.72'ye çıkarken, ÖLÇEKLİ softmax'ın 0.26'da kalması NEYİ gösteriyor?
  1. Ölçekleme, sonucu YANLIŞ hale getiriyor
  2. Boyut büyüdükçe nokta çarpımların VARYANSI artıyor, bu da softmax'ı SATÜRE ediyor (neredeyse tek bir token'a kilitleniyor); √d_k'ye bölmek bu varyansı SABİT tutup dengeli bir dağılım sağlıyor (doğru cevap)
  3. Ölçekleme sadece küçük modellerde gereklidir
  4. İkisi arasında pratik bir fark yoktur

Neden: olceklemenin-nedeni bloğunda var(Q·K) boyutla ORANTILI büyürken var(Q·K/√boyut) ~1 civarında SABİT kalıyor -- bu da satürasyonu önlüyor, tıpkı 5. DL Vision dersindeki sigmoid satürasyonu gibi gradyan akışını bozmadan.

3. 'o' token'ının 'Kedi'ye bakma ağırlığı (0.517) ile 'Kedi'nin 'o'ya bakma ağırlığı (0.354) NEDEN FARKLI?
  1. Bu bir hesaplama hatasıdır, aynı olmalıydı
  2. Dikkat matrisi SİMETRİK DEĞİLDİR -- 'o'nun sorgusu (Q) ile 'Kedi'nin anahtarı (K) arasındaki benzerlik, 'Kedi'nin sorgusu ile 'o'nun anahtarı arasındaki benzerlikten FARKLI olabilir, çünkü Q ve K ayrı matrislerle üretiliyor (doğru cevap)
  3. Sadece 'o' zamir olduğu için ağırlığı farklıdır
  4. Softmax her zaman simetrik sonuç üretir

Neden: dikkat-matrisi bloğundaki agirliklar matrisi kare ama SİMETRİK değil -- satır i, sütun j'deki değer Q[i]·K[j]'den gelir, satır j sütun i'deki değer ise Q[j]·K[i]'den; bu ikisi genelde eşit değildir.

Özet

Özet

  • Self-attention'da HER token, diğer TÜM token'lar (kendisi dahil) için bir sorgu üretir -- dikkat dizinin İÇİNDE kurulur.
  • Q (sorgu), K (anahtar), V (değer) AYNI gömmeden ÜÇ AYRI öğrenilmiş matrisle hesaplanır -- her biri farklı bir rol oynar.
  • Dikkat matrisi = softmax(QKᵀ/√d_k) -- N×N boyutunda, HER token-token çiftinin ağırlığını taşır ve SİMETRİK DEĞİLDİR.
  • √d_k ile ölçekleme, boyut büyüdükçe softmax satürasyonunu (ve gradyan kaybını) önler.
  • Çıktı = ağırlıklar × V -- her token'ın YENİ, bağlamsallaştırılmış temsili.
Sonraki adım: Attention'ın Python'la adım adım kurulumu →