Gradient Descent
Hạ gradient
Bạn đứng trên đỉnh đồi trong sương mù, không thấy gì. Cách tốt nhất để xuống thung lũng là gì?
Bạn đã chọn đúng — bước theo hướng dốc nhất. Bây giờ hãy THỬ làm điều đó trên một hàm mất mát thực sự. Nhấp vào đường cong để đặt vị trí, rồi bắt đầu đi xuống.
Hình minh họa
Nhấp vào đường cong để đặt vị trí xuất phát, rồi tự tay tối ưu!
Quy trình bạn vừa thực hiện — đi theo hướng gradient, với bước nhảy tỷ lệ thuận với learning rate — chính là Gradient Descent!
Công thức:
So sánh đường đi tối ưu: mượt mà vs. nhiễu nhưng cùng hướng
Batch GD: đường đi mượt mà
Thí nghiệm: điều chỉnh tham số
Sau 30 bước: loss = 1.000
Momentum — khi optimizer có "quán tính"
SGD thuần giống một người đi bộ thận trọng: mỗi bước chỉ nhìn gradient ngay tại đó. Còn Momentum giống một quả bóng lăn — khi đã có đà, nó tiếp tục lăn theo hướng cũ ngay cả khi gradient đổi chiều nhẹ. Hãy xem khác biệt trên cùng một loss landscape.
Hình minh họa
Quan sát: tại chỗ lõm nông ở giữa, SGD có xu hướng lùng nhùng. Momentum tích luỹ đà nên lướt qua và tới cực tiểu chính nhanh hơn. β càng cao, đà càng mạnh — nhưng β quá gần 1 có thể gây vọt quá.
So sánh 4 optimizer trên cùng một landscape 2D
Cùng xuất phát tại góc trên-phải, cùng chạy 60 bước, cùng learning rate — nhưng 4 optimizer tới cực tiểu theo 4 cách rất khác nhau.
Hình minh họa
Landscape có thung lũng hẹp theo trục y (dốc 6x). SGD dao động lên xuống; Momentum lướt nhanh hơn nhưng có thể vọt quá; RMSprop tự động giảm bước trên trục dốc; Adam thường tới đích mượt nhất. Chỉnh LR để thấy khi nào optimizer nào nổ.
Learning rate không cần cố định — lịch điều phối
Giai đoạn đầu huấn luyện bạn muốn bước lớn để tiến nhanh. Gần cuối bạn muốn bước nhỏ để tinh chỉnh. Đó là lý do learning rate scheduler ra đời. Hãy so sánh 3 lịch phổ biến.
Hình minh họa
Cosine annealing:
Mượt, giảm chậm đầu-cuối, giảm nhanh giữa. Mặc định của nhiều LLM.
Learning rate = 5.0 trên hàm loss này sẽ gây ra điều gì?
Bạn đang huấn luyện GPT-2 với Adam, loss giảm tới epoch 8 thì bắt đầu dao động lên xuống không hội tụ. Can thiệp nào HỢP LÝ NHẤT?
Giải thích
Gradient Descent cập nhật trọng số theo công thức (gradient được tính bởi backpropagation):
Trong đó θ là vector trọng số, α là learning rate, và ∇L(θ) là gradient của hàm loss. Việc tính đạo hàm dựa trên nền tảng vi tích phân, đặc biệt là đạo hàm riêng.
Ba biến thể cơ bản theo lượng dữ liệu
| Biến thể | Dữ liệu mỗi bước | Đặc điểm |
|---|---|---|
| Batch GD | Toàn bộ tập dữ liệu | Ổn định, chậm với dữ liệu lớn |
| SGD | 1 mẫu ngẫu nhiên | Nhanh, dao động nhiều |
| Mini-batch GD | Một lô nhỏ (32-256 mẫu) | Cân bằng tốc độ và ổn định |
Công thức cập nhật của 4 optimizer quan trọng
Gọi . Bảng dưới thể hiện bước cập nhật của SGD (thuần), SGD+Momentum, RMSprop và Adam.
"""Gradient Descent thuần bằng NumPy.
Ví dụ với hàm 1D: L(w) = (w - 2)^2 + 1
Cực tiểu tại w = 2, L = 1.
"""
import numpy as np
def loss_fn(w: float) -> float:
return (w - 2.0) ** 2 + 1.0
def grad_fn(w: float) -> float:
return 2.0 * (w - 2.0)
def gradient_descent(
w_init: float,
lr: float = 0.1,
steps: int = 200,
tol: float = 1e-6,
) -> tuple[float, list[float]]:
"""Trả về (w_cuối, lịch sử loss)."""
w = w_init
history: list[float] = [loss_fn(w)]
for step in range(steps):
g = grad_fn(w)
w = w - lr * g
history.append(loss_fn(w))
if abs(g) < tol:
print(f"Hội tụ tại bước {step}, |grad| = {abs(g):.2e}")
break
return w, history
if __name__ == "__main__":
w_star, hist = gradient_descent(w_init=6.0, lr=0.1, steps=100)
print(f"w* = {w_star:.6f}, loss = {loss_fn(w_star):.6f}")"""Huấn luyện một MLP nhỏ với 4 optimizer, so sánh đường hội tụ.
Yêu cầu: torch >= 2.0
"""
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
# ---------------------------------------------------------
# 1. Dữ liệu giả lập — hồi quy y = 3x + nhiễu
# ---------------------------------------------------------
torch.manual_seed(42)
X = torch.randn(1000, 4)
y = X @ torch.tensor([3.0, -1.5, 2.0, 0.5]) + 0.1 * torch.randn(1000)
loader = DataLoader(TensorDataset(X, y), batch_size=32, shuffle=True)
# ---------------------------------------------------------
# 2. Kiến trúc MLP đơn giản
# ---------------------------------------------------------
def build_model() -> nn.Module:
return nn.Sequential(
nn.Linear(4, 16),
nn.ReLU(),
nn.Linear(16, 1),
)
# ---------------------------------------------------------
# 3. Bốn optimizer, cùng learning rate
# ---------------------------------------------------------
def make_optimizers(model: nn.Module, lr: float = 1e-2):
return {
"SGD": torch.optim.SGD(model.parameters(), lr=lr),
"Momentum": torch.optim.SGD(model.parameters(), lr=lr, momentum=0.9),
"RMSprop": torch.optim.RMSprop(model.parameters(), lr=lr, alpha=0.99),
"Adam": torch.optim.Adam(model.parameters(), lr=lr,
betas=(0.9, 0.999), eps=1e-8),
}
# ---------------------------------------------------------
# 4. Vòng huấn luyện + LR scheduler
# ---------------------------------------------------------
def train_one(name: str, model: nn.Module, opt: torch.optim.Optimizer,
epochs: int = 20):
loss_fn = nn.MSELoss()
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=epochs)
losses: list[float] = []
for epoch in range(epochs):
epoch_loss = 0.0
for xb, yb in loader:
opt.zero_grad(set_to_none=True)
pred = model(xb).squeeze(-1)
loss = loss_fn(pred, yb)
loss.backward() # tính gradient = backpropagation
opt.step() # cập nhật trọng số
epoch_loss += loss.item() * len(xb)
scheduler.step() # cập nhật learning rate
epoch_loss /= len(loader.dataset)
losses.append(epoch_loss)
if epoch % 5 == 0:
lr_now = scheduler.get_last_lr()[0]
print(f"[{name}] epoch {epoch:3d} loss={epoch_loss:.4f} lr={lr_now:.5f}")
return losses
if __name__ == "__main__":
for name in ["SGD", "Momentum", "RMSprop", "Adam"]:
model = build_model()
opt = make_optimizers(model)[name]
train_one(name, model, opt)- Gradient chỉ hướng tăng nhanh nhất của loss; đi NGƯỢC gradient để giảm loss. Công thức: θ ← θ − α∇L(θ).
- Learning rate α là siêu tham số số-một: quá nhỏ thì ì, quá lớn thì dao động/nổ. Thử tầm 1e-1 đến 1e-5.
- Ba biến thể theo dữ liệu: Batch (toàn bộ), SGD (1 mẫu), Mini-batch (32–256 — lựa chọn mặc định).
- Momentum tích luỹ đà, giúp đi thẳng xuống ravine; RMSprop chuẩn hoá bước theo độ lớn gradient; Adam = Momentum + RMSprop + bias correction.
- LR scheduler (step/cosine/plateau) hầu như luôn tốt hơn LR cố định; Transformer thường cần thêm warm-up.
- Deep learning hoạt động được vì trong không gian cao chiều, đa số điểm tới hạn là saddle point (thoát được) chứ không phải local minimum.
Kiểm tra hiểu biết
Gradient tại điểm cực tiểu của hàm loss có giá trị bằng bao nhiêu?