LeNet, AlexNet, VGG

Các kiến trúc CNN quan trọng: LeNet, AlexNet, VGG

Mạng nơ-ron tích chập (Convolutional Neural Networks - CNN) đã cách mạng hóa lĩnh vực thị giác máy tính và đóng vai trò quan trọng trong sự phát triển của trí tuệ nhân tạo hiện đại. Các kiến trúc CNN tiên phong như LeNet, AlexNet và VGG không chỉ là những cột mốc quan trọng trong lịch sử AI mà còn thiết lập nền tảng cho các kiến trúc hiện đại hơn ngày nay. Bài viết này sẽ khám phá chi tiết ba kiến trúc CNN quan trọng này, so sánh các đặc điểm nổi bật và tác động của chúng đến lĩnh vực học sâu.

LeNet-5 (1998)

Tổng quan

LeNet-5 là một trong những mạng CNN đầu tiên, được phát triển bởi Yann LeCun và cộng sự vào năm 1998. Kiến trúc này được thiết kế chủ yếu để nhận dạng chữ số viết tay và đã đạt được thành công đáng kể trong việc tự động xử lý séc ngân hàng.

Kiến trúc

LeNet-5 bao gồm 7 lớp với các thành phần chính:

  1. Lớp đầu vào: Hình ảnh 32×32 pixel

  2. C1: Lớp tích chập với 6 bộ lọc kích thước 5×5, tạo ra 6 feature maps kích thước 28×28

  3. S2: Lớp subsampling (pooling) giảm kích thước xuống 14×14

  4. C3: Lớp tích chập với 16 bộ lọc, tạo ra 16 feature maps kích thước 10×10

  5. S4: Lớp subsampling giảm kích thước xuống 5×5

  6. C5: Lớp tích chập với 120 feature maps kích thước 1×1

  7. F6: Lớp fully connected với 84 đơn vị

  8. Lớp đầu ra: 10 đơn vị (tương ứng với các chữ số 0-9)

Đặc điểm nổi bật

  • Tổng số tham số: khoảng 60,000

  • Sử dụng các khối tích chập-pooling lặp lại

  • Hàm kích hoạt: tanh (hyperbolic tangent)

  • Không có lớp batch normalization hay dropout

  • Được thiết kế để chạy trên phần cứng hạn chế

Tầm ảnh hưởng

LeNet-5 đặt nền móng cho kiến trúc CNN hiện đại với các nguyên tắc cơ bản vẫn được sử dụng đến ngày nay:

  • Tính chất địa phương (local connectivity)

  • Chia sẻ tham số (parameter sharing)

  • Sử dụng các lớp tích chập theo sau là subsampling/pooling

AlexNet (2012)

Tổng quan

AlexNet, được phát triển bởi Alex Krizhevsky, Ilya Sutskever và Geoffrey Hinton, đã tạo nên một bước ngoặt trong lĩnh vực thị giác máy tính khi giành chiến thắng tại cuộc thi ImageNet Large Scale Visual Recognition Challenge (ILSVRC) năm 2012 với tỷ lệ lỗi top-5 chỉ 15.3%, vượt xa đội xếp thứ hai (26.2%).

Kiến trúc

AlexNet có 8 lớp (5 lớp tích chập và 3 lớp fully connected):

  1. Lớp đầu vào: Hình ảnh RGB 227×227×3

  2. Conv1: 96 bộ lọc kích thước 11×11, stride 4, không padding

  3. MaxPool1: Kích thước 3×3, stride 2

  4. Conv2: 256 bộ lọc kích thước 5×5, padding 2

  5. MaxPool2: Kích thước 3×3, stride 2

  6. Conv3: 384 bộ lọc kích thước 3×3, padding 1

  7. Conv4: 384 bộ lọc kích thước 3×3, padding 1

  8. Conv5: 256 bộ lọc kích thước 3×3, padding 1

  9. MaxPool3: Kích thước 3×3, stride 2

  10. FC6: 4096 đơn vị

  11. FC7: 4096 đơn vị

  12. FC8: 1000 đơn vị (số lớp trong ImageNet)

Đặc điểm nổi bật

  • Tổng số tham số: khoảng 60 triệu

  • Sử dụng hàm kích hoạt ReLU thay vì tanh/sigmoid

  • Áp dụng kỹ thuật Local Response Normalization (LRN)

  • Sử dụng Dropout với tỷ lệ 0.5 để giảm overfitting

  • Sử dụng Data Augmentation (cắt ngẫu nhiên, phản chiếu, thay đổi màu sắc)

  • Huấn luyện trên 2 GPU (kiến trúc song song)

Tầm ảnh hưởng

AlexNet được coi là cột mốc quan trọng đánh dấu sự bắt đầu của kỷ nguyên học sâu hiện đại:

  • Chứng minh tính hiệu quả của CNN trên bộ dữ liệu lớn

  • Phổ biến hóa ReLU và Dropout

  • Làm nổi bật vai trò của GPU trong huấn luyện mạng học sâu

  • Thiết lập xu hướng mạng CNN sâu hơn và lớn hơn

VGG (2014)

Tổng quan

VGG (Visual Geometry Group) được phát triển bởi Karen Simonyan và Andrew Zisserman từ Đại học Oxford, đã đạt vị trí thứ hai trong ILSVRC 2014. Mặc dù không giành chiến thắng (xếp sau GoogLeNet), VGG nhanh chóng trở nên phổ biến nhờ kiến trúc đơn giản, rõ ràng và dễ hiểu.

Kiến trúc

VGG có nhiều biến thể, nhưng phổ biến nhất là VGG-16 và VGG-19. VGG-16 bao gồm:

  1. Lớp đầu vào: Hình ảnh RGB 224×224×3

  2. Block 1: 2 lớp tích chập 3×3, 64 kênh + MaxPool 2×2

  3. Block 2: 2 lớp tích chập 3×3, 128 kênh + MaxPool 2×2

  4. Block 3: 3 lớp tích chập 3×3, 256 kênh + MaxPool 2×2

  5. Block 4: 3 lớp tích chập 3×3, 512 kênh + MaxPool 2×2

  6. Block 5: 3 lớp tích chập 3×3, 512 kênh + MaxPool 2×2

  7. FC6: 4096 đơn vị

  8. FC7: 4096 đơn vị

  9. FC8: 1000 đơn vị (số lớp trong ImageNet)

Đặc điểm nổi bật

  • Tổng số tham số: khoảng 138 triệu (VGG-16)

  • Thiết kế cực kỳ đồng nhất, chỉ sử dụng các lớp tích chập 3×3 với stride 1 và padding 1

  • Sử dụng MaxPooling 2×2 với stride 2 để giảm kích thước không gian

  • Sử dụng ReLU sau mỗi lớp tích chập

  • Tăng dần số lượng kênh theo độ sâu (64 → 128 → 256 → 512)

Tầm ảnh hưởng

VGG đã đặt ra một số nguyên tắc thiết kế quan trọng cho CNN:

  • Đơn giản hóa kiến trúc bằng cách sử dụng các khối đồng nhất

  • Chứng minh hiệu quả của việc sử dụng nhiều lớp tích chập nhỏ (3×3) thay vì ít lớp lớn hơn

  • Trở thành kiến trúc cơ sở phổ biến cho nhiều ứng dụng chuyển giao học tập

So sánh các kiến trúc

Tiêu chí
LeNet-5
AlexNet
VGG-16

Năm phát hành

1998

2012

2014

Số lớp

7

8

16

Số tham số

~60K

~60M

~138M

Kích thước đầu vào

32×32×1

227×227×3

224×224×3

Bộ dữ liệu

MNIST

ImageNet

ImageNet

Hàm kích hoạt

tanh

ReLU

ReLU

Regularization

Không

Dropout, LRN

Dropout

Bộ lọc

5×5

11×11, 5×5, 3×3

3×3

GPU

Không

2x NVIDIA GTX 580

4x NVIDIA Titan Black

Quá trình phát triển và tiến hóa

Quá trình tiến hóa từ LeNet đến VGG thể hiện một số xu hướng quan trọng trong sự phát triển của CNN:

  1. Tăng độ sâu: Từ 7 lớp (LeNet) lên 8 lớp (AlexNet) và 16-19 lớp (VGG)

  2. Tăng số tham số: Từ 60K lên 60M và 138M, phản ánh sự phức tạp ngày càng tăng

  3. Đơn giản hóa thiết kế: Xu hướng đi từ các bộ lọc đa dạng sang các bộ lọc đồng nhất 3×3 ở VGG

  4. Tinh chỉnh kỹ thuật regularization: Từ không có đến việc sử dụng Dropout, LRN và data augmentation

  5. Thay đổi hàm kích hoạt: Từ tanh/sigmoid sang ReLU

  6. Tăng cường sức mạnh tính toán: Từ CPU sang nhiều GPU mạnh hơn

Bài học kiến trúc từ ba mạng huyền thoại

1. Vai trò của các lớp tích chập

  • LeNet: Sử dụng các lớp tích chập với khả năng tận dụng tính chất không gian của dữ liệu

  • AlexNet: Các bộ lọc lớn (11×11) ở lớp đầu giúp bắt được các đặc trưng tổng thể

  • VGG: Nhiều lớp tích chập 3×3 liên tiếp tạo hiệu ứng trường nhận thức lớn hơn với ít tham số hơn

2. Chiến lược pooling

  • LeNet: Sử dụng Average Pooling (gọi là Subsampling)

  • AlexNet: Chuyển sang Max Pooling với cửa sổ 3×3

  • VGG: Sử dụng Max Pooling 2×2 đơn giản, đều đặn sau mỗi khối tích chập

3. Regularization và chống overfitting

  • LeNet: Ít tham số nên ít gặp vấn đề overfitting trên bộ dữ liệu nhỏ

  • AlexNet: Áp dụng Dropout (0.5) ở lớp fully connected và data augmentation

  • VGG: Tiếp tục sử dụng Dropout, kết hợp với pre-training và fine-tuning

4. Thiết kế mạng

  • LeNet: Thiết kế thủ công, dựa trên hiểu biết về thị giác sinh học

  • AlexNet: Thiết kế phức tạp hơn, kết hợp nhiều cải tiến kỹ thuật

  • VGG: Đơn giản hóa thiết kế, theo dõi từng khối, tăng dần số kênh theo độ sâu

Ứng dụng thực tế

LeNet-5

  • Nhận dạng chữ số viết tay (MNIST)

  • Xử lý séc ngân hàng tự động

  • OCR (Optical Character Recognition) đơn giản

  • Nhận dạng chữ cái và ký tự đặc biệt

AlexNet

  • Phân loại hình ảnh đa lớp

  • Phát hiện đối tượng

  • Cơ sở cho các mô hình transfer learning đầu tiên

  • Ứng dụng trong máy thị giác công nghiệp

VGG

  • Transfer learning cho nhiều ứng dụng thị giác khác nhau

  • Trích xuất đặc trưng hình ảnh (VGG feature extractor)

  • Phân đoạn ngữ nghĩa (Semantic segmentation)

  • Nhận dạng khuôn mặt và xác thực sinh trắc học

  • Phát hiện đối tượng chi tiết

  • Style transfer và tạo nội dung

Hiệu suất và chi phí tính toán

Kiến trúc
Top-5 Error (ImageNet)
Thời gian huấn luyện
Kích thước mô hình
FLOPs

LeNet-5

N/A (98.9% trên MNIST)

Vài giờ (CPU)

~0.5MB

~0.4M

AlexNet

15.3%

5-6 ngày (2 GPU)

~240MB

~1.5G

VGG-16

7.3%

2-3 tuần (4 GPU)

~528MB

~15.5G

Nhận xét:

  • LeNet: Nhẹ, hiệu quả, nhưng giới hạn trong các tác vụ đơn giản

  • AlexNet: Bước nhảy vọt về hiệu suất, với chi phí tính toán đáng kể

  • VGG: Cải thiện độ chính xác, nhưng với chi phí là mô hình nặng và chậm hơn nhiều

Sự chuyển tiếp từ VGG đến các kiến trúc mới hơn

VGG đánh dấu đỉnh cao của CNN "truyền thống" với thiết kế khá đơn giản. Sau VGG, các kiến trúc CNN đã phát triển theo những hướng mới:

GoogLeNet/Inception (2014)

  • Giới thiệu module Inception với tích chập song song

  • Giảm đáng kể số tham số (chỉ ~7M) so với VGG (~138M)

  • Sử dụng global average pooling thay thế cho fully connected

ResNet (2015)

  • Đột phá với kết nối tắt (skip connections)

  • Cho phép huấn luyện mạng cực kỳ sâu (lên đến 152 lớp)

  • Giải quyết vấn đề biến mất gradient

MobileNet, EfficientNet (2017-2019)

  • Tối ưu hóa cho thiết bị di động và edge computing

  • Sử dụng tích chập tách rời (depthwise separable convolutions)

  • Kết hợp kiến trúc và tìm kiếm tự động

Code mẫu: Triển khai LeNet, AlexNet và VGG với PyTorch

LeNet-5

import torch.nn as nn

class LeNet5(nn.Module):
    def __init__(self, num_classes=10):
        super(LeNet5, self).__init__()
        
        # C1: Convolutional Layer (6@28x28)
        self.c1 = nn.Conv2d(1, 6, kernel_size=5)
        # S2: Average Pooling Layer (6@14x14)
        self.s2 = nn.AvgPool2d(kernel_size=2, stride=2)
        # C3: Convolutional Layer (16@10x10)
        self.c3 = nn.Conv2d(6, 16, kernel_size=5)
        # S4: Average Pooling Layer (16@5x5)
        self.s4 = nn.AvgPool2d(kernel_size=2, stride=2)
        # C5: Convolutional Layer (120@1x1)
        self.c5 = nn.Conv2d(16, 120, kernel_size=5)
        # F6: Fully Connected Layer (84)
        self.f6 = nn.Linear(120, 84)
        # Output Layer (10)
        self.output = nn.Linear(84, num_classes)
        
    def forward(self, x):
        # C1 -> activation -> S2
        x = self.s2(torch.tanh(self.c1(x)))
        # C3 -> activation -> S4
        x = self.s4(torch.tanh(self.c3(x)))
        # C5 -> activation
        x = torch.tanh(self.c5(x))
        # Flatten -> F6 -> activation -> output
        x = x.view(-1, 120)
        x = torch.tanh(self.f6(x))
        x = self.output(x)
        
        return x

AlexNet

import torch.nn as nn

class AlexNet(nn.Module):
    def __init__(self, num_classes=1000):
        super(AlexNet, self).__init__()
        
        self.features = nn.Sequential(
            # Conv1
            nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=0),
            nn.ReLU(inplace=True),
            nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=2),
            nn.MaxPool2d(kernel_size=3, stride=2),
            
            # Conv2
            nn.Conv2d(96, 256, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=2),
            nn.MaxPool2d(kernel_size=3, stride=2),
            
            # Conv3
            nn.Conv2d(256, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            
            # Conv4
            nn.Conv2d(384, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            
            # Conv5
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        
        self.classifier = nn.Sequential(
            # FC6
            nn.Dropout(p=0.5),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
            
            # FC7
            nn.Dropout(p=0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            
            # FC8 (Output)
            nn.Linear(4096, num_classes),
        )
            
    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), 256 * 6 * 6)
        x = self.classifier(x)
        return x

VGG-16

import torch.nn as nn

class VGG16(nn.Module):
    def __init__(self, num_classes=1000):
        super(VGG16, self).__init__()
        
        # Block 1
        self.block1 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        
        # Block 2
        self.block2 = nn.Sequential(
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(128, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        
        # Block 3
        self.block3 = nn.Sequential(
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        
        # Block 4
        self.block4 = nn.Sequential(
            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        
        # Block 5
        self.block5 = nn.Sequential(
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        
        # Classifier
        self.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, num_classes)
        )
        
        # Weight initialization
        self._initialize_weights()
        
    def forward(self, x):
        x = self.block1(x)
        x = self.block2(x)
        x = self.block3(x)
        x = self.block4(x)
        x = self.block5(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x
    
    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)

Kết luận

LeNet, AlexNet và VGG đại diện cho ba giai đoạn quan trọng trong sự phát triển của CNN, mỗi kiến trúc đều mang lại những đột phá và bài học kiến trúc có giá trị:

  • LeNet đặt nền móng cho CNN với các nguyên tắc cơ bản về tích chập và pooling.

  • AlexNet chứng minh sức mạnh của học sâu trên bộ dữ liệu lớn và giới thiệu nhiều kỹ thuật mới.

  • VGG đơn giản hóa thiết kế CNN và chứng minh sức mạnh của kiến trúc đồng nhất, sâu.

Mặc dù ngày nay đã có nhiều kiến trúc hiện đại hơn, ba kiến trúc này vẫn được sử dụng rộng rãi làm nền tảng cho các ứng dụng và nghiên cứu mới. Chúng không chỉ quan trọng về mặt lịch sử mà còn tiếp tục cung cấp những hiểu biết có giá trị cho việc thiết kế các mạng CNN hiệu quả.

Hiểu rõ về LeNet, AlexNet và VGG là bước đầu tiên quan trọng cho bất kỳ ai muốn đi sâu vào lĩnh vực thị giác máy tính và học sâu. Từ những nền tảng này, các nhà nghiên cứu và kỹ sư đã phát triển các kiến trúc tiên tiến hơn như ResNet, DenseNet, EfficientNet và các biến thể Transformer cho thị giác máy tính, tiếp tục đẩy ranh giới của công nghệ AI ngày càng xa hơn.

Last updated