⭐ Vision Engineer · Chặng 10Bài 7/12Bài học~60 phút

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.

Batch Normalization, Dropout và Receptive Field

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
  1. Internal Covariate Shift: trò chơi "tam sao thất bản"
  2. Batch Normalization hoạt động thế nào?
  3. Các kiểu Normalization khác
  4. Dropout: chống overfitting bằng cách "tắt ngẫu nhiên"
  5. Mẫu sắp xếp layer phổ biến của CNN
  6. Receptive Field: một neuron "nhìn thấy" bao nhiêu?
  7. Bốn cách tăng receptive field
  8. Ghép lại: CNN kiểu VGG có BatchNorm cho CIFAR-10
  9. Tóm tắt
  10. 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.

Cập nhật từ nghiên cứuSanturkar et al. (2018, How Does Batch Normalization Help Optimization?) cho thấy BatchNorm hiệu quả chủ yếu vì nó làm mặt loss trơn hơn (gradient ổn định, dự đoán được), hơn là vì giảm internal covariate shift. Dù giải thích thế nào, hiệu quả thực nghiệm của BatchNorm là không bàn cãi.

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:

  1. Nhận mini-batch activation.
  2. Tính mean và độ lệch chuẩn của batch hiện tại cho từng kênh.
  3. Chuẩn hoá.
  4. Scale và shift bằng \(\gamma, \beta\).
  5. Cập nhật trung bình chạy (moving/running average) của mean và variance để dùng về sau.
Quy ước "momentum" trong PyTorchSlide viết \(\mu_{mov} = \alpha\,\mu_{mov} + (1-\alpha)\,\mu\) với \(\alpha\) là trọng số của giá trị cũ. PyTorch dùng quy ước ngược: \(\hat x_{new} = (1 - \text{momentum})\cdot\hat x + \text{momentum}\cdot x_t\), mặc định 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=False cho 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ạiChuẩn hoá trênPhụ thuộc batch?Dùng nhiều ởPyTorch
Batch NormN, H, W (mỗi kênh)CóCNN phân loạinn.BatchNorm2d(C)
Layer NormC, H, W (mỗi mẫu)KhôngTransformer, RNNnn.LayerNorm(shape)
Instance NormH, W (mỗi mẫu, mỗi kênh)KhôngStyle transfer, GANnn.InstanceNorm2d(C)
Group Normnhóm kênh, H, W (mỗi mẫu)KhôngDetection, 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:

  1. Một vài block Conv → (BatchNorm) → ReLU.
  2. Max-pooling.
  3. Lặp lại bước 1 và 2 cho đến khi feature map đủ bé.
  4. Flatten (làm phẳng) feature map (hoặc global average pooling).
  5. 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ớpksReceptive field rBước nhảy j
conv 3×33131
conv 3×33151
maxpool 2×22262
conv 3×331102
conv 3×331142
maxpool 2×222164
conv 3×331244
conv 3×331324
maxpool 2×222368

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×31 lớp conv 5×5
Receptive field5×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ến21

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.

\[ \underbrace{K^2 C_{in} C_{out}}_{\text{conv chuẩn}} \quad\text{so với}\quad \underbrace{K^2 C_{in} + C_{in} C_{out}}_{\text{depthwise separable}} \]

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

  1. 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.

Bài tiếp theo
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í.