Deep Learning 2 — Huấn luyện: loss, backprop & optimizer

13 thg 7, 2026 6 lượt xem
#ai
#deep-learning
#training
#adam
#backpropagation
#gradient-descent

Deep Learning 2 — Huấn luyện: loss, backprop & optimizer

bài nền tảng chúng ta đã dựng được kiến trúc một mạng nơ-ron: các lớp nơ-ron, trọng số, hàm kích hoạt, và cách một tín hiệu đi từ đầu vào tới đầu ra (forward pass). Nhưng một mạng vừa khởi tạo — với các trọng số ngẫu nhiên — thì dự đoán chẳng khác gì đoán mò. Câu hỏi cốt tử của bài này là: làm sao để mạng tự sửa hàng triệu trọng số đó cho tới khi dự đoán đúng?

Câu trả lời gói gọn trong một vòng lặp bốn bước, lặp đi lặp lại hàng nghìn lần: forward → loss → backward → update. Mạng dự đoán, ta đo sai số, ta tính xem mỗi trọng số góp bao nhiêu vào sai số đó, rồi nhích từng trọng số một chút theo hướng giảm sai số. Cả bài này là bốn bước ấy, mổ xẻ đến từng chi tiết.

1. Hàm mất mát — thước đo của sai

Trước khi sửa sai, ta phải đo được cái sai. Đó là việc của hàm mất mát (loss function): một con số duy nhất, càng lớn nghĩa là mạng càng sai, càng gần 0 nghĩa là càng đúng. Toàn bộ quá trình huấn luyện thực chất là bài toán tối ưu: tìm bộ trọng số làm loss nhỏ nhất.

Chọn loss nào tùy vào loại bài toán. Hai họ phổ biến nhất:

Hồi quy (dự đoán một số thực) — dùng Mean Squared Error (MSE), trung bình bình phương sai lệch giữa dự đoán ŷ và giá trị thật y:

MSE = (1/n) · Σ (yᵢ − ŷᵢ)²

Bình phương phạt nặng những sai số lớn (sai gấp đôi thì bị phạt gấp bốn), và luôn cho ra số dương nên không có chuyện sai âm bù sai dương. Đây chính là loss đã gặp ở hồi quy tuyến tính.

Phân loại (dự đoán một nhãn) — dùng Cross-Entropy. Với phân loại nhiều lớp, mạng xuất ra một phân phối xác suất qua softmax, và cross-entropy đo khoảng cách giữa phân phối dự đoán và nhãn thật:

CE = − Σ yᵢ · log(ŷᵢ)

Trực giác: nếu nhãn thật là "mèo" mà mạng gán xác suất 0.9 cho "mèo", loss nhỏ; nếu chỉ gán 0.1, log(0.1) rất âm nên loss lớn. Cross-entropy "trừng phạt" sự tự tin sai lầm rất mạnh, và điều đó tạo ra gradient khỏe để mạng học nhanh — lý do nó gần như luôn được ưu tiên so với MSE cho bài toán phân loại.

Điểm mấu chốt: loss phải khả vi (tính được đạo hàm). Vì bước tiếp theo — cải thiện mạng — hoàn toàn dựa vào đạo hàm của loss.

2. Gradient descent — đi xuống theo dốc

Hãy tưởng tượng loss là một mặt địa hình gồ ghề, mỗi trọng số là một chiều không gian, và ta đang đứng ở một điểm ngẫu nhiên trên đó. Mục tiêu: đi xuống thung lũng (loss thấp). Ta không thấy toàn cảnh, nhưng ở ngay dưới chân ta luôn biết hướng dốc nhất — đó chính là gradient, vector đạo hàm của loss theo từng trọng số.

Gradient chỉ hướng loss tăng nhanh nhất. Nên muốn giảm loss, ta bước ngược lại. Quy tắc cập nhật cốt lõi của cả deep learning:

w ← w − η · ∂L/∂w

Trong đó η (eta) là learning rate — độ dài mỗi bước. Lặp lại phép trừ này đủ nhiều lần, ta trượt dần xuống một vùng loss thấp. Đó là gradient descent.

Vấn đề thực tế: tính gradient trên toàn bộ tập dữ liệu mỗi bước (batch gradient descent) thì chính xác nhưng cực chậm — với hàng triệu mẫu, một bước cập nhật đã tốn cả phút. Giải pháp là ước lượng gradient trên một lô nhỏ:

  • Batch GD — dùng cả tập dữ liệu cho mỗi cập nhật. Gradient chính xác, đường đi mượt, nhưng chậm và tốn bộ nhớ.
  • Stochastic GD (SGD) — mỗi cập nhật chỉ dùng 1 mẫu. Rất nhanh, nhưng gradient nhiễu, đường đi zigzag.
  • Mini-batch GD — dùng một lô nhỏ (32, 64, 128... mẫu) cho mỗi cập nhật. Đây là chuẩn thực tế: đủ nhanh, gradient đủ ổn định, và tận dụng tốt phép nhân ma trận song song trên GPU.

Batch size vì thế là một siêu tham số đáng lưu ý. Batch nhỏ cho gradient nhiễu hơn (đôi khi nhiễu này lại giúp thoát khỏi các điểm kẹt) và cập nhật thường xuyên hơn; batch lớn cho gradient ổn định hơn, chạy hiệu quả hơn trên GPU nhưng cần nhiều bộ nhớ và mỗi epoch có ít bước cập nhật hơn.

3. Backpropagation — làm sao tính được gradient?

Đây là trái tim của huấn luyện. Ta đã biết cần gradient ∂L/∂w cho mọi trọng số. Một mạng sâu có hàng triệu trọng số, xếp thành nhiều lớp lồng vào nhau như hàm hợp. Tính từng đạo hàm riêng lẻ là bất khả thi. Backpropagation là thuật toán làm việc này một cách hiệu quả và có hệ thống.

Backprop dựa trên quy tắc chuỗi (chain rule). Loss phụ thuộc vào output lớp cuối, output đó lại phụ thuộc vào output lớp trước, cứ thế ngược về đầu — nên đạo hàm của loss theo một trọng số ở lớp sâu bằng tích các đạo hàm dọc theo chuỗi đó.

Ý tưởng vận hành: sau khi forward pass tính ra loss, ta đi ngược từ output về input. Ở lớp cuối, ta tính trực tiếp loss nhạy thế nào với output; rồi "đẩy" tín hiệu lỗi này lùi một lớp bằng chain rule, và cứ thế. Mấu chốt: mỗi lớp chỉ cần biết tín hiệu lỗi đến từ lớp sau nó, nhân với đạo hàm cục bộ của chính nó, rồi truyền phần lỗi tương ứng về cho lớp trước.

Trực giác đẹp nhất của backprop: nó cho phép quy trách nhiệm. Sau một lần lan ngược, mỗi trọng số trong mạng đều "biết" nó đã đóng góp bao nhiêu vào sai số cuối cùng, và nên tăng hay giảm để loss nhỏ đi. Một trọng số ở lớp đầu tiên, cách output rất xa, vẫn nhận được tín hiệu chỉ dẫn chính xác — nhờ chuỗi đạo hàm nối liền từ loss về tận nó.

Về mặt hiệu quả: backprop tái sử dụng các kết quả trung gian, nên chi phí tính toàn bộ gradient chỉ tương đương một lần forward pass nữa — chứ không phải tính lại từ đầu cho từng trọng số.

Trong thực tế bạn gần như không bao giờ viết backprop bằng tay. Các framework hiện đại có autograd (automatic differentiation): khi bạn viết phần forward, framework tự dựng đồ thị các phép tính, và khi bạn gọi loss.backward(), nó tự chạy backprop trên đồ thị đó, điền gradient vào mọi tham số. Đây chính là cầu nối tới PyTorch — nơi cả cơ chế này được thao tác cụ thể bằng code.

4. Vòng lặp huấn luyện

Ghép ba phần trên lại, ta có vòng lặp huấn luyện — chu trình xoay vòng cho tới khi mạng hội tụ:

Hai khái niệm nhịp độ cần phân biệt rõ:

  • Batch (lô) — một nhóm nhỏ mẫu được xử lý cùng nhau trong một bước cập nhật trọng số.
  • Epoch — một lần mạng đi qua toàn bộ tập huấn luyện. Nếu có 10.000 mẫu và batch size 100, thì một epoch gồm 100 bước cập nhật. Huấn luyện thường kéo dài hàng chục đến hàng trăm epoch.

Dưới đây là vòng lặp viết bằng PyTorch, rút gọn còn phần cốt lõi:

# Vòng lặp huấn luyện rút gọn (minh hoạ)
import torch

model = MyNetwork()                                  # mạng đã định nghĩa
loss_fn = torch.nn.CrossEntropyLoss()                # loss cho phân loại
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

for epoch in range(num_epochs):
    model.train()
    for X_batch, y_batch in train_loader:            # duyệt từng lô
        optimizer.zero_grad()                        # xoá gradient cũ
        y_pred = model(X_batch)                      # 1) forward
        loss = loss_fn(y_pred, y_batch)              # 2) tính loss
        loss.backward()                              # 3) backprop -> gradient
        optimizer.step()                             # 4) update trọng số

    # kiểm tra trên tập validation sau mỗi epoch
    model.eval()
    with torch.no_grad():
        val_loss = evaluate(model, val_loader, loss_fn)
    print(f"epoch {epoch}: train_loss={loss.item():.4f}  val_loss={val_loss:.4f}")

Đúng bốn bước forward → loss → backward → update, gói trong hai vòng lặp lồng nhau (epoch bên ngoài, batch bên trong). Lưu ý optimizer.zero_grad(): PyTorch cộng dồn gradient nên phải xoá sạch trước mỗi lô, nếu không gradient các lô sẽ chồng lên nhau.

5. Optimizer — cách nhích trọng số cho khôn ngoan

optimizer.step() ẩn giấu nhiều trí tuệ hơn vẻ ngoài. Công thức w ← w − η·∂L/∂w của SGD thuần túy tuy đúng nhưng đi khá vụng: dễ dao động qua lại ở các thung lũng hẹp, và bò rất chậm ở vùng dốc thoải. Các optimizer hiện đại cải tiến chính bước cập nhật này.

  • SGD — quy tắc gốc: bước theo gradient hiện tại. Đơn giản, đáng tin, nhưng có thể chậm và nhạy với learning rate.
  • Momentum — cộng thêm "quán tính": tích lũy trung bình các gradient gần đây rồi bước theo đó. Giống hòn bi lăn xuống dốc, nó tăng tốc theo hướng nhất quán và giảm dao động ngang. Vượt qua các vùng dốc thoải nhanh hơn hẳn.
  • RMSProp — điều chỉnh learning rate riêng cho từng trọng số dựa trên độ lớn gradient gần đây của trọng số đó: trọng số nào có gradient hay dao động mạnh thì bước ngắn lại, ổn định hơn.
  • Adam — kết hợp ý tưởng của Momentum (trung bình gradient) và RMSProp (điều chỉnh bước theo từng trọng số). Đây là lựa chọn mặc định hợp lý cho phần lớn bài toán: hội tụ nhanh, ít nhạy với việc chọn learning rate ban đầu, chạy tốt ngay từ cấu hình đơn giản. Khi chưa biết chọn gì, hãy dùng Adam.

Một lưu ý: SGD kèm Momentum, khi được tinh chỉnh cẩn thận, đôi khi cho kết quả tổng quát hóa tốt hơn Adam trong một số bài toán (đặc biệt là thị giác máy tính). Nhưng để bắt đầu và trong đa số trường hợp, Adam là điểm khởi hành an toàn.

6. Learning rate — siêu tham số quan trọng nhất

Nếu chỉ được tinh chỉnh một siêu tham số, hãy chọn learning rate. Nó quyết định độ dài mỗi bước đi xuống dốc, và tác động của nó rất nhạy:

  • Quá lớn — mỗi bước nhảy vọt qua điểm thấp, loss dao động dữ dội hoặc phân kỳ (bay lên vô cực, thường hiện thành NaN). Như chạy quá đà không phanh kịp.
  • Quá nhỏ — mạng vẫn học đúng hướng nhưng cực chậm, tốn rất nhiều epoch, và dễ mắc kẹt ở những vùng loss chưa tốt.

Không có con số vàng, nhưng 1e-3 (0.001) là điểm khởi đầu hợp lý phổ biến với Adam. Cách tốt là thử vài giá trị cách nhau theo bậc 10 (1e-2, 1e-3, 1e-4) và quan sát đường loss.

Thay vì giữ learning rate cố định, thực tế thường dùng lịch trình (schedule) thay đổi nó theo thời gian:

  • Warmup — bắt đầu bằng learning rate rất nhỏ rồi tăng dần trong vài trăm bước đầu. Tránh việc các bước lớn ban đầu (khi trọng số còn ngẫu nhiên) làm mạng bất ổn. Rất phổ biến khi huấn luyện mạng lớn.
  • Decay — giảm dần learning rate về sau (theo bậc thang, theo hàm mũ, hay cosine). Trực giác: giai đoạn đầu bước dài để tiến nhanh về vùng tốt, giai đoạn cuối bước ngắn để tinh chỉnh chính xác vào đáy thung lũng mà không nhảy vọt qua.

Một schedule điển hình kết hợp cả hai: warmup ngắn ở đầu, rồi decay dần cho tới cuối huấn luyện.

7. Các vấn đề về gradient

Vì gradient được truyền ngược qua nhiều lớp bằng phép nhân các đạo hàm cục bộ, mạng sâu gặp hai căn bệnh kinh điển:

  • Vanishing gradient (gradient tiêu biến) — khi nhân nhiều số nhỏ hơn 1 với nhau, tích tiến nhanh về 0. Gradient tới các lớp đầu gần như bằng 0, các lớp này gần như không học được gì. Đây là lý do các mạng rất sâu từng cực khó huấn luyện.
  • Exploding gradient (gradient bùng nổ) — ngược lại, khi nhân nhiều số lớn hơn 1, tích phình lên khổng lồ. Cập nhật trọng số nhảy vọt, loss thành NaN, huấn luyện đổ vỡ.

Vài kỹ thuật đã giải quyết phần lớn hai vấn đề này:

  • Khởi tạo trọng số thông minh — thay vì ngẫu nhiên tùy tiện, dùng Xavier/Glorot (hợp với tanh/sigmoid) hoặc He (hợp với ReLU). Chúng chọn độ lớn trọng số ban đầu sao cho tín hiệu và gradient không co lại hay phình ra khi đi qua các lớp — giúp huấn luyện ổn định ngay từ đầu.
  • Hàm kích hoạt ReLU — với input dương, đạo hàm của ReLU bằng đúng 1, nên gradient đi qua không bị co lại. Đây là một lý do lớn khiến ReLU thay thế sigmoid/tanh và làm mạng sâu trở nên khả thi.
  • Batch Normalization — chuẩn hóa lại phân bố đầu ra của mỗi lớp, giữ tín hiệu trong khoảng ổn định qua các lớp, giúp gradient lành mạnh và cho phép dùng learning rate lớn hơn. (Xem thêm ở bài regularization & tuning.)
  • Gradient clipping — chuyên trị exploding gradient: nếu độ lớn (norm) của gradient vượt một ngưỡng, ta co nó lại về ngưỡng đó trước khi cập nhật. Đơn giản mà hiệu quả, đặc biệt với các mạng chuỗi (RNN).

8. Trực giác về bề mặt loss

Với hàm loss của hồi quy tuyến tính/logistic, bề mặt loss là một cái bát lồi — chỉ có một đáy, gradient descent chắc chắn tìm được nghiệm tối ưu. Bề mặt loss của mạng sâu thì không lồi: gồ ghề, nhiều thung lũng, nhiều điểm phẳng và điểm yên ngựa (saddle point).

Nghe có vẻ đáng lo — liệu ta có bị kẹt ở một local minimum (cực tiểu cục bộ) tồi? Trong thực tế, điều này ít gây hại như tưởng: ở không gian rất nhiều chiều, đa số các cực tiểu tìm được đều cho loss đủ tốt, và sự nhiễu của mini-batch còn giúp mạng "rung" thoát khỏi các điểm phẳng. Vì vậy ta thường không cần nghiệm toàn cục hoàn hảo — một nghiệm đủ tốt và tổng quát hóa tốt trên dữ liệu mới mới là mục tiêu thật sự.

Use case thực tế

Một quy trình huấn luyện điển hình, an toàn để bắt đầu:

  1. Optimizer & learning rate — dùng Adam với lr=1e-3 làm mặc định. Nếu loss dao động dữ dội hoặc thành NaN, giảm learning rate (thử 1e-4); nếu học quá chậm, tăng nhẹ. Với mạng lớn, thêm warmup vài trăm bước rồi cosine decay về cuối.
  2. Batch size — chọn theo bộ nhớ GPU (32/64/128 là thông dụng). Batch lớn thì nhớ tăng learning rate tương ứng.
  3. Đọc train/val loss — vẽ cả hai đường theo epoch và đọc như bảng chẩn đoán:
    • Cả hai cùng giảm rồi phẳng → huấn luyện lành mạnh, có thể dừng.
    • Train giảm mà val bắt đầu tăng lênoverfitting: mạng học thuộc lòng dữ liệu huấn luyện. Cần chính quy hóa (xem bài regularization & tuning) hoặc early stopping — dừng ở epoch val loss thấp nhất.
    • Cả hai đều cao và không giảmunderfitting hoặc learning rate/kiến trúc có vấn đề: mô hình chưa đủ mạnh hoặc chưa học được. Tăng dung lượng mạng, kiểm tra lại learning rate và dữ liệu.
  4. Lưu lại checkpoint ở val loss tốt nhất để dùng, không nhất thiết là mô hình ở epoch cuối.

Ghi nhớ

  • Huấn luyện = tối ưu: tìm bộ trọng số làm hàm mất mát nhỏ nhất, qua vòng lặp forward → loss → backward → update lặp lại nhiều lần.
  • Loss đo sai số: MSE cho hồi quy, cross-entropy cho phân loại. Loss phải khả vi.
  • Gradient descent đi ngược hướng gradient để giảm loss; mini-batch là chuẩn thực tế, cân bằng tốc độ và ổn định.
  • Backpropagation dùng quy tắc chuỗi để tính gradient cho mọi trọng số một cách hiệu quả; mỗi lớp "biết" mình góp bao nhiêu vào lỗi. Thực tế autograd làm hộ việc này.
  • Optimizer: SGD → Momentum → RMSProp → Adam. Khi chưa chắc, dùng Adam (lr=1e-3).
  • Learning rate là siêu tham số quan trọng nhất: quá lớn phân kỳ, quá nhỏ chậm. Dùng warmup + decay khi cần.
  • Gradient tiêu biến/bùng nổ được kiềm chế bằng khởi tạo He/Xavier, ReLU, BatchNormgradient clipping.
  • Đọc train/val loss để chẩn đoán overfitting/underfitting; dùng early stopping và giữ checkpoint tốt nhất.

Bài tiếp theo: Regularization & tuning — chống overfitting và tinh chỉnh siêu tham số. Muốn gõ code chạy thật ngay bây giờ? Xem Thực hành với PyTorch.

Bài viết liên quan

Phân biệt AI/ML/DL, các kiểu học máy, quy trình ML end-to-end và thuật ngữ nền tảng.

13 thg 7, 2026 15

Chất lượng dữ liệu, xử lý thiếu/ngoại lai, mã hoá, chuẩn hoá, tạo đặc trưng và tránh data leakage.

13 thg 7, 2026 13

Mô hình ngôn ngữ lớn hoạt động ra sao, token, context window, tham số sinh, và kỹ thuật viết prompt.

13 thg 7, 2026 13

Retrieval-Augmented Generation, vector database, tool calling, agent và MCP để xây ứng dụng LLM thực tế.

13 thg 7, 2026 11