Deep Learning 3 — Regularization & kỹ thuật huấn luyện

13 thg 7, 2026 6 lượt xem
#ai
#deep-learning
#augmentation
#dropout
#regularization
#batch-norm

Deep Learning 3 — Regularization & kỹ thuật huấn luyện

bài training & backpropagation chúng ta đã biết một mạng học bằng cách lặp bốn bước: forward → tính loss → backward → cập nhật trọng số. Nhưng "học được" và "học tốt" là hai chuyện khác nhau. Một mạng sâu với hàng triệu tham số hoàn toàn có thể học thuộc lòng toàn bộ tập huấn luyện — kể cả nhiễu — mà không hiểu được quy luật chung. Kết quả: loss trên tập train đẹp long lanh, nhưng ra dữ liệu mới thì sai bét. Đó là overfit.

Bài này gom toàn bộ kho vũ khí để chống overfit và làm cho quá trình huấn luyện chạy ổn định: dropout, batch normalization, weight decay, early stopping, data augmentation, khởi tạo trọng số hợp lý, và cách xử lý gradient biến mất/bùng nổ. Đây là những "gia vị" mà bất kỳ mô hình DL nghiêm túc nào cũng dùng.

1. Hai nỗi đau: overfit và khó train

Mạng nơ-ron sâu mạnh mẽ chính vì nó có nhiều tham sốphi tuyến nhiều tầng. Nhưng chính điều đó tạo ra hai vấn đề đối lập nhau:

  • Overfit (quá khớp) — mạng đủ dung lượng để ghi nhớ đặc điểm riêng của từng mẫu train, thay vì rút ra quy luật. Biểu hiện kinh điển: khoảng cách ngày càng lớn giữa loss trên train (thấp) và loss trên validation (cao và tăng dần).
  • Underfit (chưa khớp) — mạng quá nhỏ, huấn luyện quá ít, hoặc learning rate sai, nên chưa học được cả những quy luật cơ bản. Cả train loss lẫn val loss đều cao.

Song song đó là nỗi đau khó train: khi mạng sâu, tín hiệu gradient chạy ngược qua hàng chục lớp có thể co lại gần 0 (vanishing) hoặc phình to vô hạn (exploding), khiến các lớp đầu không học được gì hoặc loss nhảy loạn thành NaN. Phần lớn kỹ thuật trong bài này ra đời để giải quyết một trong hai nỗi đau đó — và nhiều kỹ thuật giải quyết cả hai cùng lúc.

Mục tiêu cuối cùng luôn là generalization (khả năng tổng quát hoá): sai số nhỏ trên dữ liệu chưa từng thấy, chứ không phải trên dữ liệu đã học.

2. Chẩn đoán bằng learning curve

Trước khi "chữa bệnh", phải "khám bệnh". Công cụ chẩn đoán số một là learning curve — vẽ train loss và validation loss theo số epoch. Ba tình huống điển hình:

Cách đọc rất trực quan:

  • Hai đường đều cao và gần nhau → underfit. Giải pháp: mạng to hơn, train lâu hơn, giảm regularization, chỉnh learning rate.
  • Cả hai đều thấp, val đi ngang sau khi chạm đáy → vừa khéo. Đây là điểm bạn muốn dừng.
  • Train xuống rất thấp nhưng val quay đầu đi lên → overfit. Khoảng cách (gap) giữa hai đường chính là "mức độ quá khớp". Giải pháp: thêm dữ liệu, regularization, early stopping.

Quy trình thực tế: luôn chia dữ liệu thành train / validation / test, theo dõi val loss mỗi epoch, và ra quyết định dựa trên hình dạng của hai đường chứ không chỉ con số cuối cùng.

3. Dropout — ép mạng đừng phụ thuộc một neuron

Dropout là kỹ thuật đơn giản mà hiệu quả bậc nhất. Ý tưởng: trong mỗi bước huấn luyện, ta tắt ngẫu nhiên một tỉ lệ neuron (ví dụ 50%) — cho đầu ra của chúng bằng 0. Mỗi lượt forward, mạng thấy một "kiến trúc con" khác nhau.

Vì bất kỳ neuron nào cũng có thể bị tắt bất kỳ lúc nào, mạng không dám dồn toàn bộ trách nhiệm vào một vài neuron; nó buộc phải học các đặc trưng dư thừa, phân tán. Điều này giống như huấn luyện một tập hợp (ensemble) nhiều mạng con rồi lấy trung bình — nên chống overfit rất tốt.

Điểm cực kỳ quan trọng: dropout chỉ bật khi train, phải tắt khi inference. Lúc suy luận ta dùng toàn bộ neuron để dự đoán ổn định. Trong PyTorch, việc này được xử lý tự động khi bạn gọi model.train() (bật dropout) và model.eval() (tắt dropout). Quên gọi model.eval() lúc dự đoán là một lỗi kinh điển khiến kết quả nhảy lung tung.

import torch.nn as nn

# (minh hoạ)
model = nn.Sequential(
    nn.Linear(784, 256),
    nn.ReLU(),
    nn.Dropout(p=0.5),      # tắt ngẫu nhiên 50% neuron khi train
    nn.Linear(256, 128),
    nn.ReLU(),
    nn.Dropout(p=0.3),
    nn.Linear(128, 10),
)

model.train()   # dropout HOẠT ĐỘNG
# ... vòng lặp huấn luyện ...

model.eval()    # dropout TẮT — dùng khi validate / inference

Tỉ lệ p thường 0.2–0.5. Đặt quá cao (ví dụ 0.8) có thể làm mạng underfit vì mất quá nhiều thông tin mỗi lượt.

4. Batch Normalization — chuẩn hoá dòng chảy trong mạng

Khi dữ liệu đi qua nhiều lớp, phân phối đầu vào của mỗi lớp cứ dịch chuyển liên tục trong lúc train (các lớp phía trước thay đổi trọng số). Điều này làm việc học chậm và bất ổn.

Batch Normalization (BatchNorm) giải quyết bằng cách chuẩn hoá đầu ra của một lớp theo từng mini-batch: trừ trung bình, chia độ lệch chuẩn của batch để đưa về phân phối có mean ≈ 0, std ≈ 1; sau đó nhân với hai tham số học được (scale γ và shift β) để mạng vẫn tự do co giãn khi cần.

Lợi ích thực tế:

  • Train nhanh hơn — cho phép dùng learning rate lớn hơn mà không bung.
  • Ổn định hơn — giảm phụ thuộc vào cách khởi tạo trọng số.
  • Có tác dụng regularization nhẹ — vì thống kê batch mang chút nhiễu ngẫu nhiên, đôi khi giảm được nhu cầu dropout.

BatchNorm cũng phân biệt train/eval: khi train dùng thống kê của batch hiện tại, còn khi eval dùng thống kê trung bình động đã tích luỹ trong lúc train. Lại một lý do nữa để không quên model.eval().

Layer Normalization là biến thể chuẩn hoá theo từng mẫu (trên chiều đặc trưng) thay vì theo batch. Nó không phụ thuộc kích thước batch nên phù hợp với dữ liệu chuỗi và là chuẩn hoá mặc định trong kiến trúc Transformer — nơi BatchNorm hoạt động kém.

import torch.nn as nn

# (minh hoạ) BatchNorm giữa Linear và activation
model = nn.Sequential(
    nn.Linear(784, 256),
    nn.BatchNorm1d(256),    # chuẩn hoá 256 đặc trưng theo batch
    nn.ReLU(),
    nn.Linear(256, 10),
)

# LayerNorm — dùng cho chuỗi / transformer
ln = nn.LayerNorm(512)      # chuẩn hoá theo chiều đặc trưng, không theo batch

5. Weight decay (L2) và L1 — phạt trọng số quá lớn

Trọng số càng lớn thì hàm mà mạng học được càng "gấp khúc", càng dễ overfit. Ý tưởng của regularization theo chuẩn là cộng thêm vào loss một khoản phạt theo độ lớn của trọng số:

  • L2 (weight decay) — phạt tổng bình phương trọng số. Loss mới ≈ loss gốc + λ · Σ w². Kết quả: mạng ưu tiên các trọng số nhỏ và mượt, không tham số nào bị đẩy quá lớn. Đây là dạng dùng phổ biến nhất trong DL, và trong nhiều optimizer nó được cài trực tiếp dưới tên tham số weight_decay.
  • L1 — phạt tổng trị tuyệt đối λ · Σ |w|. Nó có xu hướng đẩy một số trọng số về đúng 0, tạo mô hình thưa (sparse). Ít dùng cho mạng lớn hơn L2, nhưng hữu ích khi muốn chọn lọc đặc trưng.

Siêu tham số λ (hay weight_decay) điều khiển cường độ phạt: lớn quá thì underfit, nhỏ quá thì không đủ chống overfit. Đây cũng là logic đằng sau L1/L2 trong hồi quy tuyến tính mà bài tinh chỉnh mô hình ML đã bàn — chỉ khác là ở đây áp lên mạng sâu.

import torch.optim as optim

# (minh hoạ) L2 cài sẵn trong optimizer qua weight_decay
optimizer = optim.Adam(
    model.parameters(),
    lr=1e-3,
    weight_decay=1e-4,   # đây chính là hệ số L2 (λ)
)

6. Early stopping — dừng đúng lúc

Nhìn lại learning curve overfit: val loss giảm tới một điểm rồi quay đầu đi lên. Điểm đáy đó chính là mô hình tốt nhất. Early stopping tự động hoá việc dừng ngay tại đó: theo dõi val loss mỗi epoch, lưu lại trạng thái mô hình tốt nhất, và nếu val loss không cải thiện sau patience epoch liên tiếp thì dừng train và khôi phục về trạng thái tốt nhất đã lưu.

Đây là cách chống overfit "miễn phí" — không thêm tham số, không đổi kiến trúc — chỉ đơn giản là không train quá đà.

# (minh hoạ) khung early stopping theo val loss
best_val = float("inf")
patience, wait = 5, 0

for epoch in range(100):
    train_one_epoch(model, train_loader, optimizer)
    val_loss = evaluate(model, val_loader)    # tính loss trên tập validation

    if val_loss < best_val:
        best_val = val_loss
        wait = 0
        torch.save(model.state_dict(), "best.pt")   # lưu mô hình tốt nhất
    else:
        wait += 1
        if wait >= patience:      # 5 epoch không cải thiện → dừng
            print(f"Early stop tại epoch {epoch}")
            break

model.load_state_dict(torch.load("best.pt"))   # khôi phục trạng thái tốt nhất

patience quá nhỏ dễ dừng sớm oan (val loss thường dao động lên xuống), quá lớn thì để mạng overfit thêm. Giá trị 5–15 epoch là điểm khởi đầu hợp lý.

7. Data augmentation — tạo thêm dữ liệu "miễn phí"

Nguyên nhân gốc của overfit thường là thiếu dữ liệu. Khi không thể thu thập thêm, data augmentation tạo ra các biến thể hợp lệ từ dữ liệu sẵn có, buộc mạng học đặc trưng bất biến thay vì ghi nhớ từng mẫu.

Với ảnh (nối trực tiếp với bài CNN) các phép biến đổi phổ biến: xoay nhẹ, lật ngang, cắt (crop) ngẫu nhiên, đổi độ sáng/tương phản, thu phóng. Một con mèo bị lật ngang vẫn là con mèo — nhưng với mạng đó là một mẫu "mới", giúp nó học được rằng nhãn không phụ thuộc hướng ảnh.

from torchvision import transforms

# (minh hoạ) augmentation cho ảnh khi train
train_tf = transforms.Compose([
    transforms.RandomHorizontalFlip(),           # lật ngang ngẫu nhiên
    transforms.RandomRotation(15),               # xoay ±15 độ
    transforms.RandomResizedCrop(224),           # crop + thu phóng ngẫu nhiên
    transforms.ColorJitter(brightness=0.2),      # đổi độ sáng
    transforms.ToTensor(),
])

Lưu ý: augmentation chỉ áp dụng cho tập train, không áp cho validation/test (ta muốn đánh giá trên dữ liệu "thật"). Và phép biến đổi phải bảo toàn nhãn — ví dụ không lật dọc ảnh chữ số vì "6" lật lên thành "9". Với văn bản và âm thanh cũng có augmentation riêng (thay từ đồng nghĩa, thêm nhiễu...), nguyên tắc như nhau.

8. Khởi tạo trọng số: He và Xavier

Trước khi train, trọng số phải được gán giá trị ban đầu. Khởi tạo toàn số 0 là sai lầm chết người: mọi neuron trong một lớp sẽ tính ra hệt nhau và học hệt nhau (đối xứng không bao giờ bị phá vỡ). Khởi tạo ngẫu nhiên nhưng quá lớn/quá nhỏ thì gây ngay vanishing/exploding ở forward.

Hai chiến lược được kiểm chứng, khác nhau ở việc co giãn phương sai theo số đầu vào của lớp:

  • Xavier (Glorot) — thiết kế cho các activation đối xứng quanh 0 như tanh, sigmoid.
  • He (Kaiming) — thiết kế cho họ ReLU (vì ReLU cắt nửa âm, cần phương sai lớn hơn để bù). Đây là lựa chọn mặc định cho hầu hết mạng hiện đại dùng ReLU.

Điểm hay: các framework đã tự động khởi tạo hợp lý cho từng loại lớp, nên phần lớn thời gian bạn không phải tự tay làm. Nhưng hiểu nguyên tắc giúp bạn debug khi loss "đứng im" hoặc bung ngay từ epoch đầu.

import torch.nn as nn

# (minh hoạ) khởi tạo He cho lớp Linear dùng ReLU
layer = nn.Linear(256, 128)
nn.init.kaiming_normal_(layer.weight, nonlinearity="relu")   # He init
nn.init.zeros_(layer.bias)

9. Vanishing / exploding gradient

Đây là nỗi đau "khó train" ở mạng sâu. Khi backprop nhân chuỗi đạo hàm qua nhiều lớp:

  • Nếu mỗi thừa số nhỏ hơn 1, tích của chúng teo về 0vanishing gradient: các lớp gần đầu vào gần như không được cập nhật, mạng học rất chậm hoặc đứng.
  • Nếu mỗi thừa số lớn hơn 1, tích phình lênexploding gradient: cập nhật khổng lồ, loss dao động dữ dội rồi thành NaN.

Bộ giải pháp chuẩn — tất cả đều nối thẳng với phần training ở bài dl-02:

  • Hàm ReLU thay cho sigmoid/tanh — đạo hàm bằng 1 ở vùng dương nên gradient không teo dần.
  • Batch/Layer Norm — giữ tín hiệu ở thang đo lành mạnh qua từng lớp (mục 4).
  • Kết nối tắt (residual/skip connection) — cho gradient một "đường cao tốc" chạy ngược thẳng qua các lớp, là chìa khoá giúp huấn luyện mạng cực sâu (nền tảng của ResNet và Transformer).
  • Gradient clipping — cắt ngưỡng độ lớn gradient để chống exploding, đặc biệt quan trọng với mạng chuỗi (RNN).
import torch.nn.utils as utils

# (minh hoạ) gradient clipping trong vòng lặp train
loss.backward()
utils.clip_grad_norm_(model.parameters(), max_norm=1.0)   # cắt nếu norm > 1.0
optimizer.step()

10. Chống overfit tổng hợp — chọn vũ khí theo tình huống

Không có một viên đạn bạc; thực tế bạn phối hợp nhiều kỹ thuật theo thứ tự ưu tiên:

  1. Thêm dữ liệu — luôn hiệu quả nhất nếu khả thi. Nhiều dữ liệu thật đánh bại mọi mẹo regularization.
  2. Data augmentation — khi không thêm được dữ liệu thật, đây là lựa chọn số một, gần như "miễn phí".
  3. Regularization — dropout + weight decay + BatchNorm, phối hợp linh hoạt.
  4. Early stopping — luôn nên bật, gần như không có nhược điểm.
  5. Giảm dung lượng mạng — nếu mạng quá to so với lượng dữ liệu, thu nhỏ nó lại (ít lớp/ít neuron hơn).
  6. Transfer learning — dùng lại một mô hình đã huấn luyện sẵn trên tập lớn rồi tinh chỉnh cho bài toán của mình. Cực kỳ hiệu quả khi dữ liệu ít, vì mô hình đã "biết" nhiều đặc trưng chung.

Ngược lại, nếu chẩn đoán ra underfit, hãy làm điều đối lập: mạng to hơn, train lâu hơn, giảm regularization, kiểm tra lại learning rate.

Use case thực tế

  • Phân loại ảnh y tế với dữ liệu ít — bệnh viện chỉ có vài nghìn ảnh X-quang có nhãn. Đội ngũ dùng transfer learning (backbone huấn luyện sẵn trên ảnh tự nhiên) + augmentation mạnh (lật, xoay, đổi độ sáng) + dropout ở lớp phân loại. Kết quả: tránh được overfit dù dữ liệu ít, val accuracy tăng rõ rệt so với train từ đầu.
  • Mô hình dự đoán rời bỏ khách hàng (churn) — dữ liệu bảng, lớp mất cân bằng. Ngoài các kỹ thuật ở bài xử lý mất cân bằng, đội dùng mạng nhỏ + weight decay + early stopping theo val AUC để không quá khớp vào nhóm khách hàng đa số.
  • Huấn luyện mạng rất sâu bị NaN — loss bung sau vài trăm bước. Nguyên nhân: exploding gradient. Khắc phục bằng gradient clipping (max_norm=1.0) + BatchNorm + hạ learning rate. Loss ổn định trở lại ngay.
  • Fine-tune mô hình ngôn ngữ nhỏ — dùng LayerNorm (đã có sẵn trong kiến trúc), dropoutearly stopping để tránh mô hình học thuộc tập fine-tune nhỏ.

Khi bắt tay code, mọi kỹ thuật trên đều là vài dòng trong vòng lặp huấn luyện chuẩn của PyTorch.

Ghi nhớ

  • Overfit = train tốt, val kém; underfit = cả hai đều kém. Luôn chẩn đoán bằng learning curve (train vs val loss) trước khi chỉnh sửa.
  • Dropout: tắt neuron ngẫu nhiên khi train, tắt khi inference — nhớ model.eval().
  • BatchNorm: chuẩn hoá theo batch, train nhanh và ổn định hơn; LayerNorm cho chuỗi/Transformer.
  • Weight decay (L2) phạt trọng số lớn cho hàm mượt hơn; L1 tạo mô hình thưa.
  • Early stopping theo val loss: dừng đúng điểm đáy, khôi phục mô hình tốt nhất — gần như luôn nên bật.
  • Data augmentation chỉ áp cho tập train và phải bảo toàn nhãn.
  • He init cho ReLU, Xavier cho tanh/sigmoid; đừng bao giờ khởi tạo toàn 0.
  • Chống vanishing/exploding gradient: ReLU + Norm + residual + gradient clipping.
  • Ưu tiên chống overfit: thêm dữ liệu → augmentation → regularization → early stopping → giảm mạng → transfer learning.

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