İçeriğe geç

Kitaplar / Yöntem ile Sıfırdan Dil Modeli / Bölüm 5 — Bağlamı büyütmek

Bölüm 5 — Bağlamı büyütmek

İkili modelin en büyük zayıflığı unutkanlığıdır: “ol” yazdıktan sonra “u” mu “a” mı geleceğini bilmek için yalnızca “l” harfine bakar. Bu bölümde modeli son üç karaktere bakacak şekilde genişletiyoruz. Kuracağımız model, Yoshua Bengio ve arkadaşlarının 2003’te yayımladığı sinirsel dil modelinin küçük bir halidir.

Modelin yapısı

  1. Son üç karakterin her biri, 4. bölümde tanıdığımız gömme tablosundan (burada C) 10 sayılık bir vektöre çevrilir. Tablo üç konum için ortaktır: “a” harfi nerede görülürse görülsün aynı gömmeyi alır.
  2. Üç gömme 96 nöronlu bir gizli katmana girer. Her konumun kendi ağırlık matrisi vardır (W1, W2, W3); böylece model “son harf” ile “üç önceki harf” arasındaki farkı bilir.
  3. Gizli katmanın çıktısı, 30 logit üreten bir çıkış katmanından geçer.

h=tanh(e1W1+e2W2+e3W3+b),logit=hWç+bçh = \tanh(e_1 W_1 + e_2 W_2 + e_3 W_3 + b), \qquad \text{logit} = h\,W_{\text{ç}} + b_{\text{ç}}

Makalelerde genellikle üç gömme yan yana konup 30 sayılık tek bir vektör yapılır ve tek bir büyük matrisle çarpılır. Bizim yazdığımız biçim matematiksel olarak aynıdır; büyük matrisi üç parçaya bölmüş olduk.

İki yeni alışkanlık

Eğitim ve doğrulama ayrımı. Bir modelin gerçekten öğrenip öğrenmediğini, hiç görmediği veride sınamadan bilemeyiz. Atasözlerinin ilk %90’ını eğitim, son %10’unu (18 atasözü) doğrulama için ayırıyoruz. Model doğrulama atasözlerini eğitim boyunca hiç görmez.

Mini-yığınlar. 4. bölümde her adımda tüm veriyi kullanıyorduk. Burada her adımda rastgele seçilmiş 64 örnekle çalışıyoruz. Gradyan biraz gürültülü olur ama adımlar çok daha ucuzlar. Büyük modeller de böyle eğitilir; tüm internet verisini tek adımda işlemek mümkün değildir.

Yöntem
program BağlamlıDilModeli
{ İkili model yalnızca bir önceki karaktere bakıyordu. Bu model son BAĞLAM (3)
  karaktere bakar (Bengio ve arkadaşlarının 2003'teki modelinin küçük bir hali):
     1) Her karakter, öğrenilen C tablosundan 10 sayılık bir gömmeye çevrilir.
     2) Üç gömme gizli katmana girer: h = tanh(e1 @ W1 + e2 @ W2 + e3 @ W3 + b)
        (üç gömmeyi yan yana koyup tek bir büyük matrisle çarpmakla aynı şeydir).
     3) Çıktı katmanı 30 logit üretir: logit = h @ Wç + bç
  Veriyi eğitim (%90) ve doğrulama (%10) diye ayırırız; doğrulama kaybı, modelin
  ezberleyip ezberlemediğini gösterir. Her adımda rastgele 64 örnekten oluşan bir
  yığın (mini-batch) kullanırız. }
kullan Atasozleri, DilAraclari, YZ

sabit
  BAĞLAM: tamsayı = 3
  GÖMME: tamsayı = 10
  GİZLİ: tamsayı = 96
  YIĞIN: tamsayı = 64

değişken
  veri, harfler: metin dizisi
  numara: sözlük
  bağlamlar: liste                   { her örnek: son 3 karakterin numarası }
  hedefler: tamsayı dizisi
  eğitimSayısı, n, i, k, adım: tamsayı
  C, W1, W2, W3, b, Wç, bç, kayıp: tensör
  opt: herhangi
  seçilen: tamsayı dizisi
  pencere: tamsayı dizisi
  kar, sözcük: metin

fonksiyon Logitler(bağ: liste): tensör
{ bağ: bağlam listesi (her biri 3 numara) → her bağlam için 30 logit }
değişken e1, e2, e3, h: tensör
başla
  e1 := BirSıcak(Eşle(bağ, fonksiyon(p: liste) => p[0]), Uzunluk(harfler)) @ C
  e2 := BirSıcak(Eşle(bağ, fonksiyon(p: liste) => p[1]), Uzunluk(harfler)) @ C
  e3 := BirSıcak(Eşle(bağ, fonksiyon(p: liste) => p[2]), Uzunluk(harfler)) @ C
  h := (e1 @ W1 + e2 @ W2 + e3 @ W3 + b).Tanh
  dön h @ Wç + bç
bitir

fonksiyon OrtalamaKayıp(ilk, son: tamsayı): ondalık
{ [ilk, son) aralığındaki örneklerin ortalama kaybı (eğitim yapmadan) }
başla
  dön ÇaprazEntropi(Logitler(Dilim(bağlamlar, ilk, son)), Dilim(hedefler, ilk, son)).Değer
bitir

başla
  Tohum(7)
  veri := Atasozleri.Hepsi
  harfler := DilAraclari.Harfler
  n := Uzunluk(harfler)
  numara := DilAraclari.Numaralar(harfler)

  { Örnekleri çıkar. Atasözlerinin ilk %90'ı eğitim, son %10'u doğrulama. }
  bağlamlar := []; hedefler := []
  0'dan Uzunluk(veri) - 1'e kadar her i için
  başla
    eğer i = Uzunluk(veri) * 9 böl 10 ise eğitimSayısı := Uzunluk(hedefler)
    pencere := [0, 0, 0]
    (veri[i] + '.').Karakterler içindeki her kar için
    başla
      bağlamlar.Ekle(pencere); hedefler.Ekle(numara[kar])
      pencere := [pencere[1], pencere[2], numara[kar]]
    bitir
  bitir
  yaz(Biçimle('{} eğitim, {} doğrulama örneği', eğitimSayısı, Uzunluk(hedefler) - eğitimSayısı), 1)

  C  := RastgeleTensör(n, GÖMME, 1.0, doğru)
  W1 := RastgeleTensör(GÖMME, GİZLİ, 0.2, doğru)
  W2 := RastgeleTensör(GÖMME, GİZLİ, 0.2, doğru)
  W3 := RastgeleTensör(GÖMME, GİZLİ, 0.2, doğru)
  b  := Sıfırlar(GİZLİ, boş, doğru)
  Wç := RastgeleTensör(GİZLİ, n, 0.1, doğru)
  bç := Sıfırlar(n, boş, doğru)
  opt := Adam([C, W1, W2, W3, b, Wç, bç], 0.01)
  yaz(Biçimle('Parametre sayısı: {}', n * GÖMME + 3 * GÖMME * GİZLİ + GİZLİ + GİZLİ * n + n), 1)

  1'den 3000'e kadar her adım için
  başla
    seçilen := Eşle(Aralık(YIĞIN), fonksiyon(x: tamsayı) => Rastgele(eğitimSayısı))
    opt.SıfırlaGrad
    kayıp := ÇaprazEntropi(Logitler(Eşle(seçilen, fonksiyon(x: tamsayı) => bağlamlar[x])),
                           Eşle(seçilen, fonksiyon(x: tamsayı) => hedefler[x]))
    kayıp.GeriYay
    opt.Adım
    eğer adım = 1 veya adım mod 500 = 0 ise
      yaz(Biçimle('Adım {:4}: eğitim kaybı {:.4}   doğrulama kaybı {:.4}', adım,
        OrtalamaKayıp(0, eğitimSayısı), OrtalamaKayıp(eğitimSayısı, Uzunluk(hedefler))), 1)
  bitir

  yaz('', 1); yaz('Modelin ürettiği "atasözleri":', 1)
  1'den 8'e kadar her k için
  başla
    sözcük := ''; pencere := [0, 0, 0]
    tekrarla
      i := DilAraclari.Örnekle(Softmaks(Logitler([pencere])).Satır(0).Veri)
      eğer i <> 0 ise sözcük := sözcük + harfler[i]
      pencere := [pencere[1], pencere[2], i]
    i = 0 olana dek
    yaz('  ' + sözcük, 1)
  bitir
bitir.

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

Çıktı
4724 eğitim, 558 doğrulama örneği
Parametre sayısı: 6186
Adım    1: eğitim kaybı 3.4243   doğrulama kaybı 3.4153
Adım  500: eğitim kaybı 1.8190   doğrulama kaybı 2.4013
Adım 1000: eğitim kaybı 1.5370   doğrulama kaybı 2.4427
Adım 1500: eğitim kaybı 1.4076   doğrulama kaybı 2.5698
Adım 2000: eğitim kaybı 1.3662   doğrulama kaybı 2.7467
Adım 2500: eğitim kaybı 1.2681   doğrulama kaybı 2.7659
Adım 3000: eğitim kaybı 1.2563   doğrulama kaybı 2.8837

Modelin ürettiği "atasözleri":
  kın gönül sıvamahme verkendi söylemek açtırı yoğurttur
  ayama kırk dibin olar
  akıllüyarı söyleme ne insan gele kaz geler yoğuğa döne zotuz
  davul ne gülüm üzüm üzüm uçun elin konu kokla san ne aştan ne anlı düşen kargayır gün işler ölür
  gülünen olur
  gülüşen olur
  akılsan düşmanı yürür
  kur düşen insan da da düşma meyen olur

Çıktıyı okumak: ezberleme

Üretilen metin çok daha Türkçe görünüyor. “gönül”, “söylemek”, “kırk”, “insan”, “düşmanı yürür” gibi gerçek sözcükler ve “gülüşen olur” gibi atasözü tadında dizeler belirdi. Üç karakterlik bağlam, sözcük ve hece düzeyinde büyük fark yaratıyor.

Ama kayıp sütunları önemli bir uyarı veriyor. Eğitim kaybı 3000 adım boyunca düzenli olarak düşüyor (1,82 → 1,26). Doğrulama kaybı ise en iyi değerine (2,40) 500. adımda ulaşıyor, sonra yükseliyor ve 2,88’e çıkıyor. Model, eğitim atasözlerini giderek daha iyi ezberliyor ama hiç görmediği atasözlerinde giderek kötüleşiyor. Bu duruma aşırı uyum (overfitting) denir.

Nedeni basit: 6186 parametreli bir model, 4724 örneği neredeyse tek tek ezberleyebilecek kadar büyüktür. Üretilen metinde “kırk”, “davul”, “üzüm üzüm” gibi eğitim verisinden birebir parçalar görmeniz bunun işaretidir. Büyük dil modelleri bu sorunu öncelikle devasa veriyle çözer: modelin ezberleyemeyeceği kadar çok metin. Bizim verimiz küçük olduğu için 8. bölümde iki teknik kullanacağız. Erken durdurma, doğrulama kaybı en düşükken modeli kaydetmektir. Bırakma (dropout) ise eğitim sırasında ağın bir kısmını rastgele susturmaktır.

Bir karşılaştırma notu: 3. ve 4. bölümlerdeki kayıplar tüm veri üzerinde, yani modelin gördüğü veride ölçülmüştü. Bu bölümden itibaren asıl ölçütümüz doğrulama kaybıdır.

Alıştırma. GİZLİ sabitini 96’dan 24’e indiriniz ve programı yeniden çalıştırınız. Doğrulama kaybının en düşük değeri ve yükselmeye başladığı adım nasıl değişiyor? Ardından adım sayısını 500’e indirip üretilen metne bakınız.