⭐ Vision Engineer · Chặng 10Bài 8/12Bài học~60 phút

Các kiến trúc CNN kinh điển: LeNet, AlexNet, VGG, ResNet

Hành trình ImageNet: mỗi kiến trúc giải quyết vấn đề gì, ý tưởng cốt lõi, số tham số, độ chính xác và cách dùng mô hình pretrained trong torchvision.

Các kiến trúc CNN kinh điển: LeNet, AlexNet, VGG, ResNet

Học xong bài này, bạn sẽ

  • Nắm ý tưởng chính của LeNet, AlexNet, VGG, ResNet
  • Hiểu vì sao skip connection giúp mạng sâu
  • Dùng mô hình pretrained với weights= của torchvision

Nên học trước: BatchNorm, Dropout và receptive field

Nội dung bài viết
  1. Làm sao để thiết kế kiến trúc CNN?
  2. ImageNet và cuộc thi ILSVRC
  3. LeNet-5 (1998): tổ tiên của CNN
  4. AlexNet (2012): khoảnh khắc bùng nổ
  5. VGGNet (2014): đơn giản và sâu
  6. GoogLeNet / Inception (2014): rộng thay vì chỉ sâu
  7. Vấn đề: mạng càng sâu lại càng kém?
  8. ResNet (2015): đường tắt cho gradient
  9. Sau ResNet: các hướng phát triển chính
  10. Dùng mô hình pretrained trong torchvision
  11. Tóm tắt
  12. Tài liệu tham khảo

Đến đây bạn đã tự lắp được một CNN. Nhưng các kỹ sư thị giác máy tính hiếm khi thiết kế kiến trúc từ đầu: họ đứng trên vai những mạng đã được kiểm chứng qua hàng triệu ảnh. Bài này đi qua các kiến trúc kinh điển theo dòng thời gian, mỗi mạng giải quyết vấn đề gì, ý tưởng nào còn sống đến ngày nay, và cách dùng chúng trong PyTorch.

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 đó.

Làm sao để thiết kế kiến trúc CNN?

Quy tắc 1: "Don't be a hero" (Andrej Karpathy, trang 106). Đừng tự sáng chế kiến trúc. Hãy chọn mạng đang hoạt động tốt nhất trên ImageNet phù hợp với ngân sách tính toán của bạn, tải trọng số pretrained về và fine-tune trên dữ liệu của mình. Thư viện torchvision.models có sẵn hàng chục kiến trúc: AlexNet, VGG, ResNet, SqueezeNet, DenseNet, Inception v3, GoogLeNet, ShuffleNet v2, MobileNet v2/v3, ResNeXt, Wide ResNet, MNASNet, EfficientNet, RegNet, ConvNeXt, ViT…

Quy tắc 2 (trang 107): chỉ khi quy tắc 1 không đáp ứng (thiết bị quá yếu, ảnh đặc thù như ảnh y tế 3D, ảnh hồng ngoại một kênh…), mới tự trả lời các câu hỏi:

  • Mô hình nên có bao nhiêu layer?
  • Mỗi lớp conv có bao nhiêu kênh, kernel size bao nhiêu? Dùng activation gì?
  • Pooling kernel size bao nhiêu? Flatten feature map ở đâu? Bao nhiêu lớp fully connected?
  • Có dùng BatchNorm, Dropout… không?

Hiểu các kiến trúc kinh điển chính là cách tốt nhất để trả lời những câu hỏi này.

ImageNet và cuộc thi ILSVRC

ImageNet Large Scale Visual Recognition Challenge (ILSVRC, 2010–2017) là "đấu trường" thúc đẩy cả ngành: khoảng 1,28 triệu ảnh huấn luyện, 50.000 ảnh validation, 1.000 lớp. Thước đo là top-5 error: tỉ lệ ảnh mà nhãn đúng không nằm trong 5 dự đoán đầu.

NămMô hình thắng cuộcTop-5 errorSố lớp
2010NEC (đặc trưng thủ công + SVM)28,2%nông
2011XRCE (Fisher vectors)25,8%nông
2012AlexNet16,4%8
2013ZFNet (Clarifai)11,7%8
2014GoogLeNet (hạng nhì: VGG 7,3%)6,7%22
2015ResNet3,57%152
2017SENet2,25%152+

Để tham khảo, sai số của con người (Karpathy tự thử nghiệm) vào khoảng 5,1%. Năm 2015, máy tính đã vượt qua mức này trên benchmark ImageNet.

Dòng thời gian ở trang 108: ConvNet (1989) → LeNet (1998) → AlexNet (2012) → ZFNet, GoogLeNet, VGG, Inception (2013–2014) → ResNet (2015) → DenseNet (2016) → ResNeXt (2017) → Channel Boosted CNN (2018) → EfficientNet (2019/20).

LeNet-5 (1998): tổ tiên của CNN

Yann LeCun và cộng sự thiết kế LeNet-5 để đọc chữ số viết tay trên séc ngân hàng, và nó đã được triển khai thương mại vào cuối thập niên 1990.

LớpLoạiFeature mapKích thướcKernelStrideActivation
InputẢnh132×32---
1Convolution628×285×51tanh
2Average pooling614×142×22tanh
3Convolution1610×105×51tanh
4Average pooling165×52×22tanh
5Convolution (≡ FC)1201×15×51tanh
6Fully connected-84--tanh
OutputFully connected-10--softmax
import torch.nn as nn

lenet5 = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5), nn.Tanh(), nn.AvgPool2d(2),      # 32 -> 28 -> 14
    nn.Conv2d(6, 16, kernel_size=5), nn.Tanh(), nn.AvgPool2d(2),     # 14 -> 10 -> 5
    nn.Flatten(),                                                     # 16*5*5 = 400
    nn.Linear(400, 120), nn.Tanh(),
    nn.Linear(120, 84), nn.Tanh(),
    nn.Linear(84, 10),
)
print(sum(p.numel() for p in lenet5.parameters()))   # 61706

Chỉ khoảng 61,7 nghìn tham số, nhưng đã có đủ "công thức" của CNN hiện đại: conv → pooling → conv → pooling → FC. (Bảng ở trang 112 là biến thể phổ biến trong sách dạy học: đầu vào 28×28 có padding 2 và dùng sigmoid.)

AlexNet (2012): khoảnh khắc bùng nổ

Krizhevsky, Sutskever và Hinton giảm top-5 error từ ~26% xuống 16,4%, mở ra kỷ nguyên deep learning.

LớpLoạiOutputKernel / stride
InputẢnh227×227×3-
1Conv + ReLU55×55×9611×11 / 4
Max pooling27×27×963×3 / 2
2Conv + ReLU (pad 2)27×27×2565×5 / 1
Max pooling13×13×2563×3 / 2
3, 4, 5Conv + ReLU (pad 1)13×13×384, 384, 2563×3 / 1
Max pooling → flatten6×6×256 = 92163×3 / 2
6, 7FC + ReLU + Dropout 0.54096, 4096-
8FC + softmax1000-

Những đóng góp còn nguyên giá trị:

  • ReLU thay cho tanh/sigmoid: huấn luyện nhanh hơn nhiều lần.
  • Dropout ở các lớp FC chống overfitting.
  • Data augmentation: cắt ngẫu nhiên, lật ngang, nhiễu màu.
  • Huấn luyện trên GPU (2 card GTX 580 3GB, mạng được chia đôi giữa hai GPU).
  • Max pooling chồng lấn (3×3 stride 2); Local Response Normalization (về sau bị BatchNorm thay thế).

AlexNet có khoảng 61 triệu tham số, trong đó hơn 58 triệu nằm ở 3 lớp FC. (Bài báo gốc ghi đầu vào 224×224, nhưng chỉ 227×227 mới cho ra đúng 55×55 với công thức \((227 - 11)/4 + 1 = 55\).)

VGGNet (2014): đơn giản và sâu

Nhóm Visual Geometry Group (Oxford) đặt câu hỏi: nếu chỉ dùng một loại lớp conv duy nhất thì sao? VGG chỉ gồm conv 3×3, stride 1, padding 1 và max pooling 2×2, stride 2, xếp thành 5 khối với số kênh 64 → 128 → 256 → 512 → 512 (trang 113–114).

  • VGG-16: 13 conv + 3 FC; VGG-19: 16 conv + 3 FC.
  • Xếp chồng conv 3×3 thay cho kernel lớn: hai lớp 3×3 có receptive field 5×5, ba lớp 3×3 có receptive field 7×7, ít tham số hơn và nhiều phi tuyến hơn (đã phân tích ở bài trước).
  • Nhược điểm: khoảng 138 triệu tham số (VGG-16), riêng lớp FC đầu tiên \(25088\times4096 + 4096 \approx 102,8\) triệu; nặng về bộ nhớ và tính toán (~15,5 GFLOPs).

Cấu trúc đều đặn khiến VGG rất được ưa chuộng làm bộ trích đặc trưng: "cắt" bỏ các lớp FC, giữ phần conv đã học trên ImageNet, gắn lớp FC mới cho bài toán riêng, ví dụ nhận diện 7 cảm xúc khuôn mặt (trang 114). Đây chính là transfer learning (xem Transfer Learning và Data Augmentation và dự án cuối chặng).

GoogLeNet / Inception (2014): rộng thay vì chỉ sâu

Thay vì chọn kernel 1×1, 3×3 hay 5×5, module Inception dùng tất cả song song rồi nối (concatenate) kết quả theo chiều kênh, để mạng tự chọn thang đo phù hợp. Chìa khoá giúp nó không quá đắt là conv 1×1 làm "nút cổ chai" giảm số kênh trước các conv 3×3, 5×5. GoogLeNet sâu 22 lớp nhưng chỉ khoảng 6,6–6,8 triệu tham số (ít hơn AlexNet 9 lần), nhờ thay các lớp FC bằng global average pooling. Các phiên bản sau (Inception v2, v3, v4) thêm BatchNorm và phân rã kernel (5×5 thành hai 3×3, 3×3 thành 1×3 + 3×1).

Vấn đề: mạng càng sâu lại càng kém?

Theo định lý xấp xỉ vạn năng (trang 115, xem Từ Linear Classifier đến Neural Network), mạng đủ lớn có thể biểu diễn mọi hàm, và thực nghiệm cho thấy thêm độ sâu giúp ích. Nhưng khi xếp chồng các lớp conv "trơn" lên quá 20–30 lớp, He et al. (2015) quan sát thấy mạng 56 lớp có lỗi huấn luyện cao hơn mạng 20 lớp. Đây không phải overfitting (lỗi trên tập train cũng cao hơn), mà là mạng sâu khó tối ưu: gradient phải đi qua quá nhiều phép nhân và suy yếu dần (vanishing gradient, trang 118). Về lý thuyết, mạng 56 lớp ít nhất phải làm được như mạng 20 lớp bằng cách cho 36 lớp thừa học hàm đồng nhất (identity), nhưng trên thực tế SGD không tìm được lời giải đó.

ResNet (2015): đường tắt cho gradient

x weight layer ReLU weight layer F(x) + identity x ReLU H(x) = F(x) + x
Khối residual (trang 117): các lớp chỉ cần học phần "dư" F(x) = H(x) − x

Ý tưởng của Kaiming He và cộng sự: thay vì bắt hai lớp học trực tiếp ánh xạ mong muốn \(H(x)\), cho chúng học phần dư \(F(x) = H(x) - x\), rồi cộng lại đầu vào qua một đường tắt (skip/shortcut connection):

\[ H(x) = F(x) + x \]
  • Nếu ánh xạ tối ưu gần với identity, mạng chỉ cần đẩy \(F(x)\) về 0, việc rất dễ (trọng số nhỏ).
  • Khi lan truyền ngược: \(\dfrac{\partial H}{\partial x} = \dfrac{\partial F}{\partial x} + 1\). Số "+1" đảm bảo gradient luôn có đường đi thẳng về các lớp đầu, không bị triệt tiêu dù mạng sâu hàng trăm lớp.

Kết quả: ResNet-152 thắng ILSVRC 2015 với top-5 error 3,57%, và về sau còn huấn luyện được cả mạng hơn 1.000 lớp.

Hai loại khối residual

  • Basic block (ResNet-18/34): hai conv 3×3.
  • Bottleneck block (ResNet-50/101/152): conv 1×1 giảm kênh (256 → 64), conv 3×3, conv 1×1 tăng lại (64 → 256): rẻ hơn nhiều so với hai conv 3×3 trên 256 kênh.
  • Khi số kênh hoặc kích thước thay đổi giữa hai stage, đường tắt dùng conv 1×1 (có stride) để khớp kích thước (nhánh "Conv1×1 B" ở hình (b) trang 116).
import torch
import torch.nn as nn


class BasicBlock(nn.Module):
    """Khối residual của ResNet-18/34: hai conv 3x3 + đường tắt (shortcut)."""

    def __init__(self, cin, cout, stride=1):
        super().__init__()
        self.conv1 = nn.Conv2d(cin, cout, 3, stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(cout)
        self.conv2 = nn.Conv2d(cout, cout, 3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(cout)
        self.shortcut = nn.Identity()
        if stride != 1 or cin != cout:      # kích thước đổi -> dùng conv 1x1 để khớp
            self.shortcut = nn.Sequential(
                nn.Conv2d(cin, cout, 1, stride=stride, bias=False), nn.BatchNorm2d(cout))

    def forward(self, x):
        out = torch.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        return torch.relu(out + self.shortcut(x))     # H(x) = F(x) + x


block = BasicBlock(64, 128, stride=2)
print(block(torch.rand(1, 64, 56, 56)).shape)        # torch.Size([1, 128, 28, 28])

Cấu hình các phiên bản ResNet

Mọi ResNet chung phần đầu (conv 7×7, 64 kênh, stride 2 → max pool 3×3 stride 2) và phần cuối (global average pool → FC 1000), khác nhau ở số khối trong 4 stage conv2_x … conv5_x (bảng trang 116):

Mô hìnhLoại khốiSố khối mỗi stageTham sốFLOPsTop-1 ImageNet (torchvision)
ResNet-18Basic2, 2, 2, 211,7M1,8×10⁹69,8%
ResNet-34Basic3, 4, 6, 321,8M3,6×10⁹73,3%
ResNet-50Bottleneck3, 4, 6, 325,6M3,8×10⁹76,1% (V1) / 80,9% (V2)
ResNet-101Bottleneck3, 4, 23, 344,5M7,6×10⁹77,4% / 81,9%
ResNet-152Bottleneck3, 8, 36, 360,2M11,3×10⁹78,3% / 82,3%

(V2 là trọng số huấn luyện lại năm 2021 với "công thức" hiện đại: nhiều augmentation hơn, label smoothing, huấn luyện lâu hơn. Cùng một kiến trúc, độ chính xác tăng gần 5%: kỹ thuật huấn luyện quan trọng không kém kiến trúc.)

Sau ResNet: các hướng phát triển chính

Mô hìnhNămÝ tưởng chính
DenseNet2016–17Mỗi lớp nhận đầu vào là nối (concat) đầu ra của mọi lớp trước trong khối; tái sử dụng đặc trưng, ít tham số (DenseNet-121: 8M, 74,4%).
ResNeXt2017Bottleneck dùng grouped convolution (32 nhóm): tăng "cardinality" thay vì độ sâu/độ rộng.
SENet2017Khối Squeeze-and-Excitation: học trọng số chú ý cho từng kênh; thắng ILSVRC 2017.
MobileNet v1/v2/v32017–19Depthwise separable conv, inverted residual; chạy trên điện thoại (v3-Large: 5,5M tham số, ~0,22 GFLOPs, 75,3%).
EfficientNet2019Compound scaling: tăng đồng thời độ sâu, độ rộng, độ phân giải theo một hệ số chung. B0: 5,3M tham số, 77,7%.
Vision Transformer (ViT)2020Cắt ảnh thành các patch 16×16, xử lý như chuỗi từ bằng Transformer; mạnh khi có dữ liệu cực lớn.
ConvNeXt2022"Hiện đại hoá" ResNet theo các bài học từ Transformer (kernel depthwise 7×7, LayerNorm, GELU); ConvNeXt-Tiny 82,5%.
Chọn backbone nào khi đi làm?
  • Cần nhanh, chạy trên thiết bị biên (camera, điện thoại, Jetson): MobileNetV3, EfficientNet-B0, hoặc ResNet-18.
  • Cân bằng, dễ dùng, nhiều tài liệu: ResNet-50 (trọng số V2).
  • Cần độ chính xác cao, có GPU tốt: ConvNeXt, EfficientNetV2, ViT/Swin.
Luôn đo trên dữ liệu và phần cứng thực tế của bạn; con số ImageNet chỉ là điểm khởi đầu.

Dùng mô hình pretrained trong torchvision

API đã thay đổi so với slideCách viết models.resnet18(pretrained=True) ở trang 106 đã bị deprecated từ torchvision 0.13. Cách mới dùng tham số weights, và mỗi bộ trọng số đi kèm đúng phép tiền xử lý đã dùng khi huấn luyện.
import torch
from torchvision import models
from torchvision.io import read_image

weights = models.ResNet50_Weights.DEFAULT          # hiện là IMAGENET1K_V2
model = models.resnet50(weights=weights).eval()
preprocess = weights.transforms()                  # resize, crop, normalize đúng chuẩn

img = read_image("dog.jpg")                         # tensor uint8 (C, H, W)
batch = preprocess(img).unsqueeze(0)
with torch.no_grad():
    probs = model(batch).softmax(dim=1)[0]
top5 = probs.topk(5)
for p, i in zip(top5.values, top5.indices):
    print(f"{weights.meta['categories'][i]:25s} {p:.1%}")

print(models.list_models(module=models)[:10])       # liệt kê các kiến trúc phân loại có sẵn

Tóm tắt

  • Đừng tự chế kiến trúc nếu chưa cần: chọn backbone pretrained đã kiểm chứng.
  • LeNet đặt nền móng; AlexNet mang ReLU, Dropout, GPU; VGG chứng minh "3×3 xếp chồng" là đủ.
  • Inception: song song nhiều kernel + bottleneck 1×1 + global average pooling.
  • ResNet: \(H(x) = F(x) + x\) giải quyết bài toán tối ưu mạng rất sâu; là backbone phổ biến nhất.
  • Dùng weights=... và weights.transforms() với torchvision hiện đại.

Vì sao mạng sâu khó huấn luyện, và còn những "vũ khí" nào khác ngoài skip connection? Xem Vanishing/Exploding gradient.

Tài liệu tham khảo

  • LeCun et al. (1998). Gradient-Based Learning Applied to Document Recognition.
  • Krizhevsky et al. (2012). ImageNet Classification with Deep Convolutional Neural Networks.
  • Simonyan & Zisserman (2015). Very Deep Convolutional Networks for Large-Scale Image Recognition.
  • Szegedy et al. (2015). Going Deeper with Convolutions.
  • He et al. (2016). Deep Residual Learning for Image Recognition.
  • Tan & Le (2019). EfficientNet; Liu et al. (2022). A ConvNet for the 2020s.
  • Tài liệu torchvision: Models and pre-trained weights (số tham số và độ chính xác lấy từ metadata chính thức).

Bài tập thực hành

  1. Chạy ResNet-18 và MobileNetV3 pretrained trên 10 ảnh của bạn; so sánh top-5, số tham số và thời gian suy luận trên CPU.

Làm xong bài tập mới chuyển sang bài tiếp theo: tự viết code là cách duy nhất để nhớ lâu.

Bài tiếp theo
Ban biên tập HọcFree

Chúng tôi viết những bài hướng dẫn dễ hiểu, thực tế để ai cũng có thể tự học lập trình và ngoại ngữ, hoàn toàn miễn phí.