فاز 19 · درس 71

پیاده‌سازی معیارهای کلاسیک ارزیابی LLM: از BLEU تا ROUGE

نوع
ساختنی
زمان
حدود 90 دقیقه
زبان‌ها
Python
پیش‌نیاز
مبانی فاز 19 مسیر B، درس 70
نمایی ساده و مفهومی از چند خط‌کش و شابلون چوبی در کنار مهره‌های هندسی روی میز کار چوبی
فهرست مطالب
  1. اهداف یادگیری
  2. چرا پیاده‌سازی مجدد؟
  3. توکن‌سازی
  4. تطابق دقیق
  5. معیار F1 در سطح توکن
  6. BLEU-4
  7. ROUGE-L
  8. دقت
  9. قرارداد توزیع فراخوانی
  10. مواردی که این درس به آن‌ها نمی‌پردازد
  11. راهنمای خواندن کد
  12. گام‌های بعدی

هنگام مطالعه‌ی مقالات و بنچ‌مارک‌های مدل‌های زبانی، دیدن تفاوت فاحش در اعدادی مثل BLEU یا ROUGE-L میان دو کتابخانه‌ی مختلف بسیار رایج است؛ تفاوت‌هایی که اغلب به جزئیاتی چون روش توکن‌سازی، هموارسازی یا تبدیل حروف بزرگ و کوچک برمی‌گردد و تحلیل نتایج را سردرگم‌کننده می‌کند.

در این درس پنج معیار بنیادی ارزیابی متن شامل تطابق دقیق (Exact Match)، معیار F1، دقت، BLEU-4 و ROUGE-L را از صفر و بر پایه‌ی اصول ریاضی پیاده‌سازی می‌کنیم تا بدون وابستگی کورکورانه به پکیج‌های آماده، سازوکار هر متریک و دلیل تغییر اعداد را دقیقاً بشناسید.

این پیاده‌سازی‌ها کاملاً بر پایه‌ی کتابخانه‌ی استاندارد پایتون و ابزار پایه‌ای مثل numpy انجام می‌شوند و نیازی به پردازش‌های سنگین ابری یا دسترسی به APIهای خارجی ندارند، بنابراین مستقیماً روی سیستم شخصی یا محیط‌های محلی قابل اجرا هستند.

BLEU، ROUGE-L، F1، تطابق دقیق (exact-match) و دقت (accuracy)؛ پنج معیاری که هنوز هم بار اصلی اعداد ارزیابی منتشرشده برای مدل‌های زبانی بزرگ (LLM) را به دوش می‌کشند. هر کدام را از صفر و بر پایه‌ی اصول اولیه پیاده‌سازی کنید تا دقیقاً بدانید هر عدد چه معنایی دارد.

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

  • پیاده‌سازی تطابق دقیق، F1 و دقت در سطح توکن با قواعد توکن‌سازی صریح.
  • پیاده‌سازی BLEU-4 از پایه: دقت n-gram اصلاح‌شده، میانگین هندسی روی n از 1 تا 4، و جریمه‌ی کوتاهی طول (brevity penalty).
  • پیاده‌سازی ROUGE-L با استفاده از طولانی‌ترین زیردنباله‌ی مشترک (LCS)، به همراه ترکیب F-beta برای دقت و بازخوانی.
  • توزیع فراخوانی بر اساس فیلد metric_name از درس 70، به‌گونه‌ای که اجراکننده (runner) به نوع معیار وابسته نباشد.
  • تثبیت رفتار سیستم با بردارهای مرجع برگرفته از مثال‌های حل‌شده، نه با تکیه بر کتابخانه‌های جانبی.
هم‌پوشانی N-GRAM در BLEUشمارش تطابق‌ها، برش، اعمال جریمه
BLEU N-GRAM OVERLAPBLEU is counting. Each candidate n-gram that appears in the reference scores a match; the clipped count over the candidate length is modified n-gram precision. A candidate shorter than the reference takes a brevity penalty, so high precision on a stub still loses. ROUGE-L instead rewards the longest common subsequence.کاندیدمرجعthecatsatonmatthecatsatonamat5/5دقتBP < 1(خیلی کوتاه)
معیار BLEU مبتنی بر شمارش است. هر n-gram متن کاندید که در مرجع ظاهر شود، یک تطابق امتیاز می‌گیرد؛ نسبت تعداد برش‌خورده به طول کاندید همان دقت n-gram اصلاح‌شده است. کاندیدی که کوتاه‌تر از مرجع باشد جریمه‌ی کوتاهی طول (brevity penalty) دریافت می‌کند، بنابراین دقت بالا روی متنی ناقص همچنان نمره‌ی پایینی خواهد داشت. در مقابل، ROUGE-L به طولانی‌ترین زیردنباله‌ی مشترک پاداش می‌دهد.

چرا پیاده‌سازی مجدد؟

گاهی مقاله‌ای می‌خوانید که BLEU را 28.3 گزارش کرده و در مقاله‌ی دیگری همان معیار 0.283 آمده است. یا می‌بینید امتیاز ROUGE-L در دو کتابخانه‌ی مختلف 10 نمره با هم فرق دارد، فقط به این دلیل که یکی متن را به حروف کوچک تبدیل می‌کند و دیگری نه. سریع‌ترین راه برای خلاص شدن از این سردرگمی‌ها این است که خودتان معیارها را بنویسید؛ آن‌وقت می‌توانید دقیقاً به خطی اشاره کنید که توکن‌ساز در آن تعیین شده و خطی که هموارسازی (smoothing) در آن اعمال می‌شود. بعد از آن، مقایسه‌ی اعداد میان مقالات مختلف به بررسی تنظیمات معیار تبدیل می‌شود، نه بحث و جدل درباره‌ی کتابخانه‌ها.

کتابخانه‌ی استاندارد پایتون به همراه numpy کاملاً کافی است. BLEU فقط شمارش است و یک محدودسازی مقدار (clamp). ROUGE-L برنامه‌ریزی پویا (dynamic programming) است. F1 هم اشتراک مجموعه‌ای روی توکن‌ها است. سخت‌ترین بخش کار، انتخاب یک توکن‌ساز و پایبندی به آن است.

توکن‌سازی

توکن‌ساز ما این است: re.findall(r"\w+", text.lower()). تبدیل به حروف کوچک، جدا کردن توالی کاراکترهای کلمه‌ای (حروف، ارقام و زیرخط)، و دور ریختن هر چیز دیگر. تک‌تک معیارهای این درس دقیقاً از همین توکن‌ساز استفاده می‌کنند. اجراکننده حق انتخاب ندارد. اگر توکن‌ساز را عوض کنید، عملاً بنچ‌مارک متفاوتی را اجرا کرده‌اید.

TOKEN_RE = re.compile(r"\w+", re.UNICODE)
def tokenize(text):
    return TOKEN_RE.findall(text.lower())

این یک ساده‌سازی عمدی است. در محیط‌های عملیاتی (پروداکشن) مواردی مثل نویسه‌های CJK (چینی، ژاپنی و کره‌ای)، فرم‌های مخفف (contractions) و شناسه‌های کد اهمیت پیدا می‌کنند. نکته‌ی اصلی این درس این است که توکن‌ساز یک قرارداد است، نه پیچی که مدام تنظیمش کنیم.

تطابق دقیق

def exact_match(pred, targets):
    return float(any(pred.strip() == t.strip() for t in targets))

این تابع برای هر وظیفه مقدار 1.0 یا 0.0 را برمی‌گرداند. میانگین این مقادیر روی کل مجموعه‌داده، امتیاز نهایی است. این معیار، ابزار اصلی برای ارزیابی وظایف محاسباتی، سوالات چندگزینه‌ای و دسته‌بندی‌های کوتاه است.

معیار F1 در سطح توکن

چندمجموعه‌ی (multiset) توکن‌ها را برای متن پیش‌بینی‌شده و هدف تشکیل دهید. دقت برابر است با اندازه‌ی اشتراک چندمجموعه‌ها تقسیم بر اندازه‌ی چندمجموعه‌ی پیش‌بینی. بازخوانی نیز همان اشتراک تقسیم بر اندازه‌ی چندمجموعه‌ی هدف است. F1 میانگین همساز (harmonic mean) این دو است. این پیاده‌سازی حالت‌های خاص پیش‌بینی خالی و هدف خالی را هم مدیریت می‌کند.

توکن‌سازیتوکن‌سازیمتن پیش‌بینی‌شدهتوکن‌های پیش‌بینی‌شدهمتن هدفتوکن‌های هدفاشتراک چندمجموعه‌ایprecision = inter / predrecall = inter / targetF1 = 2 P R / P + R

برای وظایفی با چند هدف، بهترین F1 را در میان فهرست اهداف در نظر می‌گیریم. این کار با رفتار استاندارد سبک SQuAD که در مقالات بسیار رایج است مطابقت دارد.

BLEU-4

معیار BLEU شاخص کلاسیک ترجمه‌ی ماشینی است و هنوز در کارهای خلاصه‌سازی هم دیده می‌شود. فرمولی که ما استفاده می‌کنیم BLEU-4 در سطح جمله است؛ یعنی برای هر جفت پیش‌بینی و مرجع محاسبه می‌شود، جریمه‌ی کوتاهی طول استاندارد را دارد، و برای تعداد n-gramهای اصلاح‌شده از هموارسازی جمع با یک (additive-one) استفاده می‌کند تا نبودِ یک 4-gram کل امتیاز را صفر نکند.

برای هر جفت کاندید و مرجع، دقت n-gram اصلاح‌شده را برای n برابر با 1، 2، 3 و 4 می‌شماریم. دقت اصلاح‌شده، تعداد تکرار هر n-gram در کاندید را به بیشترین تعداد تکرار همان n-gram در هر یک از مراجع محدود می‌کند؛ بنابراین متن کاندید نمی‌تواند با تکرار مداوم یک عبارت، امتیازش را به شکل کاذب بالا ببرد. میانگین هندسی این چهار دقت، در نهایت با جریمه‌ی کوتاهی طول تعدیل می‌شود.

توکن‌های کاندیدشمارش n-gramها n=1..4توکن‌های مرجعحداکثر شمارش به ازای هر n-gramشمارش برش‌خورده n-gramدقت اصلاح‌شده p_nطول کاندید cطول مرجع rBP = 1 if c =r else exp 1 - r/cمیانگین هندسی p_nBLEU = BP * geo mean

قاعده‌ی هموارسازی مورد استفاده همان روشی است که لین و اوچ (Lin and Och) «روش 1» نامیدند: پیش از گرفتن لگاریتم، به صورت و مخرج دقت هر n-gram یک واحد اضافه کنید. این کار مانع از بروز log 0 در شرایطی می‌شود که مرجع هیچ 4-gram مشترکی ندارد، و در متن‌های کاندید طولانی هم مقدار نهایی بسیار نزدیک به حالت بدون هموارسازی باقی می‌ماند.

ROUGE-L

معیار ROUGE-L طولانی‌ترین زیردنباله‌ی مشترک (LCS) میان توالی توکن‌های کاندید و مرجع را مقایسه می‌کند. LCS ترتیب کلمات را بدون اجبار به پیوستگی آن‌ها در نظر می‌گیرد؛ به همین دلیل معیار پیش‌فرض خلاصه‌سازی است. ما طول LCS را با یک جدول استاندارد برنامه‌ریزی پویا محاسبه می‌کنیم، سپس بازخوانی را به‌صورت lcs / طول مرجع و دقت را به‌صورت lcs / طول کاندید به دست می‌آوریم و با F-beta ترکیب می‌کنیم که در آن بتا برابر با یک است تا فرم متقارن F1 حاصل شود.

def lcs_length(a, b):
    n, m = len(a), len(b)
    dp = numpy.zeros((n + 1, m + 1), dtype=int)
    for i in range(n):
        for j in range(m):
            if a[i] == b[j]:
                dp[i+1, j+1] = dp[i, j] + 1
            else:
                dp[i+1, j+1] = max(dp[i+1, j], dp[i, j+1])
    return int(dp[n, m])

استفاده از جدول numpy خوانایی کد را بالا می‌برد؛ هرچند با لیست‌های خالص پایتون هم می‌شد آن را پیاده کرد. وظایفی که ROUGE-L را به کار می‌گیرند، به ازای هر وظیفه هزینه‌ی زمانی O(nm)O(n m) را می‌پردازند. برای طول معمول خلاصه‌ها، این زمان زیر 1 میلی‌ثانیه می‌ماند.

دقت

برای وظایف دسته‌بندی، معیار دقت به تطابق دقیق تقلیل پیدا می‌کند: اگر پیش‌بینی با هر یک از اهداف نرمال‌سازی‌شده در فهرست برابر باشد، امتیاز 1 می‌گیرد. ما آن را به‌عنوان یک تابع مجزا ارائه می‌دهیم تا توزیع‌کننده (dispatcher) بتواند بر اساس metric_name تصمیم بگیرد، بدون اینکه نیازی به مقایسه‌های رشته‌ای درون اجراکننده باشد.

قرارداد توزیع فراخوانی

تنها نقطه‌ی ورود سیستم score(metric_name, prediction, targets) است. این تابع یک عدد اعشاری در بازه‌ی [0, 1] برمی‌گرداند. اجراکننده بر اساس نام معیار شاخه‌بندی (branch) نمی‌کند؛ فراخوانی را واگذار می‌کند و نتیجه را می‌نویسد. این همان رابطی است که در درس 75 به مشخصات وظیفه (task spec) از درس 70 متصل خواهد شد.

def score(metric_name, pred, targets):
    if metric_name == "exact_match":
        return exact_match(pred, targets)
    if metric_name == "f1":
        return max(f1_score(pred, t) for t in targets)
    if metric_name == "bleu_4":
        return max(bleu4(pred, t) for t in targets)
    if metric_name == "rouge_l":
        return max(rouge_l(pred, t) for t in targets)
    if metric_name == "accuracy":
        return accuracy(pred, targets)
    raise ValueError(f"unknown metric_name: {metric_name}")

معیار code_exec در درس 72 بررسی می‌شود و همان‌جا به توزیع‌کننده اضافه خواهد شد.

مواردی که این درس به آن‌ها نمی‌پردازد

این درس مدلی را فراخوانی نمی‌کند. خروجی‌ها را فراتر از قواعد پس‌پردازش درس 70 نرمال‌سازی نمی‌کند. بازه‌های اطمینان (confidence intervals) را حساب نمی‌کند. معیارهای BLEURT یا BERTScore را هم پیاده‌سازی نمی‌کند (چون آن‌ها به مدل نیاز دارند و در درس دیگری بررسی می‌شوند). تمرکز این درس روی کف ماجرا است: پنج معیار، یک توکن‌ساز، یک جدول توزیع.

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

فایل main.py هر معیار را در قالب یک تابع مستقل به همراه توزیع‌کننده تعریف می‌کند. بردارهای مرجع در بلوک _reference_examples در انتهای فایل قرار دارند. دموی برنامه، توزیع‌کننده را روی 8 مثال اجرا کرده و امتیاز هر معیار را چاپ می‌کند. تست‌های موجود در code/tests/test_metrics.py بردارهای مرجع را تثبیت می‌کنند و تمام حالت‌های مرزی (پیش‌بینی خالی، مرجع خالی، نبود توکن مشترک، تطابق دقیق و محدودسازی عبارت‌های تکراری) را تحت فشار می‌سنجند.

فایل main.py را از بالا به پایین بخوانید. توابع به ترتیب پیچیدگی چیده شده‌اند. exact_match و accuracy هر کدام یک خط هستند. تابع F1 شش خط است. بخش‌های سنگین کار BLEU و ROUGE-L هستند که توضیحات کاملی درباره‌ی قاعده‌ی هموارسازی و رابطه‌ی بازگشتی LCS در کامنت‌های آن‌ها آمده است.

گام‌های بعدی

معیارهای کلاسیک لازم هستند، اما کافی نیستند. آن‌ها به هم‌پوشانی ظاهری پاداش می‌دهند و از درک معنا بازمی‌مانند. راه‌حل این است که وقتی پایه‌ی معیارهای کلاسیک تثبیت شد، معیارهای مبتنی بر مدل (مثل BLEURT، BERTScore و GEval) را روی آن‌ها سوار کنید. این موضوع به درس‌های بعدی مربوط می‌شود. فعلاً همین پنج معیار را به کار بیندازید، آن‌ها را با تست‌ها تثبیت کنید؛ آن‌وقت یک پشته‌ی ارزیابی در اختیار دارید که قابل بررسی، سریع و تکرارپذیر است.

خودآزمایی

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

  1. پیش از درسچرا توکنایزر به‌جای لایه‌ی runner، در لایه‌ی معیار (metric) انتخاب می‌شود؟
  2. پیش از درسدقت n-gram اصلاح‌شده چه کاری انجام می‌دهد که دقت معمولی n-gram انجام نمی‌دهد؟
  3. حین درسچرا BLEU از جریمه‌ی کوتاهی طول (brevity penalty) استفاده می‌کند؟
  4. حین درسمعیار ROUGE-L چه چیزی را مقایسه می‌کند؟
  5. حین درسمعیار F1 در سطح توکن در کدام حالت مقدار 1.0 را برمی‌گرداند؟
  6. پس از درسچرا در تابع score، توزیع فراخوانی‌ها (dispatch) بر اساس metric_name انجام می‌شود نه category؟

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

چرا امتیاز یک معیار ارزیابی مانند BLEU یا ROUGE-L در کتابخانه‌های مختلف تفاوت دارد؟

تفاوت اصلی ناشی از نحوه‌ی پیش‌پردازش، انتخاب توکن‌ساز (Tokenizer)، تبدیل حروف و قواعد هموارسازی (Smoothing) است که هر پکیج به‌صورت پیش‌فرض متفاوتی پیاده می‌کند.

معیار ROUGE-L چگونه شباهت میان دو متن را اندازه می‌گیرد؟

این معیار با استفاده از برنامه‌ریزی پویا طولانی‌ترین زیردنباله‌ی مشترک (LCS) بین توکن‌های کاندید و مرجع را پیدا می‌کند و ترتیب کلمات را بدون اجبار به پیوستگی آن‌ها می‌سنجد.

نقش هموارسازی (Smoothing) در محاسبه‌ی BLEU-4 چیست؟

هموارسازی با افزودن یک واحد به صورت و مخرج کسر n-gramها مانع از این می‌شود که در صورت غیبت یک 4-gram مشترک، مقدار لگاریتم نامعتبر شده و امتیاز نهایی کل جمله صفر شود.

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

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