İçeriğe geç

Kitaplar / Yöntem ile Sıfırdan Dil Modeli / Bölüm 6 — Öz-dikkat

Bölüm 6 — Öz-dikkat

Bir önceki bölümdeki model son üç karaktere bakıyordu. Bağlamı 30 karaktere çıkarmak istesek ne olur? Her konum için ayrı bir ağırlık matrisi gerekir. Parametre sayısı bağlamla birlikte büyür. Daha kötüsü, model “8 karakter önceki harf” ile “9 karakter önceki harf” için ayrı ayrı kurallar öğrenmek zorunda kalır.

2017’de yayımlanan “Attention Is All You Need” makalesi farklı bir yol önerdi: her karakter geçmişteki tüm karakterlere baksın, ama hangisine ne kadar bakacağına kendisi karar versin. Bu düzeneğe öz-dikkat (self-attention) denir. Bugünkü tüm büyük dil modellerinin kalbi budur.

Adım 1: geçmişin ortalaması

En basit fikirle başlayalım. Her karakter, kendisi dahil önceki tüm karakterlerin vektörlerinin ortalamasını alsın. Böylece geçmişin bir özetine sahip olur. Bunu bir matris çarpımıyla yapabiliriz: alt üçgen bir ağırlık matrisi ile çarpmak, her satırda geçmişin ağırlıklı toplamını verir.

Ağırlıkları doğrudan yazmak yerine bir hile kullanırız. Önce her çift için bir puan hesaplarız. Gelecekteki karakterlerin puanına çok büyük bir eksi sayı ekleriz; bu işleme nedensel maske denir. Sonra softmaks uygularız. Softmaks, eksi sonsuza yakın puanları sıfır olasılığa, kalanları toplamı 1 olan ağırlıklara çevirir. Tüm puanlar eşitse sonuç tam olarak ortalamadır.

Maske neden gerekli? Eğitimde model bir pencerenin tüm konumları için aynı anda tahmin yapar. Maske olmasaydı 3. konumdaki karakter, tahmin etmesi gereken 4. karakteri görebilirdi; model kopya çekmeyi öğrenir, metin üretirken de işe yaramazdı.

Adım 2: sorgu, anahtar, değer

Eşit ağırlık kötü bir fikirdir: “gönül” sözcüğündeki “l”, kendinden önceki “ü”ye “g”den daha çok bakmak isteyebilir. Öz-dikkatte her karakter üç vektör üretir:

Bir karakterin sorgusu ile ötekinin anahtarının nokta çarpımı puanı verir: ikisi ne kadar uyuşuyorsa puan o kadar yüksektir. Puanlar maskelenip softmakstan geçirilir ve ağırlıklar, değerlerin ağırlıklı ortalamasını almak için kullanılır. Üç vektör de karakterin gömmesinden öğrenilen üç matrisle (Wq, Wk, Wv) elde edilir.

Dikkat(Q,K,V)=softmaks(QK⊤d+M)V\text{Dikkat}(Q, K, V) = \text{softmaks}\!\left(\frac{Q K^\top}{\sqrt{d}} + M\right) V

Buradaki √d bölümü (d: sorgu vektörünün uzunluğu) puanların çok büyümesini önler. Puanlar çok büyük olursa softmaks tüm ağırlığı tek bir karaktere verir ve gradyanlar neredeyse sıfırlanır.

Adım 3: konum bilgisi

Öz-dikkatin şaşırtıcı bir özelliği vardır: sırayı bilmez. Ağırlıklı ortalama, toplanan şeylerin sırasına bağlı değildir. “elma” ile “lema”nın son harfi aynı karakterlere bakar ve aynı sonucu üretir. Çözüm, her konuma öğrenilen bir konum gömmesi vermek ve bunu karakter gömmesine eklemektir. Böylece “1. konumdaki e” ile “2. konumdaki e” farklı vektörler olur.

Program bu üç adımı sırasıyla gösteriyor. Ağırlıklar henüz eğitilmedi; bu bölümde yalnızca hesabın nasıl işlediğini görüyoruz.

Yöntem
program ÖzDikkat
{ Öz-dikkat, her karakterin kendinden önceki karakterlerden bilgi toplamasını sağlar.
  A) En basit hali: geçmişin ortalamasını almak. Nedensel maske, bir karakterin
     GELECEKTEKİ karakterleri görmesini engeller.
  B) Gerçek öz-dikkat: ortalama eşit ağırlıklı değildir. Her karakter bir sorgu (q),
     bir anahtar (k) ve bir değer (v) üretir. q·k büyükse o karaktere çok bakılır.
        ağırlıklar = softmaks(Q Kᵀ / √d + maske),   çıktı = ağırlıklar @ V
  C) Dikkat sırayı bilmez: aynı karakterler farklı sırayla gelse de sonuç aynıdır.
     Konum gömmesi eklemek bu sorunu çözer. }
kullan Atasozleri, DilAraclari, YZ, Matematik

değişken
  x, M, A, Q, K, V, E, Wq, Wk, Wv, Konum, girdi: tensör
  harfler: metin dizisi
  numara: sözlük

fonksiyon NedenselMaske(n: tamsayı): tensör
değişken i, j: tamsayı; satırlar: liste; s: ondalık dizisi
başla
  satırlar := []
  0'dan n - 1'e kadar her i için
  başla
    s := []
    0'dan n - 1'e kadar her j için eğer j <= i ise s.Ekle(0.0) değilse s.Ekle(-1000000000.0)
    satırlar.Ekle(s)
  bitir
  dön Tensör(satırlar)
bitir

prosedür MatrisYaz(başlık: metin; mat: tensör)
değişken i, j: tamsayı; s: metin
başla
  yaz(başlık, 1)
  0'dan mat.Şekil[0] - 1'e kadar her i için
  başla
    s := '   '
    0'dan mat.Şekil[1] - 1'e kadar her j için s := s + Biçimle('{:7.2}', mat[i, j])
    yaz(s, 1)
  bitir
bitir

fonksiyon SonSatırınÇıktısı(sözcük: metin; konumlu: mantıksal): tensör
{ Sözcüğün karakterlerini gömer, öz-dikkati uygular, son karakterin çıktısını döndürür. }
değişken G, Ağ: tensör; n: tamsayı
başla
  n := Uzunluk(sözcük)
  G := BirSıcak(DilAraclari.Kodla(sözcük, numara), Uzunluk(harfler)) @ E
  eğer konumlu ise G := G + BirSıcak(Aralık(n), 8) @ Konum
  Ağ := Softmaks((G @ Wq) @ (G @ Wk).T / Kök(4) + NedenselMaske(n))
  dön (Ağ @ (G @ Wv)).Satır(n - 1)
bitir

başla
  { A) Geçmişin ortalaması }
  x := Tensör([[1.0, 0.0], [3.0, 2.0], [5.0, 4.0], [7.0, 2.0]])
  M := NedenselMaske(4)
  A := Softmaks(Sıfırlar(4, 4) + M)          { tüm puanlar eşit → eşit ağırlık }
  MatrisYaz('A) Maskeli eşit ağırlıklar (satır: bakan, sütun: bakılan):', A)
  MatrisYaz('   ağırlıklar @ x  =  her satırda, o ana kadarki satırların ortalaması:', A @ x)

  { B) Sorgu, anahtar, değer }
  Tohum(3)
  harfler := DilAraclari.Harfler
  numara := DilAraclari.Numaralar(harfler)
  E := RastgeleTensör(Uzunluk(harfler), 8, 1.0)      { 8 boyutlu karakter gömmeleri }
  Wq := RastgeleTensör(8, 4, 0.5)
  Wk := RastgeleTensör(8, 4, 0.5)
  Wv := RastgeleTensör(8, 4, 0.5)
  girdi := BirSıcak(DilAraclari.Kodla('gönül', numara), Uzunluk(harfler)) @ E
  Q := girdi @ Wq; K := girdi @ Wk; V := girdi @ Wv
  A := Softmaks(Q @ K.T / Kök(4) + NedenselMaske(5))
  yaz('', 1)
  MatrisYaz('B) "gönül" için dikkat ağırlıkları (rastgele, henüz eğitilmemiş Wq, Wk):', A)
  yaz('   Satır toplamları: ' + Eşle(Aralık(5), fonksiyon(r: tamsayı) => Yuvarla(A.Satır(r).Toplam.Değer, 3)), 1)
  yaz('   Çıktının şekli: ' + (A @ V).Şekil + '  (5 karakter, her biri için 4 sayı)', 1)

  { C) Konum bilgisi }
  Konum := RastgeleTensör(8, 8, 1.0)
  yaz('', 1)
  yaz('C) Son karakterin ("a") çıktısı:', 1)
  yaz('   konumsuz  "elma": ' + Eşle(SonSatırınÇıktısı('elma', yanlış).Veri, fonksiyon(z: ondalık) => Yuvarla(z, 3)), 1)
  yaz('   konumsuz  "lema": ' + Eşle(SonSatırınÇıktısı('lema', yanlış).Veri, fonksiyon(z: ondalık) => Yuvarla(z, 3)), 1)
  yaz('   konumlu   "elma": ' + Eşle(SonSatırınÇıktısı('elma', doğru).Veri, fonksiyon(z: ondalık) => Yuvarla(z, 3)), 1)
  yaz('   konumlu   "lema": ' + Eşle(SonSatırınÇıktısı('lema', doğru).Veri, fonksiyon(z: ondalık) => Yuvarla(z, 3)), 1)
bitir.

Programın çıktısı şöyledir:

Çıktı
A) Maskeli eşit ağırlıklar (satır: bakan, sütun: bakılan):
      1.00   0.00   0.00   0.00
      0.50   0.50   0.00   0.00
      0.33   0.33   0.33   0.00
      0.25   0.25   0.25   0.25
   ağırlıklar @ x  =  her satırda, o ana kadarki satırların ortalaması:
      1.00   0.00
      2.00   1.00
      3.00   2.00
      4.00   2.00

B) "gönül" için dikkat ağırlıkları (rastgele, henüz eğitilmemiş Wq, Wk):
      1.00   0.00   0.00   0.00   0.00
      0.76   0.24   0.00   0.00   0.00
      0.23   0.30   0.47   0.00   0.00
      0.41   0.14   0.32   0.13   0.00
      0.01   0.02   0.03   0.51   0.43
   Satır toplamları: [1.0, 1.0, 1.0, 1.0, 1.0]
   Çıktının şekli: [5, 4]  (5 karakter, her biri için 4 sayı)

C) Son karakterin ("a") çıktısı:
   konumsuz  "elma": [-0.273, -0.001, -0.047, 1.101]
   konumsuz  "lema": [-0.273, -0.001, -0.047, 1.101]
   konumlu   "elma": [0.153, 2.455, -0.951, 0.739]
   konumlu   "lema": [0.536, -1.385, 0.089, 0.55]

Çıktıyı okumak

A bölümündeki matris tam beklendiği gibidir: köşegenin üstü sıfır (geleceğe bakılmıyor) ve her satırda ağırlıklar eşittir. Çarpımın ikinci satırı (2, 1), ilk iki satırın ortalamasıdır: (1 + 3) / 2 = 2 ve (0 + 2) / 2 = 1.

B bölümünde ağırlıklar artık eşit değildir. Son satırda “l”, ağırlığının %51’ini “ü”ye, %43’ünü kendisine veriyor; “g”ye neredeyse hiç bakmıyor. Bu ağırlıklar rastgele Wq ve Wk’den geldiği için şimdilik anlamsızdır. Eğitim, bu matrisleri faydalı bakış desenleri üretecek biçimde ayarlayacak. Her satırın toplamı yine 1’dir.

C bölümü konum sorununu gösteriyor. Konum gömmesi yokken “elma” ve “lema”nın son harfi birebir aynı çıktıyı üretiyor; model iki sözcüğü ayırt edemez. Konum gömmesi eklenince çıktılar farklılaşıyor.

Alıştırma. B bölümünde puanları √4’e bölmeyi kaldırınız ve Wq ile Wk’nin ölçeğini 0,5’ten 2,0’a çıkarınız. Ağırlık matrisi nasıl değişiyor? Neden çoğu satırda tek bir 1,00 görüyorsunuz?