Batch Normalization, Dropout và Receptive Field
BatchNorm khác nhau thế nào lúc train và lúc suy luận, Dropout chống overfitting ra sao, cách tính receptive field và các mẫu sắp xếp lớp CNN.
Học xong bài này, bạn sẽ
- Hiểu BatchNorm khi train và khi suy luận
- Dùng Dropout đúng chỗ
- Tính receptive field qua từng lớp
Nên học trước: Backpropagation: lan truyền ngược
Nội dung bài viết
- Internal Covariate Shift: trò chơi "tam sao thất bản"
- Batch Normalization hoạt động thế nào?
- Các kiểu Normalization khác
- Dropout: chống overfitting bằng cách "tắt ngẫu nhiên"
- Mẫu sắp xếp layer phổ biến của CNN
- Receptive Field: một neuron "nhìn thấy" bao nhiêu?
- Bốn cách tăng receptive field
- Ghép lại: CNN kiểu VGG có BatchNorm cho CIFAR-10
- Tóm tắt
- Tài liệu tham khảo
Bài Mạng nơ-ron và CNN hoạt động thế nào đã có các "viên gạch" chính của CNN: convolution và pooling. Nhưng xếp chồng thật nhiều lớp conv thì mạng lại khó huấn luyện và dễ overfitting. Bài này giới thiệu các lớp bổ trợ đã trở thành tiêu chuẩn (Batch Normalization, Dropout), cùng khái niệm then chốt để thiết kế kiến trúc: receptive field và các mẫu sắp xếp layer phổ biến.
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 đó.
Internal Covariate Shift: trò chơi "tam sao thất bản"
Hình minh hoạ ở trang 91 là trò chơi truyền tai: "peas" thành "bees", rồi "knees", "cheese", cuối cùng thành "fleas?". Trong mạng sâu, mỗi lớp nhận đầu vào là đầu ra của lớp trước. Khi lớp trước cập nhật trọng số, phân phối đầu vào của lớp sau thay đổi theo; lớp sau liên tục phải thích nghi với một "mục tiêu di động". Ioffe & Szegedy (2015) gọi hiện tượng này là internal covariate shift và đề xuất Batch Normalization để khắc phục.
Một lý do khác (trang 93): với activation như sigmoid, đầu vào có giá trị tuyệt đối lớn rơi vào vùng bão hoà, nơi đạo hàm gần 0. Chuẩn hoá kéo các giá trị về quanh 0, vùng mà gradient lớn nhất, nên mạng học nhanh hơn.
Batch Normalization hoạt động thế nào?
Với feature map kích thước \(N\times C\times H\times W\), BatchNorm (BN) chuẩn hoá riêng từng kênh \(c\), lấy thống kê trên cả batch và toàn bộ vị trí không gian (trang 92):
\[ \mu_c = \frac{1}{NHW}\sum_{n,h,w} x_{nchw}, \qquad \sigma_c^2 = \frac{1}{NHW}\sum_{n,h,w}\left(x_{nchw} - \mu_c\right)^2 \] \[ \hat x = \frac{x - \mu_c}{\sqrt{\sigma_c^2 + \epsilon}}, \qquad y = \gamma_c\, \hat x + \beta_c \]\(\gamma\) (scale) và \(\beta\) (shift) là tham số học được. Chúng cho phép mạng tự "huỷ" phép chuẩn hoá nếu cần (với \(\gamma = \sigma, \beta = \mu\) ta lấy lại đúng \(x\)), nên BN không làm giảm khả năng biểu diễn.
Giai đoạn huấn luyện (training)
Theo sơ đồ 5 bước ở trang 94:
- Nhận mini-batch activation.
- Tính mean và độ lệch chuẩn của batch hiện tại cho từng kênh.
- Chuẩn hoá.
- Scale và shift bằng \(\gamma, \beta\).
- Cập nhật trung bình chạy (moving/running average) của mean và variance để dùng về sau.
momentum=0.1 (tương đương \(\alpha = 0.9\)). PyTorch lưu variance (không phải std) và dùng ước lượng không chệch (chia \(n-1\)) cho running variance, còn khi chuẩn hoá trong lúc train thì dùng variance chệch (chia \(n\)).Giai đoạn suy luận (inference)
Khi dự đoán, ta có thể chỉ có một ảnh, và kết quả của một ảnh không được phép phụ thuộc vào các ảnh khác trong batch. Vì vậy BN dùng running mean/variance đã tích luỹ, không dùng thống kê batch (trang 95). Lúc này BN chỉ là một phép biến đổi tuyến tính cố định, thậm chí có thể "gộp" (fuse) vào lớp conv phía trước để tăng tốc khi triển khai.
import torch
import torch.nn as nn
torch.manual_seed(0)
bn = nn.BatchNorm2d(3)
print([n for n, _ in bn.named_parameters()]) # ['weight', 'bias'] = gamma, beta (học được)
print([n for n, _ in bn.named_buffers()]) # ['running_mean', 'running_var', 'num_batches_tracked']
x = torch.randn(8, 3, 4, 4) * 5 + 10 # dữ liệu có mean ≈ 10, std ≈ 5
bn.train()
y = bn(x)
print(y.mean(dim=(0, 2, 3))) # ≈ [0, 0, 0]: chuẩn hoá theo thống kê batch
print(bn.running_mean) # ≈ [1.02, 1.04, 1.00] = 0.9*0 + 0.1*10.2
bn.eval()
print(bn(x).mean(dim=(0, 2, 3))) # ≈ [4.86, 5.31, 5.25]: running stats chưa hội tụ!
Ví dụ trên cho thấy hai bài học: (1) model.train() và model.eval() cho kết quả khác nhau; (2) running stats cần nhiều batch để hội tụ, nên huấn luyện quá ít bước rồi đánh giá sẽ cho kết quả tệ khó hiểu.
Lưu ý thực hành với BatchNorm
- Đặt
bias=Falsecho lớp Conv/Linear ngay trước BN: bias bị trừ đi khi trừ mean, \(\beta\) đã đảm nhận vai trò đó. - Thứ tự phổ biến: Conv → BN → ReLU.
- BN cần batch đủ lớn (thường ≥ 16). Batch quá nhỏ (detection, segmentation ảnh lớn chỉ vừa 1–2 ảnh/GPU) làm thống kê nhiễu: hãy dùng GroupNorm hoặc "đóng băng" BN.
- Lợi ích: cho phép learning rate lớn hơn, ít nhạy với khởi tạo trọng số, có tác dụng điều chuẩn (regularization) nhẹ.
Các kiểu Normalization khác
Bốn khối lập phương ở trang 96 khác nhau ở tập giá trị dùng để tính mean/variance (trục N: các mẫu trong batch, C: kênh, H,W: không gian):
| Loại | Chuẩn hoá trên | Phụ thuộc batch? | Dùng nhiều ở | PyTorch |
|---|---|---|---|---|
| Batch Norm | N, H, W (mỗi kênh) | Có | CNN phân loại | nn.BatchNorm2d(C) |
| Layer Norm | C, H, W (mỗi mẫu) | Không | Transformer, RNN | nn.LayerNorm(shape) |
| Instance Norm | H, W (mỗi mẫu, mỗi kênh) | Không | Style transfer, GAN | nn.InstanceNorm2d(C) |
| Group Norm | nhóm kênh, H, W (mỗi mẫu) | Không | Detection, segmentation batch nhỏ | nn.GroupNorm(G, C) |
Group Norm là "trung gian": với \(G = 1\) nó thành Layer Norm, với \(G = C\) thành Instance Norm.
Dropout: chống overfitting bằng cách "tắt ngẫu nhiên"
Trong lúc huấn luyện, mỗi neuron bị đặt bằng 0 với xác suất \(p\) (trang 97). Mỗi bước, mạng thực chất là một mạng con khác nhau. Hệ quả:
- Neuron không thể dựa dẫm vào một vài neuron cụ thể khác (giảm co-adaptation), buộc phải học đặc trưng bền vững hơn.
- Có thể xem như huấn luyện đồng thời một tập hợp (ensemble) khổng lồ các mạng con chia sẻ trọng số.
PyTorch cài đặt inverted dropout: lúc train, các neuron còn lại được nhân \(1/(1-p)\) để kỳ vọng đầu ra không đổi; lúc suy luận Dropout không làm gì cả.
d = nn.Dropout(p=0.5)
t = torch.ones(1, 8)
d.train(); print(d(t)) # tensor([[2., 2., 0., 2., 0., 0., 0., 0.]]) ← ngẫu nhiên, nhân 1/(1-0.5)=2
d.eval(); print(d(t)) # tensor([[1., 1., 1., 1., 1., 1., 1., 1.]]) ← không làm gì
Thực hành: dùng Dropout chủ yếu trước các lớp fully connected (AlexNet, VGG dùng \(p = 0.5\)). Trong phần conv, nếu cần, dùng nn.Dropout2d (tắt cả kênh). Mạng có BN thường cần ít hoặc không cần Dropout ở phần conv.
Mẫu sắp xếp layer phổ biến của CNN
Trang 98 tóm tắt "công thức" kinh điển:
- Một vài block Conv → (BatchNorm) → ReLU.
- Max-pooling.
- Lặp lại bước 1 và 2 cho đến khi feature map đủ bé.
- Flatten (làm phẳng) feature map (hoặc global average pooling).
- Một (vài) lớp fully connected.
Nguyên tắc thường gặp: mỗi lần pooling giảm H, W một nửa thì tăng gấp đôi số kênh (64 → 128 → 256…), giữ khối lượng tính toán mỗi tầng tương đối cân bằng.
Receptive Field: một neuron "nhìn thấy" bao nhiêu?
Receptive field (vùng tiếp nhận) của một neuron là vùng trên ảnh đầu vào có ảnh hưởng đến giá trị của neuron đó. Một neuron sau conv 3×3 nhìn 3×3 pixel; một neuron ở lớp conv 3×3 thứ hai nhìn 3×3 neuron của lớp trước, tức 5×5 pixel của ảnh gốc (trang 99).
Vì sao quan trọng? Để nhận ra một chiếc ô tô, neuron ở lớp cuối phải "nhìn" đủ rộng để bao trùm chiếc xe. Trong phân vùng ảnh (trang 100), để biết một pixel thuộc "xe" hay "đường", mô hình cần ngữ cảnh rộng xung quanh pixel đó.
Công thức tính dần qua từng lớp \(l\) (kernel \(k_l\), stride \(s_l\)):
\[ r_l = r_{l-1} + (k_l - 1)\cdot j_{l-1}, \qquad j_l = j_{l-1}\cdot s_l, \qquad r_0 = j_0 = 1 \]\(j\) là "bước nhảy" tích luỹ: sau mỗi lớp có stride, mỗi bước trên feature map tương ứng nhiều pixel hơn trên ảnh gốc. Áp dụng cho phần trích đặc trưng của CNN ở cuối bài:
| Lớp | k | s | Receptive field r | Bước nhảy j |
|---|---|---|---|---|
| conv 3×3 | 3 | 1 | 3 | 1 |
| conv 3×3 | 3 | 1 | 5 | 1 |
| maxpool 2×2 | 2 | 2 | 6 | 2 |
| conv 3×3 | 3 | 1 | 10 | 2 |
| conv 3×3 | 3 | 1 | 14 | 2 |
| maxpool 2×2 | 2 | 2 | 16 | 4 |
| conv 3×3 | 3 | 1 | 24 | 4 |
| conv 3×3 | 3 | 1 | 32 | 4 |
| maxpool 2×2 | 2 | 2 | 36 | 8 |
Sau 6 lớp conv và 3 lớp pooling, mỗi neuron đã nhìn vùng 36×36, bao trùm toàn bộ ảnh CIFAR 32×32. Để ý: conv sau pooling làm receptive field tăng nhanh hơn hẳn (+8 thay vì +2).
Effective Receptive Field
Không phải pixel nào trong receptive field cũng đóng góp như nhau (trang 103). Pixel ở trung tâm có nhiều "đường đi" tới neuron đầu ra hơn pixel ở rìa, nên ảnh hưởng mạnh hơn nhiều. Luo et al. (2016) chỉ ra effective receptive field có dạng gần Gauss và nhỏ hơn đáng kể so với receptive field lý thuyết. Vì vậy trên thực tế người ta thiết kế receptive field lý thuyết lớn hơn kích thước đối tượng cần nhận.
Bốn cách tăng receptive field
Theo trang 101:
1. Thêm lớp conv (mạng sâu hơn)
Mỗi conv 3×3 (stride 1) cộng thêm 2 vào receptive field. Đây cũng là lý do của nguyên tắc ở trang 102: nhiều conv kernel nhỏ tốt hơn một conv kernel lớn.
| 2 lớp conv 3×3 | 1 lớp conv 5×5 | |
|---|---|---|
| Receptive field | 5×5 | |
| Số tham số (C kênh vào/ra) | \(2\cdot3\cdot3\cdot C^2 = 18C^2\) | \(5\cdot5\cdot C^2 = 25C^2\) |
| Số lớp phi tuyến | 2 | 1 |
Tương tự, ba conv 3×3 có receptive field 7×7 với \(27C^2\) tham số, so với \(49C^2\) của một conv 7×7. Đây chính là triết lý thiết kế của VGG.
2. Thêm pooling hoặc stride (sub-sampling)
Pooling 2×2 nhân đôi bước nhảy \(j\), khiến mọi lớp phía sau "nhìn xa" gấp đôi (trang 104). Đổi lại là mất độ phân giải không gian, điều bất lợi cho phân vùng ảnh.
3. Dilated (Atrous) Convolution
Chèn "lỗ hổng" giữa các phần tử của kernel (trang 105). Kernel 3×3 với dilation \(d\) phủ vùng \(k + (k-1)(d-1)\): với \(d = 2\) phủ 5×5, \(d = 4\) phủ 9×9, nhưng vẫn chỉ 9 tham số và không giảm độ phân giải. Được dùng nhiều trong phân vùng ảnh (DeepLab) và xử lý âm thanh (WaveNet).
dil = nn.Conv2d(64, 64, kernel_size=3, dilation=2, padding=2) # padding = dilation để giữ kích thước
print(dil(torch.rand(1, 64, 32, 32)).shape) # torch.Size([1, 64, 32, 32])
Nhược điểm: xếp chồng nhiều lớp cùng dilation có thể gây hiện tượng "lưới" (gridding), bỏ sót pixel; nên xen kẽ các mức dilation khác nhau (1, 2, 5…).
4. Depthwise (separable) Convolution
Bản thân depthwise conv không làm receptive field lớn hơn conv thường cùng kích thước kernel. Lợi ích của nó là rẻ: tách một conv chuẩn thành hai bước, (a) depthwise: mỗi kênh một filter riêng (groups=C_in), (b) pointwise: conv 1×1 trộn các kênh.
Với \(K = 3, C_{in} = C_{out} = 256\): 589.824 so với 67.840 tham số (ít hơn khoảng 8,7 lần). Nhờ rẻ như vậy, các kiến trúc như MobileNet chạy được trên điện thoại, và ConvNeXt dùng được kernel depthwise lớn 7×7 để có receptive field rộng mà không tốn kém.
depthwise_separable = nn.Sequential(
nn.Conv2d(256, 256, kernel_size=3, padding=1, groups=256, bias=False), # depthwise
nn.Conv2d(256, 256, kernel_size=1, bias=False), # pointwise
)
Ghép lại: CNN kiểu VGG có BatchNorm cho CIFAR-10
import torch.nn as nn
def conv_bn(cin, cout):
return nn.Sequential(
nn.Conv2d(cin, cout, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(cout),
nn.ReLU(inplace=True),
)
class CifarCNN(nn.Module):
def __init__(self, num_classes=10):
super().__init__()
self.features = nn.Sequential(
conv_bn(3, 64), conv_bn(64, 64), nn.MaxPool2d(2), # 32 -> 16
conv_bn(64, 128), conv_bn(128, 128), nn.MaxPool2d(2), # 16 -> 8
conv_bn(128, 256), conv_bn(256, 256), nn.MaxPool2d(2), # 8 -> 4
)
self.classifier = nn.Sequential(
nn.AdaptiveAvgPool2d(1), # global average pooling: (N, 256, 4, 4) -> (N, 256, 1, 1)
nn.Flatten(),
nn.Dropout(0.3),
nn.Linear(256, num_classes),
)
def forward(self, x):
return self.classifier(self.features(x))
model = CifarCNN()
print(sum(p.numel() for p in model.parameters())) # 1148874
Khoảng 1,15 triệu tham số, nhưng gần như toàn bộ nằm ở các lớp conv; nhờ global average pooling, lớp FC chỉ còn 2.570 tham số. Thay model này vào script huấn luyện của bài PyTorch: 7 bước huấn luyện, dùng learning rate 0.05–0.1 (BN cho phép learning rate lớn) và thêm data augmentation (xem Dataset, DataLoader và augmentation), mạng thường đạt khoảng 88–91% trên CIFAR-10 sau vài chục epoch.
Tóm tắt
- BatchNorm: chuẩn hoá từng kênh theo thống kê batch khi train, theo running stats khi suy luận; \(\gamma, \beta\) học được.
- Luôn gọi
model.train()/model.eval()đúng lúc; batch nhỏ thì cân nhắc GroupNorm. - Dropout tắt ngẫu nhiên neuron khi train, không làm gì khi suy luận.
- Mẫu kinh điển: [Conv → BN → ReLU] × n → Pool, lặp lại, rồi GAP/Flatten → FC.
- Receptive field tăng nhờ thêm lớp, pooling/stride, dilated conv; nhiều kernel 3×3 tốt hơn một kernel lớn.
Tiếp theo: Các kiến trúc CNN kinh điển: LeNet, AlexNet, VGG, ResNet.
Tài liệu tham khảo
- Ioffe & Szegedy (2015). Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift.
- Santurkar et al. (2018). How Does Batch Normalization Help Optimization?
- Wu & He (2018). Group Normalization.
- Srivastava et al. (2014). Dropout: A Simple Way to Prevent Neural Networks from Overfitting.
- Luo et al. (2016). Understanding the Effective Receptive Field in Deep Convolutional Neural Networks.
- Yu & Koltun (2016). Multi-Scale Context Aggregation by Dilated Convolutions; Howard et al. (2017). MobileNets.
- Tài liệu PyTorch:
nn.BatchNorm2d,nn.Dropout.
Bài tập thực hành
- Tính receptive field của CNN bạn đã viết ở bài PyTorch cơ bản; thêm BatchNorm và so sánh đường loss trước và sau.
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.


