Ana içeriğe geç

Orta10 dk

Transformer'ın eğitimi ve çıkarımı

Önkoşul:Encoder-decoder yapısı

Kanca

10-16. derste bir Transformer’ın TÜM parçalarını (attention, FFN, layer norm, encoder-decoder) elle kurduk. Bu ders, bu parçaları GERÇEKTEN bir araya getirip, GERÇEKTEN eğitiyor — ilk kez, elle tasarlanmış vektörler DEĞİL, gradyan inişiyle ÖĞRENİLEN ağırlıklar.

Sezgi

Eğitimde model “öğretmen zorlaması” (teacher forcing) ile öğrenir: HER adımda GERÇEK (doğru) önceki kelimeler verilir. Çıkarımda (inference) ise model KENDİ ürettiği kelimeleri bir sonraki adımın girdisi olarak kullanmak ZORUNDADIR. Bu iki mod arasındaki fark, hem HIZ hem GÜVENİLİRLİK açısından önemli sonuçlar doğurur.

Mekanizma

Önce, öğretmen zorlamasıyla küçük bir decoder’ı GERÇEKTEN eğitelim — “ABCABCABC…” örüntüsünü öğrensin:

Öğretmen zorlamasıyla eğitim

# Eğitimde "öğretmen zorlaması" (teacher forcing) kullanılır: decoder'a HER ZAMAN
# GERÇEK (doğru) önceki token'lar verilir -- kendi (belki yanlış) tahminleri DEĞİL.
model = MiniDecoder(V, D, PENCERE)
optimize_edici = torch.optim.Adam(model.parameters(), lr=0.01)
kayip_gecmisi = []

for adim in range(400):
    baslangic = torch.randint(0, len(ids) - PENCERE - 1, (8,))
    girdi = torch.stack([ids[b:b + PENCERE] for b in baslangic])       # GERÇEK önceki token'lar
    hedef = torch.stack([ids[b + 1:b + PENCERE + 1] for b in baslangic])  # bir sonraki GERÇEK token
    logits = model(girdi)
    kayip = F.cross_entropy(logits.reshape(-1, V), hedef.reshape(-1))
    optimize_edici.zero_grad()
    kayip.backward()
    optimize_edici.step()
    kayip_gecmisi.append(kayip.item())
    if adim % 100 == 0:
        print(f"adım {adim:<5} kayıp={kayip.item():.4f}")
print(f"adım {399:<5} kayıp={kayip_gecmisi[-1]:.6f}")
print("\nModel, 'ABCABCABC...' örüntüsünü GERÇEK (doğru) bağlamla eğitildi -- kayıp neredeyse sıfıra indi.")
adım 0     kayıp=1.1733
adım 100   kayıp=0.0007
adım 200   kayıp=0.0003
adım 300   kayıp=0.0002
adım 399   kayıp=0.000117

Model, 'ABCABCABC...' örüntüsünü GERÇEK (doğru) bağlamla eğitildi -- kayıp neredeyse sıfıra indi.

Kayıp 400 adımda 1.17’den 0.0001’e indi — model örüntüyü ÖĞRENDİ. Şimdi eğitim ile çıkarımın HIZINI karşılaştıralım:

Eğitim paralel, çıkarım sıralı

# Eğitimde bir BÜYÜK avantaj var: 11. dersteki nedensel maske sayesinde, PENCERE'deki
# TÜM 8 pozisyonun kaybı TEK bir matris işleminde (paralel) hesaplanır. Çıkarımda
# (inference) İSE, her yeni token'ı üretmek için TÜM geçmişi yeniden BEKLEMEK gerekir --
# 8. dersteki "sıralı hesaplamanın maliyeti" konusunun AYNISI, şimdi ÜRETİM aşamasında.
def egitim_ileri_gecisi_sure(tekrar=200):
    girdi = torch.stack([ids[b:b + PENCERE] for b in torch.randint(0, len(ids) - PENCERE - 1, (8,))])
    with torch.no_grad():
        baslangic = time.perf_counter()
        for _ in range(tekrar):
            model(girdi)  # TEK çağrı, 8 pozisyonun HEPSİNİ birden işler
        return (time.perf_counter() - baslangic) / tekrar

def cikarim_uretim_sure(uzunluk=8, tekrar=200):
    with torch.no_grad():
        baslangic = time.perf_counter()
        for _ in range(tekrar):
            uretilen = [stoi["A"]]
            for _ in range(uzunluk):
                pencere_ids = torch.tensor(uretilen[-PENCERE:]).unsqueeze(0)
                model(pencere_ids)  # HER token için AYRI bir çağrı
                uretilen.append(0)
        return (time.perf_counter() - baslangic) / tekrar

egitim_suresi = egitim_ileri_gecisi_sure()
cikarim_suresi = cikarim_uretim_sure()
print(f"\n8 pozisyonluk bir pencereyi EĞİTİMDE işlemek (TEK paralel çağrı): {egitim_suresi*1000:.3f} ms")
print(f"8 token'lık bir diziyi ÇIKARIMDA üretmek (8 AYRI sıralı çağrı):  {cikarim_suresi*1000:.3f} ms")
print(f"Çıkarım, eğitimin {cikarim_suresi/egitim_suresi:.1f} katı yavaş -- AYNI iş, ama SIRALI yapılmak zorunda.")

8 pozisyonluk bir pencereyi EĞİTİMDE işlemek (TEK paralel çağrı): 0.611 ms
8 token'lık bir diziyi ÇIKARIMDA üretmek (8 AYRI sıralı çağrı):  4.470 ms
Çıkarım, eğitimin 7.3 katı yavaş -- AYNI iş, ama SIRALI yapılmak zorunda.

Matematik

Öğretmen zorlaması ve çapraz entropi
L=1Tt=1TlogP(yty1,,yt1GERC¸EK)\mathcal{L} = -\frac{1}{T}\sum_{t=1}^{T} \log P(y_t \mid y_1, \dots, y_{t-1}^{\text{GERÇEK}})

Dikkat: formüldeki yt1GERC¸EKy_{t-1}^{\text{GERÇEK}} — kayıp hesaplanırken, model t1t-1. adımda NE tahmin ETTİĞİNE bakılmaksızın, HER ZAMAN gerçek (etiketlenmiş) yt1y_{t-1} ile beslenir. Bu, 8. DL Görüntü dersindeki (sınıflandırma kaybı) çapraz entropinin AYNISI — tek fark, her token pozisyonunda AYRI bir sınıflandırma problemi olması (VV = kelime dağarcığı boyutu kadar sınıf).

Kod

Şimdi GERÇEK çıkarımı (autoregressive generation) çalıştıralım — model artık öğretmensiz, KENDİ başına:

Otoregresif üretim: model kendi çıktısını yer

# Şimdi gerçek çıkarımı (autoregressive generation) çalıştıralım: model, KENDİ
# ürettiği token'ları bir sonraki adımın GİRDİSİ olarak kullanır.
model.eval()
uretilen = [stoi["A"]]
with torch.no_grad():
    for _ in range(20):
        pencere_ids = torch.tensor(uretilen[-PENCERE:]).unsqueeze(0)
        logits = model(pencere_ids)
        sonraki = logits[0, -1].argmax().item()
        uretilen.append(sonraki)
uretilen_metin = "".join(VOCAB[i] for i in uretilen)
print(f"\n'A' tohumundan başlayarak, model KENDİ tahminleriyle 20 token üretti:")
print(f"  {uretilen_metin}")
print("Model, ÖĞRETMEN ZORLAMASI olmadan, SADECE kendi çıktısını kullanarak örüntüyü DOĞRU sürdürdü.")

'A' tohumundan başlayarak, model KENDİ tahminleriyle 20 token üretti:
  ABCABCABCABCABCABCABC
Model, ÖĞRETMEN ZORLAMASI olmadan, SADECE kendi çıktısını kullanarak örüntüyü DOĞRU sürdürdü.
Eğitim adımına karşı çapraz entropi kaybını log ölçekte gösteren grafik; kayıp hızla 1'den 0.0001'e düşüyor.
Öğretmen zorlamasıyla eğitim, 400 adımda kaybı 1.17'den 0.0001'e indiriyor -- model, örüntüyü neredeyse mükemmel öğreniyor.

Peki ya model erken bir HATA yaparsa? “Maruz kalma önyargısı” (exposure bias) tam olarak bunu sorar:

Elle bir hata enjekte et: model toparlanır mı?

# Eğitim ve çıkarım arasındaki GİZLİ bir uyumsuzluk: eğitimde model HİÇBİR ZAMAN
# kendi HATALI bir tahminini görmedi (her zaman GERÇEK bağlamla beslendi). Çıkarımda
# ise erken bir HATA, TÜM sonraki bağlamı BOZABİLİR. Buna "maruz kalma önyargısı"
# (exposure bias) denir. Elle bir hata enjekte edip test edelim:
uretilen_bozuk = [stoi["A"], stoi["B"], stoi["C"], stoi["A"]]
uretilen_bozuk.append(stoi["A"])  # YANLIŞ token -- doğrusu 'B' olmalıydı
with torch.no_grad():
    for _ in range(10):
        pencere_ids = torch.tensor(uretilen_bozuk[-PENCERE:]).unsqueeze(0)
        logits = model(pencere_ids)
        sonraki = logits[0, -1].argmax().item()
        uretilen_bozuk.append(sonraki)
bozuk_metin = "".join(VOCAB[i] for i in uretilen_bozuk)
print(f"\nElle bozulan dizi (5. karakter YANLIŞ 'A' yapıldı): {bozuk_metin}")
print("Bu ÖZEL örnekte model HIZLA toparlandı -- çünkü 'ABC' örüntüsü SON İKİ karaktere bakarak")
print("HER YERDEN yeniden başlatılabilir (aşırı REDUNDANT/öngörülebilir bir dil). GERÇEK doğal dilde")
print("bağlam çok daha AZ öngörülebilir olduğu için, erken bir hata GENELDE daha KALICI bozulmaya yol açar --")
print("bu yüzden 'maruz kalma önyargısı' büyük dil modellerinde CİDDİ bir araştırma konusudur.")

Elle bozulan dizi (5. karakter YANLIŞ 'A' yapıldı): ABCAABCABCABCAB
Bu ÖZEL örnekte model HIZLA toparlandı -- çünkü 'ABC' örüntüsü SON İKİ karaktere bakarak
HER YERDEN yeniden başlatılabilir (aşırı REDUNDANT/öngörülebilir bir dil). GERÇEK doğal dilde
bağlam çok daha AZ öngörülebilir olduğu için, erken bir hata GENELDE daha KALICI bozulmaya yol açar --
bu yüzden 'maruz kalma önyargısı' büyük dil modellerinde CİDDİ bir araştırma konusudur.

Nerede işe yarar

Bu ders, teoriyi (10-16. ders) GERÇEK bir eğitim döngüsüyle birleştirdi:

  • Gerçek dil modelleri, BİZİM 60 karakterlik örüntümüzden MİLYARLARCA kat daha büyük ve DAHA AZ öngörülebilir metinlerle eğitilir — bu yüzden maruz kalma önyargısı, GERÇEK modellerde bizim örneğimizdeki kadar KOLAY “toparlanmaz”.
  • Çıkarımın SIRALI olması (bu dersteki 7.3x yavaşlık), büyük dil modellerinin en PAHALI kısmıdır — bu yüzden hız optimizasyonları (KV-cache, speculative decoding gibi teknikler) ML mühendisliğinin AKTİF bir araştırma alanıdır.
  • “Sampling” (22. ders) her zaman en olası token’ı SEÇMEZ — burada argmax (en olası token) kullandık, ama gerçek modeller genelde ÇEŞİTLİLİK için rastgelelik (temperature, top-k/top-p) ekler.

Bu 2 hatayı yaparsın:

  1. Öğretmen zorlamasının çıkarımda da kullanıldığını sanmak — HAYIR, çıkarımda GERÇEK gelecek token’lar YOKTUR (henüz üretilmediler); model SADECE kendi geçmiş çıktısına güvenebilir.
  2. Bu dersteki “model hatadan hızla toparlandı” sonucunu HER modele genellemek — bu, örüntünün AŞIRI tekrarlayan (redundant) olmasından kaynaklanıyor; doğal dilde toparlanma ÇOK daha ZORDUR.

Kendini test et

1. Eğitimde 'öğretmen zorlaması' (teacher forcing) NE anlama gelir?
  1. Model, kendi ürettiği token'ları bir sonraki adımın girdisi olarak kullanır
  2. Kayıp hesaplanırken, model bir önceki adımda NE tahmin ETMİŞ olursa olsun, HER ZAMAN gerçek (etiketli) önceki token'larla beslenir -- bu, öğrenmeyi hızlandırır ve kararlı hale getirir (doğru cevap)
  3. Öğretmen, modelin her adımını elle kontrol eder
  4. Sadece ilk token için gerçek veri kullanılır

Neden: ogretmen-zorlamasi-egitimi bloğunda girdi = ids[b:b+PENCERE] (GERÇEK token'lar) olarak sabitleniyor, modelin KENDİ tahminleri DEĞİL -- MathBox'taki formülde y_{t-1}^GERÇEK notasyonu bunu vurguluyor.

2. Notebook'ta çıkarım (inference), eğitimin 7.3 katı yavaş çıkıyor. Bu farkın NEDENİ ne?
  1. Çıkarım kodu kötü yazılmış
  2. Eğitimde 11. dersteki nedensel maske sayesinde TÜM pencere pozisyonları TEK bir paralel matris işleminde hesaplanır; çıkarımda ise her YENİ token, bir öncekine BAĞIMLI olduğu için AYRI AYRI, SIRAYLA üretilmek zorundadır (doğru cevap)
  3. Çıkarım daha fazla bellek kullanır
  4. Model çıkarımda daha büyük hale gelir

Neden: paralel-egitim-sirali-cikarim bloğunda eğitim TEK bir model(girdi) çağrısıyla 8 pozisyonu birden işlerken, çıkarım 8 AYRI model() çağrısı gerektiriyor -- 8. dersteki 'sıralı hesaplamanın maliyeti' konusunun üretim aşamasındaki karşılığı.

3. Notebook'ta elle enjekte edilen bir hatadan sonra model HIZLA toparlandı. Metinde bu sonucun NEDEN her modele genellenemeyeceği açıklanıyor?
  1. Genellenebilir, her model aynı şekilde toparlanır
  2. 'ABCABC...' örüntüsü AŞIRI tekrarlayan (redundant) olduğu için, SON birkaç karaktere bakarak HER YERDEN yeniden başlanabilir; gerçek doğal dilde bağlam ÇOK DAHA AZ öngörülebilir olduğu için erken bir hata GENELDE daha kalıcı bozulmaya yol açar (doğru cevap)
  3. Model çok küçük olduğu için toparlandı
  4. Bu sonuç sadece bu notebook için geçerlidir, genel bir ilkesi yoktur

Neden: maruz-kalma-onyargisi bloğunun çıktısı 'ABCAABCABCABCAB' -- model toparlandı, AMA bu, kullanım bölümünde açıklandığı gibi periyodik örüntünün AŞIRI redundant olmasından kaynaklanıyor; gerçek dilde maruz kalma önyargısı ciddi bir araştırma konusudur.

Özet

Özet

  • Bu ders, 10-15. dersteki TÜM parçaları (attention, FFN, layer norm, artık bağlantı) BİR ARAYA getirip GERÇEKTEN eğitti -- kayıp 1.17'den 0.0001'e indi.
  • Eğitimde 'öğretmen zorlaması' kullanılır: model HER ZAMAN gerçek önceki token'larla beslenir, kendi tahminleriyle DEĞİL.
  • Çıkarımda (inference) model KENDİ ürettiği token'ları kullanmak ZORUNDADIR -- bu, eğitime göre 7.3 kat daha YAVAŞTIR çünkü SIRALI olmak zorundadır.
  • 'Maruz kalma önyargısı' (exposure bias), eğitimde hiç görülmeyen 'kendi hatasıyla besleme' durumunun çıkarımda YARATTIĞI riski ifade eder.
  • Bu basit örnekte model hatadan hızla toparlandı -- ama bu, örüntünün aşırı redundant olmasından kaynaklanır; gerçek dilde durum daha zordur.
Sonraki adım: GPT ile "Attention is All You Need" farkı →