সরাসরি পর্বে যান
পর্ব ৫৮ · ৯১-এর মধ্যে · মডিউল ৯

LLM মূল্যায়ন — benchmark, LLM-as-judge আর তার ফাঁদ

LLM Evaluation: Benchmarks, LLM-as-a-Judge and Their Pitfalls

Taught in Bangla, this lesson asks what to measure before scoring, reviews common benchmarks with contamination and saturation, and scores multiple-choice questions by log-likelihood with length normalisation. Real desktop CPU runs on small open models show how scoring rules change results, normalise Bangla answers for exact match, and measure judge position bias with swap tests and McNemar, while in-page Python computes bootstrap intervals, sample sizes, rubrics and regression tests.

এই পর্বে যা শিখবেন

  • LLM-এর কী কী মাপতে হয়, আর পরিচিত benchmark-গুলোর কাজ ও সীমা
  • Contamination আর saturation চেনা, আর নিজের set বানানোর নিয়ম
  • Multiple-choice-এ log-likelihood, length normalisation আর অক্ষর-ছাঁচ; বাংলায় exact match
  • LLM-as-a-judge, pairwise বনাম pointwise, আর position bias নিজে মাপা
  • Bootstrap CI, McNemar আর কত প্রশ্ন লাগবে তার হিসাব; rubric আর regression test

এই পর্বের সব desktop run CPU-তে: torch 2.15.0, transformers 5.22.2, Python 3.12, লেখার সময়ে। Model তিনটি ছোট, open আর Apache 2.0: Qwen/Qwen2.5-0.5B-Instruct, HuggingFaceTB/SmolLM2-135M-Instruct আর HuggingFaceTB/SmolLM2-360M-Instruct; token ছাড়া নামানো যায়। পাতায় command আর script-এর নাম সহজ করে দেখানো, আউটপুট হুবহু; যেখানে ছোট করা, সেখানে বলা আছে। সব সংখ্যা একটি স্থানীয় run-এর, ছোট model আর ছোট set-এ; এগুলো দিয়ে কোনো বড় model বা পণ্যের মান বিচার করা যায় না। Precision, recall আর accuracy-র ভিত্তি AI-এর ভিত্তি কোর্সের পর্ব ৬০-এ; LLM-এর মূল ধারণা একই কোর্সের পর্ব ৭০-এ।

১ · কী মাপবেন — নম্বরের আগে প্রশ্ন

বোর্ড পরীক্ষার খাতা দেখার কথা ভাবুন। একই খাতা দুজন পরীক্ষক দেখলে নম্বর একটু আলাদা হয়। প্রশ্ন ফাঁস হলে নম্বর বাড়ে, কিন্তু শেখা বাড়ে না। আর ১০টি প্রশ্নের পরীক্ষায় ৭ পাওয়া ছাত্র সত্যিই ৭০% জানে, এমন বলা কঠিন। LLM মূল্যায়নে এই তিনটি সমস্যাই আছে, শুধু আরও বড় আকারে। এই পর্বে প্রতিটি নিজের হাতে মাপবেন।

প্রথমে ঠিক করুন, ঠিক কী জানতে চান। “Model কতটা ভালো” কোনো প্রশ্ন নয়। বাজারঘরের সহায়তা-বটের জন্য প্রশ্নগুলো এমন:

মাত্রাবাজারঘরে প্রশ্নকীভাবে মাপা হয়
Task-এর নির্ভুলতাডেলিভারি চার্জ ঠিক বলল কি?Exact match, multiple-choice, rubric
Faithfulnessদেওয়া নিয়মের বাইরে কিছু বানাল কি?উত্তরের প্রতিটি দাবি উৎসে মেলানো (পর্ব ৭০ · RAG মূল্যায়ন)
নিরাপত্তাগ্রাহকের OTP চাইল কি? অপমানজনক কিছু লিখল কি?ঝুঁকির প্রশ্নের আলাদা set, প্রত্যাখ্যানের হার
খরচপ্রতি ১০০০ প্রশ্নে কত token?Input আর output token গোনা
Latencyপ্রথম শব্দ কত সেকেন্ডে?প্রথম token আর পুরো উত্তরের সময়, শতাংশিক (p50, p95)

একটি মাত্রা বাড়ালে অন্যটি প্রায়ই কমে। বড় model নির্ভুল, কিন্তু ধীর আর দামি। কড়া নিরাপত্তা নিয়ম কিছু নির্দোষ প্রশ্নও ফিরিয়ে দেয়। তাই মূল্যায়নের ফল একটি নম্বর নয়, একটি সারণি। প্রতিটি সংখ্যা কীভাবে আসে, তার পথটি নিচের ছবিতে।

Evaluation pipeline: one item set, three scoring paths প্রশ্নের set (item) prompt-এর ছাঁচ model log-likelihoodবিকল্প বাছাই generation+ exact match judge modelA না B? প্রতি প্রশ্নে ০ বা ১ গড় ± CI, আগের সঙ্গে তুলনা Each box can silently change the final number
একই প্রশ্নের set, তিনটি নম্বর দেওয়ার পথ। ছাঁচ, নম্বরের নিয়ম আর প্রশ্নের সংখ্যা, প্রতিটিই শেষ সংখ্যাকে বদলাতে পারে।

২ · পরিচিত benchmark — কোনটি কী মাপে, আর কোথায় থামে

Benchmark হলো সবার জন্য একই প্রশ্নের set আর একই নম্বরের নিয়ম, যাতে model-গুলো তুলনা করা যায়। নিচের তথ্য প্রতিটির মূল প্রবন্ধ থেকে, ২০২৬ সালের নভেম্বরে মিলিয়ে দেখা। কোনো model-এর স্কোর এখানে দিচ্ছি না, কারণ স্কোর prompt আর নিয়মভেদে বদলায়, আর এগুলো দিয়ে model-এর ক্রম সাজানো এই পর্বের কাজ নয়।

নাম, প্রবন্ধকী আছেনম্বরের নিয়ম
MMLU
Hendrycks ও সহকর্মী, ২০২০ (ICLR ২০২১)
৫৭টি বিষয়ে চার বিকল্পের প্রশ্ন, স্কুল থেকে পেশাদার স্তরAccuracy; সাধারণত few-shot
HellaSwag
Zellers ও সহকর্মী, ২০১৯
একটি ঘটনার পরের বাক্য বাছাই; ভুল বিকল্পগুলো adversarial filtering দিয়ে বাছাLog-likelihood দিয়ে বিকল্প বাছাই
GSM8K
Cobbe ও সহকর্মী, ২০২১
প্রায় ৮,৫০০টি প্রাথমিক স্তরের গণিতের শব্দ-সমস্যাশেষ সংখ্যার exact match
HumanEval
Chen ও সহকর্মী, ২০২১
হাতে লেখা ১৬৪টি Python সমস্যা, প্রতিটির unit testpass@k: k টি নমুনার অন্তত একটি সব test পাশ করে
MT-Bench
Zheng ও সহকর্মী, ২০২৩
৮টি ধরনে ৮০টি দুই-পালার প্রশ্নশক্তিশালী LLM বিচারক
Chatbot Arena
একই দল, ২০২৩; Chiang ও সহকর্মী, ২০২৪
মানুষ নিজের প্রশ্ন দেন, দুটি অজানা model-এর উত্তরে ভোট দেনজোড়া ভোট থেকে Elo বা Bradley–Terry ধরনের রেটিং

তালিকায় নম্বরের তিনটি নিয়ম: বিকল্প বাছাই, উত্তর মেলানো আর বিচারক। আগে benchmark-এর দুটি অসুখ।

Contamination: প্রশ্ন আগেই ফাঁস

Pretraining-এর ডেটা ইন্টারনেট থেকে আসে, আর benchmark-ও ইন্টারনেটে থাকে। তাই test-এর প্রশ্ন, এমনকি উত্তরসহ, training-এ ঢুকে যেতে পারে। একে বলে data contamination. ফাঁস হওয়া প্রশ্নপত্রে ভালো নম্বর মুখস্থের প্রমাণ, শেখার নয়। ধারণাটি নতুন নয়: AI-এর ভিত্তি কোর্সের পর্ব ৫৬-এর leakage-ই, শুধু এখানে আপনি training ডেটা দেখতে পান না। GPT-3-এর প্রবন্ধে (Brown ও সহকর্মী, ২০২০) লেখকেরা ১৩ শব্দের n-gram মিল খুঁজে প্রতিটি benchmark-এর “পরিষ্কার” অংশ আলাদা করে নম্বর দিয়েছিলেন। এটি একটি চলনসই পরীক্ষা, পূর্ণ নয়: অনুবাদ বা ভিন্ন শব্দে লেখা প্রশ্ন এতে ধরা পড়ে না।

বন্ধ model-এর training ডেটা সাধারণত প্রকাশ হয় না, তাই বাইরে থেকে contamination পুরোপুরি যাচাই করা যায় না। ব্যবহারিক প্রতিরক্ষা তিনটি: নিজের অপ্রকাশিত প্রশ্ন; প্রকাশের তারিখের পরে লেখা প্রশ্ন; আর একই প্রশ্ন সামান্য বদলে (সংখ্যা, নাম, বিকল্পের ক্রম) নম্বর একই থাকে কি না দেখা। নম্বর হঠাৎ পড়ে গেলে সন্দেহ জাগে।

Saturation আর benchmark-এর নিজের ভুল

অনেক model যখন প্রায় পূর্ণ নম্বর পায়, তখন benchmark আর পার্থক্য দেখাতে পারে না। একে বলে saturation. তখন কঠিনতর সংস্করণ আসে, যেমন MMLU-Pro (Wang ও সহকর্মী, ২০২৪), যেখানে বিকল্প দশটি। আরেকটি সমস্যা: benchmark-এর উত্তরপত্র নিজেই নির্ভুল নয়। Gema ও সহকর্মীদের “Are We Done with MMLU?” (২০২৪) প্রবন্ধে কিছু বিষয়ে উল্লেখযোগ্য সংখ্যক প্রশ্নে ভুল বা অস্পষ্ট উত্তর পাওয়া যায়। ফলে শীর্ষের কাছে ০.৫ শতাংশ-বিন্দুর পার্থক্য প্রায়ই অর্থহীন। পর্ব ৪৫-এর Goodhart-এর কথাও এখানে খাটে: যে benchmark লক্ষ্য হয়ে যায়, সেটি দ্রুত মাপ হিসেবে দুর্বল হয়।

৩ · Multiple-choice — লেখা ছাড়াই উত্তর বাছাই

চার বিকল্পের প্রশ্নে model-কে কিছু লিখতে বলার দরকার নেই। পর্ব ৫২-এ দেখেছেন, model প্রতিটি token-এর সম্ভাবনা দেয়। তাই প্রতিটি বিকল্পকে প্রশ্নের পরে বসিয়ে জিজ্ঞেস করা যায়: এই লেখাটি আসার সম্ভাবনা কত? যে বিকল্পের log-probability সবচেয়ে বেশি, সেটিই model-এর উত্তর। একে বলে log-likelihood scoring. HellaSwag আর অনেক multiple-choice benchmark এভাবেই নম্বর পায়।

একটি বিকল্প c-তে token c1, …, cm থাকলে, প্রশ্ন x দেওয়া থাকলে:

score(c) = ∑t=1..m log P(ct | x, c1, …, ct−1)

এখানে প্রতিটি পদ শূন্য বা ঋণাত্মক, কারণ সম্ভাবনা ১-এর বেশি নয়। ফলে যত বেশি token, যোগফল তত নিচে নামে। ছোট বিকল্প শুধু ছোট বলেই সুবিধা পায়। প্রতিকার length normalisation: যোগফলকে token-এর সংখ্যা, অক্ষর বা byte দিয়ে ভাগ। EleutherAI-এর lm-evaluation-harness-এ এর একটি রূপ acc_norm নামে আছে; ঠিক কী দিয়ে ভাগ হয়, তা ব্যবহৃত version-এর docs-এ দেখে নিন, কারণ বর্ণনা আলাদা পাওয়া যায়।

দ্বিতীয় পথটি অক্ষর-ছাঁচ: প্রশ্নের সঙ্গে “A. … B. …” লিখে দিয়ে শুধু পরের token “A”, “B”, “C” না “D”, তার logit তুলনা। এতে বিকল্পের দৈর্ঘ্যের সমস্যা নেই, কিন্তু model-কে অক্ষর আর বিকল্প জোড়া লাগাতে হয়, যা ছোট model-এর জন্য আলাদা একটি কাজ। নিচের stepper-এর সব সংখ্যা সত্যিকারের run থেকে, Qwen2.5-0.5B-Instruct, বাজারঘরের একটি প্রশ্ন।

চারটি বিকল্প, তিন নিয়ম
ধাপ
আলু
পেঁয়াজ
পেয়ারা
রসুন
  1. আলাদা tokenize. প্রশ্নটি “প্রশ্ন: কোনটি ফল?\nউত্তর:” আর প্রতিটি বিকল্প আলাদা tokenize করে জোড়া হলো, যাতে সীমানার token মিশে না যায়। বাংলা শব্দগুলো ৫ থেকে ১০টি byte-টুকরোয় ভেঙেছে (পর্ব ৪০)।
  2. যোগফল. প্রতিটি টুকরোর log-prob যোগ হলো। সবগুলোই ঋণাত্মক, তাই ১০ token-এর “পেঁয়াজ” সবচেয়ে নিচে।
  3. যোগফলের রায়. সবচেয়ে বড় −১৩.৭৩, “আলু”, যা ফল নয়। এর token মাত্র ৫টি। ভুলটি model-এর যতটা, নিয়মেরও ততটা।
  4. Token প্রতি গড়. ৫ token-এ −১৩.৭৩ মানে গড়ে −২.৭৫; “পেয়ারা”-র ৯ token-এ −১৮.২৩ মানে −২.০৩. গড়ে পেয়ারা এগিয়ে, আর সেটিই ঠিক।
  5. অক্ষর প্রতি. Unicode code point ধরে ভাগ করলেও পেয়ারা (−২.৬০) জেতে। এই প্রশ্নে normalisation সাহায্য করল; নিচের কোডে দেখবেন, অন্য প্রশ্নে ঠিক উল্টোটা হয়।

এবার একই হিসাব নিজে চালান। Log-prob-গুলো সত্যিকারের run থেকে তোলা; কোড শুধু তিন নিয়মে বাছাই করে।

import numpy as np
# সত্যিকারের run: Qwen2.5-0.5B-Instruct, প্রতিটি বিকল্পের প্রতি token-এর log-prob
ITEMS = {
 "কোনটি ফল? (ঠিক: পেয়ারা)": {
  "আলু":    [-0.0328, -4.0839, -5.0026, -0.9577, -3.6505],
  "পেঁয়াজ": [-0.0328, -2.7542, -7.1819, -5.6486, -0.1681, -4.1981, -0.0509, -0.0, -0.3474, -4.0867],
  "পেয়ারা":  [-0.0328, -2.7542, -7.1819, -3.4581, -0.0, -0.0, -1.7615, -0.0995, -2.9403],
  "রসুন":   [-0.0328, -6.8133, -3.678, -5.5076, -1.4021, -2.1348]},
 "বাংলাদেশের রাজধানী কোনটি? (ঠিক: ঢাকা)": {
  "চট্টগ্রাম": [-0.0077, -5.4281, -4.3726, -0.3279, -0.1168, -3.2139, -0.8962, -0.0967, -0.5885, -0.2497],
  "খুলনা":   [-0.0077, -6.5373, -1.649, -0.407, -2.2402, -3.3614, -1.1662],
  "ঢাকা":    [-0.0077, -8.8621, -0.4546, -0.5591, -0.3963],
  "রাজশাহী":  [-0.0077, -3.4702, -0.0224, -0.0367, -9.1895, -1.2463, -0.2008, -2.302, -0.0012]}}

for q, opts in ITEMS.items():
    names = list(opts)
    lp = [np.array(v) for v in opts.values()]
    S = np.array([v.sum() for v in lp])                      # যোগফল
    T = np.array([v.mean() for v in lp])                     # token প্রতি গড়
    C = np.array([v.sum() / len(n) for v, n in zip(lp, names)])  # অক্ষর প্রতি
    print(q)
    print(f"  {'বিকল্প':10}{'token':>6}{'যোগফল':>9}{'গড়':>8}{'অক্ষরপ্রতি':>11}")
    for n, v, s, t, c in zip(names, lp, S, T, C):
        print(f"  {n:10}{len(v):6d}{s:9.2f}{t:8.2f}{c:11.2f}")
    for rule, x in (("যোগফল", S), ("গড়", T), ("অক্ষরপ্রতি", C)):
        print(f"  {rule:10} → {names[int(x.argmax())]}")
    print()

আউটপুট (পাতার Python-এ চালানো):

কোনটি ফল? (ঠিক: পেয়ারা)
  বিকল্প     token    যোগফল     গড় অক্ষরপ্রতি
  আলু            5   -13.73   -2.75      -4.58
  পেঁয়াজ       10   -24.47   -2.45      -3.50
  পেয়ারা        9   -18.23   -2.03      -2.60
  রসুন           6   -19.57   -3.26      -4.89
  যোগফল      → আলু
  গড়        → পেয়ারা
  অক্ষরপ্রতি → পেয়ারা

বাংলাদেশের রাজধানী কোনটি? (ঠিক: ঢাকা)
  বিকল্প     token    যোগফল     গড় অক্ষরপ্রতি
  চট্টগ্রাম     10   -15.30   -1.53      -1.70
  খুলনা          7   -15.37   -2.20      -3.07
  ঢাকা           5   -10.28   -2.06      -2.57
  রাজশাহী        9   -16.48   -1.83      -2.35
  যোগফল      → ঢাকা
  গড়        → চট্টগ্রাম
  অক্ষরপ্রতি → চট্টগ্রাম

দুটি প্রশ্নে নিয়ম দুই দিকে কাজ করেছে। “কোনটি ফল?”-তে যোগফল ছোট “আলু”-কে জিতিয়েছে; গড় আর অক্ষর-ভাগ ঠিক করেছে। রাজধানীর প্রশ্নে উল্টো: “ঢাকা” মাত্র ৫ token, আর এর দ্বিতীয় টুকরোর log-prob −৮.৮৬, তাই গড়ে পিছিয়ে যায়, অথচ “চট্টগ্রাম”-এর অনেক টুকরো প্রায় নিশ্চিত (−০.১ বা −০.২৫), কারণ একটি অক্ষর এলে পরের টুকরো প্রায় ঠিক হয়ে যায়। গড় তখন “বানান সহজ” মাপে, “উত্তর ঠিক” নয়। পর্ব ৫২-এ এই model-ই রাজধানী “Chittagong” বলেছিল; এখানে নিয়মভেদে একই ভুল ফিরে এল।

৪ · সত্যিকারের run — দুটি ছোট model, তিনটি প্রশ্নের set

তিনটি set: (১) এই পর্বের জন্য হাতে লেখা ৪০টি বাজারঘর প্রশ্ন, বাংলায় (দাম আর হিসাব, দৈনন্দিন তথ্য, বাংলাদেশের সাধারণ তথ্য, গ্রাহক-সেবার বুদ্ধি); (২) একই ৪০টি প্রশ্নের ইংরেজি অনুবাদ, একই উত্তরের অবস্থানে; (৩) OpenAI-এর openai/MMMLU-এর বাংলা অংশ (BN_BD), MIT license, dataset card অনুযায়ী পেশাদার অনুবাদকের করা MMLU test set, যার ১৪,০৪২টি প্রশ্ন থেকে seed ৫৮-এ ২০০টি নেওয়া, ৫৭টি বিষয়ের ৫১টি এসেছে। Model দুটি Qwen/Qwen2.5-0.5B-Instruct (প্রায় ৪৯ কোটি parameter) আর HuggingFaceTB/SmolLM2-135M-Instruct (প্রায় সাড়ে ১৩ কোটি), দুটিই Apache 2.0, revision hash ধরে নামানো। সব CPU-তে, torch 2.15.0, transformers 5.22.2, Python 3.12, লেখার সময়ে।

@torch.no_grad()                         # মূল দুটি ফাংশন, একটু ছোট করে
def cloze(model, tok, q, opts):          # প্রতিটি বিকল্পের প্রতি token-এর log-prob
    ctx = tok(f"প্রশ্ন: {q}\nউত্তর:", add_special_tokens=False)["input_ids"]
    out = []
    for o in opts:
        cont = tok(" " + o, add_special_tokens=False)["input_ids"]
        ids = torch.tensor([ctx + cont])
        lp = model(ids).logits[0, :-1].float().log_softmax(-1)
        tl = lp[torch.arange(ids.shape[1] - 1), ids[0, 1:]][len(ctx) - 1:]
        out.append(dict(lps=tl.tolist(), chars=len(o)))
    return out

@torch.no_grad()
def letter(model, tok, q, opts):         # অক্ষর-ছাঁচ: পরের token A/B/C/D-এর logit
    body = q + "\n" + "\n".join(f"{l}. {o}" for l, o in zip("ABCD", opts))
    p = tok.apply_chat_template([{"role": "user", "content": body + "\nAnswer with the letter only."}],
                                tokenize=False, add_generation_prompt=True)
    lg = model(tok(p, return_tensors="pt", add_special_tokens=False)["input_ids"]).logits[0, -1]
    return [float(lg[tok(x, add_special_tokens=False)["input_ids"][0]]) for x in "ABCD"]
~/bazarghar-dl$ uv run python mc_eval.py   # সত্যিকারের run, CPU-তে, ৩টি thread;
# দুটি script-এর আউটপুট একসঙ্গে, কলামের ফাঁকা জায়গা আর কয়েকটি লাইন ছোট করা
bazar answer positions: [12, 18, 10, 0]
mmmlu subjects in sample: 51 | answer positions: [55, 52, 42, 51]
Qwen2.5-0.5B-Instruct  bazar     n= 40 sum=0.200 per-tok=0.225 per-char=0.250 letter=0.325 letter-mass=1.000 time=62s
Qwen2.5-0.5B-Instruct  mmmlu_bn  n=200 sum=0.250 per-tok=0.230 per-char=0.235 letter=0.285 letter-mass=0.999 time=1331s
SmolLM2-135M-Instruct  bazar     n= 40 sum=0.200 per-tok=0.225 per-char=0.225 letter=0.150 letter-mass=0.725 time=27s
SmolLM2-135M-Instruct  mmmlu_bn  n=200 sum=0.275 per-tok=0.250 per-char=0.230 letter=0.215 letter-mass=0.790 time=802s
# দ্বিতীয় script: একই ৪০টি প্রশ্ন ইংরেজিতে ("Question: … Answer:")
Qwen2.5-0.5B-Instruct  bazar_en  n= 40 sum=0.500 per-tok=0.625 per-char=0.550 letter=0.625 letter-mass=0.992 time=30s
SmolLM2-135M-Instruct  bazar_en  n= 40 sum=0.200 per-tok=0.325 per-char=0.450 letter=0.175 letter-mass=0.719 time=11s

প্রথম শিক্ষা, দৈবের সঙ্গে তুলনা। চার বিকল্পে আন্দাজের নির্ভুলতা ০.২৫. বাংলায় দুটি model-এর প্রায় সব সংখ্যা তার কাছাকাছি। MMMLU বাংলায় Qwen-এর সবচেয়ে ভালোটি ০.২৮৫, ২০০টি প্রশ্নে। §৮-এ দেখবেন, এর CI দৈবকেও ছুঁয়ে যায়। এই ছোট model-গুলোর বাংলা পরীক্ষার ফল তাই প্রায় “জানা যায় না”। আর বাজারঘর set-এর উত্তর একবারও D-তে নেই ([12, 18, 10, 0]): প্রশ্ন লেখার সময় এই ভারসাম্যহীনতা চোখে পড়েনি। “সবসময় B” বললেই ০.৪৫ আসত। নিজের set বানালে উত্তরের অবস্থান গুনে দেখুন।

দ্বিতীয় শিক্ষা, ভাষা। একই প্রশ্ন ইংরেজিতে দিলে Qwen-এর অক্ষর-ছাঁচ ০.৩২৫ থেকে ০.৬২৫. প্রশ্নের জ্ঞান একই, বদলেছে শুধু ভাষা। পার্থক্যটি দৈব কি না, §৮-এ জোড়া পরীক্ষায় দেখব।

তৃতীয় শিক্ষা, নিয়ম। একই model, একই প্রশ্ন, শুধু নম্বরের নিয়মে Qwen-এর ইংরেজি ফল ০.৫০ থেকে ০.৬২৫. SmolLM2-এর অক্ষর-ছাঁচ ০.১৭৫, দৈবের চেয়েও কম, আর letter-mass=0.719 বলে, পরের token-এর সম্ভাবনার প্রায় ২৮% A–D-এর বাইরে গেছে: model ছাঁচটিই মানছে না। তার জন্য বিকল্প-পদ্ধতি (০.৪৫) বেশি অর্থবহ। “এই model-এর নম্বর কত” প্রশ্নের উত্তর তাই সবসময় “কোন নিয়মে?” দিয়ে শুরু।

৫ · Generation আর exact match — বাংলার normalisation

GSM8K-এর মতো প্রশ্নে বিকল্প নেই; model উত্তর লেখে, আর শেষের সংখ্যাটি reference-এর সঙ্গে মেলানো হয়। একে বলে exact match. সমস্যা হলো “হুবহু”: “১৮০ টাকা” আর “180 taka” অর্থে এক, string-এ আলাদা। তাই মেলানোর আগে দুই পাশে একই answer normalisation: NFC, বাংলা অঙ্ক ইংরেজিতে, দাঁড়ি-কমা বাদ, বাড়তি ফাঁকা জায়গা এক, আর এককের সমার্থক শব্দ এক রূপে।

নিচের উত্তরগুলো Qwen2.5-0.5B-Instruct-এর সত্যিকারের output: বাজারঘর set-এর ১২টি হিসাবের প্রশ্ন, দুটি উদাহরণসহ (2-shot) prompt, greedy decoding, প্রথম লাইন রাখা। একবার প্রশ্ন বাংলায়, একবার ইংরেজিতে; reference দুই ক্ষেত্রেই বাংলায়।

import re, unicodedata
# সত্যিকারের run: Qwen2.5-0.5B-Instruct, 2-shot, greedy; প্রশ্ন বাংলায় (bn) আর ইংরেজিতে (en)
GOLD = ["১৮০ টাকা", "১৪৪ টাকা", "৪৫০ টাকা", "১৮০ টাকা", "৩৫০ টাকা", "৫০০ টাকা",
        "৫০ টাকা", "১০০০ গ্রাম", "২৮০ টাকা", "৭০ টাকা", "২৫%", "৪০০ টাকা"]
BN = ["৫৪ টাকা", "৩৪ টাকা", "৪০০ টাকা", "৫৪০ টাকা", "৭৩০ টাকা", "৫৩০ টাকা",
      "৩০০ টাকা", "৩", "৫৪ টাকা", "৫৪ টাকা", "৩৪০ টাকা", "৩০০ টাকা"]
EN = ["180 taka", "144 taka", "450 taka", "180 taka", "450 taka", "500 taka",
      "50 taka", "1000", "350 taka", "210 taka", "25%", "300 taka"]
EXTRA = [("১৮০ টাকা", " ১৮০  টাকা।"), ("১৮০ টাকা", "180 Tk."), ("১৮০ টাকা", "১৫০ নয়, ১৮০ টাকা"),
         ("১৮০ টাকা", "১৮০ নয়, ১৫০ টাকা")]          # শেষের ৪টি হাতে লেখা পরীক্ষা

DIG = str.maketrans("০১২৩৪৫৬৭৮৯", "0123456789")
UNIT = {"taka": "টাকা", "tk": "টাকা", "grams": "গ্রাম", "gram": "গ্রাম"}
def norm(s):
    s = unicodedata.normalize("NFC", s).translate(DIG).lower()
    s = re.sub(r"[।,.!?;:'\"]", " ", s)
    return " ".join(UNIT.get(w, w) for w in s.split())
def number(s):
    m = re.findall(r"\d+(?:\.\d+)?", norm(s))
    return m[-1] if m else None                # শেষ সংখ্যাটি, GSM8K-এর মতো

def em(golds, preds):
    raw = sum(g == p for g, p in zip(golds, preds))
    nm = sum(norm(g) == norm(p) for g, p in zip(golds, preds))
    num = sum(number(g) == number(p) for g, p in zip(golds, preds))
    return f"{raw:2d} {nm:2d} {num:2d}  / {len(golds)}"
print("             কাঁচা  norm  সংখ্যা")
print("বাংলা প্রশ্ন  ", em(GOLD, BN))
print("ইংরেজি প্রশ্ন ", em(GOLD, EN))
for g, p in EXTRA:
    print(f"{p!r:22} norm={norm(g) == norm(p)!s:5} সংখ্যা={number(g) == number(p)}")

আউটপুট (পাতার Python-এ চালানো):

             কাঁচা  norm  সংখ্যা
বাংলা প্রশ্ন    0  0  0  / 12
ইংরেজি প্রশ্ন   0  7  8  / 12
' ১৮০  টাকা।'          norm=True  সংখ্যা=True
'180 Tk.'              norm=True  সংখ্যা=True
'১৫০ নয়, ১৮০ টাকা'    norm=False সংখ্যা=True
'১৮০ নয়, ১৫০ টাকা'    norm=False সংখ্যা=False

ইংরেজি প্রশ্নের সারি: কাঁচা তুলনায় ০/১২, normalisation-এর পর ৭/১২, সংখ্যা ধরে ৮/১২। “1000” বনাম “১০০০ গ্রাম” এককের অভাবে norm-এ মেলেনি, সংখ্যায় মিলেছে। কোন নিয়ম ঠিক? এটি সিদ্ধান্তের বিষয়: দামের প্রশ্নে একক বাদ পড়া চলে, ওষুধের মাত্রায় চলে না। বাংলা প্রশ্নের সারি: তিন নিয়মেই ০/১২। “৫৪ টাকা” বারবার এসেছে: model সংখ্যার ছাঁচ নিয়েছে, হিসাব নয়। Normalisation ভুল উত্তরকে ঠিক বানায় না, শুধু মাপার ভুল সরায়।

শেষের চারটি হাতে লেখা লাইন নিয়মের সীমা দেখায়। দাঁড়ি আর দুই ফাঁকা জায়গা, বা “Tk.”, norm-এ ঠিক ধরা পড়ল। “১৫০ নয়, ১৮০ টাকা” শেষ সংখ্যা ধরে ঠিক গোনা হলো, কারণ আমরা শেষ সংখ্যা নিই; প্রথম সংখ্যা নিলে ভুল হতো। কিন্তু model যদি লিখত “১৮০ হবে না, ১৫০ হবে, মোট ১৮০ টাকার জিনিস”, তখন কোনো regex নিশ্চিত নয়। তাই নিয়ম লিখে রাখুন, আর অমিলগুলোর একটি নমুনা নিজে পড়ুন।

৬ · LLM-as-judge — খোলা উত্তরের পরীক্ষক

“বৃষ্টিতে ডেলিভারি দেরি হলে কী বলবেন?” এর কোনো একক সঠিক লেখা নেই। Exact match এখানে অচল, আর পর্ব ৪৫-এর BLEU বা chrF অর্থ বোঝে না। মানুষ দিয়ে হাজার উত্তর পড়ানো ধীর আর দামি। তাই একটি LLM-কেই পরীক্ষক বানানো হয়: LLM-as-a-judge. ধারণাটি বড় আকারে পরীক্ষা করেন Zheng ও সহকর্মীরা, “Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena” প্রবন্ধে (২০২৩, NeurIPS-এর Datasets and Benchmarks track-এ প্রকাশিত)। তাঁদের হিসাবে GPT-4-এর মতো শক্তিশালী বিচারক মানুষের পছন্দের সঙ্গে ৮০%-এর বেশি ক্ষেত্রে মিলেছিল, যা তাঁদের ডেটায় দুজন মানুষের নিজেদের মিলের কাছাকাছি। একই প্রবন্ধে তাঁরা সীমাগুলোও তালিকা করেন।

ধরনবিচারককে যা দেওয়া হয়সুবিধাদুর্বলতা
Pairwiseপ্রশ্ন, উত্তর A, উত্তর B: “কোনটি ভালো?”সূক্ষ্ম পার্থক্য ধরে; দুই version তুলনায় সরাসরিক্রমের পক্ষপাত; জোড়ার সংখ্যা দ্রুত বাড়ে
Pointwiseপ্রশ্ন আর একটি উত্তর: “১ থেকে ৫ নম্বর দিন”ক্রম নেই; নতুন version আগের নম্বরের সঙ্গে মেলানো সহজস্কেল ঝাপসা; কাছাকাছি উত্তর একই নম্বর পায়
Reference-সহওপরের যেকোনোটি, সঙ্গে সঠিক উত্তর বা নিয়মনির্ভুলতার প্রশ্নে বিচারক কম ভুল করেReference লিখতে মানুষের সময় লাগে

Zheng ও সহকর্মীদের তালিকার তিনটি পক্ষপাত এই পর্বে মাপব। Position bias: মান না দেখে প্রথম (বা দ্বিতীয়) অবস্থানের উত্তরকে বেশি পছন্দ। Verbosity bias: লম্বা উত্তরকে বেশি পছন্দ, ভুল হলেও। Self-preference (তাঁদের ভাষায় self-enhancement): নিজের লেখার ধাঁচকে বেশি পছন্দ। গণিত বা যুক্তির প্রশ্নে বিচারকের নিজের ভুলও তাঁরা সীমা হিসেবে লেখেন।

ক্রিকেটের DRS-এর কথা ভাবুন। আম্পায়ারের সিদ্ধান্ত দ্রুত, কিন্তু মানুষের চোখ কোণ আর দূরত্বে ভুল করে। তাই দ্বিতীয় দিক থেকে দেখা হয়, আর দুই দিক না মিললে “umpire's call”. Swap test ঠিক এটাই: একই জোড়া দুই ক্রমে দেখানো, দুই দিকে একই রায় হলেই সিদ্ধান্ত।

৭ · Position bias মাপা — swap test, সত্যিকারের বিচারকে

পরীক্ষার জন্য এই পর্বে হাতে লেখা ৩৬টি বাজারঘর উত্তর-জোড়া। প্রতিটিতে একটি প্রশ্ন (কখনো সঙ্গে দোকানের নিয়ম), একটি ভালো আর একটি খারাপ উত্তর। কোনটি ভালো, তা লেখকের নিজের লেবেল; এটিই এখানে “মানুষের রায়”। তিন ধরনের জোড়া, প্রতিটি ১২টি: fact (খারাপটি নিয়ম বা হিসাব ভাঙে, দৈর্ঘ্য প্রায় সমান), long (খারাপটি লম্বা, ভদ্র, কিন্তু ভুল বা এড়িয়ে যাওয়া), tone (খারাপটি রূঢ় বা অসহযোগী)। বিচারক দুটি ছোট model: Qwen2.5-0.5B-Instruct আর HuggingFaceTB/SmolLM2-360M-Instruct, দুটিরই license Apache 2.0. প্রতিটি জোড়া দুই ক্রমে দেখানো হলো, আর রায় নেওয়া হলো পরের token “A” আর “B”-এর logit-এর পার্থক্য d থেকে, কোনো sampling ছাড়া। Pointwise-এ প্রতিটি উত্তরকে ১–৫ নম্বর, “1” থেকে “5” token-এর সম্ভাবনা দিয়ে প্রত্যাশিত নম্বর।

system: You are a strict judge of customer-support answers. Judge correctness against
        the rule first, then helpfulness and politeness. Length is not quality.
user:   Question: … [Answer A] … [Answer B] … Which answer is better? Reply with one letter: A or B.
~/bazarghar-dl$ uv run python judge_swap.py    # সত্যিকারের run, CPU-তে
== judge Qwen2.5-0.5B-Instruct  (125s, CPU)
 good-first correct 1.000 | good-second correct 0.000 | picks 'A' 1.000
 consistent after swap 0.000 | correct in both orders 0.000
  fact n=12 both-orders-correct 0.000 | pointwise good>bad 0.500 mean good 3.02 bad 3.17
  long n=12 both-orders-correct 0.000 | pointwise good>bad 0.250 mean good 2.47 bad 2.84
  tone n=12 both-orders-correct 0.000 | pointwise good>bad 0.333 mean good 2.49 bad 2.47
== judge SmolLM2-360M-Instruct  (158s, CPU)
 good-first correct 0.778 | good-second correct 0.167 | picks 'A' 0.806
 consistent after swap 0.056 | correct in both orders 0.000
  fact n=12 both-orders-correct 0.000 | pointwise good>bad 0.417 mean good 1.77 bad 1.80
  long n=12 both-orders-correct 0.000 | pointwise good>bad 0.667 mean good 1.68 bad 1.67
  tone n=12 both-orders-correct 0.000 | pointwise good>bad 0.750 mean good 1.76 bad 1.71

Qwen ৭২টি রায়ের ৭২টিতেই “A” বলেছে। ভালোটি আগে থাকলে সে “শতভাগ নির্ভুল”, পরে থাকলে “শতভাগ ভুল”। যদি কেউ শুধু প্রথম ক্রমে চালাতেন, রিপোর্টে লিখতেন: বিচারক ৩৬-এ ৩৬. SmolLM2 ৮১% রায়ে “A”; দুই ক্রমে একই উত্তর বেছেছে মাত্র ২টি জোড়ায়, আর দুটিতেই খারাপটি। Pointwise-এ ক্রম নেই, কিন্তু নম্বর প্রায় সমান: Qwen long জোড়ায় খারাপ লম্বা উত্তরকে গড়ে বেশি দিয়েছে (২.৮৪ বনাম ২.৪৭), যা verbosity bias-এর লক্ষণ, যদিও system বার্তায় স্পষ্ট লেখা ছিল যে দৈর্ঘ্য মানের মাপ নয় (“Length is not quality”). SmolLM2-এর ৩৬% জোড়ায় দুই নম্বরের ফারাক ০.০৫-এরও কম।

একটি জোড়া ধাপে ধাপে দেখুন। SmolLM2-360M, fact জোড়া: “নিয়ম: ঢাকার ভেতরে ডেলিভারি চার্জ ৬০ টাকা, বাইরে ১২০ টাকা। রাজশাহীতে কত?” ভালো উত্তর ১২০, খারাপ উত্তর ৬০।

d = logit(A) − logit(B)
ধাপ
দেখানো
ফল
  1. ক্রম ১. ভালো উত্তর A-তে। d = ০.৫৪, ধনাত্মক, তাই রায় “A”: ঠিক।
  2. ক্রম ২. একই দুটি উত্তর, জায়গা বদল। ভালোটি এখন B-তে, তবু d = ০.৫২, রায় আবার “A”: ভুল। বিচারক উত্তর নয়, অবস্থান দেখছে।
  3. সিদ্ধান্ত. Zheng ও সহকর্মীদের নিয়মে দুই ক্রমে একই উত্তর জিতলেই জয়, নইলে টাই। এখানে টাই। একটি ক্রম দেখলে “ঠিক” লিখে ফেলতেন।
  4. ৩৬টি জোড়া. দুই ক্রমে একই রায় মাত্র ২টিতে (swap consistency ০.০৬), দুটিতেই খারাপটি জিতেছে। আসল জয় ০, টাই ৩৪।
  5. McNemar. b = শুধু ভালোটি আগে থাকলে ঠিক, ২৮টি জোড়া; c = শুধু পরে থাকলে ঠিক, ৬টি। ক্রমের প্রভাব না থাকলে এই ৩৪টি অমিল দুই দিকে সমান ভাগ হতো। ২৮ বনাম ৬ দৈবে আসার সম্ভাবনা প্রায় ০.০০০২।
  6. Logit দিয়ে শোধরানো. দুই ক্রমের d বিয়োগ করলে অবস্থানের সমান টান কেটে যায়: ০.০২ > ০, অর্থাৎ ভালোটির দিকে সামান্য ঝোঁক। সব জোড়ায় এভাবে ০.৬৪, কিন্তু ৩৬টি জোড়ায় CI [০.৪৭, ০.৭৮], ০.৫-কে ছোঁয়। শোধরানোর পরও এই বিচারক নির্ভরযোগ্য নয়।

এবার রায়গুলো নিজে বিশ্লেষণ করুন। নিচে দুই বিচারকের ৭২টি করে আসল রায়, অক্ষরে।

import numpy as np
from scipy.stats import binomtest
# সত্যিকারের run: ৩৬টি বাজারঘর জোড়া (১২ fact, ১২ long, ১২ tone), প্রতিটি দুই ক্রমে।
# v1: ভালো উত্তর A-তে থাকলে রায়; v2: ভালো উত্তর B-তে থাকলে রায়
J = {"Qwen2.5-0.5B": dict(v1="A" * 36, v2="A" * 36),
     "SmolLM2-360M": dict(v1="BAAAAABAAAAAAAAAAAAAAAAABBAAAABABABB",
                          v2="AAAAAABAAAAAAAAAAAAAAAAABBAAAABABAAB")}
kind = np.array(["fact"] * 12 + ["long"] * 12 + ["tone"] * 12)

for name, j in J.items():
    v1, v2 = np.array(list(j["v1"])), np.array(list(j["v2"]))
    ok1, ok2 = v1 == "A", v2 == "B"            # দুই ক্রমে ঠিক কি না
    same = ok1 == ok2                          # ক্রম বদলেও একই উত্তর জিতল
    win = ok1 & ok2                            # দুই ক্রমেই ভালোটি: আসল জয়
    tie = ~same                                # রায় ক্রমের সঙ্গে বদলাল: টাই
    b, c = int((ok1 & ~ok2).sum()), int((~ok1 & ok2).sum())
    p = binomtest(c, b + c, 0.5).pvalue if b + c else 1.0
    print(f"== বিচারক {name}")
    print(f"  'A' বলেছে {np.mean(np.r_[v1, v2] == 'A'):.2f} | swap consistency {same.mean():.2f}")
    print(f"  এক ক্রমে ঠিক: ভালোটি আগে {ok1.mean():.2f}, পরে {ok2.mean():.2f}")
    print(f"  McNemar: শুধু-আগে-ঠিক b={b}, শুধু-পরে-ঠিক c={c}, p={p:.2g}")
    print(f"  Zheng-নিয়মে: জয় {win.sum()}, হার {(~ok1 & ~ok2).sum()}, টাই {tie.sum()}")
    for k in ("fact", "long", "tone"):
        m = kind == k
        print(f"    {k:4}: ভালোটি আগে ঠিক {ok1[m].sum():2d}/12, পরে ঠিক {ok2[m].sum():2d}/12")

আউটপুট (পাতার Python-এ চালানো):

== বিচারক Qwen2.5-0.5B
  'A' বলেছে 1.00 | swap consistency 0.00
  এক ক্রমে ঠিক: ভালোটি আগে 1.00, পরে 0.00
  McNemar: শুধু-আগে-ঠিক b=36, শুধু-পরে-ঠিক c=0, p=2.9e-11
  Zheng-নিয়মে: জয় 0, হার 0, টাই 36
    fact: ভালোটি আগে ঠিক 12/12, পরে ঠিক  0/12
    long: ভালোটি আগে ঠিক 12/12, পরে ঠিক  0/12
    tone: ভালোটি আগে ঠিক 12/12, পরে ঠিক  0/12
== বিচারক SmolLM2-360M
  'A' বলেছে 0.81 | swap consistency 0.06
  এক ক্রমে ঠিক: ভালোটি আগে 0.78, পরে 0.17
  McNemar: শুধু-আগে-ঠিক b=28, শুধু-পরে-ঠিক c=6, p=0.0002
  Zheng-নিয়মে: জয় 0, হার 2, টাই 34
    fact: ভালোটি আগে ঠিক 10/12, পরে ঠিক  1/12
    long: ভালোটি আগে ঠিক 12/12, পরে ঠিক  0/12
    tone: ভালোটি আগে ঠিক  6/12, পরে ঠিক  5/12

McNemar-এর যুক্তি এখানে সহজ (বিস্তারিত মেশিন লার্নিং কোর্সের পর্ব ২২-এ)। যে জোড়ায় দুই ক্রমেই ঠিক বা দুই ক্রমেই ভুল, তা ক্রম নিয়ে কিছু বলে না। শুধু অমিলগুলো গুনুন। ক্রমের প্রভাব না থাকলে সেগুলো কয়েন ছোড়ার মতো দুই দিকে যেত। Qwen-এ ৩৬-০, p = ২.৯ × ১০−১১. SmolLM2-এর ভাগে একটি সূক্ষ্মতা: tone জোড়ায় ৬/১২ আর ৫/১২, অর্থাৎ এখানে ক্রমের টান কম, কিন্তু নির্ভুলতাও দৈবের কাছে। Long জোড়ায় ১২/১২ আর ০/১২: পুরোটাই অবস্থান।

Desktop-এ logit-এর পার্থক্য দিয়ে শোধরানোর পর (সত্যিকারের run, ১০,০০০ bootstrap):

judge Qwen2.5-0.5B-Instruct: swap-consistent 0.000 CI [0. 0.] (n=36)
   debiased (d1 - d2 > 0) picks the better answer 0.444 CI [0.278 0.611]
judge SmolLM2-360M-Instruct: swap-consistent 0.056 CI [0.    0.139] (n=36)
   debiased (d1 - d2 > 0) picks the better answer 0.639 CI [0.472 0.778]

Qwen-এর শোধরানো রায় long জোড়ার ১২টির ১১টিতে লম্বা, ভুল উত্তরের দিকে হেলে (একই রায়গুলো ধরন অনুযায়ী ভাগ করে গোনা)। অবস্থানের আড়ালে দৈর্ঘ্যের পক্ষপাতও ছিল। এই দুটি ছোট model দিয়ে বড় বিচারক সম্পর্কে কিছু বলা যায় না; Zheng ও সহকর্মীদের শক্তিশালী বিচারকের ফল অনেক ভালো ছিল। শেখার বিষয় পদ্ধতি: যেকোনো বিচারকের ওপর নির্ভরের আগে ঠিক এই পরীক্ষাটি চালান।

৮ · কত প্রশ্ন লাগবে — CI, জোড়া তুলনা আর নমুনার আকার

৪০টি প্রশ্নে ০.৬২৫ মানে কী? আরও ৪০টি প্রশ্ন নিলে হয়তো ০.৫০ বা ০.৭৫ আসত। পর্ব ৪৫-এ বাক্য ধরে bootstrap দেখেছেন; এখানে প্রতিটি প্রশ্নের ফল ০ বা ১, তাই হিসাব আরও সহজ। প্রশ্ন ধরে resample করুন, আর দুই model বা দুই ভাষা তুলনায় একই সূচক দুই পাশে নিন (তত্ত্ব মেশিন লার্নিং কোর্সের পর্ব ২১-এ)।

পরিকল্পনার জন্য একটি সূত্রই যথেষ্ট। n টি প্রশ্নে accuracy p হলে standard error √(p(1 − p)/n), আর ৯৫% CI-এর অর্ধ-প্রস্থ প্রায়

h ≈ 1.96 × √(p(1 − p) / n),  তাই  n ≈ 1.96² × p(1 − p) / h²

এখানে ১.৯৬ normal বণ্টনের ৯৭.৫ শতাংশিক বিন্দু, আর h চাওয়া অর্ধ-প্রস্থ। n-এর বর্গমূল হরে আছে, তাই প্রস্থ অর্ধেক করতে প্রশ্ন চারগুণ লাগে। p = ০.৫-এ অনিশ্চয়তা সবচেয়ে বেশি; p ০ বা ১-এর কাছে গেলে কম। খুব ছোট n বা p একেবারে কিনারায় থাকলে এই normal approximation দুর্বল; তখন bootstrap বা Wilson interval ভালো।

Qwen, ইংরেজি বাজারঘর set
ধাপ
হিসাব
  1. এখনকার অবস্থা. ৪০টি প্রশ্নে ০.৬২৫. অর্ধ-প্রস্থ ০.১৫০: আসল মান ০.৪৭৫ থেকে ০.৭৭৫-এর যেকোনো জায়গায় হতে পারে।
  2. ±০.০৫ চাই. p(1 − p) = ০.৬২৫ × ০.৩৭৫ ≈ ০.২৩৪. লাগবে ৩৬১টি প্রশ্ন, এখনকার প্রায় ৯ গুণ।
  3. ±০.০৩ চাই. ১,০০১টি প্রশ্ন। প্রস্থ ০.০৫ থেকে ০.০৩, অর্থাৎ ০.৬ গুণ করতে প্রশ্ন প্রায় ২.৮ গুণ (১ ÷ ০.৬²)। হাতে লেখা set-এ এটি বড় খরচ।
  4. প্রশ্ন বাড়ানো কঠিন? অনেক সময় প্রশ্ন জানতে চায় পার্থক্য, আলাদা মান নয়। একই প্রশ্নে দুটি অবস্থা মাপলে প্রশ্নের কাঠিন্য দুই পাশে বাতিল হয়।
  5. জোড়া তুলনা. ৪০টির মধ্যে ২০টিতে দুই ভাষার ফল আলাদা: ১৬টিতে শুধু ইংরেজি ঠিক, ৪টিতে শুধু বাংলা। McNemar p ≈ ০.০১২, পার্থক্যের জোড়া CI [০.১০, ০.৫০]। মান আলাদা করে অনিশ্চিত হলেও পার্থক্যটি দৈব নয়, অন্তত এই set আর এই model-এ।
  6. নিয়ম লেখা. ফল দেখার আগে লিখুন: কত প্রশ্ন, কোন test, কোন সীমা পেরোলে নতুন version চালু। ফল দেখে প্রশ্ন যোগ-বিয়োগ করলে CI-এর অর্থ থাকে না; এটি A/B test-এর “peeking”-এর মতোই (মেশিন লার্নিং কোর্সের পর্ব ৬২)।
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import binomtest
# সত্যিকারের run: Qwen2.5-0.5B-Instruct, অক্ষর-ছাঁচ, একই ৪০টি বাজারঘর প্রশ্ন বাংলায় আর ইংরেজিতে
bn = np.array(list("0100000000101001011000101000010110000101"), int)
en = np.array(list("1000011001100110110011101010111111101111"), int)
rng = np.random.default_rng(58)
idx = rng.integers(0, 40, size=(10000, 40))          # একই সূচক দুই পাশে: জোড়া
ci = lambda v: np.percentile(v, [2.5, 97.5]).round(3)
print(f"বাংলা   {bn.mean():.3f} CI {ci(bn[idx].mean(1))}")
print(f"ইংরেজি  {en.mean():.3f} CI {ci(en[idx].mean(1))}")
print(f"পার্থক্য {en.mean() - bn.mean():+.3f} জোড়া CI {ci((en[idx] - bn[idx]).mean(1))}")
b, c = int(((en == 1) & (bn == 0)).sum()), int(((en == 0) & (bn == 1)).sum())
print(f"McNemar: শুধু ইংরেজিতে ঠিক {b}, শুধু বাংলায় ঠিক {c}, p = {binomtest(c, b + c).pvalue:.4f}")

def n_needed(p, half):                                  # ±half চাইলে কত প্রশ্ন
    return int(np.ceil(1.96**2 * p * (1 - p) / half**2))
for p in (0.5, 0.8):
    print(f"p={p}:", {f"±{h}": n_needed(p, h) for h in (0.10, 0.05, 0.03, 0.01)})
hits = []                                               # সূত্রটি কি ঠিক? অনুকরণে যাচাই
for n in (40, 385):
    acc = rng.binomial(n, 0.5, size=20000) / n
    half = 1.96 * np.sqrt(0.25 / n)
    hits.append(f"n={n}: ±{half:.3f}-এর ভেতরে {np.mean(abs(acc - 0.5) <= half):.3f}")
print(" | ".join(hits))

n = np.arange(20, 2001)
fig, ax = plt.subplots(figsize=(6, 3.2))
for p in (0.5, 0.8, 0.95):
    ax.plot(n, 1.96 * np.sqrt(p * (1 - p) / n), label=f"accuracy {p}")
ax.axhline(0.05, color="gray", ls="--", lw=1)
ax.set_xscale("log"); ax.set_xlabel("number of eval items (log scale)")
ax.set_ylabel("95% CI half-width"); ax.legend(); ax.set_title("More items, narrower interval")

আউটপুট (পাতার Python-এ চালানো):

বাংলা   0.325 CI [0.175 0.475]
ইংরেজি  0.625 CI [0.475 0.775]
পার্থক্য +0.300 জোড়া CI [0.1 0.5]
McNemar: শুধু ইংরেজিতে ঠিক 16, শুধু বাংলায় ঠিক 4, p = 0.0118
p=0.5: {'±0.1': 97, '±0.05': 385, '±0.03': 1068, '±0.01': 9604}
p=0.8: {'±0.1': 62, '±0.05': 246, '±0.03': 683, '±0.01': 6147}
n=40: ±0.155-এর ভেতরে 0.960 | n=385: ±0.050-এর ভেতরে 0.942

দুটি ভাষার আলাদা CI একে অপরকে ছোঁয় (০.৪৭৫-এ), তবু জোড়া পার্থক্যের CI শূন্যের ওপরে। আলাদা CI-এর ওপর চোখ বুলিয়ে “পার্থক্য নেই” বলা তাই ভুল পথ। সারণিতে p = ০.৮-এ একই প্রস্থে প্রশ্ন কম লাগে (±০.০৫-এ ২৪৬, p = ০.৫-এ ৩৮৫)। অনুকরণে সূত্রটি যাচাই হলো: n = ৩৮৫-এ ৯৪.২% আর n = ৪০-এ ৯৬.০% নমুনা ±h-এর ভেতরে, ৯৫%-এর কাছাকাছি; ছোট n-এ binomial-এর ধাপের কারণে হারটি একটু সরে। ছবিতে দেখুন, ±০.০৫-এর দাগে পৌঁছাতে p = ০.৫-এ কয়েকশো প্রশ্ন লাগে।

Desktop-এ সব set-এর জন্য একই হিসাব (সত্যিকারের run, ১০,০০০ bootstrap, কয়েকটি লাইন):

model                    set       rule       n    acc  95% bootstrap CI   ±1.96·SE
Qwen2.5-0.5B-Instruct    bazar     letter    40  0.325  [0.175, 0.475]   ±0.145
Qwen2.5-0.5B-Instruct    bazar_en  letter    40  0.625  [0.475, 0.775]   ±0.150
Qwen2.5-0.5B-Instruct    mmmlu_bn  letter   200  0.285  [0.225, 0.350]   ±0.063
SmolLM2-135M-Instruct    mmmlu_bn  letter   200  0.215  [0.160, 0.275]   ±0.057
Qwen − Smol, letter, mmmlu_bn: diff +0.070 paired CI [-0.015  0.155] | unpaired CI [-0.015  0.155]

MMMLU বাংলায় Qwen-এর ০.২৮৫-এর CI [০.২২৫, ০.৩৫০] দৈবের ০.২৫-কে ধরে। দুটি model-এর পার্থক্য ০.০৭, কিন্তু CI শূন্যকে ছোঁয়। ২০০টি প্রশ্নেও “Qwen বাংলায় ভালো” বলা যায় না। এখানে জোড়া আর আলাদা CI প্রায় এক, কারণ দুটি model-ই প্রায় দৈবে উত্তর দিচ্ছে, তাদের ভুলের মধ্যে মিল কম। জোড়ার সুবিধা আসে যখন দুই পাশের ফল একসঙ্গে ওঠানামা করে।

৯ · বাজারঘরের নিজের eval set, rubric আর regression test

প্রকাশিত benchmark সাধারণ ক্ষমতার আভাস দেয়; বাজারঘরের সিদ্ধান্ত নিতে লাগে নিজের প্রশ্ন। এই পর্বের ৪০টি প্রশ্ন আর ৩৬টি জোড়া তার একটি ছোট শুরু। বড় করার নিয়মগুলো:

  • প্রশ্ন আসবে কাজ থেকে। আসল গ্রাহকের প্রশ্ন, ফোন নম্বর আর নাম মুছে। প্রতিটি নতুন ভুলের পর একটি নতুন প্রশ্ন যোগ হবে, যাতে একই ভুল ফিরে এলে ধরা পড়ে।
  • ভাগ করে রিপোর্ট। দাম আর হিসাব, ফেরতের নিয়ম, ভদ্রতা, নিরাপত্তা। গড় ০.৮০ হলেও নিরাপত্তার ভাগে একটি ভুল বড় ক্ষতি।
  • ফল দেখার আগে নিয়ম লেখা। কোন ছাঁচ, কোন normalisation, কোন বিচারক, কত প্রশ্ন, আর কোন সীমা পেরোলে নতুন version চালু হবে।

Rubric: “ভালো উত্তর” লিখে ফেলা

Rubric হলো নম্বর দেওয়ার লিখিত নিয়ম, যাতে দুজন পরীক্ষক একই খাতায় কাছাকাছি নম্বর দেন। বাজারঘরের সহায়তা-উত্তরের জন্য একটি ছোট rubric:

মাপকাঠি০১২
নিয়মের সঙ্গে মিলনিয়ম ভাঙে বা ভুল সংখ্যাআংশিক ঠিকপুরো ঠিক
পরের পদক্ষেপনেইঅস্পষ্টস্পষ্ট (কী পাঠাবেন, কোথায় চাপবেন)
ভদ্রতাদোষারোপ বা রূঢ়নিরপেক্ষসহানুভূতিশীল
নিরাপত্তা (বাধ্যতামূলক)OTP, পিন বা পাসওয়ার্ড চাইলে পুরো উত্তর ০

মানুষ দুজন একই rubric-এ নম্বর দিলে তাঁদের মিল Cohen's kappa দিয়ে মাপুন (হিসাব পর্ব ৪৫-এ)। তারপর একই মাপে বিচারক model আর মানুষের মিল। মানুষে-মানুষে মিল কম হলে আগে rubric ঠিক করুন; তখন বিচারকের দোষ ধরা অর্থহীন। Zheng ও সহকর্মীরা (২০২৩) এভাবেই তুলনা করেছিলেন: তাঁদের ডেটায় শক্তিশালী বিচারক আর মানুষের মিল মানুষে-মানুষে মিলের কাছাকাছি ছিল। আমাদের ছোট বিচারকের ক্ষেত্রে, §৭-এর ফল অনুযায়ী, সেই কাছাকাছি হওয়ার কোনো লক্ষণ নেই।

Regression test: নতুন version কী ভাঙল

Prompt, model, chat template বা library বদলালে পুরো set আবার চালাতে হয়। একে বলে regression test. গড় নম্বরের পাশাপাশি দেখুন, কোন প্রশ্ন আগে ঠিক ছিল আর এখন ভুল। এটিই McNemar-এর ছকের “শুধু পুরনোটি ঠিক” ঘর। গড় ২ শতাংশ-বিন্দু বাড়লেও সেই ঘরে যদি “OTP চাওয়া” প্রশ্নটি থাকে, version চালু হবে না। Prompt-এর regression নিয়ে বিস্তারিত পর্ব ৬৬ · Prompt পরীক্ষায়; RAG-এর faithfulness মাপা পর্ব ৭০ · RAG মূল্যায়নে।

আরও গভীরে: Zheng ও সহকর্মীরা (২০২৩) দুই ক্রমে রায় না মিললে টাই ধরার নিয়ম দেন; Wang ও সহকর্মীদের “Large Language Models are not Fair Evaluators” (২০২৩) দেখায়, শুধু ক্রম বদলে দুই model-এর তুলনার ফল উল্টে যেতে পারে, আর একাধিক ক্রমের ফল মেলানোর প্রস্তাব দেয়। Pointwise বিচারে G-Eval (Liu ও সহকর্মী, ২০২৩) score token-এর সম্ভাবনা দিয়ে প্রত্যাশিত নম্বর নেয়, যা আমাদের run-এর পদ্ধতির মতো। Miller-এর “Adding Error Bars to Evals” (২০২৪) eval-এ জোড়া পার্থক্য আর একই নথি থেকে আসা প্রশ্নের জন্য clustered standard error ব্যবহারের পরামর্শ দেয়। HumanEval-এর প্রবন্ধে pass@k-এর একটি unbiased estimator আছে, n বার চেষ্টা করে c-টি ঠিক হলে মানটি ১ − C(n−c, k) ÷ C(n, k).

পরীক্ষা ও ইন্টারভিউতে: Multiple-choice-এ length normalisation কেন? প্রতিটি token-এর log-prob ঋণাত্মক, তাই যোগফল ছোট বিকল্পের দিকে হেলে। Position bias কীভাবে মাপবেন? প্রতিটি জোড়া দুই ক্রমে; swap consistency আর অমিলগুলোর দিক। দুটি model একই প্রশ্নে তুলনায় কোন test? McNemar বা জোড়া bootstrap. CI-এর প্রস্থ অর্ধেক করতে কত প্রশ্ন? চারগুণ, কারণ প্রস্থ ∝ ১/√n. Contamination কী? Test-এর প্রশ্ন training-এ ঢুকে যাওয়া; নম্বর মুখস্থ মাপে।

১০ · AI-এর যুগে — মূল্যায়নের কোড আর দাবি যাচাই

মূল্যায়নের কোড AI assistant দ্রুত লিখে দেয়, আর এখানকার ভুল প্রায় কখনো ক্র্যাশ করে না। ফল শুধু একটু ভালো দেখায়, আর সেটিই বিপদ।

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
Context আর বিকল্প একসঙ্গে tokenize করে বিকল্পের অংশ আন্দাজে কাটেসীমানার token ভুল হলে প্রসঙ্গের log-prob বিকল্পে ঢোকেবিকল্পের token decode করে পড়া; একটি প্রশ্নে হাতে যোগ মেলানো
Log-prob-এর সাধারণ যোগফল, কোন নিয়ম তা না বলেদৈর্ঘ্যের পক্ষপাত; নম্বর অন্যের সঙ্গে তুলনীয় নয়যোগফল আর দৈর্ঘ্য-ভাগ দুটোই রিপোর্ট
বিচারকের একটি ক্রমের রায়ই ফলPosition bias সরাসরি নম্বরে ঢোকেSwap test, দুই ক্রমে মিললেই জয়
বাংলা উত্তরে কাঁচা ==অঙ্ক, দাঁড়ি, ফাঁকা জায়গায় সঠিক উত্তরও ভুললিখিত normalisation, কিছু অমিল হাতে পড়া
Benchmark-এর নম্বর বা প্রবন্ধ স্মৃতি থেকেবানানো বা পুরনো সংখ্যা; ছাঁচ অজানামূল প্রবন্ধ আর তারিখসহ উৎস
একটি run, CI ছাড়া, “নতুন version ভালো”৪০টি প্রশ্নে ±১৫ শতাংশ-বিন্দুর মতো অনিশ্চয়তা লুকায়জোড়া bootstrap, McNemar, প্রশ্নের সংখ্যা লেখা
Test set দেখে prompt বা নিয়ম ঠিক করাTest নিজেই tuning-এর অংশ হয়ে যায়আলাদা dev set; test শুধু শেষে

কোড চাওয়ার সময় যাচাইও চেয়ে নিন। গ্রাহকের আসল প্রশ্ন বা ব্যক্তিগত তথ্য কোনো hosted assistant-এর prompt-এ দেবেন না।

আমি Hugging Face transformers-এ (version লিখে দিচ্ছি) একটি ছোট open model দিয়ে বাংলা multiple-choice আর pairwise judge মূল্যায়ন করব। ১. বিকল্পের log-likelihood হিসাবে context আর বিকল্প আলাদা tokenize করে জোড়ো, আর বিকল্পের token decode করে দেখানোর কোড দাও। ২. যোগফল, token-প্রতি আর অক্ষর-প্রতি, তিন নিয়মেই accuracy দাও; কোনটি কখন, এক লাইনে বলো। ৩. Judge-এ প্রতিটি জোড়া দুই ক্রমে চালাও; swap consistency আর McNemar-ধরনের exact test-এর কোড দাও। ৪. বাংলা উত্তরের normalisation লিখে দাও (NFC, বাংলা অঙ্ক, দাঁড়ি, ফাঁকা জায়গা), আর তিনটি ছোট test. ৫. প্রতিটি সংখ্যার পাশে জোড়া bootstrap CI দাও, আর কত প্রশ্ন লাগবে তার সূত্র। ৬. কোনো benchmark-এর নম্বর বা প্রবন্ধ নিশ্চিত না হলে বলো; কিছু বানিয়ে লিখবে না। Model “বোঝে” এমন দাবি করবে না।

AI বা LLM-এর দেওয়া মূল্যায়নের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।

প্র ০১একটি model MMLU-তে খুব উঁচু নম্বর পেল। এটি কি “সব বিষয়ে জানে”-র প্রমাণ?

না, আর কারণ অন্তত চারটি। প্রথম, ছাঁচ। MMLU-র মূল প্রবন্ধে (Hendrycks ও সহকর্মী, ২০২০) few-shot ছাঁচে উত্তরের অক্ষর বাছাই করা হয়েছিল। পরে বিভিন্ন দল ভিন্ন prompt, ভিন্ন সংখ্যক উদাহরণ, chat template-সহ বা ছাড়া, অক্ষর বা পুরো বিকল্প দিয়ে নম্বর দিয়েছে। আমাদের ছোট run-এই অক্ষর-পদ্ধতি আর বিকল্প-পদ্ধতির ফল আলাদা এসেছে। দুটি প্রকাশিত নম্বর পাশাপাশি রাখার আগে তাই দেখতে হয়, দুটি একই নিয়মে মাপা কি না।

দ্বিতীয়, contamination. MMLU বহু বছর ধরে ইন্টারনেটে আছে। Training ডেটা প্রকাশ না হলে বাইরে থেকে নিশ্চিত হওয়া যায় না, প্রশ্নগুলো আগে দেখা কি না। তৃতীয়, উত্তরপত্রের ভুল। Gema ও সহকর্মীদের ২০২৪ সালের পর্যালোচনায় কিছু বিষয়ে উল্লেখযোগ্য অংশে ভুল বা একাধিক সঠিক উত্তর পাওয়া যায়। শীর্ষের কাছে তাই ছোট পার্থক্য প্রায়ই শব্দ মাত্র, তথ্য নয়।

চতুর্থ, প্রশ্নের ধরন। চার বিকল্পের প্রশ্নে চিনে নেওয়া আর নিজে লিখে বোঝানো এক দক্ষতা নয়। তার ওপর বাংলার কথা আলাদা। OpenAI-এর MMMLU-তে MMLU-র test set পেশাদার অনুবাদকেরা অনুবাদ করেছেন বলে dataset card-এ লেখা আছে, কিন্তু প্রশ্নের বিষয় মূলত আমেরিকান পাঠ্যক্রমের। বাংলায় অনুবাদ করা মার্কিন আইনের প্রশ্নে ভালো করা আর বাংলাদেশের গ্রাহকের প্রশ্নে ভালো করা আলাদা কাজ।

তাহলে MMLU-র মূল্য কী? এটি একটি মোটা দাগের সংকেত: একই নিয়মে মাপা অনেক model-এর মধ্যে বড় পার্থক্য সাধারণত কিছু বলে। কাজের সিদ্ধান্তের জন্য নিজের task-এর প্রশ্ন লাগে।

মূল উপলব্ধি: Benchmark-এর নম্বর একটি নির্দিষ্ট ছাঁচে, নির্দিষ্ট প্রশ্নে, নির্দিষ্ট নিয়মে পাওয়া ফল। “জানে” বা “বোঝে” দাবির প্রমাণ এটি নয়।

প্র ০২Chatbot Arena-য় লক্ষ মানুষের ভোট। তাহলে কি এটিই চূড়ান্ত বিচার?

Arena-র শক্তি স্পষ্ট। প্রশ্ন আগে থেকে ঠিক করা নেই, তাই contamination-এর ভয় কম। মানুষ নিজের আসল প্রশ্ন দেন, দুটি অজানা model-এর উত্তর দেখে ভোট দেন। জোড়া ভোট থেকে Elo বা Bradley–Terry ধরনের রেটিং বের হয়, যে মডেলটি পর্ব ৫৭ · Preference alignment-এ reward model-এর ভিত্তি হিসেবেও আসবে। Zheng ও সহকর্মী (২০২৩) আর পরে Chiang ও সহকর্মী (২০২৪) প্ল্যাটফর্মটির পদ্ধতি বর্ণনা করেছেন।

তবু সীমা আছে। এক, কারা ভোট দেন। স্বেচ্ছাসেবক ব্যবহারকারীরা সাধারণত প্রযুক্তিতে আগ্রহী, প্রশ্নের বড় অংশ ইংরেজিতে আর কোডিং বা সাধারণ আলাপে। বাংলায় গ্রাহক-সহায়তার মান এখান থেকে সরাসরি বোঝা যায় না। দুই, ভোটার কী দেখে ভোট দেন। লম্বা, সাজানো, আত্মবিশ্বাসী উত্তর মানুষের কাছেও আকর্ষণীয়, যদিও সেটি ভুল হতে পারে। যিনি প্রশ্ন করেছেন, তিনি প্রায়ই উত্তর যাচাই করার অবস্থায় থাকেন না। তাই ভোটটি “কোনটি পছন্দ হলো”-র মাপ, “কোনটি সঠিক”-এর নয়।

তিন, leaderboard-এর নিয়ম। Singh ও সহকর্মীদের “The Leaderboard Illusion” (২০২৫) প্রবন্ধে যুক্তি দেওয়া হয়, কিছু প্রতিষ্ঠান প্রকাশের আগে অনেকগুলো ব্যক্তিগত সংস্করণ পরীক্ষা করে সবচেয়ে ভালোটি দেখাতে পারলে রেটিং পক্ষপাতদুষ্ট হয়। Arena-র পরিচালকেরা এর জবাব দিয়েছেন; বিতর্কটি নিজে পড়ে বিচার করাই ভালো। চার, রেটিংয়ের নিজের অনিশ্চয়তা। কাছাকাছি রেটিংয়ের model-এর confidence interval প্রায়ই একে অপরের ওপর পড়ে, তাই ক্রমের ছোট বদল অর্থহীন হতে পারে।

মূল উপলব্ধি: মানুষের পছন্দ একটি মূল্যবান সংকেত, তবে সেটি কাদের পছন্দ, কোন প্রশ্নে, আর সঠিকতার কতটা কাছে, এই তিনটি জেনে তবেই কাজে লাগাতে হয়।

প্র ০৩বিচারক model আর পরীক্ষার্থী model একই পরিবারের হলে কী সমস্যা? সমাধান কী?

Zheng ও সহকর্মীরা (২০২৩) তিনটি পক্ষপাতের নাম দেন: অবস্থান (position), দৈর্ঘ্য (verbosity) আর self-enhancement, অর্থাৎ নিজের লেখা উত্তরকে বেশি পছন্দ করার প্রবণতা। তাঁরা সতর্ক ছিলেন: নিজেদের ডেটায় কিছু বিচারকের মধ্যে এমন ইঙ্গিত দেখেছেন, তবে ডেটা সীমিত বলে চূড়ান্ত দাবি করেননি। পরে Panickssery ও সহকর্মীদের “LLM Evaluators Recognize and Favor Their Own Generations” (২০২৪) প্রবন্ধে যুক্তি দেওয়া হয়, কিছু model নিজের লেখা চিনতে পারে, আর সেই চেনার ক্ষমতার সঙ্গে নিজের লেখার প্রতি পক্ষপাতের সম্পর্ক আছে।

কারণটি অনুমান করা কঠিন নয়। একই ডেটা আর একই alignment পদ্ধতিতে শেখা model-গুলোর লেখার ধাঁচ, শব্দ বাছাই আর ফরম্যাট কাছাকাছি। বিচারক যে ধাঁচ “ভালো লেখা” বলে শিখেছে, নিজের পরিবারের উত্তরে সেটিই বেশি পায়। পরীক্ষক যদি নিজের ছাত্রের খাতা চেনেন, নিরপেক্ষ থাকা কঠিন; এ কারণেই বোর্ড পরীক্ষায় খাতায় রোল নম্বর গোপন রাখা হয়।

প্রতিকার কয়েক স্তরের। এক, ভিন্ন পরিবারের বিচারক, বা কয়েকটি ভিন্ন বিচারকের ভোট। দুই, উত্তর থেকে পরিচয়ের চিহ্ন (model-এর নাম, বিশেষ ফরম্যাট) সরানো। তিন, swap test সব সময়, আর দুই ক্রমে মিললেই জয়। চার, স্পষ্ট rubric আর reference উত্তর, যাতে বিচার রুচির বদলে নিয়মের দিকে যায়। পাঁচ, সবচেয়ে জরুরি: একটি ছোট অংশে মানুষের রায়ের সঙ্গে বিচারকের মিল (kappa) মেপে রাখা, আর বিচারক বদলালে আবার মাপা।

মূল উপলব্ধি: বিচারক model নিজেও একটি মাপার যন্ত্র, আর প্রতিটি যন্ত্রের মতো তাকেও calibrate করতে হয়, মানুষের একটি ছোট কিন্তু যত্নে বানানো রায়ের সঙ্গে।

প্র ০৪একই model, একই প্রশ্ন, শুধু prompt-এর ফরম্যাট বদলে নম্বর অনেক বদলে যায় কেন? এটি benchmark-এর জন্য কী বোঝায়?

Model-এর কাছে ফরম্যাটও input. “A.” না “(A)”, বিকল্পের পরে নতুন লাইন না কমা, উত্তরের আগে “Answer:” না “উত্তর:”, সব আলাদা token, আলাদা প্রসঙ্গ। Sclar ও সহকর্মীদের ২০২৩ সালের প্রবন্ধ (“Quantifying Language Models' Sensitivity to Spurious Features in Prompt Design”) দেখায়, অর্থ না বদলে শুধু ফরম্যাট বদলালে কিছু open model-এর few-shot নির্ভুলতা বড় ব্যবধানে বদলাতে পারে। তাঁরা তাই একটি ফরম্যাটের বদলে ফরম্যাটের একটি পরিসরে নম্বর দেওয়ার প্রস্তাব দেন।

আমাদের run-এও এর ছোট সংস্করণ দেখা গেল। একই চল্লিশটি প্রশ্নে একই model অক্ষর বাছাইয়ে এক নম্বর পেল, বিকল্পের log-likelihood-এ আরেক। কোনটি “আসল” ক্ষমতা? দুটিই একটি নির্দিষ্ট প্রশ্নের উত্তর: এই ছাঁচে model কেমন করে। ইংরেজি set-এ প্রায় সাড়ে ১৩ কোটি parameter-এর SmolLM2 অক্ষর-ছাঁচে দৈবের নিচে নামল (০.১৭৫), বিকল্প-পদ্ধতিতে পেল ০.৪৫; প্রায় ৪৯ কোটির Qwen-এ উল্টো, অক্ষর-ছাঁচই এগিয়ে। “A, B, C, D”-র সঙ্গে বিকল্পের অর্থ জোড়া লাগানো নিজেই একটি দক্ষতা।

এর তিনটি ব্যবহারিক মানে আছে। এক, প্রকাশিত নম্বরের সঙ্গে নিজের নম্বর তুলনার আগে ছাঁচ, shot-এর সংখ্যা, template আর নম্বরের নিয়ম মেলাতে হয়। এ কারণেই EleutherAI-এর lm-evaluation-harness-এর মতো উন্মুক্ত টুল কাজে লাগে: সবাই একই কোড চালায়। দুই, দুটি model তুলনায় একটি ছাঁচ বেছে তার পক্ষে যুক্তি লিখে রাখতে হয়, ফল দেখে ছাঁচ বাছা নয়। তিন, পণ্যে যে prompt চলবে, সেটি দিয়েই চূড়ান্ত মূল্যায়ন, কারণ ব্যবহারকারী সেই ছাঁচই দেখবেন। Prompt বদলালে পর্ব ৬৬ · Prompt পরীক্ষার regression set আবার চালাতে হয়।

মূল উপলব্ধি: নম্বর model-এর নয়, model আর ছাঁচের জোড়ার। ছাঁচ না লিখে নম্বর দেওয়া অর্ধেক তথ্য দেওয়া।

প্র ০৫বাংলার জন্য অনুবাদ করা benchmark যথেষ্ট, নাকি বাংলায় লেখা নিজস্ব set লাগবে?

দুটিরই জায়গা আছে, তবে কাজ আলাদা। অনুবাদ করা set-এর সুবিধা হলো তুলনা: একই প্রশ্ন বহু ভাষায় থাকলে বোঝা যায়, ভাষা বদলালে একই model কতটা পড়ে। MMMLU-র dataset card অনুযায়ী অনুবাদ পেশাদার মানুষের করা, যন্ত্রের নয়, যা গুরুত্বপূর্ণ; যন্ত্রে অনুবাদ করা প্রশ্নে অনুবাদের ভুল আর model-এর ভুল আলাদা করা কঠিন।

অসুবিধা হলো বিষয়বস্তু। মূল প্রশ্নগুলো একটি দেশের পাঠ্যক্রম আর সংস্কৃতি থেকে। Singh ও সহকর্মীদের Global-MMLU (২০২৪) কাজে লেখকেরা MMLU-র প্রশ্নের একটি উল্লেখযোগ্য অংশকে সাংস্কৃতিকভাবে নির্দিষ্ট জ্ঞানের ওপর নির্ভরশীল বলে চিহ্নিত করেন, আর সেই অংশ আলাদা করে রিপোর্ট করার পরামর্শ দেন। বাংলায় অনুবাদ করা মার্কিন ইতিহাসের প্রশ্ন বাংলা ভাষার দক্ষতা কম, মার্কিন ইতিহাস বেশি মাপে।

নিজস্ব set-এর সুবিধা হলো প্রাসঙ্গিকতা আর পরিষ্কারতা। বাজারঘরের ৪০টি প্রশ্ন কোথাও প্রকাশিত হয়নি, তাই contamination-এর সম্ভাবনা কম, আর প্রশ্নগুলো সরাসরি আমাদের কাজের। কিন্তু ছোট set-এ CI চওড়া, প্রশ্ন একজনের লেখা বলে তাঁর অভ্যাসের পক্ষপাত থাকে, আর সংখ্যাটি অন্য কারও সঙ্গে তুলনা করা যায় না। বাংলার নিজস্ব সমস্যাও আছে: সাধু-চলিত, উপভাষা, বাংলা আর ইংরেজি অঙ্ক, “ডেলিভারি” না “delivery”, এমন অনেক দ্বিধা। Normalisation-এর নিয়ম না লিখলে একই উত্তর একবার ঠিক, একবার ভুল গোনা হয়।

ব্যবহারিক পথ: সাধারণ ক্ষমতার জন্য প্রকাশিত set (license পড়ে), কাজের সিদ্ধান্তের জন্য নিজের set, যা একাধিক লেখক মিলে ধীরে ধীরে বড় করবেন।

মূল উপলব্ধি: অনুবাদ করা benchmark ভাষা-বদলের প্রভাব মাপে; নিজের বাংলা set কাজের মান মাপে। একটি অন্যটির বিকল্প নয়।

অনুশীলন

খাতা-কলমে করুন, তারপর উত্তর মেলান। বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এই ধরনের হিসাব কাজে লাগে।

  1. CI, নমুনার আকার আর McNemar: ৪০টি প্রশ্নে একটি model-এর accuracy ০.৬০. (ক) Normal approximation-এ ৯৫% CI-এর অর্ধ-প্রস্থ কত? (খ) একই p-তে অর্ধ-প্রস্থ ০.০৩ চাইলে কত প্রশ্ন লাগবে? (গ) দুটি version একই প্রশ্নে চালানো হলো: শুধু নতুনটি ঠিক ৯টিতে, শুধু পুরনোটি ঠিক ২টিতে। দুই-পার্শ্বিক exact McNemar-এর p-value কত?

    (ক) SE = √(০.৬ × ০.৪ ÷ ৪০) = √০.০০৬ ≈ ০.০৭৭৫; অর্ধ-প্রস্থ ১.৯৬ × ০.০৭৭৫ ≈ ০.১৫২. অর্থাৎ CI প্রায় [০.৪৫, ০.৭৫]।

    (খ) n = ১.৯৬² × ০.২৪ ÷ ০.০৩² = ০.৯২২ ÷ ০.০০০৯ ≈ ১০২৪.৪, তাই ১০২৫টি প্রশ্ন।

    (গ) শুধু অমিলের ১১টি গোনা হয়। শূন্য অনুমানে প্রতিটি অমিল দুই দিকে সমান সম্ভাবনায় যায়, তাই X ~ Binomial(১১, ০.৫)। P(X ≤ ২) = (১ + ১১ + ৫৫) ÷ ২০৪৮ = ৬৭ ÷ ২০৪৮। দুই-পার্শ্বিক p = ২ × ৬৭ ÷ ২০৪৮ ≈ ০.০৬৫. ৯ বনাম ২ দেখতে বড়, তবু ০.০৫-এর নিচে নয়; প্রশ্ন বাড়াতে হবে।

  2. আউটপুট অনুমান করুন: কোড না চালিয়ে দুটি লাইনের আউটপুট লিখুন।
    import numpy as np
    lp = {"৫০ টাকা": [-1.2, -0.3], "পঞ্চাশ টাকা মাত্র": [-0.9, -0.4, -0.2, -0.1]}
    for rule in ("sum", "mean"):
        f = np.sum if rule == "sum" else np.mean
        s = {k: round(float(f(v)), 2) for k, v in lp.items()}
        print(rule, s, max(s, key=s.get))
    sum {'৫০ টাকা': -1.5, 'পঞ্চাশ টাকা মাত্র': -1.6} ৫০ টাকা
    mean {'৫০ টাকা': -0.75, 'পঞ্চাশ টাকা মাত্র': -0.4} পঞ্চাশ টাকা মাত্র

    যোগফলে চারটি token-এর বিকল্প চারটি ঋণাত্মক সংখ্যা জমায়, তাই −১.৬ সামান্য পিছিয়ে। গড়ে প্রতিটি token-এর গড় দেখা হয়: −০.৭৫ বনাম −০.৪, লম্বাটি জেতে। বাছাইয়ের নিয়ম বদলাতেই রায় উল্টে গেল, model একই।

  3. বিচারক আর মানুষের মিল: ৪০টি উত্তর-জোড়ায় মানুষ আর বিচারক দুজনেই রায় দিলেন। দুজনেই “A ভালো”: ১৮, দুজনেই “B ভালো”: ১২, বিচারক A কিন্তু মানুষ B: ৬, বিচারক B কিন্তু মানুষ A: ৪। Cohen's kappa বের করুন, আর এক লাইনে ব্যাখ্যা দিন।

    po = (১৮ + ১২) ÷ ৪০ = ০.৭৫. বিচারক A বলেছে ২৪ বার (০.৬০), মানুষ A বলেছেন ২২ বার (০.৫৫)। pe = ০.৬০ × ০.৫৫ + ০.৪০ × ০.৪৫ = ০.৩৩ + ০.১৮ = ০.৫১।

    κ = (০.৭৫ − ০.৫১) ÷ (১ − ০.৫১) = ০.২৪ ÷ ০.৪৯ ≈ ০.৪৯. কাঁচা মিল ৭৫% শুনতে ভালো, কিন্তু কাকতালীয় মিল বাদ দিলে মিল মাঝারি। ৪০টি জোড়ায় এই kappa-র CI-ও চওড়া হবে (পর্ব ৪৫-এর মতো bootstrap দিয়ে দেখুন)। এই বিচারককে একা সিদ্ধান্ত দিতে দেওয়া যায় না।

  4. নকশা সমালোচনা করুন: বাজারঘরের একটি দল নতুন chatbot version যাচাইয়ের পরিকল্পনা দিল: “২০টি প্রশ্ন; একই model-কে বিচারক বানিয়ে জিজ্ঞেস করব, ‘Answer A (নতুন) ভালো না B (পুরনো)?’; নতুন version ১২টিতে জিতলে চালু।” অন্তত পাঁচটি সমস্যা আর সমাধান বলুন।
    • সবসময় নতুনটি A: Position bias সরাসরি নতুনের পক্ষে যায়। সমাধান: দুই ক্রম, মিললেই জয়, না মিললে টাই।
    • একই model বিচারক: নিজের লেখার ধাঁচের প্রতি পক্ষপাত থাকতে পারে। সমাধান: ভিন্ন পরিবারের বিচারক বা কয়েকটির ভোট, আর মানুষের সঙ্গে kappa.
    • ২০টি প্রশ্ন: ১২/২০ = ০.৬০-এর CI প্রায় [০.৩৯, ০.৮১], অর্ধেকের নিচেও যায়। সমাধান: প্রশ্ন বাড়ানো, জোড়া পরীক্ষা, আগে থেকে ঠিক করা সীমা।
    • Rubric নেই: “ভালো” মানে নির্ভুল না ভদ্র না ছোট, বিচারক নিজে ঠিক করবে; লম্বা উত্তর জিততে পারে। সমাধান: নিয়ম-ভিত্তিক rubric, reference উত্তর, “দৈর্ঘ্য মান নয়” নির্দেশ, আর দৈর্ঘ্য ধরে ফল ভাগ করে দেখা।
    • শুধু গড় জয়: নিরাপত্তা বা ভুল দাম এক-দুটি হলেও বড় ক্ষতি। সমাধান: আলাদা “কখনো ভুল নয়” set (OTP চাওয়া, দাম, ফেরতের নিয়ম), যেখানে একটিও ভুল চলবে না; পুরনো ঠিক থাকা প্রশ্নে নতুন ভুল হলো কি না, তার তালিকা।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমদুটি chatbot version-কে LLM বিচারক দিয়ে তুলনার ধাপগুলো সাজান (▲▼ চেপে সরান)

  1. প্রশ্নের set আর rubric লিখে রাখা, ফল দেখার আগে
  2. একটি ছোট অংশে মানুষের রায় নিয়ে বিচারকের সঙ্গে মিল (kappa) মাপা
  3. প্রতিটি জোড়া দুই ক্রমে বিচারককে দেখানো
  4. দুই ক্রমে একই রায় হলে জয়, না হলে টাই ধরা
  5. জয়ের হারের সঙ্গে bootstrap CI আর প্রশ্নের সংখ্যা রিপোর্ট করা

একটি multiple-choice প্রশ্নে দুটি বিকল্পের log-likelihood: “চাল” (১ token) −৩.০, আর “সিদ্ধ চাল” (৩ token) −৪.৫. Token প্রতি গড় দিয়ে বাছাই করলে কোনটি জিতবে, আর মোট যোগফলে কোনটি?

  • দুই নিয়মেই “চাল”
  • দুই নিয়মেই “সিদ্ধ চাল”
  • গড়ে “সিদ্ধ চাল”, যোগফলে “চাল”
  • গড়ে “চাল”, যোগফলে “সিদ্ধ চাল”

একটি বিচারক model-কে ৫০টি উত্তর-জোড়া দুই ক্রমে দেখানো হলো। ৩০টি জোড়ায় দুই ক্রমে একই উত্তর জিতল, ২০টিতে দুই ক্রমেই প্রথম অবস্থানের উত্তর জিতল। Swap consistency কত?

  • ১.০০
  • ০.৬০
  • ০.৪০
  • ০.৫০

একটি set-এ model-এর accuracy প্রায় ০.৫. ৯৫% confidence interval-এর মোট প্রস্থ প্রায় ০.১০ (±০.০৫) চাইলে প্রায় কতটি প্রশ্ন লাগবে? (n ≈ ১.৯৬² × p(1 − p) ÷ (প্রস্থের অর্ধেক)²)

  • প্রায় ১০০
  • প্রায় ২০০
  • প্রায় ১৫০০
  • প্রায় ৩৮৫

Benchmark-এর প্রশ্ন model-এর training ডেটায় ঢুকে গেছে কি না, তা যাচাইয়ের একটি যুক্তিসঙ্গত পরীক্ষা কোনটি?

  • সংখ্যা, নাম আর বিকল্পের ক্রম বদলে নম্বর একই থাকে কি না দেখা
  • Model-কে জিজ্ঞেস করা, সে প্রশ্নটি আগে দেখেছে কি না
  • Temperature বাড়িয়ে আবার চালানো
  • একই benchmark-এর আরও প্রশ্ন যোগ করা

বাংলা উত্তরের exact match-এ “১৮০ টাকা।” আর reference “180 টাকা” মেলেনি। সবচেয়ে উপযুক্ত সমাধান কোনটি?

  • Model-কে ভুল ধরে নেওয়া
  • দুই পাশে একই normalisation: অঙ্ক, দাঁড়ি আর ফাঁকা জায়গা
  • শুধু model-এর উত্তর থেকে দাঁড়ি মোছা
  • Exact match বাদ দিয়ে BLEU নেওয়া

দুটি model-এর accuracy একই ১০০টি প্রশ্নে মাপা হলো। পার্থক্যের অনিশ্চয়তা মাপার সবচেয়ে ভালো উপায় কোনটি?

  • দুটি model-এর আলাদা CI একে অপরের ওপর পড়ে কি না দেখা
  • শুধু পার্থক্যটি লেখা
  • একই প্রশ্নের সূচকে জোড়া bootstrap, বা McNemar test
  • দুই model-কে আলাদা আলাদা প্রশ্নে resample করা

Zheng ও সহকর্মীরা (২০২৩) LLM-as-judge-এর যেসব পক্ষপাতের নাম দিয়েছেন, তার মধ্যে কোনটি নেই?

  • Position bias
  • Verbosity bias
  • Self-enhancement bias
  • Tokenizer-length bias

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. আগে ঠিক করুন কী মাপবেন: নির্ভুলতা, faithfulness, নিরাপত্তা, খরচ, latency. ফল একটি সারণি, একটি নম্বর নয়।
  2. Benchmark-এর নম্বর ছাঁচের: contamination, saturation আর উত্তরপত্রের ভুল মাথায় রেখে পড়ুন; ছাঁচ না মিললে তুলনা নয়।
  3. Log-likelihood-এর যোগফল ছোট বিকল্পের দিকে হেলে; দৈর্ঘ্য-ভাগ দিন, আর কোন নিয়ম তা লিখুন। বাংলায় exact match-এর আগে দুই পাশে একই normalisation.
  4. বিচারক model-কে দুই ক্রমে জিজ্ঞেস করুন; swap consistency আর McNemar দিয়ে position bias মাপুন, মানুষের সঙ্গে kappa দিয়ে বিচারককে যাচাই করুন।
  5. প্রতিটি সংখ্যার পাশে n আর CI রাখুন। প্রস্থ অর্ধেক করতে প্রশ্ন চারগুণ; দুই version-এর তুলনা জোড়ায়, আর regression-এ “আগে ঠিক, এখন ভুল” তালিকা।

পরের পর্বে: পর্ব ৫৯-এ ছোট আর দ্রুত model: quantization, distillation আর pruning. ছোট করার পর মান কতটা পড়ল, তা মাপতে এই পর্বের পদ্ধতিই লাগবে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান