Học AI & Lập trình

CNN: phép tích chập, padding, stride và pooling

Vì sao mạng fully connected không hợp với ảnh, tích chập trên ảnh nhiều kênh, công thức kích thước đầu ra và số tham số lớp Conv, kèm ví dụ số.

CNN: phép tích chập, padding, stride và pooling
Nội dung bài viết
  1. Vì sao mạng fully connected không hợp với ảnh?
  2. Kiến trúc tổng quát của CNN
  3. Phép tích chập (convolution) trên ảnh một kênh
  4. Stride và Padding
  5. Công thức kích thước đầu ra
  6. Tích chập trên khối nhiều kênh
  7. Nhiều filter, nhiều feature map
  8. Tích chập 1×1
  9. Ưu điểm của lớp Convolution
  10. Pooling layer
  11. Ghép lại: một CNN cho CIFAR-10 trong PyTorch
  12. CNN không chỉ dành cho ảnh
  13. Tóm tắt
  14. Tài liệu tham khảo

Trong bài PyTorch: 7 bước huấn luyện, mạng fully connected (MLP) 2,6 triệu tham số chỉ đạt khoảng 50% trên CIFAR-10. Convolutional Neural Network (CNN, mạng nơ-ron tích chập) giải quyết đúng điểm yếu đó bằng cách tôn trọng cấu trúc không gian của ảnh. Đây là kiến trúc nền tảng của gần như mọi hệ thống thị giác máy tính: từ phân loại, phát hiện đối tượng đến phân vùng ảnh.

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

Vì sao mạng fully connected không hợp với ảnh?

Trang 78 của tài liệu tóm gọn hai vấn đề:

  • Không chịu được biến đổi (variations): vị trí, hướng, tỉ lệ. Mỗi neuron FC gắn cứng với từng pixel cụ thể; chữ số "7" dịch sang trái 3 pixel là một đầu vào hoàn toàn khác, mạng phải học lại từ đầu. Ảnh trọng số học được cũng chỉ là các "khuôn mẫu trung bình" mờ nhòe.
  • Không mở rộng được (scalable): bộ nhớ và tính toán. Ảnh 224×224×3 có 150.528 giá trị; chỉ một lớp FC với 1.000 neuron đã cần hơn 150 triệu trọng số.

Ảnh có hai tính chất mà FC bỏ qua: tính cục bộ (pixel gần nhau liên quan chặt chẽ, tạo thành cạnh, góc, kết cấu) và tính dừng (một chiếc tai mèo ở góc trái hay góc phải vẫn là tai mèo). Tích chập khai thác chính hai điều đó.

Kiến trúc tổng quát của CNN

Một CNN điển hình gồm hai phần (trang 77, 80):

  1. Trích đặc trưng (feature extraction): chuỗi các khối Convolution + ReLU xen kẽ Pooling. Mỗi lớp sinh ra các feature map: không gian (H, W) nhỏ dần, số kênh (depth) tăng dần.
  2. Phân loại (classification): làm phẳng (flatten) feature map cuối, qua một vài lớp fully connected, rồi softmax ra phân phối xác suất (ví dụ Horse 0.2, Zebra 0.7, Dog 0.1).

Khác với MLP xếp neuron thành hàng, CNN xếp neuron thành khối 3 chiều (width × height × depth), mỗi neuron chỉ "nhìn" một vùng nhỏ của khối trước.

Phép tích chập (convolution) trên ảnh một kênh

Một filter (hay kernel) là một ma trận nhỏ, ví dụ 3×3. Ta trượt nó trên ảnh; tại mỗi vị trí, nhân từng phần tử của filter với vùng ảnh bên dưới rồi cộng tất cả lại thành một số. Ví dụ ở trang 81: ảnh 6×6, filter phát hiện cạnh dọc:

Input 6×6
492583
562403
245452
565478
577921
585384
∗
Filter 3×3
10-1
10-1
10-1
=
Output 4×4
26-45
040-1
-5036
-2503

Ô đầu tiên: \(4\cdot1 + 9\cdot0 + 2\cdot(-1) + 5\cdot1 + 6\cdot0 + 2\cdot(-1) + 2\cdot1 + 4\cdot0 + 5\cdot(-1) = 2\). Dịch filter sang phải 1 ô: \(9 + 6 + 4 - 5 - 4 - 4 = 6\). Cứ thế đến hết ảnh.

Filter này cho giá trị dương lớn khi cột trái sáng hơn cột phải, tức là phát hiện cạnh dọc. Điều kỳ diệu của CNN: ta không thiết kế tay các filter như vậy; giá trị của filter chính là trọng số được học bằng backpropagation. Lớp đầu tự học ra các bộ phát hiện cạnh, màu; lớp sau học các mẫu phức tạp hơn.

Chi tiết kỹ thuậtVề toán học, "convolution" trong deep learning thực ra là cross-correlation (không lật kernel trước khi trượt). Vì kernel được học, việc lật hay không không quan trọng, nên mọi framework đều cài đặt cross-correlation nhưng vẫn gọi là convolution.

Stride và Padding

Stride (bước trượt)

Stride \(s\) là số ô filter nhảy mỗi lần. Với \(s = 2\), ví dụ trên chỉ còn 4 vị trí và output là 2×2:

Output, stride 2
2-4
-53
Đính chính hình ở trang 81Hình minh hoạ stride = 2 ghi ô thứ hai bằng 1. Tính đúng với cửa sổ ở cột 3–5 (giá trị 2 5 8 / 2 4 0 / 5 4 5): \(2 + 2 + 5 - 8 - 0 - 5 = -4\). Kết quả \(-4\) đã được kiểm chứng bằng torch.nn.functional.conv2d.

Padding (đệm viền)

Không đệm, output nhỏ hơn input và pixel ở viền ít được "nhìn" hơn pixel ở giữa. Zero-padding \(p\) thêm \(p\) hàng/cột số 0 quanh ảnh. Với \(p = 1, s = 2\), ví dụ trên cho output 3×3 bắt đầu bằng \(-15\) (\(= -9 - 6\), vì cột trái là số 0 đệm). Với stride 1, chọn \(p = (k-1)/2\) (ví dụ \(p = 1\) cho kernel 3×3) giữ nguyên kích thước: gọi là "same" padding.

Công thức kích thước đầu ra

\[ O = \left\lfloor \frac{N + 2P - D\,(K - 1) - 1}{S} \right\rfloor + 1 \]

với \(N\) kích thước vào, \(K\) kernel, \(P\) padding, \(S\) stride, \(D\) dilation (mặc định 1, khi đó công thức rút gọn thành \(\lfloor (N + 2P - K)/S \rfloor + 1\)). Đây đúng là công thức trang 87 và trong tài liệu nn.Conv2d.

NKPSOGặp ở đâu
63014ví dụ trên
63123ví dụ có padding
3250128lớp đầu LeNet-5
227110455lớp đầu AlexNet
224732112lớp đầu ResNet
224311224"same" conv trong VGG

Tích chập trên khối nhiều kênh

Ảnh màu có 3 kênh, nên filter cũng phải có 3 kênh: filter luôn có độ sâu bằng số kênh đầu vào (trang 83). Tại mỗi vị trí, ta tích chập từng kênh của filter với kênh tương ứng của ảnh, rồi cộng cả ba kết quả và cộng bias, được một số. Do đó một filter 3×3×3 trên ảnh 6×6×3 cho ra feature map 4×4×1.

Ví dụ có số liệu ở trang 84: kênh R cho 308, kênh G cho −498, kênh B cho 164, bias = 1, tổng \(308 - 498 + 164 + 1 = -25\).

Nhiều filter, nhiều feature map

Một filter chỉ phát hiện một loại mẫu. Dùng \(C_{out}\) filter khác nhau, ta được \(C_{out}\) feature map xếp chồng thành output có độ sâu \(C_{out}\) (trang 85: 2 filter 3×3×3 trên ảnh 6×6×3 cho output 4×4×2). Một lớp convolution hoàn chỉnh (trang 87) = tích chập với nhiều filter + bias + hàm kích hoạt (thường ReLU).

Đếm tham số của một lớp Conv

\[ \#\text{params} = (K_h \cdot K_w \cdot C_{in} + 1)\cdot C_{out} \]

Lớp ở trang 87 (2 filter 4×4×3): \((4\cdot4\cdot3 + 1)\cdot2 = 98\) tham số. Lưu ý số tham số không phụ thuộc kích thước ảnh.

import torch.nn as nn

conv = nn.Conv2d(in_channels=3, out_channels=2, kernel_size=4)
print(conv.weight.shape)                           # torch.Size([2, 3, 4, 4]) = (C_out, C_in, K_h, K_w)
print(sum(p.numel() for p in conv.parameters()))   # 98

Tích chập 1×1

Kernel 1×1 nghe vô nghĩa với ảnh một kênh, nhưng với khối nhiều kênh, nó tính tổ hợp tuyến tính của tất cả các kênh tại cùng một pixel: như một lớp fully connected nhỏ áp dụng riêng cho từng pixel. Trang 86: input 6×6×5, hai filter 1×1×5 cho output 6×6×2. Công dụng:

  • Đổi số kênh rẻ tiền (giảm 256 kênh xuống 64 trước một conv 3×3 đắt đỏ): "bottleneck" trong Inception, ResNet-50.
  • Trộn thông tin giữa các kênh mà không đụng đến chiều không gian: "pointwise convolution" trong MobileNet.
  • Thêm phi tuyến (kèm ReLU) với chi phí thấp.

Ưu điểm của lớp Convolution

  • Local connectivity (kết nối cục bộ): mỗi neuron output chỉ nối với một vùng nhỏ \(K\times K\) của input, không phải toàn bộ ảnh.
  • Parameter sharing (chia sẻ tham số): cùng một filter dùng cho mọi vị trí. Một bộ phát hiện cạnh hữu ích ở góc trái thì cũng hữu ích ở góc phải.
  • Translation equivariance: dịch ảnh đầu vào thì feature map dịch theo tương ứng. Kết hợp với pooling, mạng đạt được tính bất biến (gần đúng) với dịch chuyển nhỏ: con mèo ở đâu trong ảnh cũng được nhận ra (trang 89).

So sánh cụ thể, biến input 32×32×3 thành output 32×32×64:

CáchSố tham số
Fully connected (mỗi output nối mọi input)\(3072 \times 65536 \approx 201\) triệu
Conv 3×3, 64 filter\((3\cdot3\cdot3+1)\cdot64 = 1.792\)

Pooling layer

Pooling giảm kích thước không gian của feature map: tóm tắt mỗi vùng nhỏ bằng một số. Phổ biến nhất là cửa sổ 2×2, stride 2, giảm H và W đi một nửa (giảm 75% số giá trị).

Input 4×4
4925
5624
2454
5684
→
Max pooling
95
68
Average pooling
6.03.25
4.255.25

(Hình trang 90 làm tròn thành 3.3, 4.3, 5.3; giá trị chính xác là 3.25, 4.25, 5.25.)

  • Không có tham số để học.
  • Áp dụng độc lập cho từng kênh: input 6×6×3 cho output 3×3×3, số kênh giữ nguyên.
  • Max pooling giữ lại tín hiệu mạnh nhất ("đặc trưng có xuất hiện trong vùng này không?") và tăng khả năng chịu dịch chuyển nhỏ. Khi backprop, gradient chỉ đi về ô lớn nhất (cổng max ở bài Backpropagation).
  • Average pooling lấy trung bình. Phiên bản global average pooling (lấy trung bình cả feature map thành 1 số mỗi kênh) thay cho flatten trong các mạng hiện đại như ResNet, giúp giảm mạnh tham số FC.

Ngày nay nhiều kiến trúc thay pooling bằng conv stride 2 để mạng tự học cách giảm kích thước.

Ghép lại: một CNN cho CIFAR-10 trong PyTorch

import torch
import torch.nn as nn

net = nn.Sequential(
    nn.Conv2d(3, 32, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
    nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
    nn.Conv2d(64, 128, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
    nn.Flatten(),
    nn.Linear(128 * 4 * 4, 256), nn.ReLU(),
    nn.Linear(256, 10),
)

x = torch.rand(1, 3, 32, 32)
for layer in net:
    x = layer(x)
    print(f"{layer.__class__.__name__:10s} {tuple(x.shape)}")
print(sum(p.numel() for p in net.parameters()))   # 620362

Kết quả theo dõi kích thước:

LớpOutputTham số
Conv 3→32, 3×332×32×32896
MaxPool 2×216×16×320
Conv 32→64, 3×316×16×6418.496
MaxPool 2×28×8×640
Conv 64→128, 3×38×8×12873.856
MaxPool 2×24×4×1280
Flatten → Linear 2048→256256524.544
Linear 256→10102.570

Tổng khoảng 620 nghìn tham số, ít hơn 4 lần so với MLP ở bài PyTorch 7 bước, nhưng thay vào script huấn luyện của bài đó, mạng này thường đạt khoảng 70–75% sau 20 epoch (so với ~52% của MLP). Để ý: 85% tham số nằm ở lớp FC đầu tiên. Bài Batch Normalization, Dropout và Receptive Field sẽ thêm các mẹo thiết kế để vượt mốc 85%.

CNN không chỉ dành cho ảnh

Tài liệu (trang 79) còn minh hoạ CNN cho văn bản (Kim, 2014): mỗi từ biểu diễn bằng một vector, câu thành ma trận \(n\times k\); các filter 1D trượt theo chiều chuỗi từ để bắt các cụm 2–5 từ, sau đó max-over-time pooling và lớp FC để phân loại cảm xúc. Cùng một ý tưởng (kết nối cục bộ + chia sẻ trọng số) áp dụng cho âm thanh, chuỗi thời gian và cả ảnh 3D y tế.

Tóm tắt

  • Tích chập = trượt filter, nhân từng phần tử, cộng lại; filter là trọng số học được.
  • Output: \(\lfloor (N + 2P - K)/S \rfloor + 1\); "same" padding \(P = (K-1)/2\).
  • Filter có độ sâu bằng số kênh vào; số filter = số kênh ra; tham số \((K^2 C_{in} + 1) C_{out}\).
  • Conv 1×1 trộn kênh và đổi số kênh rẻ tiền.
  • Local connectivity + parameter sharing: ít tham số, chịu được dịch chuyển.
  • Pooling giảm kích thước, không có tham số, áp dụng từng kênh.

Đọc tiếp: Batch Normalization, Dropout và Receptive Field.

Tài liệu tham khảo

  • CS231n: Convolutional Neural Networks: Architectures, Convolution / Pooling Layers.
  • Dumoulin & Visin (2016). A guide to convolution arithmetic for deep learning (arXiv:1603.07285).
  • Andrew Ng, khoá Convolutional Neural Networks (deeplearning.ai), tuần 1.
  • Yoon Kim (2014). Convolutional Neural Networks for Sentence Classification.
  • Tài liệu PyTorch: nn.Conv2d, nn.MaxPool2d.
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í.