فاز 19 · درس 53

طراحی ارزیاب نتایج و صدور رأی آماری فرضیه‌ها

نوع
ساختنی
زمان
حدود 90 دقیقه
زبان‌ها
Python
پیش‌نیاز
فاز 19 مسیر A، درس‌های 20 تا 29
ترازوی فلزی دقیق دو کفه روی میز چوبی کارگاهی با دو وزنه و نمودار مقایسه آماری ساده در پس‌زمینه
فهرست مطالب
  1. اهداف یادگیری
  2. چرا آزمون جفت‌شده؟
  3. بهبود حساس به جهت
  4. معماری
  5. نرمال‌سازی لگاریتمی
  6. آزمون جفت‌شده به ازای هر سید
  7. ساختار رأی نهایی
  8. راهنمای خواندن کد
  9. جایگاه این بخش در سیستم کلی

در فرآیند توسعه و تیونینگ مدل‌های یادگیری ماشین، دیدن یک عدد بهتر در خروجی اجرا همیشه به معنای ارتقای واقعی مدل نیست. نوسانات ناشی از تغییر سیدهای تصادفی (seeds) و نویزهای محیطی می‌توانند به‌سادگی تغییرات تصادفی را به جای بهبود واقعی جا بزنند. برای اتوماسیون تصمیم‌گیری و جلوگیری از پیشروی بر اساس توهم آماری، نیازمند سازوکاری دقیق هستیم که خروجی‌های خام را تحلیل کند.

در این درس یاد می‌گیرید چگونه یک ارزیاب نتایج (result evaluator) قطعی و خالص بسازید که آزمایش‌های نسخه‌ی کاندید را در برابر خط مبنا مقایسه کند. با پیاده‌سازی آزمون تی جفت‌شده (paired t-test) بدون وابستگی به بسته‌های سنگین خارجی و با به‌کارگیری نرمال‌سازی مقیاس‌های لگاریتمی، خروجی‌های چندگانه‌ی آزمایش‌ها را به یک رأی آماری شفاف و یک‌خطی تبدیل می‌کنید.

رانر (runner) اعداد را تولید کرد. حالا ارزیاب باید تصمیم بگیرد که آیا این اعداد نشان‌دهنده‌ی بهبودند، پسرفت، یا صرفاً نویز. در این درس، مسیر صدور رأی (verdict path) را می‌سازیم؛ مسیری که این معیارها را می‌گیرد و به یک جمع‌بندی یک‌خطی تبدیل می‌کند.

اهداف یادگیری

  • مقایسه‌ی اجرای کاندید (candidate run) با خط مبنا با در نظر گرفتن جهت مطلوب تغییرات و یک آستانه‌ی ثابت.
  • پیاده‌سازی آزمون تی جفت‌شده (paired t-test) از صفر روی معیارهای به‌دست‌آمده از هر سید (seed) و خواندن p-value حاصل.
  • نرمال‌سازی معیارهای با مقیاس لگاریتمی، تا گزارش‌های بعدی بتوانند آن‌ها را در کنار معیارهای خطی ترکیب کنند.
  • صدور رأی برای هر فرضیه، به‌گونه‌ای که هماهنگ‌کننده بتواند آن را به صف طراحی‌شده در درس پنجاه ضمیمه کند.
  • خالص نگه‌داشتن همه‌ی گام‌ها به‌صورت تابع خالص، تا ورودی‌های یکسان همیشه رأیی کاملاً یکسان تولید کنند.

چرا آزمون جفت‌شده؟

یک عددِ تنها از سمت رانر مشخص نمی‌کند که آیا تغییر ایجادشده واقعی است یا نه. همان پیکربندی با یک سید متفاوت، پرپلکسیتی متفاوتی به دست می‌دهد؛ بنابراین شاید تغییر مشاهده‌شده صرفاً نویز باشد. مقایسه‌ی درست، یک مقایسه‌ی جفت‌شده است: همان سیدها با همان داده‌ها، یک بار با کاندید و یک بار با خط مبنا اجرا می‌شوند. هر سید یک اختلاف تولید می‌کند. میانگین این اختلاف‌ها اثر واقعی تغییر است و خطای استاندارد آن‌ها کف نویز را مشخص می‌کند.

در این درس، این آزمون را کاملاً از صفر پیاده می‌کنیم؛ بدون هیچ وابستگی به scipy.stats. کل محاسبات ریاضی آن‌قدر جمع‌وجور است که در یک صفحه جا می‌شود.

diffs    = [a_i - b_i for i in seeds]
mean     = sum(diffs) / n
variance = sum((d - mean) ** 2 for d in diffs) / (n - 1)
t_stat   = mean / sqrt(variance / n)
df       = n - 1
p_value  = two_sided_p(t_stat, df)

محاسبه‌ی p-value دوطرفه از تابع بتای ناکامل منظم‌شده (regularised incomplete beta function) استفاده می‌کند. در این درس پیاده‌سازی جمع‌وجوری از آن را با استفاده از کسر مسلسل لنتز (Lentz continued fraction) ارائه می‌دهیم. کل این بخش در 60 خط و فقط با توابع ریاضی کتابخانه‌ی استاندارد پایتون پیاده شده است.

بهبود حساس به جهت

برخی معیارها با افزایش بهبود می‌یابند (دقت، نرخ پردازش) و برخی با کاهش (تابع زیان (loss)، پرپلکسیتی، زمان اجرا). ارزیاب برای هر معیار یک فیلد direction نگه می‌دارد.

if direction == "higher_is_better":
    improvement = (candidate - baseline) / abs(baseline)
elif direction == "lower_is_better":
    improvement = (baseline - candidate) / abs(baseline)

مقدار بهبود علامت‌دار است. بهبود منفی در معیاری که «بیشترش بهتر است»، یعنی نسخه‌ی کاندید ضعیف‌تر شده است. مسیر صدور رأی علامت و اندازه‌ی عددی را هم‌زمان بررسی می‌کند.

یک آستانه‌ی ثابت (improvement_threshold=0.02 یا همان 2 درصد) مشخص می‌کند که آیا تغییر به اندازه‌ی کافی بزرگ هست که آن را به حساب بیاوریم یا نه. کمتر از این مقدار، بدون در نظر گرفتن p-value، رأی «نویز» صادر می‌شود؛ حلقه‌ی ارزیابی علاقه‌ای به تغییراتی ندارد که برای کاربر ملموس نیستند.

ارزیاب نتایجتفاضل‌های جفت‌شده · آزمون t · رأی نهایی
RESULT EVALUATOROne run per configuration proves nothing; the same spec with a new seed lands on a different number. The evaluator pairs each seed with itself, candidate against baseline, and lets the per-seed differences testify. Their mean is the effect, their spread is the noise floor, and a from-scratch paired t test says whether the mean stands clear of it. The verdict attaches back to the hypothesis queue, and identical inputs always produce the identical verdict.جفت‌کردن هر سید با خودش · آزمون تفاضل‌هاs0s1s2s3ppl مبنای خاکستری · کاندید آبی0Δ pplکف نویزمیانگین Δimprovement · t 5.9 · p < 0.01
یک بار اجرا به ازای هر پیکربندی چیزی را اثبات نمی‌کند؛ همان مشخصات با یک سید (seed) جدید عدد متفاوتی تولید می‌کند. ارزیاب هر سید را با خودش جفت می‌کند، کاندید را در برابر خط مبنا می‌سنجد و اجازه می‌دهد تفاضل‌های هر سید نتیجه را نشان دهند. میانگین آن‌ها اثر واقعی، پراکندگی آن‌ها کف نویز، و یک آزمون t جفت‌شده از صفر مشخص می‌کند که آیا میانگین فراتر از نویز قرار دارد یا خیر. رأی نهایی به صف فرضیه‌ها متصل می‌شود و ورودی‌های یکسان همواره رأی نهایی یکسانی تولید می‌کنند.

معماری

ExperimentResult کاندیدنرمال‌سازی متریک‌هاExperimentResult خط مبنابهبود مبتنی بر جهتآزمون تی زوجیمسیر داوریرکورد داوریاتصال به صف فرضیه

ارزیاب سه محاسبه‌ی کاملاً مستقل از هم را اجرا می‌کند و نتایج آن‌ها را در مسیر صدور رأی به هم پیوند می‌دهد. هر محاسبه یک تابع خالص است، بدون هیچ وضعیت مشترک.

نرمال‌سازی لگاریتمی

پرپلکسیتی نسبت به تابع زیان (loss) رفتاری نمایی دارد. کاهش 0.1 واحدی در تابع زیان، افتی به‌مراتب شدیدتر در پرپلکسیتی ایجاد می‌کند. مقایسه‌ی مستقیم پرپلکسیتی بین دو پیکربندی ایرادی ندارد، اما ترکیب آن با معیارهای خطی در یک گزارش واحد نیازمند نرمال‌سازی است.

در این درس، هر معیاری را که فیلد scale آن روی "log" تنظیم شده باشد، با گرفتن لگاریتم طبیعی پیش از محاسبه‌ی بهبود نرمال می‌کنیم. سپس آستانه را در فضای لگاریتمی اعمال می‌کنیم. افت پرپلکسیتی از 32 به 28 تبدیل می‌شود به log(32) = 3.466 در برابر log(28) = 3.332. بهبود نسبی برای معیاری که «کمترش بهتر است» برابر می‌شود با (3.466 - 3.332) / 3.466 = 0.039، که از آستانه‌ی 2 درصدی عبور می‌کند.

if scale == "log":
    a = log(candidate)
    b = log(baseline)
else:
    a = candidate
    b = baseline

معیارهایی با scale="linear" (حالت پیش‌فرض) از این تبدیل صرف‌نظر می‌کنند. یک مسیر کد واحد هر دو حالت را مدیریت می‌کند.

آزمون جفت‌شده به ازای هر سید

رانرِ درس پنجاه‌ودو برای هر اجرا یک شیء نهایی از معیارها تحویل می‌دهد. برای آزمون جفت‌شده، ارزیاب به یک شیء به ازای هر سید برای کاندید و یکی به ازای هر سید برای خط مبنا نیاز دارد. هماهنگ‌کننده آزمایش یکسانی را تحت هر دو پیکربندی روی فهرستی از سیدها اجرا می‌کند و دو فهرست از رکوردهای ExperimentResult را به ارزیاب می‌سپارد.

ارزیاب آن‌ها را بر اساس سید جفت می‌کند (سید داخل result.metrics["seed"] قرار دارد) و روی معیار خواسته‌شده پیش می‌رود. اگر سیدهای این دو فهرست با هم نخوانند، ارزیاب خطای PairingError صادر می‌کند؛ در این صورت هماهنگ‌کننده باید آزمایش را دوباره اجرا کند.

ساختار رأی نهایی

Verdict
  hypothesis_id          : int
  metric                 : str
  direction              : "higher_is_better" | "lower_is_better"
  scale                  : "linear" | "log"
  candidate_mean         : float
  baseline_mean          : float
  improvement            : float       (signed, fraction; see direction rules)
  p_value                : float | None  (None if n < 2)
  significance_threshold : float
  improvement_threshold  : float
  verdict                : "improved" | "regressed" | "noise" | "failed"
  rationale              : str

مسیر صدور رأی یک جدول تصمیم‌گیری جمع‌وجور است:

1. If any candidate result has terminal != "ok": verdict = "failed"
2. else if |improvement| < improvement_threshold:  verdict = "noise"
3. else if p_value is None or p_value > significance: verdict = "noise"
4. else if improvement > 0:                          verdict = "improved"
5. else:                                             verdict = "regressed"

فیلد Rationale یک توضیح یک‌خطی و خوانا برای انسان است که هماهنگ‌کننده می‌تواند آن را جلوی شناسه‌ی فرضیه لاگ کند.

راهنمای خواندن کد

فایل code/main.py کلاس‌های MetricSpec، Verdict و Evaluator را به همراه توابع کمکی آماره‌ی t و بتای ناکامل و یک دموی قطعی تعریف می‌کند. آزمون تی کاملاً با توابع ریاضی کتابخانه‌ی استاندارد پیاده شده است؛ از numpy صرفاً برای خواندن فهرست معیارها و محاسبه‌ی میانگین‌ها و واریانس‌ها استفاده می‌شود.

فایل code/tests/test_evaluator.py این بخش‌ها را پوشش می‌دهد: مسیر بهبودیافته، مسیر پسرفت، مسیر نویز (بهبود ناچیز)، مسیر نویز (تعداد نمونه یا n پایین)، مسیر شکست نهایی، مسیر نرمال‌سازی لگاریتمی، آزمون تی در برابر یک مقدار مرجع مشخص، و خطای عدم تطابق سیدها.

جایگاه این بخش در سیستم کلی

درس پنجاه صف فرضیه‌ها را ساخت. درس پنجاه‌ویک مواردی را که در ادبیات پژوهش به نتیجه رسیده بودند فیلتر کرد. درس پنجاه‌ودو آزمایش را در هر دو پیکربندی کاندید و خط مبنا روی چند سید اجرا کرد. درس پنجاه‌وسه این خروجی‌ها را می‌خواند و رأی نهایی را صادر می‌کند. هماهنگ‌کننده این چهار بخش را به هم وصل می‌کند:

for hypothesis in queue:
    literature = retrieval.search(hypothesis.text)
    if literature_settles(hypothesis, literature):
        attach(hypothesis, verdict="settled")
        continue
    candidates = runner.run_all(specs_for(hypothesis))
    baselines  = runner.run_all(baseline_specs_for(hypothesis))
    metric_spec = MetricSpec("perplexity", direction=LOWER, scale=LOG)
    verdict = evaluator.evaluate(hypothesis.id, metric_spec, candidates, baselines)
    attach(hypothesis, verdict)

پیاده‌سازی خود این هماهنگ‌کننده در این درس نیامده است؛ این چهار درس طوری طراحی شده‌اند که بدون هیچ کد واسط اضافی، و فقط با تکیه بر dataclassهای تعریف‌شده در هر کدام، کاملاً با یکدیگر ترکیب می‌شوند.

خودآزمایی

به سؤال‌ها جواب بدهید تا ببینید چقدر از درس در ذهنتان مانده است.

  1. پیش از درسچرا ارزیاب به جای مقایسه دو عدد منفرد، از آزمون t جفت‌شده استفاده می‌کند؟
  2. پیش از درسچرا ارزیاب روی هر متریک یک فیلد جهت (direction) نگه می‌دارد؟
  3. حین درسوقتی |improvement| زیر آستانه باشد، حتی اگر مقدار p معنادار باشد، مسیر رأی نهایی چه چیزی برمی‌گرداند؟
  4. حین درسچرا ارزیاب متریک‌های دارای مقیاس لگاریتمی را پیش از محاسبه‌ی بهبود تبدیل می‌کند؟
  5. حین درستابع کمکی آزمون t جفت‌شده چه زمانی p_value = None را برمی‌گرداند؟
  6. حین درساگر حتی یک نتیجه‌ی کاندید دارای برچسب پایانی غیر از ok باشد، چه اتفاقی می‌افتد؟

سؤالات متداول

چرا در مقایسه‌ی مدل‌ها باید از آزمون جفت‌شده (paired test) استفاده کنیم؟

چون تغییر مقدار معیار در یک اجرای منفرد ممکن است ناشی از نویز یا تغییر سید تصادفی باشد. اجرای کاندید و خط مبنا روی سیدها و داده‌های دقیقاً یکسان نشان می‌دهد که آیا تغییر مشاهده‌شده معنادار است یا فقط نویز تصادفی به شمار می‌آید.

آیا برای محاسبه‌ی p-value در آزمون تی به SciPy نیاز داریم؟

خیر؛ در این درس کل محاسبات آماره‌ی t و تابع بتای ناکامل منظم‌شده با استفاده از کسر مسلسل لنتز، در حدود 60 خط و تنها با توابع ریاضی کتابخانه‌ی استاندارد پایتون (stdlib math) پیاده‌سازی شده است.

چرا معیارهایی مثل پرپلکسیتی پیش از ارزیابی نرمال‌سازی لگاریتمی می‌شوند؟

پرپلکسیتی رابطه‌ای نمایی با تابع زیان (loss) دارد. برای اینکه بتوان تغییرات آن را در کنار معیارهای خطی در یک آستانه‌ی درصدی مشترک مقایسه و ترکیب کرد، ابتدا با اعمال لگاریتم طبیعی به فضای خطی منتقل می‌شود.

ما رو توی گوگل بیشتر ببین

با افزودن نکست ادمین به «منابع ترجیحی» گوگل، درس‌ها و مطالب ما را در بخش اخبار برتر جستجو بیشتر می‌بینید.