Ana içeriğe geç

Orta10 dk

Bir sonraki token: örnekleme, temperature, top-k/top-p

Önkoşul:Ön eğitim, ince ayar, hizalama (RLHF/DPO)

Kanca

17-21. derste modelimiz HEP en olası token’ı (argmax) seçti — çünkü eğitim verisi %100 ÖNGÖRÜLEBİLİRDİ. Ama GERÇEK dil BÖYLE değil: aynı bağlamdan sonra BİRDEN FAZLA makul devam VARDIR. Bu son ders, model bir olasılık dağılımı ÜRETTİKTEN sonra, o dağılımdan bir kelimeyi NASIL “seçtiğimizi” ele alıyor.

Sezgi

Model her zaman TEK bir doğru cevap değil, bir OLASILIK DAĞILIMI üretir. “Açgözlü” (greedy) çözme her zaman en olasıyı seçer — bu, modelin İÇSEL belirsizliğini YOK SAYAR. Örnekleme (sampling), bu dağılımdan RASTGELE çeker — ve temperature/top-k/top-p, bu rastgeleliği NE KADAR ve NASIL uygulayacağımızı kontrol eder.

Mekanizma

Önce GERÇEKTEN belirsiz bir dil üzerinde bir model eğitelim — “AB”den sonra %60 “C”, %40 “D” gelsin:

Gerçekten belirsiz bir dil

import random
import torch
import torch.nn as nn
import torch.nn.functional as F

torch.manual_seed(0)
random.seed(0)

VOCAB = ["A", "B", "C", "D"]
V = len(VOCAB)
stoi = {c: i for i, c in enumerate(VOCAB)}
D, PENCERE = 16, 8

# GERÇEKTEN belirsiz bir dil: "AB"den sonra %60 ihtimalle "C", %40 ihtimalle "D" geliyor --
# 17-21. dersteki "ABC" örüntüsünün AKSİNE, burada TEK bir doğru cevap YOK.
parcalar = []
for _ in range(80):
    parcalar.append("AB" + random.choices(["C", "D"], weights=[0.6, 0.4])[0])
DIZI = "".join(parcalar)
ids = torch.tensor([stoi[c] for c in DIZI])
print(f"Eğitim verisinin ilk 30 karakteri: {DIZI[:30]}")
print("Gerçek üretim kuralı: 'AB'den sonra %60 'C', %40 'D' (rastgele).")

class MiniGPT(nn.Module):
    def __init__(self, vocab_size, d_model, pencere):
        super().__init__()
        self.gomme = nn.Embedding(vocab_size, d_model)
        self.pos = nn.Embedding(pencere, d_model)
        self.Wq = nn.Linear(d_model, d_model, bias=False)
        self.Wk = nn.Linear(d_model, d_model, bias=False)
        self.Wv = nn.Linear(d_model, d_model, bias=False)
        self.norm1 = nn.LayerNorm(d_model)
        self.ffn = nn.Sequential(nn.Linear(d_model, 4 * d_model), nn.GELU(), nn.Linear(4 * d_model, d_model))
        self.norm2 = nn.LayerNorm(d_model)
        self.cikis = nn.Linear(d_model, vocab_size)

    def forward(self, x_ids):
        B, T = x_ids.shape
        h = self.gomme(x_ids) + self.pos(torch.arange(T))
        Q, K, Vv = self.Wq(h), self.Wk(h), self.Wv(h)
        maske = torch.tril(torch.ones(T, T))
        puanlar = (Q @ K.transpose(-2, -1)) / (D ** 0.5)
        puanlar = puanlar.masked_fill(maske == 0, float("-inf"))
        agirliklar = F.softmax(puanlar, dim=-1)
        h = self.norm1(h + agirliklar @ Vv)
        h = self.norm2(h + self.ffn(h))
        return self.cikis(h)

model = MiniGPT(V, D, PENCERE)
optimize_edici = torch.optim.Adam(model.parameters(), lr=0.01)
for adim in range(600):
    b = torch.randint(0, len(ids) - PENCERE - 1, (16,))
    girdi = torch.stack([ids[x:x + PENCERE] for x in b])
    hedef = torch.stack([ids[x + 1:x + PENCERE + 1] for x in b])
    logit = model(girdi)
    kayip = F.cross_entropy(logit.reshape(-1, V), hedef.reshape(-1))
    optimize_edici.zero_grad()
    kayip.backward()
    optimize_edici.step()
print(f"\nEğitim tamamlandı, son kayıp: {kayip.item():.4f}")

model.eval()
with torch.no_grad():
    baglam = torch.tensor([[stoi["A"], stoi["B"]]])
    ham_logit = model(baglam)[0, -1]
    P_taban = F.softmax(ham_logit, dim=-1)
print(f"\nP(sıradaki | 'AB'): {dict(zip(VOCAB, [round(v,3) for v in P_taban.tolist()]))}")
print("Model, GERÇEK %60/%40 oranını NEREDEYSE tam olarak ÖĞRENDİ -- 'C' KESİN değil, sadece DAHA olası.")
Eğitim verisinin ilk 30 karakteri: ABDABDABCABCABCABCABDABCABCABC
Gerçek üretim kuralı: 'AB'den sonra %60 'C', %40 'D' (rastgele).

Eğitim tamamlandı, son kayıp: 0.2181

P(sıradaki | 'AB'): {'A': 0.0, 'B': 0.0, 'C': 0.616, 'D': 0.384}
Model, GERÇEK %60/%40 oranını NEREDEYSE tam olarak ÖĞRENDİ -- 'C' KESİN değil, sadece DAHA olası.

Şimdi açgözlü çözme ile örneklemeyi KARŞILAŞTIRALIM:

Açgözlü çözme belirsizliği siler, örnekleme korur

# 17-20. derste HEP argmax (en olası token) kullandık -- "açgözlü" (greedy) çözme.
# Bu, belirsizliği YOK SAYAR. Gerçek örnekleme (sampling), dağılımdan RASTGELE seçer.
acgozlu_secim = VOCAB[P_taban.argmax().item()]
print(f"\nAçgözlü (greedy) seçim: HER ZAMAN '{acgozlu_secim}' -- model %38.4 ihtimalle 'D' olacağını")
print("DÜŞÜNSE bile, açgözlü çözme bunu HİÇBİR ZAMAN üretmez.")

torch.manual_seed(7)
ornekler = [VOCAB[torch.multinomial(P_taban, 1).item()] for _ in range(1000)]
sayimlar = {t: ornekler.count(t) for t in VOCAB}
print(f"\n1000 örneklemede (sampling) gerçek dağılım: {sayimlar}")
print(f"Oran: C=%{100*sayimlar['C']/1000:.1f}, D=%{100*sayimlar['D']/1000:.1f} -- modelin ÖĞRENDİĞİ")
print("%60/%40 oranına ÇOK yakın. Örnekleme, modelin İÇSEL belirsizliğini SAKLIYOR; açgözlü çözme SİLİYOR.")

Açgözlü (greedy) seçim: HER ZAMAN 'C' -- model %38.4 ihtimalle 'D' olacağını
DÜŞÜNSE bile, açgözlü çözme bunu HİÇBİR ZAMAN üretmez.

1000 örneklemede (sampling) gerçek dağılım: {'A': 0, 'B': 0, 'C': 615, 'D': 385}
Oran: C=%61.5, D=%38.5 -- modelin ÖĞRENDİĞİ
%60/%40 oranına ÇOK yakın. Örnekleme, modelin İÇSEL belirsizliğini SAKLIYOR; açgözlü çözme SİLİYOR.

Matematik

Temperature, top-k, top-p
PT(xi)=exp(zi/T)jexp(zj/T)P_T(x_i) = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)}top-k:sadece en olası k token\text{top-}k: \text{sadece en olası } k \text{ token}top-p:tutulanP(xi)p\text{top-}p: \sum_{\text{tutulan}} P(x_i) \geq p
YöntemNe yaparNe zaman kullanılır
Temperature (TT)Logit’leri TT‘ye böler — 1’den küçük TT keskinleştirir, 1’den büyük TT düzleştirirGenel “yaratıcılık” ayarı
top-kSADECE en olası kk token’ı tutar, SABİT sayıÇok düşük olasılıklı “kuyruğu” kesmek
top-p (nucleus)Kümülatif olasılığı pp‘yi aşan EN KÜÇÜK kümeyi tutar, DEĞİŞKEN sayıDağılımın ŞEKLİNE uyarlanabilir filtreleme

Kod

Temperature’ın etkisini SAYILARLA görelim:

Sıcaklık: keskinleştirme ve düzleştirme

# "Sıcaklık" (temperature), softmax'tan ÖNCE logit'leri T'ye BÖLEREK dağılımı
# 'keskinleştirir' (T<1) veya 'düzleştirir' (T>1).
print(f"\n{'Sıcaklık (T)':<14} {'P(C)':<8} {'P(D)':<8} {'Yorum':<30}")
for T in [0.5, 1.0, 2.0]:
    P_T = F.softmax(ham_logit / T, dim=-1)
    yorum = "daha KESKİN (az çeşitlilik)" if T < 1 else "daha DÜZ (çok çeşitlilik)" if T > 1 else "değişmedi (taban)"
    print(f"{T:<14} {P_T[stoi['C']].item():<8.3f} {P_T[stoi['D']].item():<8.3f} {yorum:<30}")
P_05 = F.softmax(ham_logit / 0.5, dim=-1)[stoi["C"]].item()
P_20 = F.softmax(ham_logit / 2.0, dim=-1)[stoi["C"]].item()
print(f"\nT=0.5, modelin ZATEN olan tercihini ABARTIR (C: {P_taban[stoi['C']].item():.3f} -> {P_05:.3f}). T=2.0 bu tercihi")
print(f"YUMUŞATIR (C: {P_taban[stoi['C']].item():.3f} -> {P_20:.3f}) -- T, modelin öğrendiği OLASILIKLARI DEĞİL, ne kadar")
print("'kararlı' davranacağını kontrol eder.")

Sıcaklık (T)   P(C)     P(D)     Yorum                         
0.5            0.721    0.279    daha KESKİN (az çeşitlilik)   
1.0            0.616    0.384    değişmedi (taban)             
2.0            0.546    0.431    daha DÜZ (çok çeşitlilik)     

T=0.5, modelin ZATEN olan tercihini ABARTIR (C: 0.616 -> 0.721). T=2.0 bu tercihi
YUMUŞATIR (C: 0.616 -> 0.546) -- T, modelin öğrendiği OLASILIKLARI DEĞİL, ne kadar
'kararlı' davranacağını kontrol eder.

Şimdi top-k ve top-p’yi KARŞILAŞTIRALIM:

Sabit sayı (top-k) vs uyarlanabilir kütle (top-p)

# top-k: SADECE en olası k token'ı TUT, gerisini SIFIRLA, yeniden normalize et.
# top-p (nucleus): kümülatif olasılığı p'yi AŞAN EN KÜÇÜK token kümesini TUT.
def top_k_filtrele(olasiliklar, k):
    deger, indeks = torch.topk(olasiliklar, k)
    yeni = torch.zeros_like(olasiliklar)
    yeni[indeks] = deger
    return yeni / yeni.sum()

def top_p_filtrele(olasiliklar, p):
    siralanmis_deger, siralanmis_indeks = torch.sort(olasiliklar, descending=True)
    kumulatif = torch.cumsum(siralanmis_deger, dim=0)
    kesme = (kumulatif < p).sum().item() + 1  # eşiği AŞAN ilk noktaya kadar tut
    tutulan_indeks = siralanmis_indeks[:kesme]
    yeni = torch.zeros_like(olasiliklar)
    yeni[tutulan_indeks] = olasiliklar[tutulan_indeks]
    return yeni / yeni.sum()

P_topk2 = top_k_filtrele(P_taban, k=2)
P_topp05 = top_p_filtrele(P_taban, p=0.5)

print(f"\nTaban dağılım:      {dict(zip(VOCAB, [round(v,3) for v in P_taban.tolist()]))}")
print(f"top-k (k=2) sonrası: {dict(zip(VOCAB, [round(v,3) for v in P_topk2.tolist()]))}")
print(f"top-p (p=0.5) sonrası: {dict(zip(VOCAB, [round(v,3) for v in P_topp05.tolist()]))}")
print("\ntop-k=2, HER ZAMAN tam olarak 2 token tutar (burada zaten A,B ~0 olduğundan sonuç")
print("değişmiyor). top-p=0.5 İSE, KÜMÜLATİF kütleye bakar: 'C' TEK BAŞINA (0.616) 0.5'i AŞTIĞI")
print("için SADECE 'C' tutuluyor -- top-p, dağılımın ŞEKLİNE göre ADAPTİF, top-k SABİT sayıda.")

Taban dağılım:      {'A': 0.0, 'B': 0.0, 'C': 0.616, 'D': 0.384}
top-k (k=2) sonrası: {'A': 0.0, 'B': 0.0, 'C': 0.616, 'D': 0.384}
top-p (p=0.5) sonrası: {'A': 0.0, 'B': 0.0, 'C': 1.0, 'D': 0.0}

top-k=2, HER ZAMAN tam olarak 2 token tutar (burada zaten A,B ~0 olduğundan sonuç
değişmiyor). top-p=0.5 İSE, KÜMÜLATİF kütleye bakar: 'C' TEK BAŞINA (0.616) 0.5'i AŞTIĞI
için SADECE 'C' tutuluyor -- top-p, dağılımın ŞEKLİNE göre ADAPTİF, top-k SABİT sayıda.
Solda: farklı sıcaklık değerlerinde P(C) ve P(D)'yi gösteren çubuk grafik. Sağda: taban dağılım, top-k=2 ve top-p=0.5 filtrelemesi sonrası yığılmış çubuk grafik.
Solda: T=0.5 C'yi öne çıkarır, T=2.0 D'ye daha fazla şans tanır. Sağda: top-k=2 dağılımı DEĞİŞTİRMEZ (zaten 2 token anlamlı), top-p=0.5 İSE dağılımı SADECE 'C'ye indirger.

Nerede işe yarar

Bu ayarlar, GERÇEK bir dil modeliyle (ChatGPT, Claude…) her etkileşimde AKTİF olarak çalışır:

  • Kod üretimi gibi görevlerde DÜŞÜK temperature (0-0.3) tercih edilir — tutarlılık ve doğruluk, çeşitlilikten daha ÖNEMLİDİR.
  • Yaratıcı yazı gibi görevlerde DAHA YÜKSEK temperature (0.7-1.0) tercih edilir — çeşitlilik, tek “doğru” cevabın olmadığı durumlarda DEĞERLİDİR.
  • top-p, PRATİKTE top-k’dan daha YAYGIN kullanılır — çünkü dağılımın “keskinliğine” göre KENDİLİĞİNDEN uyum sağlar; top-k SABİT bir sayı olduğu için bazen çok DAR (kararlı bir bağlamda), bazen çok GENİŞ (belirsiz bir bağlamda) kalabilir.

Bu 2 hatayı yaparsın:

  1. Temperature’ın modelin ÖĞRENDİĞİ olasılıkları DEĞİŞTİRDİĞİNİ sanmak — HAYIR, model AYNI kalır; temperature SADECE bu olasılıklardan NASIL örnekleme yapıldığını etkiler.
  2. Açgözlü çözmenin HER ZAMAN “en iyi” seçim olduğunu düşünmek — gerçek dilde çeşitlilik, DOĞALLIK için GEREKLİDİR; her zaman en olası kelimeyi seçmek tekrarlayan, “robotik” metin üretebilir.

Kendini test et

1. Notebook'ta model 'AB'den sonra P(C)=0.616, P(D)=0.384 öğreniyor. Açgözlü (greedy) çözme ile örnekleme (sampling) arasındaki fark nedir?
  1. Hiçbir fark yoktur, ikisi de aynı sonucu verir
  2. Açgözlü çözme HER ZAMAN 'C'yi seçer (en olası), modelin %38.4 ihtimalle 'D' olacağını düşünmesini YOK SAYAR; örnekleme İSE 1000 denemede gerçek %61.5/%38.5 oranını ÜRETİR, modelin belirsizliğini KORUR (doğru cevap)
  3. Örnekleme her zaman yanlış sonuç verir
  4. Açgözlü çözme rastgele bir seçimdir

Neden: acgozlu-vs-ornekleme bloğunda açgözlü çözme HER ZAMAN 'C' üretirken, 1000 örneklemenin sonucu {C: 615, D: 385} -- modelin öğrendiği orana ÇOK yakın bir dağılım.

2. Temperature (T) NEYİ değiştirir, NEYİ değiştirmez?
  1. Modelin öğrendiği olasılıkları KALICI olarak değiştirir
  2. Modelin AĞIRLIKLARINI veya ÖĞRENDİĞİ olasılıkları DEĞİŞTİRMEZ -- SADECE softmax'a girmeden ÖNCE logit'leri T'ye bölerek, dağılımın ne kadar 'keskin' ya da 'düz' örnekleneceğini kontrol eder (doğru cevap)
  3. Sadece top-k ile birlikte kullanılabilir
  4. Modelin kelime dağarcığını değiştirir

Neden: temperature bloğunda AYNI ham_logit (modelin ürettiği, DEĞİŞMEYEN çıktı) farklı T değerleriyle bölünüyor -- T=0.5 P(C)'yi 0.721'e çıkarırken T=2.0 0.546'ya indiriyor, ama bu SADECE örnekleme dağılımını etkiliyor, modeli DEĞİL.

3. top-k=2 ile top-p=0.5 arasındaki temel fark nedir?
  1. İkisi de aynı sonucu üretir
  2. top-k HER ZAMAN sabit sayıda (k) token tutar, dağılımın şekline BAKMAKSIZIN; top-p İSE kümülatif olasılık kütlesine bakar -- notebook'ta 'C' TEK BAŞINA (0.616) p=0.5'i aştığı için top-p SADECE 'C'yi tutarken, top-k=2 hem 'C' hem 'D'yi tutuyor (doğru cevap)
  3. top-p her zaman daha fazla token tutar
  4. top-k, olasılıkları değiştirmez

Neden: top-k-top-p bloğunda top_k_filtrele(k=2) sonucu {C: 0.616, D: 0.384} (değişmedi) iken top_p_filtrele(p=0.5) sonucu {C: 1.0, D: 0.0} -- top-p'nin KÜMÜLATİF kütleye göre ADAPTİF filtrelediğinin doğrudan kanıtı.

Özet

Özet

  • Bir dil modeli, bir sonraki token için TEK bir cevap değil, bir OLASILIK DAĞILIMI üretir.
  • Açgözlü (greedy) çözme her zaman en olası token'ı seçer -- modelin belirsizliğini YOK SAYAR.
  • Örnekleme (sampling), bu dağılımdan RASTGELE çeker -- 1000 denemede gerçek oran modelin öğrendiği olasılıklara YAKINSAR.
  • Temperature, modeli DEĞİL, örnekleme dağılımının keskinliğini kontrol eder (1'in altı keskin, üstü düz).
  • top-k SABİT sayıda token tutar; top-p (nucleus) KÜMÜLATİF olasılık kütlesine göre UYARLANABİLİR şekilde tutar -- pratikte top-p daha yaygın tercih edilir.