فاز 19 · درس 44

زمان‌بندی نرخ یادگیری کسینوسی با وارم‌آپ خطی در PyTorch

نوع
ساختنی
زمان
حدود 90 دقیقه
زبان‌ها
Python
نمودار هندسی منحنی کسینوسی که ابتدا با شیب خطی صعود کرده و سپس به آرامی به سمت پایین فرود می‌آید روی صفحه‌ای تاریک و شبکه‌بندی‌شده
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. مبانی نظری
  4. فرمول وارم‌آپ
  5. فرمول کسینوسی
  6. کف نرخ یادگیری پس از اتمام گام‌ها
  7. ثبت لاگ نُرم گرادیان در کنار نرخ یادگیری
  8. پیاده‌سازی
  9. الگوهای محیط عملیاتی
  10. به‌کارگیری
  11. تحویل نهایی
  12. تمرین‌ها
  13. اصطلاحات کلیدی
  14. برای مطالعه‌ی بیشتر

تنظیم نرخ یادگیری (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 <= warmupwarmup < step <= totalstep totalگام آموزشوضعیت گامافزایش خطی از 0 تا lr_maxکاهش کسینوسی از lr_max تا lr_minتثبیت روی lr_minAdamW.stepمحاسبه نُرم L2 گرادیانثبت لاگ گامنمودار متنی + CSV

فرمول وارم‌آپ

به‌ازای هر 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 تنها رکورد ماندگار به شمار می‌رود.

وارم‌آپ کسینوسی نرخ یادگیریافزایش تدریجی · کاهش نزولی
COSINE LR WARMUPThe schedule ramps the learning rate linearly from zero to lr_max over the warmup steps, protecting the brittle first updates, then follows the upper half of a cosine curve back toward lr_min. The marker rides the exact value the optimizer uses at each step.lrstepوارم‌آپکاهش کسینوسیlr_max
این زمان‌بندی نرخ یادگیری را به‌صورت خطی از صفر تا lr_max در طول گام‌های وارم‌آپ افزایش می‌دهد تا از به‌روزرسانی‌های حساس اولیه محافظت کند، سپس نیمه بالایی منحنی کسینوسی را به سمت lr_min طی می‌کند. نشانگر دقیقاً مقدار مورد استفاده اپتیمایزر در هر گام را نشان می‌دهد.

پیاده‌سازی

فایل 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 به دست آمده است. در این درس، موتور اصلی این فرآیند را پیاده کردیم و تحویل می‌دهیم.

تمرین‌ها

  1. یک نسخه‌ی جذر معکوس (inverse-square-root) از این برنامه‌ی زمان‌بندی بسازید و آن را در یک اجرای آزمایشی 200 گامی مقایسه کنید. کدام منحنی به تابع زیان نهایی کمتری می‌رسد؟
  2. یک فلگ --restart اضافه کنید که یک وارم‌آپ دوم را در نقطه‌ی total_steps / 2 آغاز کند. بررسی و استدلال کنید که آیا شروع‌های مجدد گرم (warm restarts) در این اجرای آزمایشی خروجی را بهبود می‌دهند یا خراب می‌کنند.
  3. یک تست واحد (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)) کمتر یا مساوی باشد. یک بازه‌ی کسینوسی کوتاه ممکن است از رمپ وارم‌آپ شیب تندتری داشته باشد؛ بنابراین اتکا به شیب وارم‌آپ به‌تنهایی کران امنی محسوب نمی‌شود.
  4. برنامه‌ی زمان‌بندی را داخل یک torch.optim.lr_scheduler.LambdaLR قرار دهید تا با کدهای فریم‌ورک ترکیب شود. این درس از یک تابع گام ساده استفاده می‌کند؛ این رپر (wrapper) چه تغییری ایجاد می‌کند؟
  5. فلگ --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) «محور زمان‌بندی» شمارنده‌ی صعودی یکنواخت گام‌ها که با راه‌اندازی مجدد سیستم حفظ می‌شود و مبنای زمان‌بندی است

برای مطالعه‌ی بیشتر

خودآزمایی

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

  1. پیش از درسچرا زمان‌بندی کسینوسی با وارم‌آپ به جای lr_max از صفر شروع می‌شود؟
  2. حین درسچرا زمان‌بندی به عنوان یک تابع واحد روی step پیاده‌سازی می‌شود و نه سه بخش متصل به هم؟
  3. حین درسبرای حالت step > total_steps در زمان‌بندی چه اتفاقی می‌افتد؟
  4. حین درسچرا نرم L2 گرادیان را در کنار نرخ یادگیری لاگ می‌کنند؟
  5. پس از درسچرا در محیط پروداکشن warmup_steps به صورت کسری از total_steps تعیین می‌شود؟
  6. پس از درسچرا در محیط پروداکشن معمولاً lr_min غیرصفر است؟

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

چرا در ابتدای آموزش مدل زبانی به وارم‌آپ (Warmup) خطی نیاز داریم؟

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

چرا مقدار حداقل نرخ یادگیری (lr_min) در پایان زمان‌بندی نباید صفر باشد؟

اگر نرخ پایانی صفر شود، مدل در گام‌های انتهایی عملاً یادگیری را متوقف می‌کند؛ نگه‌داشتن کف حدود 10 درصد از lr_max باعث حفظ قدرت یادگیری مدل در فاز پایانی می‌شود.

ارتباط پایش نُرم L2 گرادیان با نرخ یادگیری چیست؟

نُرم گرادیان سلامت روند آموزش را نشان می‌دهد؛ جهش‌های غیرعادی نُرم گرادیان پیش از افزایش تابع زیان رخ می‌دهند و نشانه‌ی تنظیم بیش از حد تهاجمی نرخ یادگیری هستند.

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

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