Learning Rate
Tốc độ học
Bạn đi Grab bike về nhà. Tài xế biết hướng đi nhưng phải chọn tốc độ. Nếu chạy quá nhanh trên đường hẹp, điều gì xảy ra?
Hình minh họa
Learning ratelà "bước chân" trong gradient descent. Quá nhỏ → rùa bò, mất cả ngày. Quá lớn → nhảy qua nhảy lại, không bao giờ dừng. Vừa phải → đi thẳng đến đích!
Công thức:
Loss giảm nhanh trong 5 epoch đầu, sau đó dao động lên xuống quanh giá trị 0.3 mà không giảm thêm. Bạn nên làm gì?
Giải thích
Trước khi chọn LR, Leslie Smith đề xuất chạy một bài kiểm tra đơn giản: LR Range Test (còn gọi là LR Finder). Ý tưởng: huấn luyện vài batch với LR tăng mũ từ rất nhỏ (ví dụ ) lên rất lớn (ví dụ ), sau đó vẽ loss theo LR. Chọn LR ở vùng dốc xuống mạnh nhất.
Hình minh họa
Trục hoành: log(learning rate) — trục tung: loss sau ít bước huấn luyện
Nguyên tắc đọc biểu đồ:
- Vùng bên trái (LR quá nhỏ): loss gần như phẳng — mô hình không học được gì đáng kể.
- Vùng giữa (dốc xuống): loss giảm nhanh — đây là nơi nên chọn. Cụ thể: điểm có slope âm nhất, thường ngay trước khi biểu đồ bắt đầu đi ngang.
- Vùng bên phải (LR quá lớn): loss bùng nổ, có thể tới NaN. Đây là ranh giới tối đa tuyệt đối.
Giải thích
Một LR tốt ở đầu thường quá lớn ở cuối. Giải pháp: LR scheduler — một hàm thay đổi LR theo số bước/epoch. Dưới đây là 4 chiến lược phổ biến nhất, hãy so sánh hình dáng:
Hình minh họa
Constant
Step Decay
Cosine Annealing
One-Cycle
Khám phá một scheduler cụ thể:
Cosine Annealing — LR giảm mượt mà theo đường cosine
| Chiến lược | Mô tả | Khi nào dùng |
|---|---|---|
| Constant | LR cố định suốt quá trình | Prototype nhanh, ít dữ liệu |
| Step Decay | Giảm LR x0.1 mỗi N epoch | CNN truyền thống (ResNet) |
| Cosine Annealing | Giảm mượt theo cosine | Transformer, Vision Transformer |
| Warmup + Decay | Tăng dần → đỉnh → giảm dần | BERT, GPT (bắt buộc với LLM) |
| One-Cycle | Tăng → đỉnh → giảm trong 1 cycle | FastAI, huấn luyện nhanh |
import torch
import torch.optim as optim
# Tạo optimizer với LR ban đầu
model = torch.nn.Linear(10, 1)
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01)
# ---- 1) Step Decay: giảm LR x0.5 mỗi 30 epoch ----
sched_step = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.5)
# ---- 2) Cosine Annealing: giảm từ 1e-3 → 0 trong 100 epoch ----
sched_cos = optim.lr_scheduler.CosineAnnealingLR(
optimizer, T_max=100, eta_min=1e-6
)
# ---- 3) Warmup + Cosine (dùng với Transformer) ----
# Dùng SequentialLR để ghép 2 scheduler
warmup = optim.lr_scheduler.LinearLR(
optimizer, start_factor=0.01, end_factor=1.0, total_iters=10
)
cosine = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=90)
sched_wc = optim.lr_scheduler.SequentialLR(
optimizer,
schedulers=[warmup, cosine],
milestones=[10],
)
# ---- 4) OneCycle (Leslie Smith — super-convergence) ----
sched_one = optim.lr_scheduler.OneCycleLR(
optimizer,
max_lr=1e-3,
steps_per_epoch=100,
epochs=10,
pct_start=0.1, # 10% đầu warmup
anneal_strategy="cos",
div_factor=25, # lr ban đầu = max_lr / 25
final_div_factor=1e4, # lr cuối = max_lr / (25 * 1e4)
)
for epoch in range(100):
for batch in dataloader:
optimizer.zero_grad()
loss = loss_fn(model(batch.x), batch.y)
loss.backward()
optimizer.step()
# OneCycle cần step() mỗi batch, không phải mỗi epoch
sched_one.step()
# Các scheduler còn lại cập nhật mỗi epoch
sched_cos.step()Giải thích
Tại sao Transformer cần warmup? Ở bước huấn luyện đầu tiên, trọng số được khởi tạo ngẫu nhiên, LayerNorm chưa có statistics ổn định, và attention softmax có thể "saturate" (một head hút hết attention về chính nó). Một update đầy đủ kích thước ngay lập tức sẽ đẩy mô hình ra khỏi vùng có gradient hữu ích. Warmup là "liều an thần" — tăng dần LR từ gần 0 lên giá trị mục tiêu trong vài phần trăm đầu.
Hình minh họa
from transformers import get_linear_schedule_with_warmup
from torch.optim import AdamW
optimizer = AdamW(model.parameters(), lr=5e-5, weight_decay=0.01)
total_steps = len(train_loader) * num_epochs
warmup_steps = int(0.1 * total_steps) # 10% warmup
scheduler = get_linear_schedule_with_warmup(
optimizer,
num_warmup_steps=warmup_steps,
num_training_steps=total_steps,
)
# Trong vòng lặp huấn luyện
for batch in train_loader:
optimizer.zero_grad()
loss = model(**batch).loss
loss.backward()
# Clip gradient trước khi step (cũng quan trọng với Transformer)
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
scheduler.step() # cập nhật LR mỗi bước, không phải mỗi epoch
# Biến thể: cosine warmup thay vì linear
# from transformers import get_cosine_schedule_with_warmup
# scheduler = get_cosine_schedule_with_warmup(
# optimizer,
# num_warmup_steps=warmup_steps,
# num_training_steps=total_steps,
# num_cycles=0.5, # nửa chu kỳ cosine — về 0 ở cuối
# )Giải thích
Trong thực tế, loss surface có nhiều trục với độ cong khác nhau. Đây là hiện tượng "ill-conditioning". Cùng một LR có thể vừa với trục thoải nhưng quá lớn với trục dốc, gây zig-zag. Hãy xem trên một bowl 2D có tỉ lệ độ cong 6:1:
Hình minh họa
Các vòng là đường đồng mức của loss. Bowl này dốc theo trục y gấp 6 lần trục x — nên đường đỏ dao động dữ dội theo trục y trước khi tiến chậm theo trục x.
Bạn chuyển từ SGD (lr=0.1) sang Adam. Nên đặt lr cho Adam bằng bao nhiêu?
- LR là siêu tham số quan trọng nhất: w_mới = w_cũ - α × gradient. Chọn sai LR = huấn luyện thất bại bất kể kiến trúc.
- Quá nhỏ → hội tụ chậm, có thể kẹt saddle. Quá lớn → dao động hoặc phân kỳ. Vừa phải → hội tụ ổn định và tổng quát tốt.
- LR Range Test (Leslie Smith): sweep LR từ nhỏ đến lớn, chọn điểm dốc nhất trên biểu đồ loss vs log(LR).
- Scheduler là bắt buộc: step/cosine/onecycle. Bước lớn lúc đầu (khám phá), bước nhỏ lúc cuối (hội tụ chính xác).
- Warmup 5-10% bước đầu là bắt buộc cho Transformer — tránh gradient explosion khi LayerNorm và attention chưa ổn định.
- Khi loss surface ill-conditioned (các trục có độ cong rất khác), cần momentum hoặc adaptive LR (Adam) — một LR duy nhất không đủ.
Kiểm tra hiểu biết
Learning rate = 0.001 là mặc định phổ biến cho optimizer nào?