ข้ามไปยังเนื้อหาหลัก

[LLM 9/10] Benchmarking: ตัวเลข accuracy ที่ไม่มี confidence interval คือข่าวลือ

· อ่าน 8 นาที
Kobkrit Viriyayudhakorn
CEO, iApp Technology

ตั้งแต่บทที่ 1 ซีรีส์นี้พูดประโยคเดิมซ้ำทุกครั้งที่รายงานตัวเลข: "accuracy ที่ไม่มี confidence interval ไม่ใช่ผลการทดลอง มันคือข่าวลือ" และสัญญาไว้ว่าจะอธิบายเต็ม ๆ ในบทที่ 9 — บทนี้แหละครับ เราจะไม่เทรนอะไรเลยแม้แต่ step เดียว แต่จะสร้างระบบวัดผลสามโหมดขึ้นจากศูนย์ เอาทุก checkpoint ที่เทรนมาตลอดซีรีส์ขึ้นมาวัดบนข้อสอบไทยจริง แล้วพิสูจน์ว่า การตั้งค่าที่ไม่มีใครเขียนไว้ใน paper เปลี่ยนคะแนนของโมเดลตัวเดิมได้มากกว่าช่องว่างบน leaderboard ที่คนเถียงกัน

Open in Colab09_benchmarking.ipynb

1. ปัญหา (Problem statement)

ลองอ่านประโยคแบบนี้ที่เจอได้ทุกสัปดาห์: "โมเดล X ได้ 71.2% บน ThaiExam แซงโมเดล Y ที่ได้ 69.8%"

คำถามเดียวที่ควรถามคือ วัดจากกี่ข้อ ถ้าชุดทดสอบมี 100 ข้อ ช่วงความเชื่อมั่น 95% ของแต่ละตัวเลขกว้างราว ±8–10 จุด แปลว่า 71.2% กับ 69.8% คือตัวเลขเดียวกันที่บังเอิญสุ่มออกมาไม่เท่ากัน การประกาศผู้ชนะจากช่องว่าง 1.4 จุดบนชุดทดสอบ 100 ข้อ ไม่ต่างจากโยนเหรียญสิบครั้งแล้วสรุปว่าเหรียญเอียง

ปัญหาไม่ได้หยุดที่ขนาดชุดทดสอบ เพราะ "คะแนน benchmark" หนึ่งตัวเลข เกิดจากการตัดสินใจหลายชั้นที่แทบไม่มีใครรายงาน:

การตัดสินใจที่ซ่อนอยู่ผลต่อคะแนน
ให้คะแนนแบบ log-likelihood หรือ generativeเปลี่ยนได้หลายจุด
normalize ความยาวตัวเลือกหรือไม่ (γ\gamma)สลับอันดับบน leaderboard ได้
0-shot หรือ 5-shot, template เขียนยังไงเปลี่ยนได้หลายจุด
ใส่ chat template ให้โมเดลไหนบ้างลำเอียงเข้าข้างโมเดลใดโมเดลหนึ่ง
ข้อสอบรั่วอยู่ในข้อมูลเทรนหรือไม่ (contamination)คะแนนสูงปลอมทั้งแท่ง

ตัวเลขหนึ่งตัวที่ปกปิดการตัดสินใจห้าชั้น บวกกับ error bar ที่ไม่มีใครวาด — นั่นคือสิ่งที่เราเรียกกันว่า leaderboard

2. เราจะทำอะไร (Solution)

บทนี้ไม่มีการเทรนเลย และนั่นคือจุดเด่น ไม่ใช่จุดอ่อน — การวัดผลเป็นงานคนละชนิดกับการเทรน และมันสมควรได้บทของตัวเอง

เราจะทำสี่อย่าง:

  1. เขียนระบบให้คะแนนสามโหมดจากศูนย์ — log-likelihood multiple-choice, generative exact-match (พร้อม normalization ภาษาไทย), และ LLM-as-judge พร้อม rubric ที่เขียนเป็นลายลักษณ์อักษร แล้วแสดงให้เห็นว่าทั้งสามโหมดให้คะแนนโมเดลตัวเดียวกันไม่เท่ากัน
  2. เอาทุก checkpoint จากบทที่ 1–8 มาวัดบนแกนเดียวกัน ด้วยข้อสอบไทยจริง (scb10x/thai_exam), โจทย์เลขไทย (VISAI-AI/gsm8k-thai) และ KobEval-TH ชุดประจำซีรีส์
  3. ติด Wilson 95% CI ให้ทุกตัวเลข แล้วดูว่าข้อสรุปไหนของซีรีส์รอดจาก error bar บ้าง
  4. ลอง reproduce ตัวเลขบน leaderboard สาธารณะ ของ Qwen3-0.6B บน ThaiExam — และถ้าไม่ตรง เราจะไล่หาสาเหตุแทนที่จะเงียบ
แนวคิดหลักของบทนี้

คะแนน benchmark ไม่ใช่คุณสมบัติของโมเดล มันคือคุณสมบัติของ (โมเดล × วิธีวัด × ชุดข้อสอบ × จำนวนข้อ) การรายงานตัวเลขโดยไม่รายงานอีกสามอย่างที่เหลือ คือการรายงานแค่เศษหนึ่งส่วนสี่ของความจริง และ confidence interval คือราคาขั้นต่ำของคำว่า "ผลการทดลอง"

3. สมการ (Equation)

3.1 Wilson score interval — CI ประจำซีรีส์

ถ้าตอบถูก ss จาก nn ข้อ ได้ p^=s/n\hat p = s/n ช่วงความเชื่อมั่นแบบ Wilson ที่ระดับ zz (95% → z=1.96z = 1.96) คือ

p^+z22n1+z2n  ±  z1+z2np^(1p^)n+z24n2\frac{\hat p + \frac{z^2}{2n}}{1 + \frac{z^2}{n}} \;\pm\; \frac{z}{1 + \frac{z^2}{n}}\sqrt{\frac{\hat p(1-\hat p)}{n} + \frac{z^2}{4n^2}}

ทำไมไม่ใช้สูตร normal approximation (p^±zp^(1p^)/n\hat p \pm z\sqrt{\hat p(1-\hat p)/n}) ที่จำง่ายกว่า เพราะมันพังตรงที่เราต้องใช้มันบ่อยที่สุด: ขอบ 0 กับ 1 และ nn เล็ก

ลองกรณีจริงจากบทที่ 8: guardrail ปล่อยคำขออันตรายหลุด 0 ครั้งจากชุดทดสอบ 30 ข้อ

from kobeval import wilson_ci      # ฟังก์ชันเดียวกับที่ใช้มาตั้งแต่บทที่ 1

wilson_ci(0, 30) # → (0.000, 0.114)
  • Normal approximation: 0±1.9601/30=0±00 \pm 1.96\sqrt{0 \cdot 1/30} = 0 \pm 0 — ช่วงกว้างศูนย์ ราวกับเรามั่นใจ 100% ว่าอัตราหลุดเป็น 0% จากการดูแค่ 30 ตัวอย่าง
  • Wilson: [0%, 11.4%][0\%,\ 11.4\%] — "เท่าที่ดูมา 30 ครั้งยังไม่เคยหลุด แต่ค่าจริงอาจสูงถึง 11%"

ช่วงที่สองคือประโยคที่ซื่อสัตย์ ช่วงแรกคือคำโกหกที่สูตรผลิตให้โดยอัตโนมัติ สังเกตในสูตรว่า Wilson ดึงจุดกึ่งกลางเข้าหา 1/21/2 ด้วยพจน์ z2/2nz^2/2n (เหมือนเติมข้อมูลเสมือน z24z^2 \approx 4 ข้อ ถูกครึ่งผิดครึ่ง) และมีพจน์ z2/4n2z^2/4n^2 กันความกว้างไม่ให้ยุบเป็นศูนย์ — นี่คือเหตุผลที่ kobeval ใช้ Wilson มาตลอดซีรีส์

3.2 Length-normalised multiple-choice scoring — ตัวเลขที่ตัดสิน leaderboard เงียบ ๆ

โหมด log-likelihood ให้โมเดลอ่านโจทย์ qq แล้วเทียบความน่าจะเป็นของตัวเลือก c1,,cmc_1,\dots,c_m ทั้งประโยค:

i^=argmaxilogpθ(ciq)ciγ\hat i = \arg\max_i \frac{\log p_\theta(c_i \mid q)}{|c_i|^{\gamma}}
  • γ=0\gamma = 0 → ใช้ log-prob รวมดิบ ๆ ซึ่งลำเอียงเข้าข้างตัวเลือกสั้น (token น้อย = โดนคูณความน่าจะเป็นน้อยครั้ง)
  • γ=1\gamma = 1 → หารด้วยจำนวน token คือใช้ log-prob เฉลี่ยต่อ token

สองบรรทัดนี้คือ acc กับ acc_norm ใน lm-evaluation-harness และบน benchmark จริง มันให้คะแนนไม่เท่ากันและบางครั้งสลับอันดับโมเดล — เวลาเห็นตาราง paper สองฉบับรายงาน ThaiExam ไม่เท่ากัน สาเหตุอันดับต้น ๆ คือ γ\gamma คนละค่า โดยที่ทั้งสองฉบับไม่ได้เขียนไว้เลยว่าใช้ค่าไหน

3.3 pass@k แบบไม่ลำเอียง — สำหรับโจทย์ที่ตรวจอัตโนมัติได้

โจทย์เลขและโจทย์โค้ดยอมให้เราสุ่มหลายคำตอบแล้วถามว่า "มีสักอันไหมที่ถูก" สุ่ม nn ครั้ง ถูก cc ครั้ง ตัวประมาณที่ถูกต้องของ pass@kk คือ

pass@k^=1(nck)/(nk)\widehat{\text{pass@}k} = 1 - \binom{n-c}{k}\Big/\binom{n}{k}

ตัวประมาณแบบสัญชาตญาณ 1(1c/n)k1 - (1 - c/n)^k นั้นลำเอียง: ฟังก์ชัน f(p)=1(1p)kf(p) = 1-(1-p)^k เป็นฟังก์ชันเว้า (concave) ใน pp ดังนั้นตาม Jensen's inequality E[f(p^)]f(p)\mathbb{E}[f(\hat p)] \le f(p) — เสียบค่าประมาณเข้าไปในฟังก์ชันไม่เชิงเส้นแล้วค่าคาดหวังจะไม่ตรงกับของจริง ส่วนสูตรทวินามข้างบนคือ "สัดส่วนของการหยิบ kk ตัวจาก nn ตัวที่ผิดยกชุด" ซึ่งพิสูจน์ได้ว่า unbiased พอดีเป๊ะ

3.4 McNemar's test — เทียบสองโมเดลบนข้อสอบชุดเดียวกัน

โมเดล A กับ B ทำข้อสอบชุดเดียวกัน ให้ bb = จำนวนข้อที่ A ถูกแต่ B ผิด, cc = จำนวนข้อที่ B ถูกแต่ A ผิด:

χ2=(bc1)2b+c\chi^2 = \frac{(|b-c|-1)^2}{b+c}

เทียบกับ χ12\chi^2_1 (แบบมี continuity correction) จุดสำคัญคือคำว่า paired: ข้อที่ทั้งคู่ถูกและข้อที่ทั้งคู่ผิดไม่อยู่ในสูตรเลย เพราะมันไม่ได้บอกอะไรว่าใครเก่งกว่า ถ้าคุณใช้ t-test เทียบ accuracy สองตัวเหมือนมาจากคนละชุดข้อสอบ คุณกำลังโยนโครงสร้าง "ข้อเดียวกัน" ทิ้ง แล้วต้องใช้ข้อสอบมากกว่าหลายเท่าเพื่อ power เท่าเดิม

3.5 Contamination check — ข้อสอบรั่วอยู่ในข้อมูลเทรนไหม

สำหรับข้อสอบ xx และคลังข้อมูลเทรน D\mathcal{D} นิยามอัตราการทับซ้อนของ kk-gram:

overlapk(x)=Gk(x)Gk(D)Gk(x)\text{overlap}_k(x) = \frac{\left|G_k(x) \cap G_k(\mathcal{D})\right|}{\left|G_k(x)\right|}

โดย Gk()G_k(\cdot) คือเซตของ kk-gram ทั้งหมด สำหรับภาษาไทยเราใช้ k-gram ระดับตัวอักษร (เช่น 20 ตัวอักษร) เพราะการตัดคำไทยมีความกำกวม ถ้า overlapk\text{overlap}_k สูง (เช่นเกิน 0.7) ให้สงสัยว่าโมเดลเคย "เห็นเฉลย" มาแล้ว — คะแนนบนข้อนั้นวัดความจำ ไม่ได้วัดความสามารถ

เนื้อหาเต็มอยู่ในคอร์ส

บทความนี้คือประมาณ 30% แรกของบทเรียน — ส่วนที่เหลือ (การเตรียมสภาพแวดล้อม, การเตรียมข้อมูล, โค้ดหลัก, ผลลัพธ์จริง และบทสรุป) อยู่ในคอร์ส LLM Finetuning ซึ่งเรียนฟรี เพียงเข้าสู่ระบบด้วย Google

อ่านเนื้อหาเต็มในคอร์ส →