Model sudah dipra-latih, disesuaikan dengan instruksi, dioptimalkan terhadap preferensi, dan dijalankan dengan decoding yang tepat — sebelum semua itu berguna, seseorang harus menjawab: model ini seberapa bagus, dan untuk apa persisnya? Evaluasi LLM adalah wilayah yang lebih licin daripada evaluasi classifier pada section NLP : keluarannya kalimat terbuka yang tidak punya satu jawaban benar, tugasnya ratusan macam, dan pengukur yang dipakai — manusia atau model lain — membawa bias sendiri. Bab ini memetakan metode evaluasinya satu per satu, membahas jebakan yang paling sering menjebak angka benchmark, lalu menutup dengan demo perplexity yang menjalankan seluruh logika evaluasi pada model mainan.
Peta Metode Evaluasi #
| Metode | Yang diukur | Kekuatan | Kelemahan |
|---|---|---|---|
| Benchmark pilihan ganda (MMLU) | pengetahuan lintas bidang | murah, terukur, bisa dibandingkan antar model | rentan contamination; format sempit |
| Benchmark eksekusi kode (HumanEval) | kebenaran program yang ditulis | objektif karena diuji unit test | cakupan tugas pemrograman terbatas |
| Benchmark klasik (GLUE) | kemampuan linguistik tugas tunggal | standar lama, dataset bersih | tidak merepresentasikan LLM modern |
| Perplexity | kecocokan distribusi model dengan teks | satu angka, tidak butuh annotator | tidak mengukur kebenaran, hanya kecocokan |
| Human eval | kualitas menurut penilai manusia | menangkap nuansa yang tak terformula | mahal, lambat, penilai bervariasi |
| LLM-as-judge | kualitas menurut model penilai | skalabel, murah dibanding manusia | mewarisi bias posisi, panjang, dan afinitas |
Benchmark dan Jebakan Contamination #
Benchmark LLM umumnya berbentuk soal dengan kunci jawaban terverifikasi. MMLU menyajikan puluhan ribu soal pilihan ganda dari 57 bidang; HumanEval memberi 164 masalah pemrograman yang keluarannya diuji lewat unit test; GLUE — generasi sebelum LLM — mengukur tugas linguistik spesifik seperti kesepadanan kalimat dan masih dipakai untuk model kecil (gluebenchmark.com ). Semakin besar dan beragam sukatunya, semakin luas cakupan hasilnya — dan itulah masalahnya ketika sukatunya bocor ke data latih.
Data contamination terjadi ketika soal benchmark — atau variasi dekatnya — ikut termuat di korpus pra-latih. Model tidak “menjawab” soal; model mengingatnya. Deteksinya bisa sederhana: cari kecocokan n-gram antara soal benchmark dan korpus latih, atau bandingkan skor pada soal sebelum dan sesudah tanggal potongan data. Ironisnya, model yang dilatih pada data lebih baru bisa tampak lebih pintar semata karena benchmarknya ikut tertelan. Praktik yang sehat menahan soal benchmark dari korpus, menjaga versi privat (held-out) yang tidak dipublikasikan, dan melaporkan hasil pada sukatuan yang lahir setelah cutoff model.
Demo: Perplexity sebagai Detektor Kecocokan #
Di antara semua metode, perplexity adalah yang bisa kita jalankan dan bedah sepenuhnya di mesin ini. Rumusnya sederhana — rata-rata negative log-likelihood tiap token, dieksponensikan — dan maknanya intuitif: berapa banyak pilihan efektif yang model ragu-ragu tiap token. Kita membangun LM bigram mini, melatihnya pada korpus siklus empat kata, lalu menilai dua teks uji: kelanjutan siklus yang sama, dan urutan acak dari kata yang sama:
"""Evaluasi LM mainan: perplexity membedakan teks pola dan teks acak.
Bagian 1: korpus siklus 4 kata (pola) + teks urutan acak kata yang sama.
Bagian 2: latih model bigram kecil (matriks logit 4x4) pada korpus pola.
Bagian 3: evaluasi perplexity dan akurasi token berikutnya pada teks
pola (held-out) vs teks acak — angka yang berbeda jauh.
"""
import numpy as np
import torch
import torch.nn.functional as F
torch.manual_seed(7)
rng = np.random.default_rng(7)
KATA = ["ombak", "pasang", "buih", "surut"]
ID = {k: i for i, k in enumerate(KATA)}
# ---------- 1. Korpus pola dan teks acak ----------
latih = (KATA * 24)[:-1] # 95 token siklus berulang
uji_pola = (KATA * 6)[1:] # 23 token kelanjutan siklus
uji_acak = [KATA[i] for i in rng.permutation(np.tile(np.arange(4), 8))]
print("== 1. data ==")
print("latih (12 token awal):", " ".join(latih[:12]))
print("uji pola (12 token) :", " ".join(uji_pola[:12]))
print("uji acak (12 token) :", " ".join(uji_acak[:12]))
# ---------- 2. Latih bigram ----------
logit = torch.zeros(len(KATA), len(KATA), requires_grad=True)
def ke_indeks(teks):
return torch.tensor([ID[w] for w in teks], dtype=torch.long)
idx_latih = ke_indeks(latih)
X, Y = idx_latih[:-1], idx_latih[1:]
opt = torch.optim.Adam([logit], lr=0.05)
print()
print("== 2. pelatihan bigram (150 epoch, full batch) ==")
for ep in range(1, 151):
opt.zero_grad()
nll = F.cross_entropy(logit[X], Y)
nll.backward()
opt.step()
if ep in (1, 50, 150):
print(f" epoch {ep:3d}: loss NLL {nll.item():.4f}")
with torch.no_grad():
print(" transisi terpelajari (argmax):")
for i, k in enumerate(KATA):
j = int(logit[i].argmax())
print(f" {k:7s} -> {KATA[j]}")
# ---------- 3. Evaluasi dua teks ----------
def evaluasi(teks):
idx = ke_indeks(teks)
Xe, Ye = idx[:-1], idx[1:]
with torch.no_grad():
log_p = torch.log_softmax(logit[Xe], dim=1)
nll = -log_p.gather(1, Ye.unsqueeze(1)).mean().item()
akurasi = (logit[Xe].argmax(dim=1) == Ye).float().mean().item()
return nll, akurasi
print()
print("== 3. evaluasi: teks pola vs teks acak ==")
print(f"{'teks':10s} {'NLL rata2':>10s} {'perplexity':>11s} {'akurasi':>8s}")
for nama, teks in (("uji pola", uji_pola), ("uji acak", uji_acak)):
nll, akurasi = evaluasi(teks)
print(f"{nama:10s} {nll:10.4f} {float(np.exp(nll)):11.2f} {akurasi:8.3f}")
print("perplexity = exp(NLL rata-rata); kata tunggal yang mungkin =", len(KATA))Output yang Dihasilkan:
== 1. data ==
latih (12 token awal): ombak pasang buih surut ombak pasang buih surut ombak pasang buih surut
uji pola (12 token) : pasang buih surut ombak pasang buih surut ombak pasang buih surut ombak
uji acak (12 token) : buih buih buih ombak pasang buih pasang pasang buih surut pasang ombak
== 2. pelatihan bigram (150 epoch, full batch) ==
epoch 1: loss NLL 1.3863
epoch 50: loss NLL 0.0838
epoch 150: loss NLL 0.0179
transisi terpelajari (argmax):
ombak -> pasang
pasang -> buih
buih -> surut
surut -> ombak
== 3. evaluasi: teks pola vs teks acak ==
teks NLL rata2 perplexity akurasi
uji pola 0.0177 1.02 1.000
uji acak 3.9838 53.72 0.226
perplexity = exp(NLL rata-rata); kata tunggal yang mungkin = 4Mari kita telaah kode di atas baris per baris:
- Baris 19-21 (dua teks uji):
uji_polaadalah kelanjutan siklus yang tidak pernah dilihat model saat latih — held-out sungguhan;uji_acakmemakai kosakata yang sama persis dengan urutan yang diacak, sehingga satu-satunya hal yang diuji adalah struktur, bukan kata. - Baris 25-40 (bigram): seluruh “model” adalah matriks logit 4×4 — baris = kata kini, kolom = kata berikutnya. Cross-entropy atas pasangan berurutan menarik tiap baris softmax menuju transisi yang ada di korpus; setelah 150 epoch loss turun dari 1,3863 (setara uniform atas 4 kata) ke 0,0179.
- Baris 42-47 (transisi terpelajari): argmax tiap baris membentuk siklus
ombak → pasang → buih → surut → ombak— model merekonstruksi aturan pembangkit korpus secara utuh dari data saja. - Baris 50-58 (
evaluasi):log_softmaxlalugathermengambil log-probabilitas token yang benar-benar muncul; NLL dirata-rata,expmengubahnya menjadi perplexity, danargmaxdibandingkan target untuk akurasi token berikutnya. - Baris 61-65 (hasil): teks pola mendapat perplexity 1,02 — model praktis tidak ragu; teks acak dari kata yang sama mendapat 53,72, jauh melampaui jumlah kata yang mungkin (4). Perplexity tidak peduli kata apa yang muncul, hanya seberapa cocok urutannya dengan distribusi model — itulah kekuatan sekaligus batasnya: teks yang salah faktapun bisa ber-perplexity rendah bila polanya lazim. Akurasi 1,000 vs 0,226 menegaskan hal yang sama dari sudut klasifikasi.
Manusia dan Model sebagai Juri #
Untuk tugas terbuka — menulis, menafsirkan, berdialog — angka benchmark tidak memadai dan perplexity tidak relevan. Dua jalur tersisa. Human eval menyusun perbandingan berpasangan (keluaran model A atau B yang lebih baik?) atau rubrik berdimensi (kebenaran, kelengkapan, gaya), lalu mengagregasi penilaian beberapa annotator — mahal, lambat, namun menjadi acuan kebenaran terakhir. LLM-as-judge menggantikan annotator dengan model besar: skalabel dan murah, tetapi membawa bias yang terdokumentasi baik — condong ke jawaban yang muncul pertama, ke jawaban yang lebih panjang, dan ke jawaban dari model yang sejenis dengan dirinya. Mitigasinya mekanis: tukar posisi kedua kandidat dan ambil rata-rata, minta juri mengikuti rubrik eksplisit, batasi perbedaan panjang, dan gunakan lebih dari satu juri.
Evaluasi generatif vs klasifikasi pada akhirnya menuntut disiplin yang sama: tentukan dulu pertanyaan yang ingin dijawab (“apakah model menulis kode yang lulus uji?” berbeda dari “apakah kalimatnya wajar?”), pilih instrumen yang menjawab pertanyaan itu, dan audit instrumennya sebelum memercayai angkanya.
Satu wilayah tetap tak tersentuh semua meteran di atas: model bisa fasih, tepat format, dan tetap salah — karena ia menjawab tanpa dasar data, usang terhadap dunia, atau mewarisi kecondongan korpusnya. Bab penutup section ini memetakan batas-batas itu secara jujur: Keterbatasan dan Risiko LLM .