یک مدل زبانی پایه (Base Model) صرفاً یاد گرفته است کلمهی بعدی متن را حدس بزند؛ به همین دلیل اگر از آن سؤالی بپرسید، بهجای پاسخ دادن ممکن است سؤال دیگری به ادامهی آن بچسباند یا متنی بیربط ببافد. تبدیل این مدل پیشآموزشدیده به دستیاری که دستور کاربر را بفهمد و اجرا کند، وظیفهی فاینتیونینگ نظارتشده (Supervised Fine-Tuning یا SFT) است.
در این درس یاد میگیرید که چطور بدون نیاز به زیرساختهای سنگین و با ابزارهای استاندارد PyTorch، دادههای جفت «دستور-پاسخ» را قالببندی کنید و با ترفند ماسکگذاری در تابع تجمیع (Collate Function)، محاسبهی گرادیان را فقط به بخش پاسخ محدود نمایید. اجرای این سناریو به پردازندهی گرافیکی خاصی نیاز ندارد و کاملاً روی CPU سیستم شخصی قابل پیادهسازی و آزمایش است.
یک مدل پایهی پیشآموزشدیده میتواند یک توالی متنی را ادامه دهد، اما از عهدهی اجرای دستورها برنمیآید. فاینتیونینگ نظارتشده (Supervised Fine-Tuning یا SFT) کوچکترین تغییری است که این مشکل را حل میکند: جفتدادههایی شامل دستور و پاسخ مطلوب را به مدل بدهید و بدنهی آن را طوری آموزش دهید که توکنهای پاسخ را پیشبینی کند. ترفند کار این است: تابع زیان (loss) باید فقط پاسخ را محاسبه کند، نه دستور را. در این درس، یک حلقهی SFT به سبک Alpaca میسازیم که با استفاده از یک تابع تجمیع (collate function) اختصاصی، توکنهای دستور را با
ignore_index=-100ماسک میکند، مدل را روی 200 جفت «دستور-پاسخ» آموزش میدهد و آن را روی دادههای ارزیابی (held-out split) با معیار تطابق دقیق (exact-match) میسنجد.
اهداف یادگیری
- قالببندی جفتدادههای «دستور-پاسخ» در قالب یک توالی علّی (causal) واحد همراه با توکنهای مرزی مشخص.
- ساخت یک تابع تجمیع که توکنهای دستور را ماسک کند تا آنتروپی متقاطع (Cross-Entropy) فقط توکنهای پاسخ را به حساب بیاورد.
- آموزش بدنهی یک ترنسفورمر بسیار کوچک تحت هدف SFT و مشاهدهی تغییرات معیار ارزیابی.
- پیادهسازی تولید متن با روشهای رمزگشایی حریصانه (Greedy decoding) و نمونهگیری بر اساس دما (temperature sampling) با رعایت مرز شروع پاسخ.
- محاسبهی تطابق دقیق روی خروجیهای تولیدشده برای دادههای ارزیابی.
صورت مسئله
مدل پایهای که صرفاً با هدف پیشبینی توکن بعدی (Next-Token Prediction) آموزش دیده، هیچ درکی از «دستور» ندارد. اگر رشتهی "پایتخت فرانسه کجاست؟" را به آن بدهید، ادامهی سؤال را مینویسد یا جملهی جدیدی از خودش میسازد. این مدل زبان را میشناسد، اما از قرارداد قالببندی چیزی سر در نمیآورد.
قرارداد SFT یک قالب متنی (string template) است. هر نمونهی آموزشی به یک توالی واحد با سه بخش تبدیل میشود:
<INST> What is the capital of France? <RESP> The capital of France is Paris.
توکنهای مرزی، توکنهای ویژهای هستند که در زمان آموزش رزرو شدهاند. مدل یاد میگیرد که هر چیزی بعد از <RESP> پاسخ است و فقط همین بخشِ پاسخ نمرهدهی میشود. هدف پیشبینی توکن بعدیِ مدل پایه همچنان پابرجاست؛ مدل فقط روی مجموعهدادهای آموزش میبیند که تمام نمونههای آن چنین ساختاری دارند.
اما نکتهای وجود دارد: اگر کل توالی را به یک تابع زیان آنتروپی متقاطع معمولی بدهید، مدل را طوری آموزش میدهید که توکنهای دستور را هم پیشبینی کند؛ در حالی که دستور از قبل مشخص است و شما گرادیان صفر را برای آن جایگاهها میخواهید. چارهی کار استفاده از ماسک است.
مفهوم اصلی
قابلیت ignore_index در torch.nn.functional.cross_entropy دقیقاً برای همین کار است. هر موقعیت هدف (target) که مقدارش برابر با ignore_index باشد، زیان و گرادیان آن صفر در نظر گرفته میشود. مقدار قراردادی این متغیر در PyTorch برابر با -100 است. تابع تجمیع برای هر نمونه دو تنسور میسازد: input_ids (کل توالی) و labels (نسخهای از input_ids که جایگاههای دستور در آن با -100 بازنویسی شدهاند).
مدل در طول گذر رو به جلو (forward pass) کل توالی را میبیند؛ بنابراین اتنشن میتواند به دستور دقت کند. اما تابع زیان فقط توکنهای پاسخ را به حساب میآورد. این دقیقاً همان چیزی است که میخواهید: شرطیسازی روی دستور، پیشبینی برای پاسخ.
دادهها
200 جفت «دستور-پاسخ» بهصورت قطعی (deterministic) در main.py تولید میشوند. این دادهها 6 نوع وظیفه (task) را پوشش میدهند:
- اطلاعات واقعی تکشات (پایتخت فلان کشور)
- محاسبات ریاضی
- استخراج فهرست
- خلاصهسازی تکجملهای
- کدنویسی (print و sort)
- تعریف مفاهیم
هر وظیفه یک دستور قالببندیشده و یک پاسخ قطعی دارد. این سادهسازی عمدی است. تطابق دقیق معیاری شکننده است و ما در این درس از سناریویی استفاده میکنیم که پاسخ صحیح آن دقیقاً یک رشتهی مشخص است. مجموعهدادههای واقعی SFT به معیارهای فازی و منعطف نیاز دارند؛ با این حال، اصل ماجرا کاملاً یکسان است.
دادهها به دو بخش 160 تایی برای آموزش و 40 تایی برای تست تقسیم میشوند. مجموعهی تست هر 6 نوع وظیفه را در بر میگیرد تا بتوانیم تطابق دقیق را به تفکیک هر دستهبندی گزارش کنیم.
توکنسازی و پدینگ
توکنساز در سطح بایت (byte-level) کار میکند و 3 توکن ویژه دارد:
INST_ID = 256: شروع بخش دستور را مشخص میکند.RESP_ID = 257: مرز میان دستور و پاسخ را تعیین میکند.PAD_ID = 258: پدینگ (حاشیهگذاری) برای یکدست کردن طول دستهها.
توالی بهصورت [INST] inst_bytes [RESP] resp_bytes [PAD]* ساخته میشود. تابع تجمیع این مراحل را طی میکند:
- هر نمونه را توکنسازی میکند.
- تمام نمونههای دسته را با پدینگ به اندازهی طولانیترین توالیِ همان دسته میرساند.
- تنسور
labelsرا میسازد که برابر است باinput_idsشیفتیافته بهاندازهی یک گام (هدف causal LM)، همراه با اعمال قواعد زیر:- بخش دستور با
-100جایگزین میشود. - بخش پدینگ با
-100جایگزین میشود. - جایگاه خودِ توکن مرزی
RESP_IDهم با-100جایگزین میشود (مدل را طوری آموزش نمیدهید که توکن مرزی را پیشبینی کند، بلکه باید آنچه بعد از آن میآید را پیشبینی کند).
- بخش دستور با
این جابهجایی، ترفند استاندارد مدلهای علّی است: موقعیت i در input_ids موقعیت i+1 را پیشبینی میکند؛ بنابراین labels[i] = input_ids[i+1] (که در آن آخرین موقعیت از ورودی و اولین موقعیت از هدف حذف میشود). ماسک بعد از این شیفت اعمال میشود تا دقیقاً روی موقعیتهای درست بنشیند.
آموزش
حلقهی آموزش، همان حلقهی استاندارد SFT در PyTorch است: Adam، نرخ یادگیری حدود 3e-4 تا 1e-3، 10 تا 20 اپوک روی این دادهها، و بدون اسکژولر. مدل به اندازهای کوچک است (ابعاد مخفی 96، 2 بلوک، حداکثر طول 64) که ظرف کمتر از 2 دقیقه روی CPU همگرا میشود.
هر 5 اپوک یک بار، حلقه یک مرحلهی ارزیابی کوتاه روی دادههای held-out اجرا میکند و تطابق دقیق را نمایش میدهد. تماشای افزایش تطابق دقیق از 0.0 در اپوک اول به حدود 0.85 در اپوک پانزدهم، پاداش زحمات این درس است: میبینید که مدل همزمان هم قالب پاسخدهی و هم خود پاسخها را یاد میگیرد.
تولید متن
در زمان ارزیابی، پیشوند دستور یعنی [INST] inst_bytes [RESP] به مدل داده میشود و مدل توکنها را تا زمانی تولید میکند که یکی از این دو شرط رخ دهد:
- طول توالی به
max_lenبرسد، یا - مدل نشانهی توقف تعریفشده را تولید کند: 2 بایت متوالیِ پایانبخش جمله (
.،!،?).
در این درس کد رمزگشایی حریصانه به همراه یک نمونهگیر اختیاری با دما ارائه شده است. برای تطابق دقیق از رمزگشایی حریصانه استفاده میشود؛ چون دما معیار را تصادفی و متغیر میکند. سیستمهای واقعی معمولاً ابتدا نمونهگیری میکنند و سپس ارزیابی فازی انجام میدهند؛ این پایپلاین در درس 41 بررسی میشود.
ارزیابی تطابق دقیق
تطابق دقیق سختگیرانهترین معیار سنجش متن است. رشتهی پاسخ تولیدشده نرمالسازی میشود (تبدیل به حروف کوچک، حذف فاصلههای اضافی ابتدا و انتها، ادغام فاصلههای دوتایی) و با پاسخ مرجع که به همین ترتیب نرمال شده مقایسه میشود. خروجی این معیار برای هر نمونه یا 1 است یا 0، و میانگین آنها نمرهی نهایی را میسازد.
پایپلاینهای واقعی SFT، تطابق دقیق را با F1 در سطح توکن (درس 41) و یک مدل داور تکمیل میکنند. با این حال تطابق دقیق همچنان مفید است؛ چون هیچ ابهامی ندارد: اگر 0.7 را نشان دهد، یعنی دقیقاً 70 درصد از دستورهای آزمایشی پاسخی موبهمو و نویسهبهنویسه مطابق با پاسخ الگو تولید کردهاند.
خروجی این درس
پیادهسازی ما شامل یک فایل main.py به همراه تستها است:
InstructionTokenizer: انکودر سطح بایت با توکنهای ویژهی رزروشده که هم پیشوند دستور و هم جفت کامل را کدگذاری میکند.make_dataset: تولید 200 جفتداده در 6 نوع وظیفه با seed ثابت.SFTDataset: خروجی(input_ids, labels)را برای هر نمونه، همراه با ماسکِ آماده برمیگرداند.sft_collate: پدینگ پویا، ساخت تنسور دسته، و تنظیم مقدار-100روی موقعیتهای دستور و پدینگ.TinyGPT: بدنهی ترنسفورمر به همراه سر مدل زبانی (LM head) با وزنهای مقید (tied) یا مستقل.train_sft: حلقهی آموزش SFT به همراه هوکهای ارزیابی در انتهای هر اپوک.generate: رمزگشایی علّی بر اساس پیشوند، به روش حریصانه یا نمونهگیریشده، به همراه منطق توقف.exact_match: مقایسهی رشتههای نرمالشده که یک مقدار اعشاری در بازهی[0, 1]برمیگرداند.run_demo: ساخت دادهها، 20 اپوک آموزش، ارزیابی، چاپ تفکیکی نتایج برای هر دسته، و خروج با کد صفر در صورت موفقیت.
چرا ماسک اهمیت دارد؟
بدون ماسک، تابع زیان با توکنهای دستور مثل توکنهای هدف رفتار میکند؛ در نتیجه مدل یاد میگیرد خودِ دستور را پیشبینی کند. این یعنی یک هدف کاملاً متفاوت که به دو دلیل مدل ضعیفتری تحویل میدهد: اول اینکه ظرفیت مدل صرف بازسازی ورودیهایی میشود که کاربر همیشه خودش وارد میکند. دوم اینکه در جمع گرادیانها، زیانِ مربوط به پاسخ سهم کمتری خواهد داشت؛ چون در بیشتر دستهها تعداد توکنهای دستور از پاسخ بیشتر است. بنابراین، نرخ یادگیری مؤثر بهینهساز روی بخشی که واقعاً برایتان اهمیت دارد، کمتر از حد انتظار خواهد بود. ماسک صرفاً یک کار تمیزکاری ظاهری نیست؛ خودِ هدف یادگیری است.
اهداف تکمیلی
- افزودن وارمآپ (پیشگرمایش) به نرخ یادگیری و سپس کاهش کسینوسی (cosine decay). فاینتیونینگ نظارتشده نسبت به پیشآموزش حساسیت بسیار بیشتری به نرخ یادگیری دارد.
- ثبت زیان به ازای هر توکن و رسم نمودار تابع زیان در طول آموزش. دقت کنید که در اپوکهای اولیه، توکنهای قالب (
<RESP>و پیشوندهای رایج) بر تابع زیان غلبه دارند و در اپوکهای بعدی، توکنهای پاسخ واقعی بخش اصلی را تشکیل میدهند. - ارتقای ارزیابی با معیارهای BLEU-1 یا chrF. تطابق دقیق توانایی مدلهایی را که پاسخی یکسان اما با بیانی دیگر ارائه میدهند، دستکم میگیرد.
- افزودن الگوی چت (chat template) با قالببندی چندنوبته (multi-turn) و آموزش روی دادههایی که شامل دنبالهی گفتوگو هستند.
این پیادهسازی قرارداد قالب، ماسک و حلقه را در اختیارتان میگذارد. تغییر هدف یادگیری از یک مدل پایه به مدلی که دستورها را اجرا میکند، فقط در منطق یک تابع تجمیع خلاصه میشود.
