تنظیم نرخ یادگیری (learning rate) حساسترین عامل در پایدار ماندن آموزش مدلهای زبانی است. در گامهای اولیه، به دلیل تصادفی بودن وزنها و نوسان شدید در تخمین گشتاورهای بهینهساز، اعمال نرخ یادگیری بالا فوراً باعث واگرایی یا گیرافتادن در کمینههای محلی نامناسب میشود. الگوی استاندارد در مدلهای امروزی، شروع ملایم با پیشگرمایش خطی (linear warmup) و سپس کاهش پیوسته با تابع کسینوسی (cosine decay) است.
این پیادهسازی به وابستگی خارجی پیچیدهای نیاز ندارد و منطق ریاضی آن بهصورت کاملاً مستقل و قابلاجرا روی CPU یا GPU محلی پیاده میشود؛ بنابراین برای بررسی رفتار و اعتبارسنجی آن نیازی به زیرساختهای ابری گرانقیمت یا دسترسی به سرویسهای پردازشی خارجی ندارید و آزمایشها را روی سیستم شخصی نیز میتوان اجرا کرد.
در این درس یاد میگیرید که چگونه یک تابع زمانبندی کسینوسی دقیق با وارمآپ خطی بنویسید، خطاهای محاسباتی متداول در نقاط مرزی را خنثی کنید و همزمان با پایش نُرم گرادیان، رفتار بهینهساز AdamW را در محیط عملیاتی زیر نظر بگیرید.
برنامهی زمانبندی نرخ یادگیری (learning-rate schedule) پس از تابع زیان (loss function)، دومین تصمیم حیاتی در آموزش مدل است. امروزه ترکیب بهینهساز AdamW با افت کسینوسی و وارمآپ (پیشگرمایش) خطی، گزینهی پیشفرض برای آموزش مدلهای زبانی است؛ چرا که به مدل اجازه میدهد در طول 1000 آپدیت اول که ساختار شکنندهای دارد، با گامهای مؤثر و کوچک پیش برود، سپس نرخ یادگیری را تا یک نقطهی اوج تنظیمشده بالا ببرد و در نهایت با شیبی ملایم آن را دوباره به سمت صفر کاهش دهد. در این درس، این برنامهی زمانبندی را میسازیم، نمودار آن را بر حسب گامهای آموزش رسم میکنیم، نُرم گرادیانها را کنار نرخ یادگیری لاگ میاندازیم و اثبات میکنیم که این برنامه به مرزهای وارمآپ، نقطهی اوج و افت پایبند میماند.
اهداف یادگیری
- پیادهسازی بهینهساز AdamW متصل به برنامهی زمانبندی کسینوسی (cosine schedule) نرخ یادگیری همراه با وارمآپ خطی.
- محاسبهی مقدار دقیق برنامهی زمانبندی در هر گام، بدون ایجاد خطای انباشتهی ممیز شناور (floating-point drift) در اجراهای مختلف.
- ثبت لاگ نُرم L2 گرادیان در کنار نرخ یادگیری، برای نظارت دقیق بر سلامت روند آموزش.
- ترسیم خروجی برنامهی زمانبندی بهصورت یک نمودار متنیِ قابلخواندن برای انسان و یک فایل CSV قابلاستفاده برای ابزارهای دیگر.
صورت مسئله
1000 آپدیت اول آموزش، پرنوسانترین مرحله هستند. وزنهای مدل هنوز به مقادیر اولیهسازی نزدیکاند. تخمین گشتاور دوم بهینهساز هنوز به پایداری نرسیده است. نُرم گرادیان نیز بزرگ و پر از نوفه (noise) است. اگر در این آپدیتهای اولیه نرخ یادگیری در نقطهی اوج خود باشد، مدل یا کلاً واگرا میشود یا در یک فلات زیان (loss plateau) گیر میافتد که هیچوقت از آن خارج نخواهد شد. دو راهکار شناختهشده برای این مشکل وجود دارد: برش گرادیان (gradient clipping) که موضوع درس 45 از فاز 19 است، و برنامهی زمانبندی نرخ یادگیری که از مقادیر اندک شروع میکند و بهمرور بالا میرود.
برنامهی زمانبندی کسینوسی همراه با وارمآپ از سه بخش تشکیل شده است. از گام صفر تا گام warmup_steps، نرخ یادگیری بهصورت خطی از صفر تا مقدار اوج تعیینشده (lr_max) بالا میرود. از گام warmup_steps تا گام total_steps، نرخ یادگیری از نیمهی بالایی یک منحنی کسینوسی پیروی میکند و از lr_max تا lr_min کاهش مییابد. پس از total_steps، نرخ یادگیری روی مقدار lr_min ثابت میماند تا اگر حلقهی آموزش اشتباه پیکربندی شده بود و از حد مجاز فراتر رفت، بدون سروصدا از محدودهی برنامه خارج نشود.
چالش پیادهسازی اینجاست که برنامههای زمانبندی بهسادگی دچار خطای یکیکموبیش (off-by-one) میشوند. این خطا ممکن است بعد از شش ساعت از شروع آموزش، خود را بهصورت نرخ یادگیریای نشان دهد که درست در لحظهی شروع بیشبرازش (overfitting) مدل، 1 درصد بالاتر یا پایینتر از مقدار واقعی است؛ مشکلی که تا وقتی رفتار برنامه را در نقاط مرزی بهطور کامل تست نکنید، پنهان میماند.
مبانی نظری
فرمول وارمآپ
بهازای هر step در بازهی [0, warmup_steps] با شرط warmup_steps > 0، نرخ یادگیری برابر با lr_max * step / warmup_steps است. حالت خاص warmup_steps = 0 بهعنوان «بدون وارمآپ» در نظر گرفته میشود: برنامه دقیقاً در گام صفر از lr_max شروع میکند و فوراً وارد افت کسینوسی میشود. برخی بسترهای آزمایشی (test harnesses) مقدار warmup_steps = 0 را ارسال میکنند تا مطمئن شوند برنامه در این حالت هم خروجی معتبری تولید میکند.
فرمول کسینوسی
بهازای هر step در بازهی (warmup_steps, total_steps]، نرخ یادگیری برابر است با:
lr_min + 0.5 * (lr_max - lr_min) * (1 + cos(pi * progress))
که در آن progress = (step - warmup_steps) / max(1, total_steps - warmup_steps) است. در نقطهی step = warmup_steps، مقدار کسینوس cos(0) = 1 میشود که نتیجهی آن دقیقاً lr_max است و با اندپوینت (endpoint) وارمآپ همخوانی کامل دارد. در نقطهی step = total_steps، مقدار کسینوس cos(pi) = -1 میشود که نتیجهاش lr_min است و کاملاً با اندپوینت افت منطبق میشود.
پیوستگی در هر دو اندپوینت اتفاقی نیست؛ دقیقاً به همین دلیل کل این برنامهی زمانبندی را بهصورت یک تابع واحد بر حسب step پیادهسازی میکنیم، نه ترکیب سرهمبندیشدهی سه تابع مجزا. وقتی توابع جداگانه را به هم وصلهپینه کنید، با اولین تغییر در lr_max یکی از این مرزها بههم میریزد.
کف نرخ یادگیری پس از اتمام گامها
بهازای step > total_steps، نرخ یادگیری روی lr_min باقی میماند. این قرارداد کاملاً صریح است: برنامه خطا نمیدهد و برونیابی هم نمیکند؛ بلکه روی این مقدار کف ثابت میماند تا سیستم آموزش بتواند یک هشدار لاگ کند. سیستمهای آموزشی که نیاز به طولانیتر کردن آموزش دارند، مقدار total_steps در برنامه را تغییر میدهند، نه کدهای حلقه را.
ثبت لاگ نُرم گرادیان در کنار نرخ یادگیری
برنامهی زمانبندی نیمی از سلامت آموزش است و نُرم گرادیان نیم دیگر آن. حلقهی آموزش هر دو شاخص را در هر گام لاگ میکند. در یک اجرای رو به واگرایی، جهش ناگهانی نُرم گرادیان قبل از بالا رفتن تابع زیان (loss) دیده میشود؛ یک وارمآپ دقیق باعث میشود نُرم گرادیان همگام با افزایش نرخ یادگیری بهطور خطی رشد کند؛ و اگر اوج نرخ یادگیری بیش از حد تهاجمی انتخاب شود، نُرم گرادیان بعد از پایان وارمآپ همچنان بالا میماند. قالبی که روی دیسک ذخیره میشود step, lr, grad_l2_norm, loss است و فایل CSV تنها رکورد ماندگار به شمار میرود.
پیادهسازی
فایل code/main.py این بخشها را پیادهسازی میکند:
CosineWithWarmup: یک تابع بیوضعیت (stateless) بهصورتlr(step) -> floatبر اساس زمانبندی تنظیمشده.TrainState: ساختاری که مدل، بهینهسازAdamWو برنامهی زمانبندی را درون یک تابع گام یکپارچه میکند.TrainState.step: اجرای یک گذر رو به جلو (forward pass)، یک گام رو به عقب (backward pass)، ثبت نُرم L2 گرادیان و اعمالlr(step)روی بهینهساز.plot_schedule_ascii: رسم برنامهی زمانبندی بهصورت یک نمودار متنی و خوانا.write_schedule_csv: ذخیرهی نرخ یادگیری در قالب یک ردیف بهازای هر گام.
در انتهای فایل، یک دمو قرار دارد که مدل کوچکی از نوع nn.Linear میسازد، آن را برای 20 گام روی یک بچ ورودی ثابت آموزش میدهد و در هر گام مقدار نرخ یادگیری، نُرم گرادیان و تابع زیان را چاپ میکند. همچنین برای بررسی چشمی، نمودار متنی برنامه نیز ترسیم میشود.
اجرای برنامه:
python3 code/main.py
اسکریپت بدون خطا (با کد خروج صفر) تمام میشود و لاگ هر گام از آموزش را همراه با نمودار برنامهی زمانبندی چاپ میکند.
الگوهای محیط عملیاتی
چهار الگو این برنامهی زمانبندی را به یک دستسازه (artifact) آماده برای محیط عملیاتی (پروداکشن) تبدیل میکنند:
برنامهی زمانبندی در فایل پیکربندی تعریف میشود، نه در کد. ترینر مقادیر warmup_steps، total_steps، lr_max و lr_min را از یک فایل پیکربندی YAML یا JSON که در git ثبت (commit) شده میخواند. این زمانبندی به دلیل آدرسدهی با محتوا (content-addressed) کاملاً تکرارپذیر است و چون بخشی از diff پولریکوئست (PR) است، بهسادگی بررسی و بازبینی میشود.
شمارندهی گام باید صعودی یکنواخت و مستقل از epoch باشد. برخی فریمورکها هنگام قطعهبندی دادهها (sharding) یا شروع مجدد dataloader، مفهوم step و epoch را با هم اشتباه میگیرند. برنامهی زمانبندی مقدار global_step را مستقیماً از چکپوینت (checkpoint) ترینر میخواند، نه از یک شمارندهی محلی. این شمارندهی پایدار باعث میشود در صورت ادامهی اجرای قبلی، آموزش دقیقاً از موقعیت درست زمانی ادامه پیدا کند.
ذخیرهی نمودار زمانبندی در دایرکتوری اجرا. در هر بار اجرای آموزش، فایل outputs/lr_schedule.png (یا در این درس، همان نمودار متنی) داخل دایرکتوری اجرا ذخیره میشود. بازبین با یک نگاه سریع به دایرکتوری، میتواند بدون اجرای مجدد کد، صحت زمانبندی را بررسی کند. این کار خطاهای ناشی از تنظیم اشتباه زمانبندی را در همان مرحلهی PR آشکار میکند.
ثابت نگهداشتن ساختار ردیفهای لاگ. ترتیب ستونها باید دقیقاً step, lr, grad_l2_norm, loss باشد. نوتبوکها یا داشبوردهای پایینی این ساختار را میخوانند؛ تغییر نام یک ستون بدون ارتقای نسخه، تمام داشبوردهای موجود را از کار میاندازد.
بهکارگیری
الگوهای محیط عملیاتی:
- قبل از هر متغیر دیگری، نقطهی اوج را جستجو (sweep) کنید. پارامتر
lr_maxحساسترین اهرم است. ابتدا آن را روی یک مدل کوچک ارزیابی کنید؛ از آنجا کهlr_maxبهینه وابستگی ضعیفی به ابعاد مدل دارد، نتایج مدل کوچک یک پیشین (prior) بسیار محکم به شما میدهد. - وارمآپ را کسری از کل گامها در نظر بگیرید، نه یک عدد ثابت. اجرایی با 200 میلیون گام و 2000 گام وارمآپ، تقریباً بلافاصله به اوج میرسد؛ اما یک اجرای 20000 گامی با همین مقدار، 10 درصد مسیرش را در وارمآپ سپری میکند. وارمآپ را درصدی تعریف کنید (معمولاً بین 1 تا 3 درصد) تا با تغییر مدتزمان آموزش، اندازه آن هم متناسب تغییر کند.
- مقدار
lr_minرا عمداً غیرصفر بگذارید. تعیین کفی معادل 10 درصدِ مقدارlr_maxباعث میشود بهینهساز در دنبالهی طولانی آموزش (long tail) همچنان یادگیری را ادامه دهد. زمانبندی با شرطlr_min = 0نمودار زیبایی تولید میکند، اما مدلی تحویل میدهد که در عمل فرآیند آموزش آن کامل نشده است.
تحویل نهایی
در یک پروژهی واقعی، فایل outputs/skill-cosine-warmup.md مشخص میکند کدام فایل پیکربندی حامل این زمانبندی است، شمارندهی سراسری از کدام گام ترینر خوانده میشود و مقدار مستقرشده بر اساس چه جستجویی روی lr_max به دست آمده است. در این درس، موتور اصلی این فرآیند را پیاده کردیم و تحویل میدهیم.
تمرینها
- یک نسخهی جذر معکوس (inverse-square-root) از این برنامهی زمانبندی بسازید و آن را در یک اجرای آزمایشی 200 گامی مقایسه کنید. کدام منحنی به تابع زیان نهایی کمتری میرسد؟
- یک فلگ
--restartاضافه کنید که یک وارمآپ دوم را در نقطهیtotal_steps / 2آغاز کند. بررسی و استدلال کنید که آیا شروعهای مجدد گرم (warm restarts) در این اجرای آزمایشی خروجی را بهبود میدهند یا خراب میکنند. - یک تست واحد (unit test) برای اثبات پیوستگی برنامه بنویسید: به شرط
warmup_steps > 0، برای هر گام در بازهی[0, total_steps]باید اختلاف|lr(step+1) - lr(step)|همواره از بیشترین مقدار بین شیب وارمآپlr_max / warmup_stepsو تندترین شیب کسینوسpi * (lr_max - lr_min) / (2 * (total_steps - warmup_steps))کمتر یا مساوی باشد. یک بازهی کسینوسی کوتاه ممکن است از رمپ وارمآپ شیب تندتری داشته باشد؛ بنابراین اتکا به شیب وارمآپ بهتنهایی کران امنی محسوب نمیشود. - برنامهی زمانبندی را داخل یک
torch.optim.lr_scheduler.LambdaLRقرار دهید تا با کدهای فریمورک ترکیب شود. این درس از یک تابع گام ساده استفاده میکند؛ این رپر (wrapper) چه تغییری ایجاد میکند؟ - فلگ
--plot-pngرا برای رسم و ذخیرهی یک نمودار واقعی با استفاده ازmatplotlibاضافه کنید. بررسی و استدلال کنید که برای اجراهای CI، نمودار متنی این درس گزینهی پیشفرض بهتری است یا فایل تصویری PNG؟
اصطلاحات کلیدی
| اصطلاح | آنچه گفته میشود | معنای واقعی آن |
|---|---|---|
| وارمآپ (Warmup) | «شروع آرام» | افزایش خطی از صفر تا lr_max در طول warmup_steps آپدیت ابتدایی |
| افت کسینوسی (Cosine decay) | «افت ملایم» | منحنی نیمهی بالایی کسینوس از lr_max تا lr_min در طول گامهای باقیمانده |
| مقدار کف (Floor) | «پس از آموزش» | مقدار ثابت lr_min که برنامه بعد از عبور از total_steps روی آن قفل میشود |
| نُرم گرادیان (Gradient norm) | «L2 گرادیانها» | نُرم اقلیدسی بردار بههمپیوستهی گرادیانها که در هر گام لاگ میشود |
| گام سراسری (Global step) | «محور زمانبندی» | شمارندهی صعودی یکنواخت گامها که با راهاندازی مجدد سیستم حفظ میشود و مبنای زمانبندی است |
برای مطالعهی بیشتر
- Loshchilov and Hutter, SGDR: Stochastic Gradient Descent with Warm Restarts (arXiv 1608.03983) - مقالهی مرجع برنامهی زمانبندی کسینوسی
- Loshchilov and Hutter, Decoupled Weight Decay Regularization (arXiv 1711.05101) - مقالهی مرجع بهینهساز AdamW
- PyTorch torch.optim.lr_scheduler - نحوهی ترکیب توابع گام با اسکژولرهای (schedulers) فریمورک
- فاز 19 · درس 42 - دانلودکنندهای که پیکرهی متنی ورودی این زمانبندی را تأمین میکند
- فاز 19 · درس 43 - دیتالودری که همگام با این زمانبندی توسعه مییابد
- فاز 19 · درس 45 - برش گرادیان و AMP؛ لایهی بعدی در حلقهی آموزش
