تنظیم نرخ یادگیری (learning rate) حساسترین گام در آموزش مدلهای یادگیری عمیق است. انتخاب یک عدد ثابت اغلب شما را میان دو پرتگاه واگرایی زودهنگام یا درجا زدن مدل در کمینههای نامناسب گرفتار میکند؛ در حالی که رسیدن به دقتهای بالا در مدلهای پیشرفته دقیقاً به تغییر پویا و حسابشدهی این مقدار در طول آموزش وابسته است.
در آموزش مدلهای مدرن مانند خانوادهی Llama یا GPT، استفاده از وارمآپ (warmup) برای پایدارسازی آمارههای اولیهی بهینهسازهایی مثل Adam و بهکارگیری کاهش کسینوسی (cosine annealing) به استاندارد قطعی تبدیل شده است. بدون درک این الگوها، هنگام آموزش از صفر یا فاینتیونینگ شبکهها با خطاهای رایج بهینهسازی و نوسان بیپایان تابع زیان روبهرو خواهید شد.
این درس به شما نشان میدهد چطور زمانبندیهای مختلف مانند کاهش پلهای، تبرید کسینوسی، وارمآپ و سیاست 1cycle را از پایه پیاده کنید، رفتار همگرایی آنها را بسنجید و بهترین استراتژی را متناسب با بودجهی پردازشی خود در PyTorch انتخاب کنید.
نرخ یادگیری (learning rate) مهمترین هایپرپارامتر مدل است؛ نه معماری، نه اندازهی مجموعهداده و نه تابع فعالسازی؛ فقط نرخ یادگیری. اگر قرار است تنها یک چیز را تنظیم کنید، همین را تنظیم کنید.
اهداف یادگیری
- پیادهسازی برنامههای زمانبندی نرخ یادگیری (learning rate schedule) ثابت (constant)، کاهش پلهای (step decay)، تبرید کسینوسی (cosine annealing)، وارمآپ همراه با کسینوسی (warmup + cosine) و تکچرخهای (1cycle) از صفر
- نمایش سه حالت شکست در انتخاب نرخ یادگیری: واگرایی (خیلی بالا)، درجا زدن و کندی مفرط (خیلی پایین) و نوسان (بدون کاهش)
- توضیح چرایی لزوم وارمآپ (warmup) برای بهینهسازهای مبتنی بر Adam و نحوهی پایدارسازی مراحل اولیهی آموزش بهکمک آن
- مقایسهی سرعت همگرایی در هر پنج برنامهی زمانبندی روی یک مسئلهی یکسان و انتخاب گزینهی مناسب بر اساس بودجهی پردازشی آموزش
صورت مسئله
نرخ یادگیری را روی 0.1 بگذارید؛ آموزش واگرا میشود و تابع زیان (loss) در 3 گام به بینهایت میرسد. آن را روی 0.0001 تنظیم کنید؛ آموزش با سرعتی لاکپشتی پیش میرود و بعد از 100 ایپاک، مدل بهسختی از حالت تصادفی اولیه فاصله میگیرد. آن را روی 0.01 بگذارید؛ آموزش تا 50 ایپاک خوب پیش میرود، اما بعد از آن تابع زیان دور نقطهی کمینهای نوسان میکند که هرگز به آن نمیرسد؛ چون گامها بیشازحد بزرگاند.
نرخ یادگیری بهینه یک مقدار ثابت نیست و در طول آموزش تغییر میکند. در گامهای اولیه، گامهای بزرگ میخواهید تا مسیر را سریعتر طی کنید. در اواخر آموزش، گامهای بسیار ریز لازم دارید تا مدل درون یک کمینهی تیز جا بیفتد. تفاوت میان مدلی با دقت 90% و مدلی با دقت 95%، اغلب فقط در برنامهی زمانبندی نرخ یادگیری است.
تکتک مدلهای بزرگ منتشرشده در سه سال گذشته از برنامهی زمانبندی نرخ یادگیری استفاده میکنند. مدل Llama 3 از نرخ یادگیری اوج lr=3e-4 همراه با 2000 گام وارمآپ و سپس کاهش کسینوسی تا 3e-5 استفاده کرد. مدل GPT-3 از lr=6e-4 همراه با وارمآپ روی 375 میلیون توکن (token) بهره برد. اینها انتخابهایی دلبخواهی نیستند، بلکه حاصل جستوجوی گستردهی هایپرپارامترها با هزینههای چند میلیون دلاریاند.
باید این برنامهها را بشناسید، چون مقادیر پیشفرض برای مسئلهی شما جواب نمیدهند. هنگام فاینتیونینگ (fine-tuning) یک مدل ازپیشآموزشدیده، برنامهی مناسب با آموزش از صفر کاملاً تفاوت دارد. وقتی اندازهی بچ (batch size) را افزایش میدهید، دورهی وارمآپ هم باید تغییر کند. اگر آموزش در گام 10,000 از کار افتاد، باید بدانید اشکال از برنامهی زمانبندی است یا چیز دیگری.
مفاهیم پایه
نرخ یادگیری ثابت
سادهترین روش ممکن: یک عدد انتخاب میکنید و در تمام گامها همان را به کار میبرید.
این روش بهندرت بهینه است؛ یا برای اواخر آموزش زیادی بزرگ است (نوسان در اطراف کمینه)، یا برای اوایل کار زیادی کوچک است (هدر رفتن توان پردازشی روی گامهای بسیار ریز). برای مدلهای کوچک و اشکالزدایی مناسب است، اما برای هر مدلی که آموزشش بیش از یک ساعت طول میکشد، گزینهای فاجعهبار به شمار میرود.
کاهش پلهای
رویکرد سنتیِ دوران ResNet: نرخ یادگیری را در ایپاکهای مشخصی با ضریبی معین (معمولاً 10 برابر) کاهش میدهید.
که در آن و step_size = 30 یعنی: نرخ یادگیری هر 30 ایپاک یکبار، 10 برابر کوچکتر میشود. شبکه عصبی (neural network) مدل ResNet-50 از همین الگو استفاده میکرد؛ lr=0.1 و کاهش 10 برابری در ایپاکهای 30، 60 و 90.
مشکل اینجاست: نقاط بهینهی کاهش به مجموعهداده و معماری وابستهاند. اگر سراغ مسئلهی دیگری بروید، باید دوباره زمان کاهش نرخ را تنظیم کنید. تغییرات هم ناگهانی هستند و افت ناگهانی نرخ ممکن است باعث جهش ناگهانی تابع زیان شود.
تبرید کسینوسی
کاهش هموار نرخ یادگیری از مقدار بیشینه به یک مقدار کمینه، مطابق با منحنی کسینوسی:
که در آن گام فعلی و تعداد کل گامها است.
در ، عبارت کسینوسی برابر با 1 است، پس . در ، عبارت کسینوسی برابر با 1- است، پس . روند کاهش در ابتدا ملایم است، در اواسط کار شتاب میگیرد و نزدیک به پایان دوباره آرام میشود.
این حالت، گزینهی پیشفرض بیشتر آموزشهای امروزی است و هایپرپارامتری جز و برای تنظیم ندارد. شکل منحنی کسینوسی با این مشاهدهی تجربی همخوانی دارد که بخش عمدهی یادگیری در میانهی آموزش رخ میدهد؛ بنابراین در این دورهی حیاتی به گامهایی با اندازهی معقول نیاز دارید.
وارمآپ: چرا باید با مقادیر کوچک شروع کرد؟
بهینهساز Adam و سایر بهینهسازهای تطبیقی، برآوردهای متحرکی از میانگین و واریانس گرادیان (gradient) نگه میدارند. در گام 0، مقدار اولیهی این برآوردها صفر است؛ بنابراین اولین بهروزرسانیهای گرادیان بر پایهی آمارههایی کاملاً نامعتبر انجام میگیرند. اگر نرخ یادگیری در این بازه بزرگ باشد، مدل گامهایی غولآسا در جهتهای نامناسب برمیدارد.
وارمآپ این مشکل را حل میکند: کار را با نرخ یادگیری بسیار کوچکی (اغلب lr_max / warmup_steps یا حتی صفر) شروع میکنید و طی گام ابتدایی، آن را بهصورت خطی تا بالا میبرید. تا زمانی که به نرخ یادگیری کامل برسید، آمارههای Adam پایدار شدهاند.
وارمآپ معمولاً 1 تا 5 درصد از کل گامهای آموزش را شامل میشود. مدل Llama 3 روی حدود 1.8 تریلیون توکن آموزش دید و وارمآپ آن 2000 گام بود. مدل GPT-3 نیز وارمآپ را در طول 375 میلیون توکن اجرا کرد.
وارمآپ خطی همراه با کاهش کسینوسی
گزینهی پیشفرض امروزی: افزایش خطی در ابتدا و سپس کاهش با منحنی کسینوسی:
if t < warmup_steps:
lr(t) = lr_max * (t / warmup_steps)
else:
progress = (t - warmup_steps) / (total_steps - warmup_steps)
lr(t) = lr_min + 0.5 * (lr_max - lr_min) * (1 + cos(pi * progress))
این همان الگویی است که Llama، مدل GPT، مدل PaLM و بیشتر ترنسفورمرهای (transformer) مدرن به کار میبرند. وارمآپ جلوی ناپایداریهای آغازین را میگیرد و کاهش کسینوسی مدل را بهخوبی در یک کمینهی مناسب مستقر میکند.
سیاست تکچرخهای (1cycle Policy)
کشف لزلی اسمیت در سال 2018: نرخ یادگیری را در نیمهی اول آموزش از مقداری پایین تا مقداری بالا افزایش دهید، سپس در نیمهی دوم دوباره آن را پایین بیاورید. در نگاه اول دور از ذهن است؛ چرا باید نرخ یادگیری را در میانهی راه افزایش داد؟
توجیه نظری: نرخ یادگیری بالا با افزودن نوفه به مسیر بهینهسازی (optimization)، مانند نوعی منظمسازی عمل میکند. در نتیجه، مدل در فاز صعودی بخشهای بیشتری از چشمانداز زیان (loss landscape) را کاوش میکند و حوضچههای بهتری مییابد. فاز نزولی هم تنظیم دقیق مدل را در همان بهترین حوضچهی کشفشده تکمیل میکند.
Phase 1 (0 to T/2): lr ramps from lr_max/25 to lr_max
Phase 2 (T/2 to T): lr ramps from lr_max to lr_max/10000
سیاست 1cycle با بودجهی پردازشی ثابت، اغلب سریعتر از تبرید کسینوسی آموزش را به سرانجام میرساند. عیبش این است که باید تعداد کل گامها را از همان ابتدا بدانید.
شکلهای مختلف برنامههای زمانبندی
فلوچارت تصمیمگیری
مقادیر واقعی در مدلهای منتشرشده
پیادهسازی گامبهگام
گام 1: توابع برنامهی زمانبندی
هر تابع گام فعلی را میگیرد و نرخ یادگیری متناظر با آن گام را برمیگرداند.
import math
def constant_schedule(step, lr=0.01, **kwargs):
return lr
def step_decay_schedule(step, lr=0.1, step_size=100, gamma=0.1, **kwargs):
return lr * (gamma ** (step // step_size))
def cosine_schedule(step, lr=0.01, total_steps=1000, lr_min=1e-5, **kwargs):
if step >= total_steps:
return lr_min
return lr_min + 0.5 * (lr - lr_min) * (1 + math.cos(math.pi * step / total_steps))
def warmup_cosine_schedule(step, lr=0.01, total_steps=1000, warmup_steps=100, lr_min=1e-5, **kwargs):
if total_steps <= warmup_steps:
return lr * (step / max(warmup_steps, 1))
if step < warmup_steps:
return lr * step / warmup_steps
progress = (step - warmup_steps) / (total_steps - warmup_steps)
return lr_min + 0.5 * (lr - lr_min) * (1 + math.cos(math.pi * progress))
def one_cycle_schedule(step, lr=0.01, total_steps=1000, **kwargs):
mid = max(total_steps // 2, 1)
if step < mid:
return (lr / 25) + (lr - lr / 25) * step / mid
else:
progress = (step - mid) / max(total_steps - mid, 1)
return lr * (1 - progress) + (lr / 10000) * progress
گام 2: مصورسازی تمام برنامههای زمانبندی
چاپ یک نمودار متنی ساده برای نمایش نحوهی تغییر هر برنامه در طول فرایند آموزش.
def visualize_schedule(name, schedule_fn, total_steps=500, **kwargs):
steps = list(range(0, total_steps, total_steps // 20))
if total_steps - 1 not in steps:
steps.append(total_steps - 1)
lrs = [schedule_fn(s, total_steps=total_steps, **kwargs) for s in steps]
max_lr = max(lrs) if max(lrs) > 0 else 1.0
print(f"\n{name}:")
for s, lr_val in zip(steps, lrs):
bar_len = int(lr_val / max_lr * 40)
bar = "#" * bar_len
print(f" Step {s:4d}: lr={lr_val:.6f} {bar}")
گام 3: شبکهی آموزش
یک شبکهی دولایهی ساده روی مجموعهدادهی دایره، درست مانند درسهای قبل، با این تفاوت که این بار برنامهی زمانبندی را تغییر میدهیم.
import random
def sigmoid(x):
x = max(-500, min(500, x))
return 1.0 / (1.0 + math.exp(-x))
def relu(x):
return max(0.0, x)
def relu_deriv(x):
return 1.0 if x > 0 else 0.0
def make_circle_data(n=200, seed=42):
random.seed(seed)
data = []
for _ in range(n):
x = random.uniform(-2, 2)
y = random.uniform(-2, 2)
label = 1.0 if x * x + y * y < 1.5 else 0.0
data.append(([x, y], label))
return data
def train_with_schedule(schedule_fn, schedule_name, data, epochs=300, base_lr=0.05, **kwargs):
random.seed(0)
hidden_size = 8
total_steps = epochs * len(data)
std = math.sqrt(2.0 / 2)
w1 = [[random.gauss(0, std) for _ in range(2)] for _ in range(hidden_size)]
b1 = [0.0] * hidden_size
w2 = [random.gauss(0, std) for _ in range(hidden_size)]
b2 = 0.0
step = 0
epoch_losses = []
for epoch in range(epochs):
total_loss = 0
correct = 0
for x, target in data:
lr = schedule_fn(step, lr=base_lr, total_steps=total_steps, **kwargs)
z1 = []
h = []
for i in range(hidden_size):
z = w1[i][0] * x[0] + w1[i][1] * x[1] + b1[i]
z1.append(z)
h.append(relu(z))
z2 = sum(w2[i] * h[i] for i in range(hidden_size)) + b2
out = sigmoid(z2)
error = out - target
d_out = error * out * (1 - out)
for i in range(hidden_size):
d_h = d_out * w2[i] * relu_deriv(z1[i])
w2[i] -= lr * d_out * h[i]
for j in range(2):
w1[i][j] -= lr * d_h * x[j]
b1[i] -= lr * d_h
b2 -= lr * d_out
total_loss += (out - target) ** 2
if (out >= 0.5) == (target >= 0.5):
correct += 1
step += 1
avg_loss = total_loss / len(data)
accuracy = correct / len(data) * 100
epoch_losses.append(avg_loss)
return epoch_losses
گام 4: مقایسهی تمام برنامههای زمانبندی
آموزش همان شبکه با هر یک از این برنامهها و مقایسهی تابع زیان نهایی و رفتار همگرایی آنها.
def compare_schedules(data):
configs = [
("Constant", constant_schedule, {}),
("Step Decay", step_decay_schedule, {"step_size": 15000, "gamma": 0.1}),
("Cosine", cosine_schedule, {"lr_min": 1e-5}),
("Warmup+Cosine", warmup_cosine_schedule, {"warmup_steps": 3000, "lr_min": 1e-5}),
("1cycle", one_cycle_schedule, {}),
]
print(f"\n{'Schedule':<20} {'Start Loss':>12} {'Mid Loss':>12} {'End Loss':>12} {'Best Loss':>12}")
print("-" * 70)
for name, schedule_fn, extra_kwargs in configs:
losses = train_with_schedule(schedule_fn, name, data, epochs=300, base_lr=0.05, **extra_kwargs)
mid_idx = len(losses) // 2
best = min(losses)
print(f"{name:<20} {losses[0]:>12.6f} {losses[mid_idx]:>12.6f} {losses[-1]:>12.6f} {best:>12.6f}")
گام 5: نرخ یادگیری خیلی بالا در برابر خیلی پایین
نمایش سه حالت شکست: زیادی بالا (واگرایی)، زیادی پایین (درجا زدن) و مقدار کاملاً مناسب.
def lr_sensitivity(data):
learning_rates = [1.0, 0.1, 0.01, 0.001, 0.0001]
print("\nLR Sensitivity (constant schedule, 100 epochs):")
print(f" {'LR':>10} {'Start Loss':>12} {'End Loss':>12} {'Status':>15}")
print(" " + "-" * 52)
for lr in learning_rates:
losses = train_with_schedule(constant_schedule, f"lr={lr}", data, epochs=100, base_lr=lr)
start = losses[0]
end = losses[-1]
if end > start or math.isnan(end) or end > 1.0:
status = "DIVERGED"
elif end > start * 0.9:
status = "BARELY MOVED"
elif end < 0.15:
status = "CONVERGED"
else:
status = "LEARNING"
end_str = f"{end:.6f}" if not math.isnan(end) else "NaN"
print(f" {lr:>10.4f} {start:>12.6f} {end_str:>12} {status:>15}")
کاربرد در دنیای واقعی
کتابخانهی PyTorch این زمانبندها را در ماژول torch.optim.lr_scheduler ارائه میدهد:
import torch
import torch.optim as optim
from torch.optim.lr_scheduler import CosineAnnealingLR, OneCycleLR, StepLR
model = nn.Sequential(nn.Linear(10, 64), nn.ReLU(), nn.Linear(64, 1))
optimizer = optim.Adam(model.parameters(), lr=3e-4)
scheduler = CosineAnnealingLR(optimizer, T_max=1000, eta_min=1e-5)
for step in range(1000):
loss = train_step(model, optimizer)
scheduler.step()
برای ترکیب وارمآپ و کسینوسی، از یک زمانبند مبتنی بر تابع lambda یا تابع get_cosine_schedule_with_warmup در کتابخانهی HuggingFace استفاده کنید:
from transformers import get_cosine_schedule_with_warmup
scheduler = get_cosine_schedule_with_warmup(
optimizer,
num_warmup_steps=2000,
num_training_steps=100000,
)
تابع ارائهشده در HuggingFace همان کدی است که بیشتر اسکریپتهای فاینتیونینگ Llama و GPT به کار میبرند. اگر شک داشتید، ترکیب وارمآپ و کسینوسی را با وارمآپی معادل 3 تا 5 درصد از کل گامها پیاده کنید؛ تقریباً برای هر کاری عالی عمل میکند.
خروجی نهایی
خروجی این درس:
outputs/prompt-lr-schedule-advisor.md-- پرامپتی (prompt) که برنامهی زمانبندی نرخ یادگیری و هایپرپارامترهای بهینه را برای ساختار آموزشی شما پیشنهاد میکند.
تمرینها
-
کاهش نمایی (exponential decay) را پیادهسازی کنید: که در آن . سپس عملکرد آن را با تبرید کسینوسی روی مجموعهدادهی دایره مقایسه کنید.
-
آزمون دامنهی نرخ یادگیری (Leslie Smith) را پیادهسازی کنید: مدل را برای چند صد گام آموزش دهید و همزمان نرخ یادگیری را بهصورت نمایی از 1e-7 تا 1 بالا ببرید. نمودار تابع زیان بر حسب نرخ یادگیری را رسم کنید. نرخ یادگیری بیشینهی بهینه، درست قبل از نقطهای است که تابع زیان شروع به افزایش میکند.
-
مدل را با وارمآپ + کسینوسی آموزش دهید، اما طول دورهی وارمآپ را تغییر دهید: 0%، 1%، 5%، 10% و 20% از کل گامها. نقطهی طلایی بیشترین پایداری آموزش را پیدا کنید.
-
تبرید کسینوسی با شروعهای مجدد گرم (SGDR) را پیادهسازی کنید: نرخ یادگیری را هر گام یکبار روی ریست کنید و دوباره کاهش دهید. عملکرد این روش را با کسینوسی استاندارد در یک آموزش طولانیتر مقایسه کنید.
-
یک «جراح برنامهی زمانبندی» بسازید که تابع زیان آموزش را پایش کند؛ به این صورت که پس از پایدار شدن تابع زیان، زمانبندی را خودکار از وارمآپ به کسینوسی تغییر دهد و در صورت درجا زدن طولانیمدت تابع زیان، نرخ یادگیری را پایین بیاورد.
اصطلاحات کلیدی
| اصطلاح | در گفتوگوها | معنای دقیق فنی |
|---|---|---|
| نرخ یادگیری (Learning rate) | «سرعت یادگیری مدل» | مقدار اسکالری که در گرادیان ضرب میشود تا اندازهی گام بهروزرسانی پارامترها را مشخص کند. |
| برنامهی زمانبندی (Schedule) | «تغییر نرخ یادگیری در طول زمان» | تابعی که گام آموزش را به نرخ یادگیری نگاشت میکند تا همگرایی بهینه شود. |
| وارمآپ (Warmup) | «شروع با نرخ یادگیری کوچک» | افزایش خطی نرخ یادگیری از مقداری نزدیک به صفر تا مقدار هدف طی گام ابتدایی برای پایدارسازی آمارههای بهینهساز. |
| تبرید کسینوسی (Cosine annealing) | «کاهش هموار نرخ یادگیری» | کاهش نرخ یادگیری مطابق منحنی کسینوسی از تا در طول دوره آموزش. |
| کاهش پلهای (Step decay) | «افت پلهای در نقاط مشخص» | ضرب کردن نرخ یادگیری در یک ضریب (معمولاً 0.1) در بازههای زمانی ثابت ایپاکها. |
| سیاست تکچرخهای (1cycle policy) | «بالا رفتن و سپس پایین آمدن» | روش ابداعی لزلی اسمیت برای بالا بردن نرخ یادگیری و سپس پایین آوردن آن در قالب یک چرخهی منفرد بهمنظور همگرایی سریعتر. |
| آزمون دامنهی نرخ یادگیری (LR range test) | «یافتن بهترین نرخ یادگیری» | آموزش کوتاهمدت مدل همزمان با افزایش نرخ یادگیری برای یافتن مقداری که تابع زیان در آن شروع به واگرایی میکند. |
| کسینوسی با شروعهای مجدد گرم (Cosine with warm restarts) | «تنظیم مجدد و تکرار» | بازنشانی دورهای نرخ یادگیری به و تکرار روند کاهش آن (SGDR). |
| حداقل نرخ یادگیری (Eta min) | «کف نرخ یادگیری» | کمترین مقدار نرخ یادگیری که برنامهی زمانبندی تا آن حد کاهش مییابد. |
| نرخ یادگیری اوج (Peak learning rate) | «بیشترین نرخ یادگیری» | بالاترین نرخ یادگیری حاصلشده در طول فرایند آموزش؛ معمولاً در پایان دورهی وارمآپ. |
برای مطالعهی بیشتر
- Loshchilov & Hutter, "SGDR: Stochastic Gradient Descent with Warm Restarts" (2017) -- مقالهی معرفی تبرید کسینوسی و شروعهای مجدد گرم
- Smith, "Super-Convergence: Very Fast Training of Neural Networks Using Large Learning Rates" (2018) -- مقالهی سیاست تکچرخهای (1cycle policy)
- Touvron et al., "Llama 2: Open Foundation and Fine-Tuned Chat Models" (2023) -- مستندات برنامهی وارمآپ همراه با کسینوسی در مقیاس بزرگ
- Goyal et al., "Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour" (2017) -- قاعدهی مقیاسبندی خطی و وارمآپ برای آموزش با مینیبچهای بزرگ
