Transfer Learning và Data Augmentation khi có ít dữ liệu
Feature extractor hay fine-tune? Chọn chiến lược theo kích thước và độ tương đồng dữ liệu, kèm kỹ thuật tăng cường ảnh với torchvision.
Nội dung bài viết
ImageNet có 1,28 triệu ảnh; dự án của bạn ở nhà máy có lẽ chỉ có vài trăm ảnh sản phẩm lỗi. Huấn luyện ResNet-50 (25 triệu tham số) từ đầu trên vài trăm ảnh gần như chắc chắn overfitting. Hai kỹ thuật giúp vượt qua giới hạn đó, và được dùng trong gần như mọi dự án thị giác máy tính thực tế: Transfer Learning (mượn kiến thức từ mô hình đã học trên dữ liệu lớn) và Data Augmentation (tạo thêm biến thể từ dữ liệu sẵn có).
Số trang (ví dụ "trang 81") trong bài chỉ tới slide của bài giảng Deep Learning – Computer Vision – NLP (Thang Nguyen, Viet Nguyen); bài này giải thích chi tiết, kiểm chứng và đính chính các slide đó.
Transfer Learning là gì?
Trang 132 so sánh hai cách làm:
- Training from scratch: khởi tạo ngẫu nhiên, mạng phải tự học từ con số 0 mọi thứ, từ cạnh, kết cấu đến hình dạng đối tượng. Cần rất nhiều dữ liệu và thời gian.
- Transfer learning: lấy một mạng đã được huấn luyện sẵn (pretrained) trên bài toán lớn (ImageNet), giữ lại kiến thức "nhìn" chung của nó, rồi điều chỉnh cho bài toán mới.
Vì sao hiệu quả? Nhớ lại đặc trưng phân cấp ở bài Tổng quan Deep Learning cho Computer Vision: các lớp đầu học cạnh, màu, kết cấu, những thứ chung cho gần như mọi loại ảnh; chỉ các lớp sâu mới chuyên biệt cho 1.000 lớp ImageNet. Ta tái sử dụng phần chung và chỉ học lại phần chuyên biệt.
Cách 1: CNN làm bộ trích đặc trưng cố định (feature extractor)
Trang 133: "cắt" bỏ phần phân loại cũ, đóng băng (freeze) toàn bộ các lớp convolution, chỉ huấn luyện bộ phân loại mới gắn ở cuối.
import torch
import torch.nn as nn
from torchvision import models
weights = models.ResNet50_Weights.DEFAULT
model = models.resnet50(weights=weights)
for p in model.parameters(): # đóng băng toàn bộ backbone
p.requires_grad = False
num_classes = 5
model.fc = nn.Linear(model.fc.in_features, num_classes) # lớp mới, mặc định requires_grad=True
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(trainable) # 10245 = 2048*5 + 5, so với 25,6 triệu của cả mạng
optimizer = torch.optim.Adam(model.fc.parameters(), lr=1e-3)
Ưu điểm: huấn luyện rất nhanh, ít nguy cơ overfitting, chạy được cả trên CPU. Có thể tính sẵn đặc trưng 2048 chiều của mọi ảnh một lần rồi huấn luyện bộ phân loại (thậm chí bằng logistic regression hay SVM).
Cách 2: Fine-tune CNN
Trang 134: sao chép (copy) trọng số các lớp 1 đến L−1 từ mô hình nguồn, lớp đầu ra mới khởi tạo ngẫu nhiên và học từ đầu, sau đó tiếp tục huấn luyện cả mạng (hoặc một phần) trên dữ liệu mới với learning rate nhỏ.
Các mẹo quan trọng khi fine-tune:
- Learning rate nhỏ cho phần pretrained (thường nhỏ hơn 10–100 lần so với huấn luyện từ đầu), để không "phá" kiến thức sẵn có.
- Learning rate khác nhau theo nhóm tham số (discriminative learning rate): backbone nhỏ, lớp mới lớn.
- Hai giai đoạn: huấn luyện lớp mới vài epoch khi backbone còn đóng băng, sau đó mới mở băng (unfreeze) dần từ các lớp sâu. Tránh để gradient lớn từ lớp đầu ra ngẫu nhiên làm hỏng backbone.
- Tiền xử lý phải giống lúc mô hình được pretrain (kích thước, mean/std ImageNet).
model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
model.fc = nn.Linear(2048, num_classes)
# Chỉ mở băng stage cuối (layer4) và lớp fc
for name, p in model.named_parameters():
p.requires_grad = name.startswith(("layer4", "fc"))
# Learning rate khác nhau cho từng nhóm tham số
optimizer = torch.optim.AdamW([
{"params": model.layer4.parameters(), "lr": 1e-4},
{"params": model.fc.parameters(), "lr": 1e-3},
], weight_decay=1e-4)
eval() trong lúc huấn luyện (đóng băng BN), như cách torchvision vẫn làm với backbone của Faster R-CNN.Chọn chiến lược nào?
Hai câu hỏi quyết định (bảng trang 135): dữ liệu của bạn nhiều hay ít, và giống hay khác ImageNet?
| Dữ liệu tương tự (ảnh tự nhiên: động vật, đồ vật…) | Dữ liệu khác biệt (X-quang, ảnh vi mạch, ảnh vệ tinh…) | |
|---|---|---|
| Ít dữ liệu | Feature extractor: dùng đặc trưng mức cao (lớp cuối) + bộ phân loại mới | Dùng đặc trưng mức thấp hơn (cắt sớm hơn, ở giữa mạng) + bộ phân loại; cân nhắc fine-tune nhẹ |
| Nhiều dữ liệu | Fine-tune toàn bộ mạng | Fine-tune toàn bộ (khởi tạo từ pretrained vẫn thường hội tụ nhanh và tốt hơn huấn luyện từ đầu) |
Lý do cho ô "ít dữ liệu, khác biệt": các đặc trưng mức cao của ImageNet (mắt chó, bánh xe) vô dụng với ảnh X-quang, nhưng đặc trưng mức thấp (cạnh, kết cấu) vẫn có ích.
Data Augmentation: tạo thêm dữ liệu "miễn phí"
Ý tưởng: áp dụng các phép biến đổi ngẫu nhiên mà không làm thay đổi nhãn lên ảnh huấn luyện. Ô tô lật ngang vẫn là ô tô, ô tô hơi tối vẫn là ô tô (trang 136). Mỗi epoch, mạng thấy một phiên bản hơi khác của mỗi ảnh, nên khó "học thuộc" và buộc phải học đặc trưng bất biến với các biến đổi đó. Augmentation là một dạng regularization rất mạnh.
Biến đổi hình học (position manipulation / geometric transformations)
Trang 137:
- Lật (flip) ngang; lật dọc chỉ khi hợp lý (ảnh vệ tinh, ảnh vi mạch có, ảnh chân dung thì không).
- Xoay (rotation) một góc nhỏ.
- Co giãn (scaling) và cắt ngẫu nhiên (random crop, trang 140), dịch chuyển.
- Biến dạng (deformation): affine, perspective, elastic (trang 140).
Biến đổi màu (color manipulation / color distortion)
Trang 138–139: thay đổi độ sáng (brightness), độ tương phản (contrast), độ bão hoà (saturation), sắc độ (hue), chuyển ảnh xám (greyscale); thêm làm mờ, làm nét (sharpening), nhiễu.
Che khuất ngẫu nhiên
Random erasing / Cutout: xoá một hình chữ nhật ngẫu nhiên, buộc mạng nhìn vào nhiều phần của đối tượng chứ không chỉ một chi tiết dễ nhận.
Code với torchvision
from torchvision import transforms as T
IMAGENET_MEAN, IMAGENET_STD = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]
train_tf = T.Compose([
T.RandomResizedCrop(224, scale=(0.6, 1.0)), # cắt + co giãn ngẫu nhiên
T.RandomHorizontalFlip(), # lật ngang, p = 0.5
T.RandomRotation(15), # xoay ±15 độ
T.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3, hue=0.05),
T.ToTensor(),
T.Normalize(IMAGENET_MEAN, IMAGENET_STD),
T.RandomErasing(p=0.25), # áp dụng trên tensor nên đặt sau ToTensor
])
# Validation / test: KHÔNG augment, chỉ resize + crop giữa + normalize
eval_tf = T.Compose([
T.Resize(256),
T.CenterCrop(224),
T.ToTensor(),
T.Normalize(IMAGENET_MEAN, IMAGENET_STD),
])
- Augment cả tập validation/test: làm kết quả đánh giá nhiễu và không phản ánh thực tế. Augmentation chỉ dành cho tập train.
- Augmentation làm đổi nhãn: lật ngang chữ "b" thành "d", xoay 180° số "6" thành "9", đổi hue khi màu sắc chính là dấu hiệu lỗi (vết cháy, đổi màu).
- Với detection/segmentation, biến đổi hình học phải áp dụng đồng thời cho ảnh và box/mask. Dùng
torchvision.transforms.v2(hỗ trợtv_tensors.BoundingBoxes,Mask) hoặc thư viện Albumentations.
Augmentation tự động và kỹ thuật trộn ảnh
- AutoAugment, RandAugment, TrivialAugmentWide: chọn ngẫu nhiên phép biến đổi từ một danh sách đã được tối ưu, gần như không cần tinh chỉnh (
T.TrivialAugmentWide()). - Mixup: trộn tuyến tính hai ảnh và nhãn của chúng; CutMix: dán một mảng ảnh này lên ảnh kia, nhãn theo tỉ lệ diện tích. Có sẵn trong
torchvision.transforms.v2.MixUpvàCutMix, áp dụng trên cả batch. - Mosaic: ghép 4 ảnh thành 1, rất phổ biến khi huấn luyện YOLO.
Ví dụ trọn vẹn: phân loại 5 loại lỗi sản phẩm với ~500 ảnh
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets, models
# Dữ liệu sắp xếp theo thư mục: data/train/<tên_lớp>/*.jpg, data/val/<tên_lớp>/*.jpg
train_ds = datasets.ImageFolder("data/train", transform=train_tf)
val_ds = datasets.ImageFolder("data/val", transform=eval_tf)
train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4)
val_loader = DataLoader(val_ds, batch_size=64, num_workers=4)
device = "cuda" if torch.cuda.is_available() else "cpu"
model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
model.fc = nn.Linear(model.fc.in_features, len(train_ds.classes))
model = model.to(device)
criterion = nn.CrossEntropyLoss(label_smoothing=0.1)
# Giai đoạn 1: chỉ huấn luyện lớp fc (5 epoch)
for p in model.parameters():
p.requires_grad = False
for p in model.fc.parameters():
p.requires_grad = True
optimizer = torch.optim.AdamW(model.fc.parameters(), lr=1e-3)
# ... gọi train_one_epoch / evaluate như bài PyTorch 7 bước ...
# Giai đoạn 2: mở băng toàn bộ, learning rate nhỏ cho backbone (15 epoch)
for p in model.parameters():
p.requires_grad = True
backbone = [p for n, p in model.named_parameters() if not n.startswith("fc.")]
optimizer = torch.optim.AdamW([
{"params": backbone, "lr": 1e-4},
{"params": model.fc.parameters(), "lr": 1e-3},
], weight_decay=1e-4)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=15)
# ... train tiếp, lưu checkpoint tốt nhất theo val accuracy ...
Hàm train_one_epoch và evaluate dùng lại nguyên văn từ bài PyTorch: 7 bước huấn luyện. Với vài trăm ảnh mỗi lớp, cách làm này thường cho kết quả tốt hơn hẳn huấn luyện từ đầu, và hội tụ chỉ sau vài chục epoch.
Overfitting: nhận biết và xử lý
Dấu hiệu: train accuracy tiếp tục tăng còn val accuracy chững lại hoặc giảm; khoảng cách giữa hai đường ngày càng rộng. Các biện pháp, xếp theo mức hiệu quả thường gặp:
- Thêm dữ liệu thật (luôn là tốt nhất), làm sạch nhãn sai.
- Transfer learning thay cho huấn luyện từ đầu.
- Data augmentation mạnh hơn.
- Regularization: weight decay, Dropout, label smoothing.
- Mô hình nhỏ hơn; early stopping (dừng khi val không cải thiện sau N epoch).
Tóm tắt
- Transfer learning tái sử dụng đặc trưng chung đã học trên ImageNet.
- Feature extractor: đóng băng backbone, chỉ học lớp mới; fine-tune: học tiếp cả mạng với learning rate nhỏ.
- Chọn chiến lược theo kích thước dữ liệu và độ tương đồng với ImageNet.
- Augmentation: hình học, màu sắc, che khuất; chỉ áp dụng cho tập train, không được làm đổi nhãn.
Áp dụng ngay trong dự án chặng 10: phân loại ảnh OK/NG bằng transfer learning. Bước tiếp theo của Vision Engineer: không chỉ biết "ảnh có gì" mà còn biết "nó ở đâu", Object Detection cơ bản.
Tài liệu tham khảo
- CS231n: Transfer Learning (cs231n.github.io/transfer-learning).
- Yosinski et al. (2014). How transferable are features in deep neural networks?
- PyTorch Tutorials: Transfer Learning for Computer Vision.
- Shorten & Khoshgoftaar (2019). A survey on Image Data Augmentation for Deep Learning.
- Cubuk et al. (2020). RandAugment; Yun et al. (2019). CutMix; Zhang et al. (2018). mixup.
- Tài liệu torchvision: Transforming and augmenting images (transforms v2).


