Convolutional Neural Network
Mạng nơ-ron tích chập
Ảnh 224×224 có ~150.000 pixel. Nếu mỗi pixel kết nối với mọi neuron (fully-connected), lớp đầu tiên cần hàng tỷ tham số. Có cách nào thông minh hơn không?
Hãy tưởng tượng bạn đang ở quán phở. Từ xa nhìn vào, bạn chỉ thấy hình dạng tổng thể — bàn, ghế, người đứng người ngồi. Tiến lại gần hơn, bạn nhận ra chi tiết: tô phở bốc khói, bó đũa để nghiêng, lọ tương ớt. Sát tận nơi, bạn nhìn rõ kết cấu: sợi phở mềm, lá hành xanh, miếng bò tái hồng. Não bạn đang "nhìn theo tầng" — từ thô đến tinh.
CNN (Convolutional Neural Network) cũng nhìn ảnh theo cách đó. Nó có hàng chục lớp xếp chồng, mỗi lớp dùng các bộ lọc (kernel) nhỏ quét qua ảnh để phát hiện đặc trưng. Lớp đầu bắt cạnh, lớp giữa bắt hình dạng, lớp sâu bắt vật thể hoàn chỉnh. Ý tưởng kinh điển này lấy cảm hứng từ cách vỏ não thị giác của động vật có vú xử lý ánh sáng.
Bí quyết quan trọng nhất: chia sẻ trọng số. Cùng một bộ lọc 3×3 được dùng ở mọi vị trí trên ảnh. Nhờ vậy, nếu mạng học được "cách nhận ra cạnh dọc" ở góc trái, nó cũng nhận ra ở góc phải — không cần học lại. Tính bất biến dịch chuyển này làm CNN hiệu quả hơn fully-connected hàng nghìn lần.
Ở phần tương tác bên dưới, bạn sẽ thấy một chữ số 7 dạng 28×28 đi qua toàn bộ pipeline CNN mini: Conv1 → ReLU → Pool → Conv2 → ReLU → Pool → Dense → Softmax. Bạn có thể chỉnh kernel của Conv1 bằng tay và nhìn kết quả thay đổi ở mọi lớp sau — kể cả dự đoán lớp cuối cùng.
Hình minh họa
Ảnh đầu vào là chữ số 7 dạng 28×28. Chọn kernel mẫu hoặc chỉnh tay trong ma trận 3×3 bên phải. Mỗi bước Conv / ReLU / Pool cập nhật trực tiếp. Cột cuối hiển thị xác suất softmax của 10 lớp số 0-9.
Kernel mẫu
Sobel dọc — làm nổi bật cạnh dọc của nét chữ
Kernel 3×3 (chỉnh trực tiếp)
Tổng trọng số = 0 · tổng > 0 thường làm sáng, < 0 thường phát hiện cạnh.
Lớp 1 — Input & Conv1 (phát hiện cạnh)
Vùng cam = giá trị dương (kích hoạt). Vùng tím = giá trị âm (bị ReLU loại bỏ ngay sau đó).
Lớp 2 — Pool1 → Conv2 (phát hiện hình dạng)
Sau pool, kích thước nhỏ đi một nửa. Conv2 kết hợp các cạnh thành hình dạng phức tạp hơn — phần đầu của "đặc trưng" cuối cùng.
Lớp 3 — Pool2 → Flatten → Dense → Softmax
Softmax — xác suất 10 lớp (0-9)
Tensor được "duỗi thẳng" thành vector 1D, qua vài lớp Dense rồi Softmax thành phân bố xác suất 10 lớp. Mạng thật sẽ học các trọng số này từ hàng chục nghìn ví dụ MNIST.
Thử nghiệm có chủ đích:chọn kernel "Cạnh ngang" — Conv1 sẽ làm nổi bật nét nằm ngang trên đầu số 7. Đổi sang "Cạnh dọc" → nét chéo của số 7 nổi lên. Thử kernel toàn số 0 ở cột giữa (ví dụ [[−1,0,1],[−1,0,1],[−1,0,1]]) — bạn vừa tự chế một "edge detector dọc" thủ công. Đây chính là điều CNN học tự động qua backprop: hàng triệu kernel khởi tạo ngẫu nhiên rồi được tinh chỉnh để tối đa hóa độ chính xác phân loại.
CNN không phải một bộ lọc đơn lẻ — nó là hàng chục lớp xếp chồng, mỗi lớp phát hiện đặc trưng phức tạp hơn lớp trước. Cạnh → hình dạng → vật thể hoàn chỉnh.
Giống cách bạn xếp LEGO: viên gạch đơn → khối hình → ngôi nhà hoàn chỉnh. Mỗi lớp CNN xây trên nền của lớp trước, và phép màu là mạng tự học mọi kernel chứ không ai lập trình tay.
Ảnh 224×224×3 dùng fully-connected cần ~22,5 tỷ tham số cho lớp đầu. CNN với bộ lọc 3×3×3 và 32 filters cần bao nhiêu?
Bạn có một feature map 14×14 ra từ lớp Pool1. Sau khi qua Conv 3×3 (no padding, stride 1) rồi MaxPool 2×2, kích thước không gian sẽ là?
Giải thích
CNN (Convolutional Neural Network) là kiến trúc mạng nơ-ron chuyên xử lý dữ liệu có cấu trúc không gian như ảnh, video, âm phổ. Khác với fully-connected (mỗi neuron nối với mọi input), CNN dùng các bộ lọc nhỏ trượt qua toàn bộ input theo phép tích chập. Ba nguyên lý cốt lõi: kết nối cục bộ, chia sẻ trọng số, và phân cấp đặc trưng.
Mỗi neuron trong lớp Conv chỉ nhìn một vùng nhỏ của input (receptive field), không phải toàn ảnh. Giống kính lúp rọi qua tấm bản đồ — bạn tập trung vào chi tiết cục bộ. Kích thước kernel phổ biến: 3×3, 5×5, 7×7.
Cùng một bộ lọc được dùng ở mọi vị trí trên ảnh. Nếu bộ lọc phát hiện cạnh dọc ở góc trái, nó cũng phát hiện cạnh dọc ở góc phải. Điều này tạo ra tính bất biến dịch chuyển (translation invariance) và giảm tham số hàng nghìn lần.
Lớp nông phát hiện cạnh, kết cấu. Lớp giữa kết hợp thành hình dạng (mắt, mũi, bánh xe). Lớp sâu nhận diện vật thể hoàn chỉnh (mặt người, ô tô, con mèo). Mỗi lớp xây trên nền lớp trước — giống xếp LEGO.
CNN sâu có hàng triệu tham số — dễ overfit nếu tập train nhỏ. Luôn kết hợp data augmentation, dropout/weight decay, và batch normalization. Với tập < 10k ảnh, ưu tiên transfer learning từ backbone đã pretrain trên ImageNet.
Công thức tích chập:
Kích thước output theo stride và padding:
W = kích thước input, K = kernel, P = padding, S = stride. Với padding = "same" (= ⌊K/2⌋ khi stride 1, K lẻ), kích thước không gian giữ nguyên sau Conv.
Số tham số của một lớp Conv:
Ví dụ Conv 3×3 với 64 input channel → 128 output channel có (3×3×64+1)×128 = 73.856 params. Không phụ thuộc vào kích thước không gian — đây chính là weight sharing.
LeNet-5 (1998): CNN đầu tiên cho nhận dạng chữ viết tay — 7 lớp, tiền đề của mọi thứ sau. AlexNet (2012): thắng ImageNet áp đảo, khởi đầu kỷ nguyên deep learning. VGGNet (2014): chỉ dùng bộ lọc 3×3 xếp chồng — đơn giản nhưng hiệu quả. ResNet (2015): skip connections, huấn luyện được mạng 152 lớp trở lên. EfficientNet (2019): tối ưu cân bằng depth/width/resolution. ConvNeXt (2022): CNN hiện đại hóa, cạnh tranh ngang Vision Transformer.
import torch
import torch.nn as nn
import torch.nn.functional as F
class SimpleCNN(nn.Module):
"""CNN đơn giản cho MNIST: 28x28 -> 10 lớp."""
def __init__(self, num_classes: int = 10):
super().__init__()
# Block 1: 28x28x1 -> 14x14x32
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
self.bn1 = nn.BatchNorm2d(32)
self.pool1 = nn.MaxPool2d(2) # halving
# Block 2: 14x14x32 -> 7x7x64
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(64)
self.pool2 = nn.MaxPool2d(2)
# Block 3: global avg pool tiết kiệm params so với flatten
self.gap = nn.AdaptiveAvgPool2d(1)
self.drop = nn.Dropout(0.3)
self.fc = nn.Linear(64, num_classes)
def forward(self, x: torch.Tensor) -> torch.Tensor:
# x: (B, 1, 28, 28)
x = F.relu(self.bn1(self.conv1(x)))
x = self.pool1(x) # (B, 32, 14, 14)
x = F.relu(self.bn2(self.conv2(x)))
x = self.pool2(x) # (B, 64, 7, 7)
x = self.gap(x) # (B, 64, 1, 1)
x = torch.flatten(x, 1) # (B, 64)
x = self.drop(x)
return self.fc(x) # (B, 10) logits
# Training loop rút gọn
model = SimpleCNN().cuda()
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
criterion = nn.CrossEntropyLoss()
for images, labels in train_loader:
images, labels = images.cuda(), labels.cuda()
logits = model(images)
loss = criterion(logits, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# Tổng tham số: ~30 nghìn — nhỏ gọn nhưng đạt >99% accuracy trên MNISTimport torch
import torchvision.models as models
import torchvision.transforms as T
from PIL import Image
# Dùng ResNet18 pretrained để khảo sát feature map
model = models.resnet18(weights="IMAGENET1K_V1").eval()
# Hook để bắt activation của lớp conv2 trong block1
activations = {}
def hook(module, inp, out):
activations["conv1"] = out.detach()
model.conv1.register_forward_hook(hook)
# Đưa một ảnh qua mạng
img = Image.open("cat.jpg").convert("RGB")
preprocess = T.Compose([
T.Resize(256),
T.CenterCrop(224),
T.ToTensor(),
T.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
])
x = preprocess(img).unsqueeze(0)
with torch.no_grad():
_ = model(x)
fmap = activations["conv1"] # (1, 64, 112, 112)
print(fmap.shape)
# Visualize 8 feature map đầu tiên
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 4, figsize=(10, 5))
for i, ax in enumerate(axes.flat):
ax.imshow(fmap[0, i].cpu(), cmap="viridis")
ax.set_title(f"filter #{i}")
ax.axis("off")
plt.tight_layout()
plt.savefig("conv1_features.png", dpi=120)Ứng dụng thực tế
- Nhận dạng ảnh: Google Photos, Facebook face tagging, nhận dạng biển số xe ở trạm thu phí tự động.
- Y tế: CNN phân tích X-quang, MRI, CT giúp bác sĩ phát hiện ung thư, tổn thương, bệnh võng mạc sớm hơn mắt thường.
- Xe tự lái: nhận diện người đi bộ, làn đường, biển báo trong thời gian thực ở độ trễ vài mili-giây.
- Nông nghiệp: phát hiện sâu bệnh qua ảnh lá cây chụp bằng điện thoại — nông dân chỉ cần một cú chụp.
- Bán lẻ: camera AI đếm người, phân tích hành vi mua sắm, theo dõi tồn kho trên kệ theo thời gian thực.
- An ninh: nhận diện khuôn mặt, phát hiện vật đáng ngờ trong video giám sát — luôn cần cân nhắc đạo đức và quyền riêng tư.
- Sản xuất: phát hiện khuyết tật trên dây chuyền ở tốc độ hàng trăm sản phẩm/phút, thay thế kiểm tra thủ công.
- Thiên văn: phân loại thiên hà, tìm hành tinh ngoài hệ mặt trời từ dữ liệu quang phổ và ảnh kính thiên văn.
Dù CNN rất mạnh cho ảnh, đừng mặc định dùng nó mọi nơi. Nếu dữ liệu của bạn là bảng (tabular), cây quyết định/XGBoost thường tốt hơn. Nếu là văn bản dài, Transformer/LLM phù hợp hơn. Nếu là ảnh nhưng tập rất nhỏ (< 500 mẫu), nên bắt đầu bằng feature cổ điển (HOG, SIFT) hoặc transfer learning từ backbone pretrained thay vì train CNN từ đầu.
Các pitfall hay gặp
- Không chuẩn hóa input: quên normalize về [0,1] hoặc mean/std của ImageNet → mạng hội tụ chậm hoặc phân kỳ. Mỗi backbone pretrained có một bộ mean/std riêng, hãy đọc kỹ tài liệu.
- Padding sai: quên padding trong mạng sâu làm kích thước giảm quá nhanh, không còn không gian cho Conv cuối. Nguyên tắc: với kernel K lẻ, padding = ⌊K/2⌋ giữ nguyên kích thước.
- Data leakage: augmentation trên test set, hoặc resize ảnh trước khi chia train/val. Kiểm tra chặt pipeline: chỉ fit thống kê trên train, áp dụng lên val/test.
- Quên BatchNorm:mạng sâu không có BN/LN thường không train được — gradient quá bất ổn. Với batch size nhỏ (< 8), cân nhắc Group Norm hoặc Layer Norm thay BN.
- FC layer khổng lồ: flatten feature map lớn rồi nối FC 1024 là nguyên nhân số một khiến mạng phình tham số. Ưu tiên Global Average Pooling — giảm tham số và thường tăng accuracy.
- Đánh giá chỉ bằng accuracy:bỏ qua precision/recall/F1 trên class mất cân bằng. Với tập 95% âm/5% dương, baseline "luôn đoán âm" đã 95% accuracy nhưng vô dụng.
- Kernel size lớn ở lớp đầu: các model hiện đại thích nhiều Conv 3×3 xếp chồng hơn một Conv 7×7 đơn lẻ — cùng receptive field nhưng ít tham số và nhiều phi tuyến hơn.
- Train quá ít epoch: CNN cần hàng chục epoch để hội tụ; learning rate schedule (cosine, warmup) tạo khác biệt lớn.
Các thuật ngữ liên quan cần nắm
- Feature map: output 2D của một kernel sau khi tích chập toàn ảnh — mỗi kênh output = 1 feature map.
- Channel (kênh): chiều sâu của tensor. Ảnh RGB có 3 kênh; output Conv N filters có N kênh.
- Receptive field:vùng input mà một neuron "nhìn thấy". Càng sâu, receptive field càng lớn.
- Global Average Pooling (GAP): lấy trung bình mỗi feature map thành 1 số; thay thế Flatten+FC, cực gọn.
- Dilated/Atrous Conv: Conv có lỗ — tăng receptive field mà không giảm kích thước, dùng nhiều trong segmentation.
- Depthwise separable Conv: tách Conv thành depthwise + pointwise — giảm tham số mạnh, nền tảng của MobileNet.
CNN chuyên cho ảnh; với dữ liệu tuần tự như văn bản hoặc chuỗi thời gian, hãy xem RNN, LSTM và Transformer. Với ảnh, lựa chọn hiện đại cạnh tranh là Vision Transformer.
- CNN dùng bộ lọc nhỏ trượt qua ảnh thay vì kết nối đầy đủ — giảm tham số hàng nghìn lần.
- Chia sẻ trọng số tạo tính bất biến dịch chuyển — vật thể ở đâu trong ảnh cũng được nhận ra.
- Phân cấp đặc trưng: cạnh → hình dạng → vật thể — mỗi lớp xây trên nền lớp trước.
- Kiến trúc điển hình: Conv → ReLU → Pool → ... → Flatten/GAP → FC → Softmax. Stride và padding kiểm soát kích thước.
- Thuộc lòng công thức kích thước: O = ⌊(W − K + 2P)/S⌋ + 1 và số params: (K·K·C_in + 1)·C_out.
- Từ LeNet đến ConvNeXt, CNN là nền tảng của thị giác máy tính — và vẫn cạnh tranh hiệu quả với Vision Transformer hiện đại.
Kiểm tra hiểu biết
Tại sao CNN dùng bộ lọc nhỏ (3×3) thay vì kết nối mỗi pixel với mọi pixel khác?