Mục lục
- Mục tiêu bài học
- Pooling Layer là gì
- Ba mục tiêu của pooling
- Max Pooling — công thức
- Average Pooling — công thức
- Tham số: kernel_size, stride, padding
- Công thức output size
- PyTorch API — MaxPool2d và AvgPool2d
- Pooling không có parameter học
- Backward của pooling
- Position trong CNN
- Global Average Pooling (GAP)
- Adaptive Pooling
- Max vs Avg — khi dùng cái nào
- Striding vs Pooling
- Pooling cho 1D và 3D
- "Pooling is a big mistake" — Hinton
- Visualization 4x4 → 2x2
- Pattern pooling trong các kiến trúc kinh điển
- Code Python — Max Pool tay và PyTorch
- Bài tập
Mục tiêu bài học
Sau bài học, bạn sẽ:
- Viết được công thức Max Pooling và Average Pooling.
- Tính được output size sau pooling với kernel / stride / padding bất kỳ.
- Phân biệt khi dùng MaxPool, AvgPool, GlobalAvgPool và AdaptivePool.
- Mô tả được gradient backward khác nhau giữa max pool và avg pool.
- Hiểu vì sao kiến trúc modern (ResNet, EfficientNet) thay nhiều pool bằng stride convolution và dùng GAP ở cuối.
- Implement Max Pool 2x2 bằng NumPy.
Bài này nối Bài 28 — Convolutional Layer với Bài 30 — Flatten và Fully Connected.
Pooling Layer là gì
Pooling là phép downsample feature map: nhận input dạng \( (B, C, H, W) \) và trả về output \( (B, C, H', W') \) với \( H' < H, W' < W \) (số channel \( C \) giữ nguyên).
Cơ chế: trượt một cửa sổ kích thước nhỏ (thường 2x2) trên feature map, mỗi cửa sổ rút gọn thành 1 giá trị duy nhất. Hai phép rút gọn phổ biến nhất là lấy max và lấy mean, ứng với Max Pooling và Average Pooling.
Khác với Conv layer, pooling thực hiện độc lập trên từng channel: không mix thông tin giữa các channel.
Ba mục tiêu của pooling
- Giảm tham số ở layer sau: feature map nhỏ hơn → input cho Conv hoặc FC kế tiếp nhỏ hơn → ít param và ít FLOPs. Ví dụ: pool 2x2 stride 2 giảm chi phí khoảng 4 lần.
- Tăng receptive field hiệu dụng: sau pooling, một pixel ở feature map mới "nhìn" được vùng rộng hơn ở input ban đầu. Conv layer phía sau có thể bắt được pattern lớn hơn mà không cần kernel to.
- Translation invariance nhẹ: nếu một feature dịch chuyển vài pixel trong vùng pooling, max / mean của vùng đó thay đổi rất ít. Mô hình bớt nhạy với shift nhỏ trong ảnh đầu vào.
Translation invariance ở đây là yếu — chỉ trong phạm vi vài pixel của cửa sổ pool. Để invariance mạnh hơn (kiểu xoay, scale), CNN dựa vào data augmentation chứ không chỉ pooling.
Max Pooling — công thức
Với input \( X \in \mathbb{R}^{H \times W} \) (xét 1 channel), kernel \( k \times k \) và stride \( s \), cửa sổ ứng với vị trí output \( (i, j) \) là tập chỉ số:
\[ W_{ij} = \{(u, v) \mid i \cdot s \le u < i \cdot s + k,\; j \cdot s \le v < j \cdot s + k\} \]
Max Pool chọn giá trị lớn nhất trong cửa sổ:
\[ Y_{ij} = \max_{(u, v) \in W_{ij}} X_{u, v} \]
Trực giác: max pool giữ lại tín hiệu mạnh nhất (peak feature). Nếu Conv phía trước đã phát hiện được "có cạnh ở đâu đó trong vùng này", max pool chuyển thông tin đó thành 1 giá trị đại diện. Đây là phép pooling phổ biến nhất trong CNN cho classification.
Average Pooling — công thức
Avg Pool lấy trung bình trong cửa sổ:
\[ Y_{ij} = \frac{1}{|W_{ij}|} \sum_{(u, v) \in W_{ij}} X_{u, v} \]
Với cửa sổ \( k \times k \) thông thường thì \( |W_{ij}| = k^2 \).
Trực giác: avg pool "làm mượt" feature map. Thay vì chọn peak, nó giữ context tổng thể của cả vùng — kể cả tín hiệu nền (background). LeNet-5 (LeCun et al., 1998) dùng avg pool; các CNN sau đó (AlexNet, VGG) chuyển sang max pool vì cho accuracy tốt hơn trên classification.
Tham số: kernel_size, stride, padding
kernel_size: kích thước cửa sổ pool. Thường 2x2 (đôi khi 3x3 — như AlexNet vớiMaxPool2d(3, stride=2)overlapping).stride: bước trượt. PyTorch defaultstride = kernel_size→ cửa sổ không chồng nhau (non-overlapping). Vớikernel=2, stride=2: output \( H/2 \times W/2 \).padding: ít dùng với pooling. Đôi khi đặt 1 với kernel 3 để giữ kích thước chẵn.ceil_mode(PyTorch): khi kích thước không chia hết, dùngceilthayfloor. DefaultFalse(dùng floor).dilation: hiếm dùng với pool, để tăng khoảng cách giữa các phần tử trong cửa sổ.
Pattern thực dụng nhất: MaxPool2d(kernel_size=2, stride=2) — gọn, không overlap, giảm spatial đúng nửa.
Công thức output size
Với input \( H_{\text{in}} \), kernel \( k \), stride \( s \), padding \( p \):
\[ H_{\text{out}} = \left\lfloor \frac{H_{\text{in}} + 2p - k}{s} \right\rfloor + 1 \]
Khi ceil_mode=True, thay floor bằng ceil.
Bốn case hay gặp:
- \( k = 2, s = 2, p = 0 \): \( H_{\text{out}} = \lfloor H_{\text{in}} / 2 \rfloor \). Giảm đúng nửa, áp dụng cho input chẵn.
- \( k = 3, s = 2, p = 1 \): \( H_{\text{out}} = \lceil H_{\text{in}} / 2 \rceil \). Overlapping pool, dùng trong ResNet đầu vào.
- \( k = 2, s = 1, p = 0 \): \( H_{\text{out}} = H_{\text{in}} - 1 \). Hiếm — chỉ giảm 1 pixel.
- Global pool (kernel = toàn bộ feature map): \( H_{\text{out}} = 1 \).
Ví dụ tính nhanh: input (1, 32, 32) qua MaxPool2d(2, 2) → output (1, 16, 16). Tiếp tục qua MaxPool2d(2, 2) → (1, 8, 8). Sau 5 lần pool 2x2 từ ảnh 224x224 → \( 224 / 2^5 = 7 \) → (C, 7, 7) (đúng kích thước feature map trước FC trong VGG / ResNet).
PyTorch API — MaxPool2d và AvgPool2d
import torch
import torch.nn as nn
x = torch.randn(1, 3, 32, 32) # (B, C, H, W)
max_pool = nn.MaxPool2d(kernel_size=2, stride=2)
avg_pool = nn.AvgPool2d(kernel_size=2, stride=2)
y_max = max_pool(x) # (1, 3, 16, 16)
y_avg = avg_pool(x) # (1, 3, 16, 16)
Vài tham số khác có sẵn:
nn.MaxPool2d(
kernel_size=2,
stride=None, # None -> mặc định = kernel_size
padding=0,
dilation=1,
return_indices=False, # True trả thêm vị trí max (dùng cho MaxUnpool2d)
ceil_mode=False,
)
nn.AvgPool2d(
kernel_size=2,
stride=None,
padding=0,
ceil_mode=False,
count_include_pad=True, # có tính padding khi lấy mean hay không
divisor_override=None, # thay mẫu số mặc định k*k
)
Functional version cho phép gọi inline không tạo module:
import torch.nn.functional as F
y = F.max_pool2d(x, kernel_size=2, stride=2)
Pooling không có parameter học
Khác Conv layer (có weight và bias học được), pooling là một phép toán cố định: max và mean không có tham số. Số param của MaxPool2d hay AvgPool2d luôn là 0.
Hệ quả thực hành:
- Không cần init, không cần regularize.
- Không xuất hiện trong
model.parameters()— optimizer bỏ qua. - Behavior train và eval giống nhau (khác Dropout và BatchNorm).
- Forward / backward nhanh, ít tốn bộ nhớ.
Có thể coi pooling là một "feature engineering layer" cứng — không học, chỉ thực hiện downsample theo quy tắc đã định.
Backward của pooling
Dù không có parameter, pooling vẫn cần backward để gradient chảy ngược về Conv layer phía trước.
Max Pool: gradient từ output \( Y_{ij} \) chỉ đi ngược về đúng vị trí đạt max trong cửa sổ forward. Các vị trí còn lại nhận gradient 0:
\[ \frac{\partial L}{\partial X_{u, v}} = \begin{cases} \dfrac{\partial L}{\partial Y_{ij}} & \text{nếu } (u, v) = \arg\max_{W_{ij}} X \\ 0 & \text{ngược lại} \end{cases} \]
PyTorch lưu lại "argmax index" của mỗi cửa sổ trong forward để dùng cho backward. Nếu hai phần tử cùng giá trị max, tie-break tùy implementation (thường chọn phần tử đầu).
Avg Pool: gradient được chia đều cho mọi vị trí trong cửa sổ:
\[ \frac{\partial L}{\partial X_{u, v}} = \frac{1}{|W_{ij}|} \cdot \frac{\partial L}{\partial Y_{ij}} \quad \text{với mọi } (u, v) \in W_{ij} \]
Tính chất so sánh:
- Max pool gradient sparse: chỉ 1 phần tử / cửa sổ có gradient → cập nhật tập trung vào neuron mạnh.
- Avg pool gradient dense: mọi phần tử nhận gradient đều → smooth hơn, nhưng tín hiệu yếu hơn.
Position trong CNN
Vị trí tiêu chuẩn của pooling trong một block CNN:
Conv → Activation (ReLU) → Pool → Conv → Activation → Pool → ...
Lý do đặt pool sau activation: ReLU đã làm sạch phần âm; max của output ReLU vẫn có ý nghĩa "feature strength". Nếu đảo thứ tự (pool trước activation), max của pre-activation có thể là số âm — sau ReLU sẽ thành 0, giảm hiệu quả.
Nếu block có BatchNorm, thứ tự đầy đủ là: Conv → BN → ReLU → Pool. BN nằm ngay sau Conv vì pre-activation \( z = Wx + b \) gần Gaussian, dễ chuẩn hóa.
Modern CNN không pool ở mọi block. Phổ biến: 2–3 conv liên tiếp (giữ nguyên spatial), sau đó 1 pool (giảm nửa). Lặp 4–5 lần, đến lúc feature map còn 7x7 hoặc 1x1.
Global Average Pooling (GAP)
Global Average Pooling lấy mean qua toàn bộ spatial dimension. Input \( (B, C, H, W) \) → output \( (B, C, 1, 1) \) (hoặc squeeze thành \( (B, C) \)):
\[ Y_c = \frac{1}{H \cdot W} \sum_{u=1}^{H} \sum_{v=1}^{W} X_{c, u, v} \]
Đề xuất lần đầu trong "Network in Network" (Lin, Chen & Yan, 2014 — arXiv:1312.4400) như cách thay Flatten → FC → Softmax ở cuối CNN bằng GAP → Softmax.
Lợi ích:
- Giảm tham số đáng kể: FC từ \( C \times H \times W \) chiều xuống 1000 class (VGG) có khoảng 100 triệu param chỉ riêng lớp FC cuối. GAP thay thế FC bằng phép mean — 0 param.
- Chịu được input size khác nhau: không phụ thuộc \( H, W \) cụ thể.
- Regularization tự nhiên: không có param FC để overfit.
- Có ý nghĩa diễn giải: mỗi channel đại diện một class / concept; mean spatial là "độ mạnh" của concept đó trong ảnh.
API PyTorch:
gap = nn.AdaptiveAvgPool2d((1, 1))
# hoặc
y = F.adaptive_avg_pool2d(x, output_size=(1, 1))
Hầu như mọi CNN modern (ResNet, Inception, EfficientNet, ConvNeXt) đều có GAP trước FC cuối cùng.
Global Max Pooling tồn tại như alternative (AdaptiveMaxPool2d), nhưng GAP phổ biến hơn nhiều.
Adaptive Pooling
Adaptive pooling đảo bài toán: thay vì truyền kernel và stride, ta chỉ định output size mong muốn; PyTorch tự tính kernel và stride.
pool = nn.AdaptiveAvgPool2d(output_size=(7, 7))
y = pool(torch.randn(1, 512, 14, 14)) # (1, 512, 7, 7)
# Cũng hoạt động với input shape khác
y = pool(torch.randn(1, 512, 28, 28)) # (1, 512, 7, 7)
Use case chính:
- Ghép feature map có spatial khác nhau (nhiều scale) về cùng size trước khi concat.
- Cho phép input ảnh kích thước thay đổi mà phần FC cuối vẫn nhận shape cố định.
- Thay thế hard-code kernel / stride khi không biết chính xác feature map đầu vào.
Lưu ý: với output size không chia hết input size, kernel / stride mỗi vị trí có thể khác nhau (kernel "biến thiên") — không hoàn toàn equivalent với pool thông thường.
Hai dòng API tương ứng: AdaptiveAvgPool2d và AdaptiveMaxPool2d. Cũng có version 1d và 3d.
Max vs Avg — khi dùng cái nào
| Task / vị trí | Khuyên dùng | Lý do |
|---|---|---|
| Classification, downsample giữa block | Max Pool | giữ peak feature, gradient sparse cập nhật neuron mạnh |
| Object detection (peak feature) | Max Pool | edge / corner cần được giữ rõ |
| Segmentation, super-resolution | Avg Pool | smooth, giữ thông tin nền |
| Style transfer | Avg Pool | thông tin texture phân bố đều, max làm mất |
| Cuối CNN modern (trước FC / softmax) | GAP | giảm param, regularize |
| Cần output size cố định cho input đa kích thước | Adaptive | linh hoạt |
Khi không chắc và làm classification — chọn Max Pool 2x2 stride 2 ở giữa block + GAP ở cuối. Hai lựa chọn này phủ phần lớn use case CNN cho ảnh.
Striding vs Pooling
Có hai cách downsample feature map:
- Pool: phép cố định (max / mean), không param.
- Stride convolution: dùng Conv với
stride=2— kernel học được vừa rút trích feature vừa downsample.
So sánh:
| Tiêu chí | Pool | Stride Conv |
|---|---|---|
| Có parameter học | không | có |
| FLOPs khi downsample | thấp | cao hơn |
| Linh hoạt | cố định | học theo data |
| Translation invariance | có (nhẹ) | không có sẵn |
| Đại diện kiến trúc | VGG, AlexNet | ResNet, EfficientNet |
Springenberg et al. (2014) — "Striving for Simplicity: The All Convolutional Net" (arXiv:1412.6806) — thay toàn bộ pool bằng stride conv, kết quả gần ngang VGG nhưng kiến trúc gọn hơn.
Modern: ResNet giữ 1 MaxPool ở đầu và 1 GAP ở cuối; phần giữa dùng Conv2d(stride=2) để downsample. EfficientNet thậm chí bỏ luôn MaxPool đầu.
Không có lựa chọn "đúng tuyệt đối" — pool tiết kiệm param, stride conv linh hoạt. Thiết kế mới có xu hướng dùng stride conv nhiều hơn, nhưng pool vẫn xuất hiện vì rẻ và đơn giản.
Pooling cho 1D và 3D
nn.MaxPool1d/nn.AvgPool1d: input \( (B, C, L) \) — time series, audio waveform, 1D signal. Ví dụ trong WaveNet-style hay TCN.nn.MaxPool2d/nn.AvgPool2d: input \( (B, C, H, W) \) — ảnh. Đã thảo luận chi tiết.nn.MaxPool3d/nn.AvgPool3d: input \( (B, C, D, H, W) \) — video (D là time-axis) hoặc medical volume (CT, MRI). Dùng cùng vớiConv3d.
Các phiên bản này khác nhau ở số trục pool, công thức output size áp dụng độc lập theo từng trục. Có đầy đủ Adaptive variant: AdaptiveAvgPool1d, AdaptiveAvgPool3d, ...
"Pooling is a big mistake" — Hinton
Geoffrey Hinton đã phát biểu nhiều lần (Reddit AMA 2014, các talk sau đó) rằng "The pooling operation used in convolutional neural networks is a big mistake and the fact that it works so well is a disaster."
Lý do của Hinton:
- Pooling vứt đi thông tin về vị trí chính xác của feature — chỉ giữ "có" hoặc "không có" ở vùng rộng.
- Translation invariance là một kiểu equivariance "thô" — không phân biệt được object với background khi cả hai cùng có cùng feature pattern.
- Mạng học pattern thay vì học cấu trúc phân cấp của object.
Hinton đề xuất Capsule Network (Sabour, Frosst & Hinton, 2017 — arXiv:1710.09829) thay pooling bằng dynamic routing between capsules: thay vì rút gọn về 1 scalar, mỗi capsule giữ một vector mã hóa pose / orientation / scale của feature.
Thực tế đến nay: Capsule Network chưa scale lên ImageNet, pooling vẫn dominant trong CNN. Critique của Hinton vẫn đáng đọc khi suy nghĩ về limitation của kiến trúc hiện hành, nhưng người dùng thực tế chưa có lý do bỏ pool.
Visualization 4x4 → 2x2
Input 4x4 chia thành 4 vùng 2x2 không overlap:
Input X (4x4):
1 3 | 2 4
5 6 | 7 8
---------+---------
3 2 | 1 0
1 2 | 3 4
Max Pool 2x2 stride 2 → Output Y (2x2):
max(1,3,5,6) max(2,4,7,8) 6 8
max(3,2,1,2) max(1,0,3,4) = 3 4
Avg Pool 2x2 stride 2 → Output Y (2x2):
mean(1,3,5,6) mean(2,4,7,8) 3.75 5.25
mean(3,2,1,2) mean(1,0,3,4) = 2.00 2.00
Quan sát: max pool giữ giá trị nổi bật trong từng cửa sổ; avg pool đưa ra số đại diện smooth hơn nhưng giảm tín hiệu peak.
Pattern pooling trong các kiến trúc kinh điển
| Kiến trúc | Năm | Pooling |
|---|---|---|
| LeNet-5 | 1998 | Conv → AvgPool 2x2 → Conv → AvgPool 2x2 → FC |
| AlexNet | 2012 | 5 Conv + 3 MaxPool 3x3 stride 2 (overlapping) |
| VGG-16 | 2014 | 13 Conv chia 5 block, mỗi block kết thúc 1 MaxPool 2x2 |
| GoogLeNet / Inception | 2014 | MaxPool xen kẽ trong Inception block + GAP cuối |
| ResNet | 2015 | 1 MaxPool 3x3 stride 2 đầu, các block sau dùng Conv stride 2, GAP cuối |
| DenseNet | 2017 | AvgPool 2x2 trong transition layer + GAP cuối |
| EfficientNet | 2019 | Hầu hết stride conv, không MaxPool, GAP cuối |
| ConvNeXt | 2022 | Downsample bằng Conv 2x2 stride 2 (no pool), GAP cuối |
Xu hướng rõ: pool giữa block ngày càng ít, GAP ở cuối ngày càng phổ biến.
Code Python — Max Pool tay và PyTorch
Max Pool 2x2 stride 2 bằng NumPy:
import numpy as np
def max_pool_2x2(x: np.ndarray) -> np.ndarray:
"""x shape (H, W) với H, W chẵn. Trả về (H/2, W/2)."""
H, W = x.shape
assert H % 2 == 0 and W % 2 == 0
# Reshape thành khối 2x2 rồi lấy max
blocks = x.reshape(H // 2, 2, W // 2, 2)
return blocks.max(axis=(1, 3))
X = np.array([
[1, 3, 2, 4],
[5, 6, 7, 8],
[3, 2, 1, 0],
[1, 2, 3, 4],
])
print(max_pool_2x2(X))
# [[6 8]
# [3 4]]
So sánh với PyTorch:
import torch
import torch.nn as nn
x = torch.tensor(X, dtype=torch.float32).unsqueeze(0).unsqueeze(0) # (1, 1, 4, 4)
pool = nn.MaxPool2d(kernel_size=2, stride=2)
print(pool(x).squeeze().numpy())
# [[6. 8.]
# [3. 4.]]
Một CNN nhỏ với Conv + Pool + GAP:
cnn = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2, 2), # 32x32 -> 16x16
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2, 2), # 16x16 -> 8x8
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.ReLU(),
nn.AdaptiveAvgPool2d((1, 1)), # GAP -> (B, 128, 1, 1)
nn.Flatten(),
nn.Linear(128, 10),
)
x = torch.randn(8, 3, 32, 32)
print(cnn(x).shape) # torch.Size([8, 10])
Demo visualize before / after pool trên feature map thật:
import torch.nn.functional as F
from torchvision import datasets, transforms
ds = datasets.MNIST("./data", train=True, download=True,
transform=transforms.ToTensor())
img, _ = ds[0] # (1, 28, 28)
img = img.unsqueeze(0) # (1, 1, 28, 28)
# Một conv đơn giản phát hiện edge
conv = nn.Conv2d(1, 1, kernel_size=3, padding=1, bias=False)
conv.weight.data = torch.tensor([[[[-1., -1., -1.],
[ 0., 0., 0.],
[ 1., 1., 1.]]]])
feat = F.relu(conv(img)) # (1, 1, 28, 28)
pooled = F.max_pool2d(feat, 2, 2) # (1, 1, 14, 14)
print(feat.shape, "->", pooled.shape)
Bài tập
- Cho input shape
(1, 32, 32)quaMaxPool2d(kernel_size=2, stride=2). Tính output shape. Tiếp tục qua hai pool 2x2 nữa, output thành bao nhiêu? - Tính output shape khi input \( 224 \times 224 \) qua
MaxPool2d(kernel_size=3, stride=2, padding=1)(pool đầu vào của ResNet). - So sánh số param giữa
Conv2d(64, 64, kernel_size=3, stride=2, padding=1)vàMaxPool2d(2, 2)— cả hai đều giảm spatial nửa. Câu trả lời bằng số. - Implement
avg_pool_2x2bằng NumPy giốngmax_pool_2x2trong bài. Verify khớp vớinn.AvgPool2d(2, 2). - Apply Global Average Pooling cho tensor
x = torch.randn(4, 256, 7, 7)bằng cảnn.AdaptiveAvgPool2d((1, 1))vàx.mean(dim=(2, 3)). Verify hai cách cho kết quả giống nhau. - Lấy 1 ảnh CIFAR-10 (kích thước 32x32), đẩy qua Conv2d(3 → 16, 3, padding=1) → ReLU. Visualize feature map của 4 channel đầu trước và sau MaxPool2d(2, 2) bằng matplotlib. Quan sát "feature mạnh ở vùng nào".
- Train 2 CNN trên CIFAR-10 (5 epoch, cùng hyperparameter): phiên bản A dùng
MaxPool2d(2, 2)giữa block, phiên bản B dùngConv2d(stride=2). So sánh test accuracy và số param. - Demo gradient sparse của Max Pool: dùng
torch.autograd.gradtrênF.max_pool2dvới input 4x4, kernel 2 stride 2. Verify chỉ 4 vị trí có gradient khác 0 (đúng vị trí max).
Đáp án ngắn
- 32 → 16 → 8 → 4. Sau 3 pool, output \( (1, 4, 4) \).
- \( \lfloor (224 + 2 \cdot 1 - 3) / 2 \rfloor + 1 = 112 \). Output 112x112.
- Conv: \( 64 \cdot 64 \cdot 3 \cdot 3 + 64 = 36{,}928 \) param. MaxPool: 0 param.
- Dùng
blocks.mean(axis=(1, 3))thay chomax. Kết quả khớp PyTorch đến lỗi float. - Output cả hai cách: shape \( (4, 256) \) sau squeeze, giá trị bằng nhau (chênh do float precision < \( 10^{-6} \)).
- Sau MaxPool 2x2, feature map còn 16x16; các đốm sáng (activation cao) giữ lại, vùng yếu bị nén.
- Hai cách cho accuracy gần nhau (chênh 1–2%). Phiên bản B nhiều param hơn (Conv stride 2 có weight) nhưng học được downsample tốt hơn trong một số seed.
- Tensor gradient có shape 4x4 với đúng 4 phần tử khác 0 — chính là các vị trí được chọn ở mỗi cửa sổ 2x2 trong forward.
Bài kế tiếp: Bài 30 — Flatten và Fully Connected — cách nối feature map sau Conv / Pool về vector phẳng và đưa vào FC để cho ra logit phân loại.
- Lin, Chen & Yan (2014) - Network in Network
- Springenberg et al. (2014) - Striving for Simplicity: The All Convolutional Net
- Sabour, Frosst & Hinton (2017) - Dynamic Routing Between Capsules
- He et al. (2016) - Deep Residual Learning for Image Recognition (ResNet)
- Simonyan & Zisserman (2015) - Very Deep Convolutional Networks for Large-Scale Image Recognition (VGG)
- Krizhevsky, Sutskever & Hinton (2012) - ImageNet Classification with Deep Convolutional Neural Networks (AlexNet)
- LeCun et al. (1998) - Gradient-Based Learning Applied to Document Recognition (LeNet)
- PyTorch Docs - nn.MaxPool2d
- PyTorch Docs - nn.AvgPool2d
- PyTorch Docs - nn.AdaptiveAvgPool2d
- PyTorch Docs - nn.AdaptiveMaxPool2d
- CS231n - Pooling Layer
