فاز 19 · درس 38

فاین‌تیونینگ دسته‌بند متنی با تعویض سر مدل زبانی

نوع
ساختنی
زمان
حدود 90 دقیقه
زبان‌ها
Python (torch, numpy)
یک مجسمه مینیاتوری سفالی با سر جداشدنی که سرِ قدیمی آن کنار گذاشته شده و یک مکعب هندسی مدرن به جای سر روی تنه قرار می‌گیرد
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. مفهوم اصلی
  4. مسئله‌ی ادغام (Pooling)
  5. داده‌ها
  6. معیارهای ارزیابی
  7. معماری
  8. در این درس چه چیزی می‌سازید
  9. چرا این مقایسه اهمیت دارد؟
  10. اهداف چالشی

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

در این درس یاد می‌گیرید چگونه سرِ خروجی ترنسفورمر را با یک لایه‌ی دسته‌بندی جایگزین کنید و تفاوت‌های عملی دو راهبرد «آموزش فقط سر با بدنه‌ی منجمد» و «فاین‌تیونینگ کامل» را با معیارهای Precision، Recall و F1 روی مجموعه‌ی ارزیابی بسنجید. معماری این درس عمداً بسیار سبک پیاده‌سازی شده است تا بدون نیاز به GPU ابری یا زیرساخت‌های پرهزینه، مستقیماً روی پردازنده‌ی سیستم شخصی شما در کمتر از دو دقیقه اجرا شود.

نخستین پروژه‌ی پایانی مسیر B. یک مدل زبانی پیش‌آموزش‌دیده، دسته‌ای از بلوک‌های خوداتنشن (self-attention) است که به یک سرِ پیش‌بینی توکن ختم می‌شود. وقتی هدفْ تشخیص هرزنامه (spam) از پیام عادی (ham) باشد، این سرِ خروجی دیگر به کار نمی‌آید، اما بدنه‌ی مدل بیشترِ مسیر را درست رفته است. در این درس، سرِ خروجی را برمی‌داریم، یک لایه‌ی خطی دوکلاسه روی نمایش ادغام‌شده (pooled representation) می‌گذاریم و دسته‌بند را به دو شیوه‌ی متفاوت آموزش می‌دهیم: فقط لایه‌ی پایانی، و فاین‌تیونینگ کامل. ارزیابی نهایی با دقت (precision)، بازخوانی (recall) و امتیاز F1 روی مجموعه‌ی داده‌ی کنارگذاشته‌شده (held-out) انجام می‌شود. در این روند یاد می‌گیرید هر استراتژی چه دستاوردی دارد و هزینه‌ی آن چقدر است.

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

  • تعویض سرِ مدل زبانی با سرِ دسته‌بندی بدون مقداردهی اولیه‌ی مجدد به بدنه‌ی مدل.
  • پیاده‌سازی دو سازوکار آموزشی: بدنه‌ی منجمد (فقط سرِ مدل) و فاین‌تیونینگ کامل با یک حلقه‌ی آموزشی مشترک.
  • ساخت یک پایپ‌لاین داده‌ی آگاه به توکنایزر که پدینگ (حاشیه‌گذاری) را انجام دهد، بخش‌های پدینگ را ماسک کند و خروجی‌های اتنشن را ادغام (pool) کند.
  • محاسبه‌ی دقت، بازخوانی، F1 و ماتریس درهم‌ریختگی (confusion matrix) مستقیماً از روی لاجیت‌های خام.
  • استدلال و تحلیل مصالحه میان تعداد پارامترها، زمان آموزش و پتانسیل عملکردی مدل (head-room).

صورت مسئله

فرض کنید یک ترنسفورمر کوچک را روی یک پیکره‌ی متنی عمومی پیش‌آموزش داده‌اید. سرِ خروجی، آخرین وضعیت پنهان (hidden state) را روی واژگانی با 1000 توکن نگاشت می‌کند. حالا 800 پیامک با برچسب هرزنامه یا پیام عادی در دست دارید و می‌خواهید یک دسته‌بند دودویی بسازید. برای این کار سه مسیر پیش رو دارید.

مسیر اشتباه این است که یک دسته‌بند جدید را از صفر روی همین 800 نمونه آموزش دهید. بدنه‌ی مدل پیش‌آموزش‌دیده ساختارهای مفیدی را از قبل در خود دارد: هویت واژه‌ها، موقعیت مکانی و هم‌آیی‌های ساده‌ی کلمات. دور انداختن این ساختارها، کل توان پردازشیِ صرف‌شده برای ساخت آن‌ها را هدر می‌دهد.

دو گزینه‌ی درست عبارت‌اند از: تعویض سر با بدنه‌ی منجمد، و تعویض سر با بدنه‌ی آموزش‌پذیر. آموزشِ فقط سر بسیار سریع است، تقریباً هیچ حافظه‌ای مصرف نمی‌کند و با چنین حجم کمی از داده به‌ندرت دچار بیش‌برازش (overfitting) می‌شود. در مقابل، فاین‌تیونینگ کامل کُندتر است، ممکن است روی داده‌های اندک بیش‌برازش کند، اما وقتی دامنه‌ی داده‌های مقصد با پیکره‌ی پیش‌آموزش فاصله می‌گیرد، دقت بالاتری به دست می‌دهد.

در این درس هر دو رویکرد را پیاده‌سازی می‌کنیم تا بتوانید آن‌ها را در شرایطی کاملاً یکسان مقایسه کنید.

مفهوم اصلی

توکن‌هاامبدینگ‌های توکن + موقعیتبدنه ترنسفورمرN بلوکقدیمی: سر LMتصویر کردن روی واژگانجدید: سربرگ دسته‌بندخطی به 2 لاجیتتابع زیان (loss) آنتروپی متقاطعدر برابر برچسب

مدل تابعی به شکل f_theta(tokens) -> hidden_states است. سرِ مدل نیز تابعی به شکل g_phi(hidden) -> logits است. تعویض سر یعنی theta را نگه داریم و g_phi را جایگزین کنیم. بخش پرهزینه‌ی کار، پارامترهای بدنه است؛ در حالی که سرِ مدل فقط یک لایه‌ی خطی ساده است.

دو دسته‌ پارامتر آموزش‌پذیر اهمیت دارند:

  • theta (بدنه): ده‌ها هزار وزن در هر بلوک اتنشن.
  • phi (سر): وزن‌هایی به ابعاد hidden_dim * num_classes به‌علاوه‌ی یک بایاس (bias).

در آموزشِ فقط سر، گرادیان‌ها را نسبت به phi محاسبه می‌کنید و مقدار آن‌ها را برای theta صفر نگه می‌دارید. در PyTorch این کار با تنظیم requires_grad=False روی پارامترهای بدنه انجام می‌شود. به این ترتیب بهینه‌ساز فقط سرِ مدل را می‌بیند و بدنه دست‌نخورده و منجمد باقی می‌ماند.

در فاین‌تیونینگ کامل، اجازه می‌دهید گرادیان‌ها در تمام لایه‌ها به سمت عقب جریان یابند. وزن‌های بدنه تغییر می‌کنند تا با هدف دسته‌بندی سازگار شوند. خطری که روی داده‌های کم وجود دارد، فراموشی فاجعه‌بار (catastrophic forgetting) است: دانش قبلی بدنه زیر نویز ناشی از بیش‌برازش شسته می‌شود و از بین می‌رود.

مسئله‌ی ادغام (Pooling)

دسته‌بند به ازای هر توالی به یک بردار نیاز دارد، نه یک بردار به ازای هر توکن. سه انتخاب متداول وجود دارد:

  • ادغام میانگین (Mean pool): میانگین‌گیری از وضعیت‌های پنهان در طول توالی با وزن‌دهی بر اساس ماسک اتنشن.
  • ادغام توکن CLS: قرار دادن یک توکن ویژه در ابتدای توالی و استفاده‌ی انحصاری از خروجی همان توکن؛ کاری که BERT انجام می‌دهد.
  • ادغام آخرین توکن (Last-token pool): استفاده از آخرین توکن غیرپدینگ؛ کاری که دسته‌بندهای خانواده‌ی GPT انجام می‌دهند.

در این درس از روش ادغام میانگین همراه با وزن‌دهی صریحِ ماسک اتنشن استفاده می‌کنیم. این رویکرد ساده‌ترین گزینه است، سیگنال پایداری در طول‌های مختلف توالی به دست می‌دهد و نیازی هم به پیش‌آموزشِ توکن اختصاصی CLS ندارد.

وضعیت‌های پنهانB x T x Dماسک کردن پدهامجموع روی Tتقسیم برتعداد غیر پدتجمیع‌شده (Pooled)B x Dسربرگ دسته‌بندD x 2

داده‌ها

800 پیامک، کاملاً متوازن (400 هرزنامه و 400 پیام عادی)، به‌طور قطعی در code/main.py تولید می‌شوند. این مولد از یک سید (seed) ثابت استفاده می‌کند، قالب‌هایی را برمی‌گزیند، جایگاه‌های متغیر را پر می‌کند و پیام‌هایی با طول 5 تا 25 توکن تحویل می‌دهد. داده‌های دنیای واقعی نویزهایی دارند که در این مجموعه‌ی نمونه دیده نمی‌شود؛ هدف اصلی این مجموعه، تضمین بازتولیدپذیری (reproducibility) است.

داده‌ها با نسبت 80 به 20 تقسیم می‌شوند: 640 نمونه برای آموزش و 160 نمونه برای آزمون. این تقسیم‌بندی به‌صورت طبقه‌بندی‌شده (stratified) انجام می‌شود تا مجموعه‌ی آزمون توازن 50/50 خود را حفظ کند. وقتی مجموعه‌ی کنارگذاشته‌شده توازن مشخصی داشته باشد، دقت و بازخوانی اعدادی واقعی و قابل‌اعتماد ارائه می‌دهند.

معیارهای ارزیابی

در دسته‌بندی دودویی با فرض کلاس 1 به عنوان کلاس مثبت (هرزنامه)، شمارش‌ها به این ترتیب است:

  • TP: پیش‌بینی‌شده هرزنامه، در واقعیت هرزنامه بوده است.
  • FP: پیش‌بینی‌شده هرزنامه، در واقعیت پیام عادی بوده است.
  • FN: پیش‌بینی‌شده پیام عادی، در واقعیت هرزنامه بوده است.
  • TN: پیش‌بینی‌شده پیام عادی، در واقعیت پیام عادی بوده است.

سه معیار ارزیابی اصلی:

  • precision = TP / (TP + FP): از میان پیام‌هایی که هرزنامه علامت خورده‌اند، چه نسبتی واقعاً هرزنامه‌اند؟
  • recall = TP / (TP + FN): از کل هرزنامه‌های واقعی، مدل چه نسبتی را شناسایی کرده است؟
  • F1 = 2 * P * R / (P + R): میانگین همساز (هارمونیک) دقت و بازخوانی.

ماتریس درهم‌ریختگی این چهار مقدار را در یک جدول 2 در 2 نمایش می‌دهد. برنامه‌ی دمو این جدول را برای هر دو روش آموزشی در خروجی استاندارد چاپ می‌کند.

تعویض سربرگ دسته‌بندانجماد بدنه · تعویض سربرگ
CLASSIFIER HEAD SWAPA pretrained model is a frozen body plus a head. To classify, you keep the expensive body and lift off the vocabulary head, gluing on a small linear layer to two logits. Train head-only for speed or unfreeze the body for accuracy when the domain drifts.بدنه ترنسفورمر(منجمد)بلاک 1بلاک 2بلاک 3بلاک 4پولینگ‌شدهسربرگ قدیمی LMتصویر کردن واژگانسربرگ جدید دسته‌بندخطی ← 2 لاجیت
یک مدل از‌پیش‌آموزش‌دیده متشکل از یک بدنه منجمد به همراه یک سربرگ است. برای دسته‌بندی، بدنه پرهزینه را نگه می‌دارید، سربرگ واژگان را برمی‌دارید و یک لایه خطی کوچک با دو لاجیت جایگزین آن می‌کنید. برای سرعت فقط سربرگ را آموزش دهید یا در صورت تغییر دامنه، برای دقت بیشتر بدنه را از انجماد خارج کنید.

معماری

فقط سرتنظیم دقیق کاملمجموعه داده پیامک800 برچسب‌خوردهByteTokenizerواژگان 260ClassificationDatasetپد + ماسکDataLoaderدسته‌ایدسته‌بندبدنه + میانگین‌گیری + سرتابع زیان (loss) آنتروپی متقاطعبهینه‌ساز AdamارزیابP / R / F1

بدنه‌ی این مدل عمداً یک ترنسفورمر بسیار کوچک طراحی شده است: اندازه‌ی واژگان 260، بعد وضعیت پنهان 64، دارای 4 سرِ اتنشن، 2 بلوک و حداکثر طول توالی 32. این ساختار آن‌قدر سبک است که اجرای هر دو شیوه‌ی آموزشی تا رسیدن به همگرایی روی CPU زیر 90 ثانیه زمان می‌برد. در این درس بدنه از قبل آموزش ندیده است؛ در عوض، تابع کمکی pretrain_quick پنج دور (epoch) آموزش مدل زبانی روی متن همین داده‌ها اجرا می‌کند تا بدنه نقطه‌ی شروع معناداری داشته باشد و درس کاملاً مستقل و خودکفا بماند.

در این درس چه چیزی می‌سازید

پیاده‌سازی شامل یک فایل main.py و یک ماژول تست (code/tests/test_main.py) است:

  1. ByteTokenizer: نگاشت بایت‌ها به شناسه‌ها و رزرو شناسه‌ی پدینگ.
  2. Block: یک بلوک ترنسفورمر همراه با اتنشن چندسر (multi-head attention) و یک لایه‌ی پیش‌خور با پیش‌نرمال‌سازی (pre-norm).
  3. LMBody: امبدینگ‌های توکن و موقعیت مکانی به‌همراه دسته‌ای از بلوک‌ها؛ این بخش وضعیت‌های پنهان را برمی‌گرداند.
  4. MeanPool: میانگین‌گیریِ وزن‌دهی‌شده با ماسک روی محور توالی.
  5. Classifier: شامل بدنه، بخش ادغام و سرِ خطی. نمونه‌ی بدنه در هر دو رویکرد آموزشی کاملاً یکسان است.
  6. freeze_body و unfreeze_body: تغییر وضعیت requires_grad برای پارامترهای بدنه.
  7. train_classifier: یک حلقه‌ی آموزشی مشترک که مدل و بهینه‌سازِ پیکربندی‌شده برای هر گروه پارامترِ آموزش‌پذیر را دریافت می‌کند.
  8. evaluate: ارزیابی مجموعه‌ی آزمون و بازگرداندن Metrics(precision, recall, f1, confusion).
  9. run_demo: پیش‌آموزش کوتاه بدنه، سپس آموزش و ارزیابی در حالت فقط سر، اجرای آموزش کامل، چاپ گزارش هر دو حالت و خروج با کد صفر.

چرا این مقایسه اهمیت دارد؟

آموزشِ فقط سر معمولاً سریع‌تر پیش می‌رود و کم‌برازشِ کنترل‌شده‌تر و تمیزتری دارد. روی این داده‌ها، پس از 20 دور آموزشِ فقط سر، معمولاً به دقتی نزدیک به 0.9 و بازخوانی در حدود 0.85 می‌رسید. فاین‌تیونینگ کامل تقریباً سه برابر بیشتر زمان می‌برد و بسته به سید تصادفی، نتیجه‌اش ممکن است یکی دو درصد بالا یا پایین شود.

این درس قصد ندارد یکی از دو روش را به عنوان برنده‌ی قطعی معرفی کند؛ هدف این است که خواندن آمارها و ارزیابی هزینه‌ها را یاد بگیرید. برای 800 نمونه داده با یک بدنه‌ی کوچک، آموزشِ فقط سر تصمیم درستی است؛ اما با 80000 نمونه و یک بدنه‌ی بزرگ‌تر، فاین‌تیونینگ کامل بازدهی واقعی‌اش را نشان می‌دهد. دستاورد کاربردی شما از این درس، ساختار API است: همان تابع train_classifier هر دو حالت را مدیریت می‌کند و جابه‌جایی میان آن‌ها تنها با یک فراخوانی ساده انجام می‌شود.

اهداف چالشی

  • حالت آموزشی سومی اضافه کنید که فقط آخرین بلوک را از حالت انجماد خارج کند. این روش که گاهی فاین‌تیونینگ جزئی (partial fine-tuning) نامیده می‌شود، هزینه‌اش از فاین‌تیونینگ کامل کمتر است و نسبت به حالت فقط سر ظرفیت یادگیری بیشتری دارد.
  • یک اسکژولر نرخ یادگیری اضافه کنید. استفاده از برنامه‌ی زمان‌بندی کسینوسی (cosine schedule) برای سر مدل در کنار یک نرخ ثابتِ کوچک‌تر برای بدنه، ساختاری متداول در محیط‌های عملیاتی (پروداکشن) است.
  • ادغام میانگین را با یک ادغام مبتنی بر اتنشن یادگرفتنی جایگزین کنید: یک لایه‌ی اتنشن کوچک همراه با یک پرس‌وجوی (query) یادگرفتنی. این ترفند معمولاً روی توالی‌های طولانی‌تر بهتر از ادغام میانگین عمل می‌کند.

پیاده‌سازی این درس بستر و ابزارهای لازم را فراهم می‌کند و آزمون‌ها درستی قراردادها را می‌سنجند. این‌که ارقام نهایی را تا کجا push کنید، بر عهده‌ی شماست.

خودآزمایی

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

  1. پیش از درسپیش از مطالعه: یک مدل زبانی از‌پیش‌آموزش‌دیده دارای بدنه‌ای با 5 میلیون پارامتر و یک سربرگ خروجی است که به واژگانی 30 هزارتایی نگاشت می‌کند. شما یک دسته‌بند دوکلاسه می‌خواهید. منطقی‌ترین اقدام اول کدام است؟
  2. حین درسچرا در این درس از میانگین‌گیری با وزن‌دهی attention-mask به جای میانگین ساده در راستای محور توالی استفاده می‌شود؟
  3. حین درسدر `train_classifier`، بهینه‌ساز با `[p for p in model.parameters() if p.requires_grad]` ساخته می‌شود. این الگو چه چیزی را تضمین می‌کند؟
  4. حین درساگر دقت 0.9 و بازخوانی 0.6 روی کلاس هرزنامه باشد، مقدار F1 چقدر است؟
  5. پس از درسشما مدل را با فاین‌تیونینگ کامل و با همان نرخ یادگیریِ آموزشِ فقط-سربرگ بازآموزی می‌کنید. مقدار F1 از 0.90 به 0.55 سقوط می‌کند. محتمل‌ترین دلیل چیست؟
  6. پس از درستیم شما می‌خواهد دسته‌بند را دیپلوی کند و می‌پرسد چک‌پوینت پروداکشن تحت کدام رژیم آموزشی تولید شده است. کدام دست‌سازه پاسخ این سؤال را به شکل تکرارپذیر ارائه می‌دهد؟

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

چه تفاوتی میان فاین‌تیونینگ کامل و آموزش فقط سر مدل وجود دارد؟

در آموزش فقط سر، پارامترهای بدنه‌ی ترنسفورمر منجمد می‌شوند (requires_grad=False) و تنها وزن‌های لایه‌ی خطی خروجی به‌روزرسانی می‌شوند که سریع‌تر است و مانع بیش‌برازش روی داده‌های کم می‌شود؛ اما در فاین‌تیونینگ کامل، گرادیان در تمام لایه‌های بدنه نیز جریان یافته و وزن‌ها برای داده‌ی مقصد بازتنظیم می‌شوند.

چرا در دسته‌بندی متن از ادغام میانگین (Mean Pooling) استفاده می‌شود؟

مدل ترنسفورمر به ازای هر توکن یک بردار وضعیت پنهان تولید می‌کند، اما دسته‌بند برای کل توالی به یک بردار واحد نیاز دارد. ادغام میانگینِ وزن‌دهی‌شده با ماسک اتنشن، خروجی پایداری در طول‌های مختلف توالی بدون نیاز به توکن اختصاصی CLS فراهم می‌کند.

چگونه خطر فراموشی فاجعه‌بار در داده‌های کم کاهش می‌یابد؟

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

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

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