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:
- Sorgu (q): “Ben ne arıyorum?”
- Anahtar (k): “Bende ne var?”
- Değer (v): “Bana bakan olursa ona ne aktarırım?”
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.
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.
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:
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?