অধ্যায় 4 · মজবুত মূল্যায়ন ও LLM-as-a-Judge
মজবুত মেট্রিক ও রিলিজ গেট
- পৃষ্ঠা 8 / 22
- 25 মিনিট পড়া
পুনের প্রিয়া তার হেল্প-সেন্টার অ্যাসিস্ট্যান্টের দুটো ভার্সন তুলনা করছে। নতুনটা ৩১টা প্রশ্নের ২৬টা পাস করে, পুরোনোটা ২৩টা। লিড জিজ্ঞেস করল, "রিলিজ করা যাবে?" "এটা ভালো" কথাটা তিনভাবে ভুল হতে পারে: লাভটা হয়তো কাকতালীয় নয়েজ, গড়ের আড়ালে হয়তো এমন একটা কেস লুকিয়ে আছে যেটা কখনো ফেল করা চলবে না, আর সস্তা-দেখানো সিস্টেম হয়তো অন্য কোথাও মান বিসর্জন দিয়ে সেই সাশ্রয় কিনেছে।
আগের পাতা মূল্যায়নের নকশা করেছে এবং শেষ হয়েছিল "ভার্সন ২ প্রার্থী, বিজয়ী নয়" বলে। এই পাতা মাপজোখকে মজবুত করে সেটাকে সিদ্ধান্তে পরিণত করে: আলাদা আলাদা মেট্রিক, ইন্টারভ্যাল ও স্যাম্পল-সাইজের হিসাব, স্লাইস, বারবার রান, গেমিং চেক, আর এমন একটা রিলিজ গেট যা কারণসহ go, review বা block বলে। সবকিছু অফলাইনে চলে সেই একই ৩১টা কৃত্রিম কেসে, বিগিনারের ফেক মডেল দিয়ে, তাই সংখ্যাগুলো পদ্ধতিটা বর্ণনা করে, কোনো আসল মডেলের আচরণ নয়।
যা শিখবেন
- সাইটেশন সমর্থনকে উপস্থিতি থেকে আলাদা করে, বিরত থাকা ও ভুল-উত্তরের হার এবং টুল-ব্যবহার কীভাবে মাপবেন, আর রিগ্রেশন ও অ্যাডভার্সারিয়াল স্যুট কীভাবে আলাদা রাখবেন।
- ৩১টা কেসে অনিশ্চয়তা কীভাবে জানাবেন: ইন্টারভ্যাল, জোড়া-তুলনা, ক্ষুদ্রতম শনাক্তযোগ্য পার্থক্য, একাধিক তুলনার সততা, আর অস্থির সিস্টেমের বারবার রান।
- ছোট-n গার্ডসহ স্লাইস কীভাবে পড়বেন, এবং মেট্রিকের অমিল ও গেমিং কীভাবে ধরবেন।
- রিলিজ গেট কীভাবে লিখবেন (ফ্লোর, সিলিং, নো-রিগ্রেস মার্জিন, স্লাইস নিয়ম, ক্রিটিক্যাল কেস, রিভিউ ব্যান্ড), ফলাফল কিসের ওপর মাপা তা কীভাবে ভার্সন করবেন, জাজের প্রমাণ নিরাপদে কীভাবে রাখবেন, আর সবটা একটা CI কমান্ডে কীভাবে চালাবেন।
কোড চালান kit/ থাকা ফোল্ডার থেকে, export PYTHONPATH=kit দিয়ে; ব্লকগুলো একটা নেমস্পেস ভাগ করে। পাতাটা দাঁড়িয়ে আছে মূল্যায়নের নকশা ও জাজ ক্যালিব্রেশন (৩১টা কেস, পরিকল্পনা, জাজ), রুব্রিক, রিগ্রেশন ও রিলিজ গেট (তার গেটের মেট্রিক-তালিকা স্থির; আমাদেরটা সাধারণ, আর ওটাকে ছোঁয় না) এবং রিরাংকিং ও রিট্রিভাল মূল্যায়ন-এর বুটস্ট্র্যাপ ও সাইন টেস্টের ওপর।
একটা রান, অনেক মেট্রিক
run_report(config) ৩১টা কেস বিগিনারের RAG পাইপলাইনে চালায় এবং একটা Report ফেরত দেয়: প্রতিটা হার একটা Count (n-এর মধ্যে k-টা, সঙ্গে Wilson ইন্টারভ্যাল, যা ০ থেকে ১-এর ভেতরে থাকে ও ছোট n-এও ঠিক আচরণ করে), প্রতি কেসে একটা পাস-পতাকা, মান থেকে আলাদা রাখা খরচ ও লেটেন্সি, আর ভার্সনের একটা ম্যানিফেস্ট। "কড়া পাস" মানে: উত্তরযোগ্য কেসে প্রত্যাশিত প্রতিটা ফ্যাক্টসহ উত্তর লাগে, আর উত্তরহীন কেসে বিরত থাকা লাগে; আগের পাতার নিয়ম ফ্যাক্ট পড়ত না, তাই তার ২৫ ও ২৭ এখানে ২৩ ও ২৬ হয়েছে।
from evidencedesk.evalkit.suite import BASELINE, CANDIDATE, Config, run_report
old, new = run_report(BASELINE), run_report(CANDIDATE) # version 1 and version 2 of the previous page
print("dataset", new.manifest.dataset, "| cases", len(new.passed), "| strict passes", sum(old.passed.values()), "->", sum(new.passed.values()))
print(f"{'measure':<20}{'old':>7}{'new':>7} new rate, 95% interval")
for name in ("answer_correct", "false_abstain_rate", "abstain_correct", "false_answer_rate", "citation_present", "citation_support", "recall_at_3"):
a, b = old.counts[name], new.counts[name]
print(f"{name:<20}{f'{a.k}/{a.n}':>7}{f'{b.k}/{b.n}':>7} {b.rate} {b.interval}")dataset 8a2bc76d5867 | cases 31 | strict passes 23 -> 26
measure old new new rate, 95% interval
answer_correct 20/25 22/25 0.88 (0.7, 0.958)
false_abstain_rate 2/25 1/25 0.04 (0.007, 0.195)
abstain_correct 3/6 4/6 0.667 (0.3, 0.903)
false_answer_rate 3/6 2/6 0.333 (0.097, 0.7)
citation_present 23/23 24/24 1.0 (0.862, 1.0)
citation_support 21/21 23/23 1.0 (0.857, 1.0)
recall_at_3 24/25 24/25 0.96 (0.805, 0.993)ভার্সন ২ ২৫টার মধ্যে ২২টায় সঠিক উত্তর দেয় (আগে ২০), আর ৬টা উত্তরহীন প্রশ্নের ৪টায় বিরত থাকে (আগে ৩); বিরত থাকার মাপ ছয়টা প্রশ্নের ওপর দাঁড়িয়ে, তাই তার ইন্টারভ্যাল ০.৩০ থেকে ০.৯০ পর্যন্ত।
সাইটেশন সমর্থনকে সাইটেশন উপস্থিতি থেকে আলাদা করে মাপুন
উপস্থিতি জিজ্ঞেস করে "এমন একটা সোর্স আইডি আছে কি, যেটা মেলে?" বিগিনারের গার্ড সেটা আগেই বাধ্য করে। সমর্থন জিজ্ঞেস করে "উদ্ধৃত টেক্সট কি দাবিটাকে সমর্থন করে?" এখানে নিয়ম: উত্তর যে প্রত্যাশিত ফ্যাক্টগুলো বলে, প্রতিটা কোনো উদ্ধৃত চাঙ্কে থাকতে হবে। স্ট্রিং থাকা-না-থাকার (containment) হিসাব একটা আনুমানিক মাপ; প্যারাফ্রেজ-করা দাবির জন্য মানুষ বা ক্যালিব্রেট-করা জাজ লাগে। ফেক মডেল সবচেয়ে ওপরের চাঙ্ক কপি করে আর তাকেই উদ্ধৃত করে, তাই সমর্থন সহজেই নিখুঁত দেখায়। miscite প্রতিটা বাক্য রেখে দেয় কিন্তু পাশের একটা রিট্রিভ-করা চাঙ্ককে উদ্ধৃত করে: আইডি আসল, মডেলকে দেখানো হয়েছে, কিন্তু টেক্সট দাবিটাকে সমর্থন করে না।
# file: evidencedesk/evalkit/measures.py (excerpt)
def miscite(observed: list[Observed]) -> list[Observed]:
"""A model that writes the right sentence but cites a neighbour: the id is real and was shown, the text does not hold the claim."""
def swap(o: Observed) -> Observed:
other = [i for i in o.answer.retrieved_ids if i not in o.cited]
return replace(o, cited=(other[0],)) if o.answered and other else o
return [swap(o) for o in observed]from pathlib import Path
from evidencedesk.evalkit.dataset import load_cases
from evidencedesk.evalkit.measures import measure, miscite, observe
from evidencedesk.evals import CaseModel, build_retriever
cases = load_cases()
obs = observe(cases, build_retriever(Path("kit/corpus"), "sentence", 0.3), CaseModel({}), 0.05)
shifted = miscite(obs) # same sentences, but each cites the next retrieved chunk
g01, g01_shifted = next(o for o in obs if o.case.id == "g01"), next(o for o in shifted if o.case.id == "g01")
print("g01 cites", g01.cited, "->", g01_shifted.cited)
for label, run in (("as written", obs), ("neighbour cited", shifted)):
m = measure(run)
print(f"{label:<16}" + " ".join(f"{k} {m[k].k}/{m[k].n}" for k in ("citation_present", "citation_support", "answer_correct")))g01 cites ('kc-returns-v3:c0',) -> ('kc-returns-v3:c1',)
as written citation_present 24/24 citation_support 23/23 answer_correct 22/25
neighbour cited citation_present 24/24 citation_support 0/23 answer_correct 22/25উপস্থিতি ২৪-এ ২৪ থাকে, সমর্থন নেমে ২৩-এ ০ হয়, আর উত্তরের সঠিকতা একটুও নড়ে না। যে ড্যাশবোর্ড শুধু "সাইটেশন বৈধ" জানায়, সে দুটো সিস্টেমকেই নিখুঁত বলে; এ কারণেই পরিকল্পনায় citation_support আলাদা মেট্রিক।
বিরত থাকা ও ভুল-উত্তরের হার মাপুন
বিরত থাকায় দুটো ভুল আছে, দাম আলাদা। ভুল উত্তর (উত্তরহীন প্রশ্নের উত্তর দেওয়া) একটা প্রতিশ্রুতি বানিয়ে ফেলে; ভুল বিরতি (উত্তরযোগ্য প্রশ্ন ফিরিয়ে দেওয়া) একটা সাপোর্ট টিকিটের খরচ ঘটায়। এগুলো আলাদা মেট্রিক, আলাদা প্রশ্নের ওপর (৬টা উত্তরহীন, ২৫টা উত্তরযোগ্য), আর পরিকল্পনা প্রথমটাকে "high" ধরে। অ্যাডভার্সারিয়াল সেটেই ভুল উত্তরগুলো লুকিয়ে থাকে:
for o in obs:
if o.case.expect == "abstain" and o.answered:
print(o.case.id, "| asked:", o.case.question, "| answered from", o.cited[0], "|", o.case.why)
for expect in ("answer", "abstain"):
rows = [o for o in obs if o.case.expect == expect]
print(f"{expect:<8} answered {sum(o.answered for o in rows):>2} abstained {sum(not o.answered for o in rows):>2}")a02 | asked: What is the warranty on electric motors? | answered from kc-warranty-v2:c0 | Shares words with the warranty page but the topic is absent.
a05 | asked: How do I return a bike to a shop in Brazil? | answered from kc-returns-v3:c0 | Plausible, but the documents do not name the country.
answer answered 24 abstained 1
abstain answered 2 abstained 4দুটোই "শব্দ মেলে, ফ্যাক্ট নেই" ধরনের। ভালো প্রম্পটে এর সমাধান হয় না; লাগে কড়া প্রমাণ-নিয়ম, যার দাম উত্তরযোগ্য প্রশ্ন, যেমন নিচের গ্রিড দেখাবে।
টুল-ব্যবহার মূল্যায়নের কেস
টুল-ব্যবহারে চারটা চেক আলাদা রাখতে হয়: নির্বাচন (সঠিক টুল, বা কোনোটাই নয়), আর্গুমেন্ট, অনুমতি (কাজের অ্যালাউলিস্টর বাইরের টুল চলবে না) এবং সম্পন্ন হওয়া (প্রত্যাশিত ডেটা ফিরেছে)। tool_cases_v1.jsonl-এ আটটা কেস আছে, প্রতিটায় মডেল কী "প্রস্তাব করেছে" তা লেখা, তাই কোনো মডেল ডাকা হয় না; কলগুলো চলে বিগিনারের রেজিস্ট্রির ওপর, অ্যালাউলিস্টর একটা ভিউয়ের ভেতর দিয়ে।
from evidencedesk.evalkit.tooluse import full_registry, load_tool_cases, run_tool_case, tool_measures
tools = load_tool_cases()
print(f"{'case':<5}{'selection':>10}{'args':>6}{'permission':>11}{'complete':>9} note")
show = lambda v: "-" if v is None else "ok" if v else "FAIL"
for c in tools:
r = run_tool_case(c, full_registry())
print(f"{c.id:<5}{show(r['selection']):>10}{show(r['args']):>6}{show(r['permission']):>11}{show(r['complete']):>9} {c.note[:34]}")
print({k: f"{v.k}/{v.n}" for k, v in tool_measures(tools).items()})case selection args permission complete note
t01 ok ok - ok right tool, right arguments
t02 ok FAIL - ok same sum written in another order:
t03 ok FAIL - FAIL wrong arguments that the tool itse
t04 ok ok - ok right tool, right arguments
t05 FAIL - ok - wrong tool, and it is not allowed
t06 FAIL - ok - a greeting needs no tool; calling
t07 ok - - - correctly calls nothing
t08 ok ok - ok an id that does not exist: the too
{'tool_selection': '6/8', 'tool_args': '3/5', 'tool_permission': '2/2', 'tool_complete': '4/5'}t02 একই যোগফল উল্টো ক্রমে লেখে: হুবহু মিল একে ভুল ধরে যদিও কল সম্পন্ন হয়, তাই আর্গুমেন্ট গেটে বসানোর আগে নরমালাইজ করুন। t03 অদ্ভুত একটা হিসাব চায়, টুল তা ফিরিয়ে দেয়। t05 ও t06 ভুল বা অপ্রয়োজনীয় কল, অ্যালাউলিস্ট সেগুলো থামায়, তাই অনুমতি ২-এ ২। যে নিষিদ্ধ কল চলে গেছে তা নিরাপত্তা-ঘটনা; নির্বাচনের ভুল তা নয়।
রিগ্রেশন ও অ্যাডভার্সারিয়াল স্যুট
set ফিল্ড দুটো স্যুট দেয়, কাজ আলাদা। রিগ্রেশন স্যুট (গোল্ডেন ও প্রোডাকশন-ঘেঁষা, ২৪টা কেস) প্রত্যাশিত আচরণ ধরে: আগে পাস করত, এখন ফেল করছে, এমন প্রতিটা উল্টে-যাওয়া কেস একটা ফাইন্ডিং, গড় যা-ই বলুক। অ্যাডভার্সারিয়াল স্যুট (৭টা শত্রুভাবাপন্ন বা চালাকির ইনপুট) চাপ মাপার যন্ত্র: এটা ১০০% হবে না, তাই এর হার ও ক্রিটিক্যাল কেস ট্র্যাক করুন, আর প্রোডাকশন নতুন কৌশল শেখালে একটা কেস যোগ করুন। দুটোর গড় কখনো করবেন না; বাড়তে থাকা অ্যাডভার্সারিয়াল স্যুট সুস্থ সিস্টেমকেও খারাপ দেখাবে।
from evidencedesk.evalkit.slices import by_suite
for name, report in (("old", old), ("new", new)):
print(name, {k: f"{v.k}/{v.n}" for k, v in by_suite(report.passed, cases).items()})
print("failing in new, by set:", {s: sorted(c.id for c in cases if c.set == s and not new.passed[c.id]) for s in ("golden", "production_like", "adversarial")})old {'regression': '19/24', 'adversarial': '4/7'}
new {'regression': '21/24', 'adversarial': '5/7'}
failing in new, by set: {'golden': ['g08'], 'production_like': ['p05', 'p08'], 'adversarial': ['a02', 'a05']}অ্যাডভার্সারিয়াল ব্যর্থতা (a02, a05) ওপরের ভুল উত্তরগুলোই; রিগ্রেশনের ব্যর্থতাগুলো পুরোনো।
ছোট ডেটাসেটে পরিসংখ্যানগত সতর্কতা
একত্রিশটা কেস স্মোক টেস্ট, বেঞ্চমার্ক নয়। প্রতিটা সংখ্যার সঙ্গে তার n ও অনিশ্চয়তা লাগে। জোড়া-তুলনা দুই সিস্টেমে একই প্রশ্নগুলো দেখে, আর শুধু যে প্রশ্নগুলো বদলেছে সেগুলোই তথ্য বহন করে।
from evidencedesk.evalkit.stats import Count, flips_needed, min_detectable, paired_diff_ci, sample_size
from evidencedesk.retrieval.metrics import paired
ids = sorted(new.passed)
a, b = [float(old.passed[i]) for i in ids], [float(new.passed[i]) for i in ids]
d = paired(dict(zip(ids, a)), dict(zip(ids, b)))
print("old", Count(k=int(sum(a)), n=31).text())
print("new", Count(k=int(sum(b)), n=31).text())
print("better", list(d.better), "worse", list(d.worse), "unchanged", d.tied, "sign-test p", d.p)
print("difference in pass rate", round((sum(b) - sum(a)) / 31, 3), "95% interval", paired_diff_ci(a, b))
print("changed cases needed, all one way, for p < 0.05:", flips_needed())
print("smallest true difference found 8 times in 10, by cases per group:", " ".join(f"{n}: {min_detectable(n):.2f}" for n in (31, 100, 300, 1000)))
print("cases per group needed to detect 0.10:", sample_size(0.10), "| 0.05:", sample_size(0.05))old 0.742 (23/31, 95% 0.568-0.863)
new 0.839 (26/31, 95% 0.674-0.929)
better ['a06', 'p02', 'p09'] worse [] unchanged 28 sign-test p 0.25
difference in pass rate 0.097 95% interval (0.0, 0.226)
changed cases needed, all one way, for p < 0.05: 6
smallest true difference found 8 times in 10, by cases per group: 31: 0.28 100: 0.16 300: 0.09 1000: 0.05
cases per group needed to detect 0.10: 251 | 0.05: 1004ভার্সন ২ তিনটা প্রশ্ন ঠিক করেছে, ভেঙেছে একটাও না: উৎসাহব্যঞ্জক, কিন্তু সাইন টেস্টে p = ০.২৫, কারণ ন্যায্য মুদ্রায় একই দিকে তিনটা উল্টানো চার ভাগের একভাগ বার ঘটে, আর লাভের ইন্টারভ্যাল শূন্য থেকে শুরু। একটাও রিগ্রেশন না থাকলেও p ০.০৫-এর নিচে যেতে ছয়টা বদলানো প্রশ্ন লাগে। ব্লকের শেষ দুই লাইন বলে ৩১টা কেস কত বড় পার্থক্য কখনো দেখাতে পারে: প্রতি গ্রুপে ৩১টা কেসের দুটো জোড়াহীন গ্রুপ প্রায় ২৮ পয়েন্টের নিচের যেকোনো সত্যিকারের পার্থক্য মিস করে (সিস্টেম দুটো বেশির ভাগ মিললে জোড়া-টেস্ট ভালো করে, তবে গুনবার মতো উল্টানো থাকলে তবেই); পাঁচ পয়েন্ট ধরতে সিস্টেম প্রতি প্রায় এক হাজার কেস লাগে। এই মাপের সেটে দেখা বেশির ভাগ পার্থক্য নয়েজ। এখানে গেট বড় পতন, ভাঙা ক্রিটিক্যাল কেস আর প্রতারণা ধরতে পারে; ছোট লাভ প্রমাণ করতে পারে না।
একাধিক তুলনা
একই ৩১টা কেসে আটচল্লিশটা সেটিং (দুই চাঙ্কিং, ছয় স্কোর-ফ্লোর, চার মার্জিন) চালিয়ে সেরাটা বাছলে আপনি আটচল্লিশটা টেস্ট চালিয়েছেন; কুড়িটার মধ্যে অন্তত একটা কাকতালে ৫% স্তরে তাৎপর্যপূর্ণ দেখানোর সম্ভাবনা ৬৪%। Holm-এর সংশোধন সবচেয়ে ছোট p-কে আলফা ÷ টেস্ট-সংখ্যার সঙ্গে মেলায়, পরেরটাকে আলফা ÷ (টেস্ট-সংখ্যা - ১)-এর সঙ্গে, আর প্রথম ব্যর্থতায় থামে। নিয়ন্ত্রণ হিসেবে, ইচ্ছাকৃতভাবে অস্থির একটা সিস্টেমের (৪০% পার্স-অযোগ্য রিপ্লাই) কুড়িটা অবিকল প্রতিলিপির প্রতিটাকে সেই সিস্টেমেরই একটা রেফারেন্স রানের সঙ্গে মেলানো হয়।
import itertools
from evidencedesk.evalkit.flaky import FlakyClient
from evidencedesk.evalkit.stats import family_error, holm
from evidencedesk.evals import CaseModel
flat = lambda passed: {i: float(ok) for i, ok in passed.items()}
noisy = lambda seed: run_report(Config("sentence", 0.35, 0.05), client=FlakyClient(CaseModel({}), 0.4, seed)).passed
grid = {} # 48 settings tried against the same 31 cases
for chunking, score, margin in itertools.product(("paragraph", "sentence"), (0.2, 0.3, 0.35, 0.4, 0.45, 0.5), (0.0, 0.05, 0.1, 0.15)):
r = run_report(Config(chunking, score, margin))
grid[f"{chunking[:4]}-{score}-{margin}"] = (sum(r.passed.values()), paired(flat(old.passed), flat(r.passed)))
best = max(grid, key=lambda k: grid[k][0])
d = grid[best][1]
print(f"best of {len(grid)}: {best} passes {grid[best][0]}/31 (old {sum(old.passed.values())}); better {len(d.better)} worse {len(d.worse)} p {d.p}")
print("p < 0.05 before correction:", sum(g[1].p < 0.05 for g in grid.values()), "| after Holm:", sum(holm({k: g[1].p for k, g in grid.items()}).values()))
ref = noisy(1000) # twenty copies of the SAME system, each compared with one reference run
p = {f"copy{s:02d}": paired(flat(ref), flat(noisy(2000 + s))).p for s in range(20)}
print("identical systems that look different (p < 0.05):", sum(v < 0.05 for v in p.values()), "of 20 | after Holm:", sum(holm(p).values()), "| chance of at least one:", family_error(20))best of 48: sent-0.35-0.05 passes 27/31 (old 23); better 4 worse 0 p 0.125
p < 0.05 before correction: 0 | after Holm: 0
identical systems that look different (p < 0.05): 1 of 20 | after Holm: 0 | chance of at least one: 0.642গ্রিডের "বিজয়ী" ৩১-এর ২৭টা পাস করে (আগে ২৩), চারটায় ভালো, একটাতেও খারাপ নয়, কিন্তু p = ০.১২৫: সংশোধনের আগেই তাৎপর্যপূর্ণ নয়। নিয়ন্ত্রণে কুড়িটা অভিন্ন সিস্টেমের একটা "তাৎপর্যপূর্ণ" ভিন্ন দেখায়, আর Holm সেটা সরিয়ে দেয়। সুইপ ব্যবহার করুন প্রার্থী প্রস্তাব করতে, তারপর সেই প্রার্থীকে বিচার করুন সুইপ কখনো দেখেনি এমন কেসে (আগের পাতার হোল্ডআউট, একবার পড়া)।
অস্থির ও অনির্ধারিত মূল্যায়ন
আসল কল temperature 0-তেও বদলায়, তাই একটা রান মানে একটা নমুনা। রান বারবার চালান, গড় ও বিস্তার জানান, আর যে কেসগুলো রানের মধ্যে উল্টায় সেগুলোর তালিকা দিন। FlakyClient ফেকটাকে ইচ্ছে করে অস্থির করে (১০% পার্স-অযোগ্য রিপ্লাই, সিড-করা), যাতে প্রভাবটা চোখে পড়ে ও পুনরাবৃত্তিযোগ্য থাকে।
from evidencedesk.evalkit.flaky import repeat_summary
runs = [run_report(Config("sentence", 0.35, 0.05), client=FlakyClient(CaseModel({}), 0.1, seed)).passed for seed in range(10)]
s = repeat_summary(runs)
print({k: s[k] for k in ("runs", "mean", "sd", "min", "max", "stable_pass")}, "always failing:", s["stable_fail"])
print(len(s["flaky"]), "flaky cases, the least stable:", {k: v for k, v in s["flaky"].items() if int(v[0]) <= 7}){'runs': 10, 'mean': 0.787, 'sd': 0.053, 'min': 0.71, 'max': 0.871, 'stable_pass': 12} always failing: ['a05', 'g08', 'p05', 'p08']
15 flaky cases, the least stable: {'g05': '6/10', 'g07': '7/10'}একই সিস্টেম দশ রানে ০.৭১ থেকে ০.৮৭-এর মধ্যে পাস করে। ভার্সন ২-এর ১০ পয়েন্টের লাভ সেই ১৬ পয়েন্টের পরিসরের চেয়ে ছোট, তাই এতটা অস্থির সিস্টেমে এক রানের লাভের মানে সামান্য। ক্রিটিক্যাল কেস g05 দশ রানের মাত্র ৬টায় পাস করে: অস্থির ক্রিটিক্যাল কেস সারান, কোয়ারেন্টিনে রাখবেন না। অস্থির নন-ক্রিটিক্যাল কেস ব্লকিং নিয়ম থেকে বাদ দেওয়া যায়, তবে রিপোর্টে রাখুন, মালিক ও তারিখসহ। নয়েজ প্রতিসম নয়: পার্স-অযোগ্য রিপ্লাই একটা উত্তরহীন কেসকে দৈবাৎ পাস করিয়ে দেয়।
প্রোভাইডার নোট (২০২৬-১০-১২ অনুযায়ী): IR পাঠ্যবইয়ে precision at k-কে বলা হয়েছে "the least stable of the commonly used evaluation measures", আর NIST-এর TREC QA পাতা বলে MRR "no credit for multiple correct answers" দেয়। বইয়ের nDCG gain 2^rel - 1 ও log2(1 + rank) ডিসকাউন্ট ব্যবহার করে; অন্য বাস্তবায়ন ভিন্ন, তাই লাইব্রেরিভেদে সংখ্যা মেলানোর আগে ভ্যারিয়েন্ট দেখে নিন। জাজ-ভিত্তিক মেট্রিক আগের পাতার নোটের পক্ষপাতগুলো সঙ্গে নিয়ে আসে; আজকের মডেলের জন্য জাজ-মানুষ সম্মতির কোনো বর্তমান সংখ্যা কোনো প্রাথমিক সূত্রের পাতায় পাওয়া যায়নি। বর্তমান ডকুমেন্টেশন ও পেপারগুলো দেখে নিন।
স্লাইস অনুযায়ী পারফরম্যান্স ট্র্যাক করুন
গড় ব্যর্থ গ্রুপটাকে লুকায়, কিন্তু প্রতিটা স্লাইস আরেকটা তুলনা, তাই ছোট সেটে ভুতুড়ে সমস্যা দেখা দেয়। judge_slices প্রতিটা স্লাইস দেখায়, কিন্তু বিচার করে শুধু অন্তত ৮টা কেসের স্লাইসের, এবং স্লাইসকে "low" বলে কেবল তার ইন্টারভ্যালের ওপরের প্রান্ত সামগ্রিক হারের নিচে থাকলে ("weak", যদি শুধু ১৫ পয়েন্ট নিচে হয়)।
from evidencedesk.evalkit.slices import judge_slices
from evidencedesk.evalkit.stats import Count
overall = Count(k=sum(new.passed.values()), n=len(new.passed))
rows = judge_slices(new.slices, overall)
print("overall", overall.rate, "| judged slices:")
for r in rows:
if r["status"] != "few":
print(f" {r['slice']:<22}{r['k']:>3}/{r['n']:<3} {r['rate']} {r['interval']} {r['status']}")
print("not judged (n < 8):", ", ".join(f"{r['slice']} {r['k']}/{r['n']}" for r in rows if r["status"] == "few"))overall 0.839 | judged slices:
lang=en 23/28 0.821 (0.644, 0.921) ok
task=lookup 12/12 1.0 (0.757, 1.0) ok
set=golden 13/14 0.929 (0.685, 0.987) ok
set=production_like 8/10 0.8 (0.49, 0.943) ok
not judged (n < 8): lang=bn 3/3, task=exact_term 3/3, task=multi_fact 5/6, task=paraphrase 2/4, task=unanswerable 4/6, set=adversarial 5/7মাত্র চারটা স্লাইস বিচারযোগ্য। বাংলা (৩টা কেস), হুবহু টার্ম (৩), প্যারাফ্রেজ (৪) ও উত্তরহীন (৬) ঠিক সেখানেই ঝুঁকি, আর সেটটা সেখানে অন্ধ। এটাই একটা ফাইন্ডিং: ওই স্লাইসগুলোতে আরও কেস সংগ্রহ করুন, গার্ড ঢিলে করবেন না।
মেট্রিকের অমিল ও গেমিং
একটা মেট্রিক ভালো আর আরেকটা খারাপ হলে সেটা মানুষের সিদ্ধান্তের বিষয়, লুকানোর ভুল নয়। একটামাত্র সংখ্যা অপ্টিমাইজ করা সিস্টেম বা দলকে প্রতারণাতেও টানে (Goodhart-এর সূত্র: যে মাপ লক্ষ্য হয়ে যায়, সে ভালো মাপ থাকে না)। প্রতিটা গেম-করা-যায় এমন মেট্রিকের সঙ্গে একটা পাল্টা-মেট্রিক জুড়ুন (বিরত থাকা ও উত্তরের সঠিকতা, সাইটেশন উপস্থিতি ও সমর্থন), আর গেটকে ইচ্ছে করে প্রতারণা করা সিস্টেম দিয়ে পরীক্ষা করুন।
from evidencedesk.evalkit.goodhart import disagreements, gaming_flags
harsh = run_report(Config("sentence", 0.5, 0.05))
print("version 2 -> min_score 0.5:", [d for d in disagreements(new.counts, harsh.counts) if "_rate" not in d])
trivial = {"always abstains": run_report(Config("sentence", 9.0, 0.0)), "never abstains": run_report(Config("sentence", 0.0, -1.0))}
for name, r in trivial.items():
c = r.counts
print(f"{name:<16} strict passes {sum(r.passed.values()):>2}/31 answer_correct {c['answer_correct'].rate} abstain_correct {c['abstain_correct'].rate}", gaming_flags(new.counts, c))version 2 -> min_score 0.5: ['abstain_correct +0.33 but answer_correct -0.20']
always abstains strict passes 6/31 answer_correct 0.0 abstain_correct 1.0 ['abstain_correct rose +0.33 while answer_correct fell -0.88']
never abstains strict passes 22/31 answer_correct 0.88 abstain_correct 0.0 []স্কোর-ফ্লোর ০.৫ করলে বিরত থাকা ৩৩ পয়েন্ট ভালো হয় আর উত্তরের সঠিকতা ২০ পয়েন্ট কমে: অমিল, চিহ্নিত। সবসময়-বিরত সিস্টেমের বিরত থাকার হার নিখুঁত, আর gaming_flags তাকে ধরে। কখনো-বিরত-নয় সিস্টেম ভার্সন ২-এর উত্তরের সঠিকতার সঙ্গে হুবহু মেলে (০.৮৮), তাই ওই মেট্রিক একা যত্নশীল সিস্টেম আর যে কখনো ফিরিয়ে দেয় না তার মধ্যে ফারাক করতে পারে না। এই দুই তুচ্ছ সিস্টেমের যেকোনোটা পাস করলে গেট ভাঙা।
রিলিজের জন্য মানের গেট
গেট মাপজোখকে go, review বা block-এ পরিণত করে। গড়ের ওপর দাঁড়ানো গেট দুইভাবে ব্যর্থ হয়: গড় বাড়তে থাকলেও একটা ক্রিটিক্যাল কেস ফেল করতে পারে, আর ছোট স্যাম্পলে ফ্লোর প্রমাণ করা যায় না। নীতিটা ডেটা (release_policy_v1.json), নিয়মগুলো এই:
- ক্রিটিক্যাল কেস (চারটা আইডি): গড় যা-ই হোক, একটা ব্যর্থতা বা অনুপস্থিতিই block।
- ফ্লোর ও সিলিং প্রতিটা মেট্রিকে, একটা অ্যাকশনসহ, মানের কাউন্ট এবং খরচ বা লেটেন্সির জন্য সমানভাবে; মাপা হয়নি এমন মেট্রিক নীরবে পাস হয় না।
- রিভিউ ব্যান্ড: ফ্লোরের ওপরে
band-এর মধ্যে থাকা মান পয়েন্ট-অনুমানে পাস করে, কিন্তু মানুষের কাছে যায়। ফ্লোর যোগ ব্যান্ড = পরিকল্পনার থ্রেশহোল্ড (একটা টেস্ট আগের পাতার পরিকল্পনার বিরুদ্ধে তা মেলায়)। - বেসলাইনের বিরুদ্ধে নো-রিগ্রেস মার্জিন, আর বেসলাইনে পাস করত কিন্তু এখন ফেল করছে এমন প্রতিটা কেসের রিভিউ-তালিকা।
- বিচারযোগ্য স্লাইসের জন্য স্লাইস নিয়ম; বিচারের অযোগ্যগুলো নোট হয়। নোট কখনো রায় বদলায় না: পয়েন্ট-অনুমানে ফ্লোর পূরণ হয়েছে অথচ ইন্টারভ্যাল তার নিচ থেকে শুরু, এমন হলে "unproven" চিহ্ন পড়ে।
সংরক্ষিত বেসলাইনের (ভার্সন ১) বিরুদ্ধে তিনটা প্রার্থী: ভার্সন ২, গ্রিডের বিজয়ী (স্কোর-ফ্লোর ০.৩৫) ও একটা কড়া সেটিং (০.৫)।
from evidencedesk.evalkit.gates import evaluate, load_policy
policy = load_policy()
candidates = {"old (v1)": BASELINE, "new (v2)": CANDIDATE, "score 0.35": Config("sentence", 0.35, 0.05), "score 0.5": Config("sentence", 0.5, 0.05)}
reports = {name: run_report(cfg) for name, cfg in candidates.items()}
for name, r in list(reports.items())[1:]: # the baseline is not judged against itself
d = evaluate(policy, r, old)
print(f"{name:<11}{d.verdict.upper()}")
for x in d.reasons:
if x.level != "note":
print(f" {x.level:<7}{x.rule}: {x.text}")
print("trivial systems:", {n: evaluate(policy, r, old).verdict for n, r in trivial.items()})new (v2) BLOCK
block critical: case a02 failed
block floor: abstain_correct 0.667 (4/6) is below the floor 0.8
score 0.35 REVIEW
review band: abstain_correct 0.833 (5/6) is inside the review band above the floor 0.8
score 0.5 BLOCK
block critical: case g05 failed
block floor: answer_correct 0.68 (17/25) is below the floor 0.8
block regress: answer_correct fell from 0.8 to 0.68 (allowed drop 0.05)
review flips: 3 cases passed in the baseline and fail now: a01, g07, p06
trivial systems: {'always abstains': 'block', 'never abstains': 'block'}ভার্সন ২ গড় বাড়ায়, তবু block হয় বিরত থাকার ফ্লোরে এবং ক্রিটিক্যাল কেস a02-তে। ০.৩৫ সেটিং block হয় না: ৬-এর ৫-এ বিরত থাকা রিভিউ ব্যান্ডে পড়ে, তাই নোটসহ একজন মানুষ সিদ্ধান্ত নেন। কড়া সেটিংয়ের বিরত থাকা সেরা (৬-এ ৬), অথচ সে একই সঙ্গে ক্রিটিক্যাল রিট্রিভাল কেস, উত্তরের ফ্লোর ও নো-রিগ্রেস নিয়মে block হয়। দুই তুচ্ছ সিস্টেমই block। ব্যান্ড হলো সৎ মাঝামাঝি: "সংখ্যায় পাস, কিন্তু সংখ্যা পাতলা"।
গেট কী প্রমাণ করতে পারে, কী পারে না
২৫টা কেসে সে ফ্লোর প্রমাণ করতে পারে না। দশগুণ কেসে একই হার (বোঝানোর হিসাব, মাপজোখ নয়) দেখায় প্রমাণের জন্য কী লাগে:
from evidencedesk.evalkit.stats import Count
for times in (1, 10):
c = Count(k=22 * times, n=25 * times)
print(f"answer_correct 0.88 from {c.n} cases: interval {c.interval} -> floor 0.8 proven: {c.interval[0] >= 0.8}")answer_correct 0.88 from 25 cases: interval (0.7, 0.958) -> floor 0.8 proven: False
answer_correct 0.88 from 250 cases: interval (0.834, 0.915) -> floor 0.8 proven: Trueএ কারণেই অপ্রমাণিত ফ্লোর নীরব পাস না হয়ে নোট বা ব্যান্ড হয়, আর প্রতিটা রায় n জানায়।
প্রম্পট, মডেল ও রিট্রিভাল সেটিং ভার্সন করুন
প্রতিটা রিপোর্টের Manifest-এ থাকে ডেটাসেট হ্যাশ, টুল-কেস সেট, মেট্রিক ভার্সন, উত্তর-প্রম্পটের হ্যাশ, মডেলের নাম, রিট্রিভাল সেটিং, আর কোনো সংখ্যা জাজ বানালে জাজ প্রম্পটের হ্যাশ; তার sha পুরো কনফিগারেশনের নাম।
from evidencedesk.evalkit.versions import changed, not_comparable
mid = reports["score 0.35"]
m = mid.manifest
print("manifest", m.sha, "| dataset", m.dataset, "| prompt", m.prompt_sha, "| model", m.model, "| retrieval", m.retrieval)
print("changed against the baseline:", changed(old.manifest, m))
print("numbers comparable:", not_comparable(old.manifest, m) == [], "| after editing one case:", not_comparable(old.manifest, m.model_copy(update={"dataset": "0123456789ab"})))manifest 5cb05a77bd | dataset 8a2bc76d5867 | prompt c3bcffe3e0 | model fake-case-model | retrieval {'chunking': 'sentence', 'min_score': 0.35, 'min_margin': 0.05, 'k': 3}
changed against the baseline: {'retrieval.chunking': ('paragraph', 'sentence'), 'retrieval.min_score': (0.2, 0.35)}
numbers comparable: True | after editing one case: ['dataset differs (8a2bc76d5867 vs 0123456789ab)']পার্থক্য দুই ধরনের। প্রম্পট, মডেল ও রিট্রিভাল সেটিং আপনি যা পরীক্ষা করছেন (changed সেগুলো তালিকা করে)। ডেটাসেট, টুল-কেস ও মেট্রিকের সংজ্ঞা যা দিয়ে আপনি মাপছেন: সেগুলো আলাদা হলে not_comparable তা জানায় আর গেট রানটাকে review-তে পাঠায়। বেসলাইন মানে একটা সংরক্ষিত রিপোর্ট, সঙ্গে একটা কারণ ও যেটাকে সে সরিয়েছে তার ম্যানিফেস্ট হ্যাশ; তা লেখা হয় সুস্পষ্ট কমান্ডে, কখনো পার্শ্বপ্রতিক্রিয়ায় নয়: --init যা আছে তা রেকর্ড করে, কিছু অনুমোদন করে না, আর গেট block বললে --promote REASON প্রত্যাখ্যান করে।
জাজের প্রমাণ ও অমিল সংরক্ষণ করুন
যে রায় পরে পর্যালোচনা করা যায় না, তাকে বিশ্বাস করা যায় না। প্রতি জোড়ার প্রমাণ-রেকর্ড রাখে জাজ প্রম্পটের হ্যাশ, দুই ক্রমে ও ক্রম-পরীক্ষার পরের রায়, মানুষের লেবেল, তিন ডাইমেনশনের লেবেল, স্কোর দিয়ে বানানো একটা কাঠামোবদ্ধ কারণ, এবং বিচার-করা টেক্সটের হ্যাশ। উত্তরের টেক্সট সে রাখে না। রিভিউয়ারের নোট রাখা যায়, তবে স্ক্রাব-করা (সিক্রেট, ইমেইল ঠিকানা, লম্বা অঙ্কের সারি) ও ১৬০ অক্ষরে কাটা।
from evidencedesk.evalkit.evidence import collect, review_queue, scrub, with_note
from evidencedesk.evalkit.judge import DEMO_JUDGES, load_pairs, load_prompts
pairs, prompts = load_pairs(), load_prompts()
ev = collect(DEMO_JUDGES["mixed"], prompts[1], pairs)
e = ev[4]
print(e.pair_id, e.lang, "| order a-b:", e.order_ab, "| order b-a:", e.order_ba, "| judged:", e.judged, "| human:", e.human, "|", e.why, "| text hash", e.input_sha)
queue = review_queue(ev)
print(len(queue), "of", len(ev), "pairs go to a person:", {r: sum(r in why for why in queue.values()) for r in ("order flip", "judge vs human")})
note = "call " + "+44 7911 " + "123456 or " + "ana" + "@" + "example.org " + "token" + "=" + "fake-value-123" # bait made at run time, never stored
print(with_note(ev[4], note).note)clear-5 bn | order a-b: tie | order b-a: b | judged: tie | human: a | score a 0.15 b 0.1 | text hash 84e5cd8fc5a1
17 of 30 pairs go to a person: {'order flip': 17, 'judge vs human': 12}
call +[number] or [email] [secret]ত্রিশটা জোড়ার সতেরোটা মানুষের কাছে যায়: প্রতিটা ক্রম-উল্টানো, আর বারোটা মানুষের লেবেলের বিরুদ্ধে। রেকর্ডটা একটা বাংলা জোড়া, যেখানে mixed জাজ এক ক্রমে "tie" ও অন্য ক্রমে "b" বলে, অথচ মানুষ বলেছিল "a"। টোপটা চালানোর সময় বানানো, তাই কিটে ব্যক্তিগত-দেখতে কোনো স্ট্রিং নেই। রিড্যাকশন একটা সুরক্ষা-জাল: কাঠামোবদ্ধ ফিল্ড লগ করুন, মুক্ত টেক্সট নয়, আর ধরে-রাখার মেয়াদ ঠিক করুন।
মানের পাশে খরচ ও লেটেন্সি
খরচ ও লেটেন্সি মানের মেট্রিক নয়, আর সস্তা সিস্টেম ভালো তখনই, যখন একই মূল্যায়ন বলে যে মান টিকে আছে। এগুলো আলাদা স্কেলার, নিজস্ব সিলিংসহ। টোকেন আনুমানিক, রিকোয়েস্ট টেক্সট থেকে; লেটেন্সি একটা সূত্র (১৫ ms রিট্রিভাল, আর প্রতি মডেল-কলে ৪০০ ms ও প্রতি ইনপুট টোকেনে ২ ms), কারণ অফলাইনে কোনো ঘড়িকে বিশ্বাস করা যায় না।
print(f"{'candidate':<12}{'tokens/case':>12}{'calls/case':>11}{'p95 ms (formula)':>17}{'answer_correct':>16} gate")
for name, r in reports.items():
s = r.scalars
print(f"{name:<12}{s['input_tokens_per_case']:>12}{s['model_calls_per_case']:>11}{s['latency_p95_ms']:>17}{r.counts['answer_correct'].rate:>16} {evaluate(policy, r, old).verdict}")candidate tokens/case calls/case p95 ms (formula) answer_correct gate
old (v1) 241.9 0.839 1065.0 0.8 block
new (v2) 223.2 0.839 1005.0 0.88 block
score 0.35 205.5 0.806 1003.0 0.88 review
score 0.5 128.4 0.581 987.0 0.68 blockকড়া সেটিং ভার্সন ২-এর চেয়ে ৪২% কম টোকেন আর প্রায় এক-তৃতীয়াংশ কম কল ব্যবহার করে, কারণ সে বেশি ফিরিয়ে দেয়, আর উত্তরের সঠিকতা ২০ পয়েন্ট হারায়। শুধু খরচ দেখলে তাকেই বিজয়ী বলা হতো।
মিনি-ল্যাব: মূল্যায়ন ও রিলিজ গেট
python -m evidencedesk.evalkit.ci স্থির কেসগুলো চালায়, বেসলাইনের বিরুদ্ধে নীতি প্রয়োগ করে, আর ভার্সন, ইন্টারভ্যালসহ মেট্রিক টেবিল ও সিদ্ধান্ত ছাপে। এক্সিট কোড ১ মানে block; go ও review ০ দিয়ে বের হয়, কারণ review মানুষকে ডাকে, বিল্ড ব্যর্থ করে না। ফ্ল্যাগ প্রার্থী ঠিক করে (--chunking, --min-score, --min-margin)।
python -m evidencedesk.evalkit.ci | grep -vE " note|tool_|input_"; echo "exit ${PIPESTATUS[0]}"policy evidencedesk-release v1 candidate 0f9236807b baseline c4a8abc04e
changed: retrieval.chunking paragraph->sentence, retrieval.min_score 0.2->0.3
metric value 95% interval baseline
answer_correct 0.88 0.7-0.958 0.8
abstain_correct 0.667 0.3-0.903 0.5
citation_support 1.0 0.857-1.0 1.0
recall_at_3 0.96 0.805-0.993 0.96
latency_p95_ms 1005.0 1065.0
GATE: BLOCK
block critical: case a02 failed
block floor: abstain_correct 0.667 (4/6) is below the floor 0.8
exit 1ভার্সন ২ block, এক্সিট ১। এবার গ্রিডের সেটিং ও কড়া সেটিং (শুধু রায়ের লাইন):
python -m evidencedesk.evalkit.ci --min-score 0.35 | grep -E "^GATE| (block|review)"; echo "exit ${PIPESTATUS[0]}"
python -m evidencedesk.evalkit.ci --min-score 0.5 | grep -E "^GATE| (block|review)"; echo "exit ${PIPESTATUS[0]}"GATE: REVIEW
review band: abstain_correct 0.833 (5/6) is inside the review band above the floor 0.8
exit 0
GATE: BLOCK
block critical: case g05 failed
block floor: answer_correct 0.68 (17/25) is below the floor 0.8
block regress: answer_correct fell from 0.8 to 0.68 (allowed drop 0.05)
review flips: 3 cases passed in the baseline and fail now: a01, g07, p06
exit 1এই পাতাকে বেঁধে রাখা টেস্টগুলো (স্পষ্ট নাম, যাতে অন্য পাতা টেস্ট যোগ করলে সংখ্যা সরে না যায়):
python -m pytest kit/tests/test_robust_stats.py kit/tests/test_robust_measures.py kit/tests/test_robust_gates.py | sed 's/ in [0-9.]*s//'.......................... [100%]
26 passedকিটে নতুন: evidencedesk/evalkit/-এ এগারোটা মডিউল (stats, measures, tooluse, slices, flaky, goodhart, versions, gates, evidence, suite, ci), তিনটা ডেটা ফাইল ও তিনটা test_robust_*.py ফাইল; সরবরাহ-করা baseline_v1.json যে কোড থেকে বানানো তার সঙ্গে মিলছে কি না একটা টেস্ট দেখে। বিগিনারের gate.py অপরিবর্তিত। মূল্যায়নের অধ্যায় আপাতত সম্পূর্ণ।
কাজের জায়গায় এটা কোথায় দেখা যায়
বোগোতার মাতেও একটা ক্লেইম অ্যাসিস্ট্যান্ট চালায়। প্রতিটা পুল রিকোয়েস্টে দেখাত "পাস রেট ৯৪%"; একটা প্রম্পট বদলের পরে লেখা হলো ৯৫%, অথচ রিফান্ড-সীমার একটা কেস চুপচাপ ভুল ঊর্ধ্বসীমা দিতে শুরু করেছিল। এখন পাইপলাইন ইন্টারভ্যালসহ কাউন্ট ছাপে, ক্রিটিক্যাল কেসের ছোট তালিকায় block করে, পাস থেকে ফেলে-যাওয়া প্রতিটা কেস রিভিউ করে, আর বেসলাইন ও তার কারণ রিপোজিটরিতে রাখে।
সাধারণ ভুল
- n ছাড়া হার জানানো। "বিরত থাকা ৮৩%" মানে ৬-এর ৫। কাউন্ট বহন করুন, ইন্টারভ্যাল দেখান, ছোট স্লাইস বিচার করতে অস্বীকার করুন।
- সুইপের বিজয়ীকে ফলাফল ধরা। তুলনার সংখ্যার জন্য সংশোধন করুন এবং অদেখা কেসে নিশ্চিত করুন।
- শুধু গড়ে গেট করা। গড় বাড়তে পারে যখন ফেল-করা-চলবে-না কেস ফেল করতে শুরু করেছে। ক্রিটিক্যাল কেসের নাম দিন, আর প্রতারক সিস্টেম দিয়ে গেট পরীক্ষা করুন।
- বদলে-যাওয়া মাপকাঠিতে তুলনা। নতুন ডেটাসেট হ্যাশ বা মেট্রিক সংজ্ঞা সংখ্যাগুলোকে অতুলনীয় করে; ম্যানিফেস্ট রাখুন আর তুলনা প্রত্যাখ্যান করুন।
- বিচার-করা টেক্সট লগ করা। হ্যাশ, স্কোর ও স্ক্রাব-করা ছোট নোট রাখুন; ধরে-রাখার মেয়াদ ঠিক করুন।
নিজে চেষ্টা করুন
- সহজ (দেখুন মূল্যায়ন ডেটাসেট ও নির্ধারিত টেস্ট): একটা প্রার্থী ৩১টার ৫টা কেস ঠিক করে, ভাঙে একটাও না। ৫% স্তরে তাৎপর্যপূর্ণ? আর ৬টা হলে?
- মাঝারি (দেখুন রুব্রিক, রিগ্রেশন ও রিলিজ গেট): একটা ভাষা-স্লাইস তার কেসের ৬০% পাস করে; সামগ্রিক হার ২৬/৩১। কত বড় স্লাইস থেকে সেটা শুধু "weak" নয়, "low"?
- কঠিন (দেখুন মূল্যায়নের নকশা ও জাজ ক্যালিব্রেশন):
with_judge(decision, record, prompt, pairs)লিখুন: পুরোনো (stale) ক্যালিব্রেশন রানকে review-তে পাঠায়, ০.৬-এর নিচে কাপ্পা block করে। mixed জাজ প্রম্পট ১ দিয়ে, আর clean জাজ প্রম্পট ১ ও ২ দিয়ে চালান।
উত্তর
সহজ। একমুখী পাঁচটা উল্টানোয় p = ০.০৬২, ছয়টায় ০.০৩১।
a = {f"c{i:02d}": 0.0 for i in range(31)}
for fixed in (5, 6):
b = {k: 1.0 if int(k[1:]) < fixed else 0.0 for k in a}
print(f"{fixed} cases fixed, none broken: p = {paired(a, b).p}")5 cases fixed, none broken: p = 0.062
6 cases fixed, none broken: p = 0.031পাস চেকলিস্ট: শুধু বদলানো কেস গুনেছেন; দ্বিমুখী এক্স্যাক্ট টেস্ট; বলেছেন যে তাৎপর্য তখনও ধরে নেয় কেসগুলো ন্যায্য নমুনা।
মাঝারি।
from evidencedesk.evalkit.slices import judge_slices
from evidencedesk.evalkit.stats import Count
for n in range(8, 20):
row = judge_slices({"lang=bn": Count(k=round(0.6 * n), n=n)}, Count(k=26, n=31))[0]
print(n, f"{row['k']}/{n}", row["status"])
if row["status"] == "low":
break8 5/8 weak
9 5/9 low৮টা কেসে ইন্টারভ্যাল তখনও সামগ্রিক হার ছোঁয় ("weak"); ৯টায় তার ওপরের প্রান্ত নিচে নামে ("low")। পাস চেকলিস্ট: ন্যূনতম আকার ও ইন্টারভ্যাল-নিয়ম আলাদা চেক; খেয়াল করেছেন যে ফল সামগ্রিক হারের সঙ্গে নড়ে।
কঠিন।
from evidencedesk.evalkit.calibrate import calibrate, is_stale
from evidencedesk.evalkit.gates import Decision, Reason
def with_judge(decision, record, prompt, pairs, min_kappa=0.6):
if is_stale(record, prompt, pairs):
extra = Reason("review", "judge", "calibration is stale")
elif record["kappa"] < min_kappa:
extra = Reason("block", "judge", f"kappa {record['kappa']} is below {min_kappa}")
else:
return decision
reasons = decision.reasons + (extra,)
return Decision("block" if any(r.level == "block" for r in reasons) else "review", reasons)
mid = evaluate(policy, reports["score 0.35"], old)
for judge, version in (("mixed", 1), ("clean", 1), ("clean", 2)):
record = calibrate(pairs, DEMO_JUDGES[judge], prompts[1], swap=False) # measured with prompt 1
d = with_judge(mid, record, prompts[version], pairs)
print(f"{judge} judge, prompt v{version}:", d.verdict, [r.text for r in d.reasons if r.rule == "judge"])mixed judge, prompt v1: block ['kappa 0.456 is below 0.6']
clean judge, prompt v1: review []
clean judge, prompt v2: review ['calibration is stale']mixed জাজ কাপ্পায় ফেল করে ও block হয়। clean জাজ যে প্রম্পটে ক্যালিব্রেট হয়েছিল তাতে পাস করে, আর প্রম্পট বদলালে review-তে যায়, কারণ পুরোনো ক্যালিব্রেশন ভিন্ন একটা জাজকে বর্ণনা করে। পাস চেকলিস্ট: কাপ্পার আগে পুরোনো (stale) কি না দেখা; জাজ আবার গেটে ফেরার আগে নতুন প্রম্পটে পুনরায় ক্যালিব্রেট।
সারসংক্ষেপ
- সাইটেশন সমর্থন উপস্থিতি থেকে, ভুল উত্তর ভুল বিরতি থেকে, আর চারটা টুল-চেক আলাদা মাপুন; রিগ্রেশন ও অ্যাডভার্সারিয়াল স্যুট আলাদা রাখুন।
- ৩১টা কেসে কাউন্ট ও ইন্টারভ্যাল বহন করুন, জোড়ায় তুলনা করুন, ক্ষুদ্রতম শনাক্তযোগ্য পার্থক্য কত বড় তা জানুন, সুইপের সংশোধন করুন, অস্থির রান বারবার চালান।
- স্লাইস বিচার করুন শুধু ন্যূনতম আকারের ওপরে; গেম-করা-যায় মেট্রিকের সঙ্গে পাল্টা-মেট্রিক জুড়ুন, তুচ্ছ প্রতারকদের block করুন।
- গেটে থাকে ক্রিটিক্যাল কেস, ফ্লোর, সিলিং, রিভিউ ব্যান্ড, নো-রিগ্রেস মার্জিন ও স্লাইস নিয়ম; রায় কারণ বলে, আর অপ্রমাণিত ফ্লোর নোট, পাস নয়।
- ডেটাসেট, প্রম্পট, মডেল ও রিট্রিভাল সেটিং ম্যানিফেস্টে ভার্সন করুন, বেসলাইন ভেবেচিন্তে প্রোমোট করুন, টেক্সট ছাড়া জাজের প্রমাণ রাখুন, আর খরচ-লেটেন্সি মানের পাশে রাখুন, ভেতরে নয়।
এরপর: ফাইন-টিউনিংয়ের সিদ্ধান্ত ও LoRA-র ধারণা। মূল্যায়নের অধ্যায় আপাতত সম্পূর্ণ: আপনি এখন মাপতে, তুলনা করতে ও গেট করতে পারেন। পরের পাতা সেটা দিয়ে ঠিক করে ফাইন-টিউনিং আদৌ সঠিক সমাধান কি না, আর কোনো ট্রেনিংয়ের আগে বেসলাইনে কী থাকতে হবে।