.. raw:: html
.. raw:: html
.. raw:: html
.. _sec_resnet:
Mạng phần dư (ResNet)
=====================
.. raw:: html
Khi thiết kế các mạng ngày càng sâu, ta cần hiểu việc thêm các tầng sẽ
tăng độ phức tạp và khả năng biểu diễn của mạng như thế nào. Quan trọng
hơn là khả năng thiết kế các mạng trong đó việc thêm các tầng vào mạng
chắc chắn sẽ làm tăng tính biểu diễn thay vì chỉ tạo ra một chút khác
biệt. Để làm được điều này, chúng ta cần một chút lý thuyết.
.. raw:: html
Các Lớp Hàm Số
--------------
.. raw:: html
| Coi :math:`\mathcal{F}` là một lớp các hàm mà một kiến trúc mạng cụ
thể (cùng với tốc độ học và các siêu tham số khác) có thể đạt được.
Nói cách khác, với mọi hàm số :math:`f \in \mathcal{F}`, luôn tồn tại
một số tập tham số :math:`W` có thể tìm được bằng việc huấn luyện trên
một tập dữ liệu phù hợp. Giả sử :math:`f^*` là hàm cần tìm. Sẽ rất
thuận lợi nếu hàm này thuộc tập :math:`\mathcal{F}`, nhưng thường
không may mắn như vậy. Thay vào đó, ta sẽ cố gắng tìm các hàm số
:math:`f^*_\mathcal{F}` tốt nhất có thể trong tập :math:`\mathcal{F}`.
| Ví dụ, có thể thử tìm :math:`f^*_\mathcal{F}` bằng cách giải bài toán
tối ưu sau:
.. math:: f^*_\mathcal{F} := \mathop{\mathrm{argmin}}_f L(X, Y, f) \text{ đối~tượng~thoả~mãn } f \in \mathcal{F}.
.. raw:: html
Khá hợp lý khi giả sử rằng nếu thiết kế một kiến trúc khác
:math:`\mathcal{F}'` mạnh mẽ hơn thì sẽ đạt được kết quả tốt hơn. Nói
cách khác, ta kỳ vọng hàm số :math:`f^*_{\mathcal{F}'}` sẽ “tốt hơn”
:math:`f^*_{\mathcal{F}}`. Tuy nhiên, nếu
:math:`\mathcal{F} \not\subseteq \mathcal{F}'`, thì không khẳng định
được :math:`f^*_{\mathcal{F}'}` “tốt hơn” :math:`f^*_{\mathcal{F}}`.
Trên thực tế, :math:`f^*_{\mathcal{F}'}` có thể còn tệ hơn. Và đây là
trường hợp thường xuyên xảy ra — việc thêm các tầng không phải lúc nào
cũng tăng tính biểu diễn của mạng mà đôi khi còn tạo ra những thay đổi
rất khó lường. :numref:`fig_functionclasses` minh hoạ rõ hơn điều này.
.. raw:: html
.. _fig_functionclasses:
.. figure:: ../img/functionclasses.svg
Hình trái: Các lớp hàm số tổng quát. Khoảng cách đến hàm cần tìm
:math:`f^*` (ngôi sao), trên thực tế có thể tăng khi độ phức tạp tăng
lên. Hình phải: với các lớp hàm số lồng nhau, điều này không xảy ra.
.. raw:: html
.. raw:: html
.. raw:: html
Chỉ khi các lớp hàm lớn hơn chứa các lớp nhỏ hơn, thì mới đảm bảo rằng
việc tăng thêm các tầng sẽ tăng khả năng biểu diễn của mạng. Đây là câu
hỏi mà He và các cộng sự đã suy nghĩ khi nghiên cứu các mô hình thị giác
sâu năm 2016. Ý tưởng trọng tâm của ResNet là mỗi tầng được thêm vào nên
có một thành phần là hàm số đồng nhất. Điều này có nghĩa rằng, nếu ta
huấn luyện tầng mới được thêm vào thành một ánh xạ đồng nhất
:math:`f(\mathbf{x}) = \mathbf{x}`, thì mô hình mới sẽ hiệu quả ít nhất
bằng mô hình ban đầu. Vì tầng được thêm vào có thể khớp dữ liệu huấn
luyện tốt hơn, dẫn đến sai số huấn luyện cũng nhỏ hơn. Tốt hơn nữa, hàm
số đồng nhất nên là hàm đơn giản nhất trong một tầng thay vì hàm null
:math:`f(\mathbf{x}) = 0`.
.. raw:: html
Cách suy nghĩ này khá trừu tượng nhưng lại dẫn đến một lời giải đơn giản
đáng ngạc nhiên, một khối phần dư (*residual block*). Với ý tưởng này,
:cite:`He.Zhang.Ren.ea.2016` đã chiến thắng cuộc thi Nhận dạng Ảnh
ImageNet năm 2015. Thiết kế này có ảnh hưởng sâu sắc tới việc xây dựng
các mạng nơ-ron sâu.
.. raw:: html
Khối phần dư
------------
.. raw:: html
Bây giờ, hãy tập trung vào mạng nơ-ron dưới đây. Ký hiệu đầu vào là
:math:`\mathbf{x}`. Giả sử ánh xạ lý tưởng muốn học được là
:math:`f(\mathbf{x})`, và được dùng làm đầu vào của hàm kích hoạt. Phần
nằm trong viền nét đứt bên trái phải khớp trực tiếp với ánh xạ
:math:`f(\mathbf{x})`. Điều này có thể không đơn giản nếu chúng ta không
cần khối đó và muốn giữ lại đầu vào :math:`\mathbf{x}`. Khi đó, phần nằm
trong viền nét đứt bên phải chỉ cần tham số hoá *độ lệch* khỏi giá trị
:math:`\mathbf{x}`, bởi vì ta đã trả về
:math:`\mathbf{x} + f(\mathbf{x})`. Trên thực tế, ánh xạ phần dư thường
dễ tối ưu hơn, vì chỉ cần đặt :math:`f(\mathbf{x}) = 0`. Nửa bên phải
:numref:`fig_residual_block` mô tả khối phần dư cơ bản của ResNet. Về
sau, những kiến trúc tương tự đã được đề xuất cho các mô hình chuỗi
(*sequence model*), sẽ đề cập ở chương sau.
.. raw:: html
.. _fig_residual_block:
.. figure:: ../img/residual-block.svg
Sự khác biệt giữa một khối thông thường (trái) và một khối phần dư
(phải). Trong khối phần dư, ta có thể nối tắt các tích chập.
.. raw:: html
.. raw:: html
.. raw:: html
ResNet có thiết kế tầng tích chập :math:`3\times 3` giống VGG. Khối phần
dư có hai tầng tích chập :math:`3\times 3` với cùng số kênh đầu ra. Mỗi
tầng tích chập được theo sau bởi một tầng chuẩn hóa theo batch và một
hàm kích hoạt ReLU. Ta đưa đầu vào qua khối phần dư rồi cộng với chính
nó trước hàm kích hoạt ReLU cuối cùng. Thiết kế này đòi hỏi đầu ra của
hai tầng tích chập phải có cùng kích thước với đầu vào, để có thể cộng
lại với nhau. Nếu muốn thay đổi số lượng kênh hoặc sải bước trong khối
phần dư, cần thêm một tầng tích chập :math:`1\times 1` để thay đổi kích
thước đầu vào tương ứng ở nhánh ngoài. Hãy cùng xem đoạn mã bên dưới.
.. code:: python
from d2l import mxnet as d2l
from mxnet import np, npx
from mxnet.gluon import nn
npx.set_np()
# Saved in the d2l package for later use
class Residual(nn.Block):
def __init__(self, num_channels, use_1x1conv=False, strides=1, **kwargs):
super(Residual, self).__init__(**kwargs)
self.conv1 = nn.Conv2D(num_channels, kernel_size=3, padding=1,
strides=strides)
self.conv2 = nn.Conv2D(num_channels, kernel_size=3, padding=1)
if use_1x1conv:
self.conv3 = nn.Conv2D(num_channels, kernel_size=1,
strides=strides)
else:
self.conv3 = None
self.bn1 = nn.BatchNorm()
self.bn2 = nn.BatchNorm()
def forward(self, X):
Y = npx.relu(self.bn1(self.conv1(X)))
Y = self.bn2(self.conv2(Y))
if self.conv3:
X = self.conv3(X)
return npx.relu(Y + X)
.. raw:: html
Đoạn mã này tạo ra hai loại mạng: một loại cộng đầu vào vào đầu ra trước
khi áp dụng hàm phi tuyến ReLU (khi ``use_1x1conv=True``), còn ở loại
thứ hai chúng ta thay đổi số kênh và độ phân giải bằng một tầng tích
chập :math:`1 \times 1` trước khi thực hiện phép cộng.
:numref:`fig_resnet_block` minh họa điều này:
.. raw:: html
.. _fig_resnet_block:
.. figure:: ../img/resnet-block.svg
Trái: khối ResNet thông thường; Phải: Khối ResNet với tầng tích chập
1x1
.. raw:: html
Giờ hãy xem xét tình huống khi cả đầu vào và đầu ra có cùng kích thước.
.. code:: python
blk = Residual(3)
blk.initialize()
X = np.random.uniform(size=(4, 3, 6, 6))
blk(X).shape
.. parsed-literal::
:class: output
(4, 3, 6, 6)
.. raw:: html
Chúng ta cũng có thể giảm một nửa kích thước chiều cao và chiều rộng của
đầu ra trong khi tăng số kênh.
.. code:: python
blk = Residual(6, use_1x1conv=True, strides=2)
blk.initialize()
blk(X).shape
.. parsed-literal::
:class: output
(4, 6, 3, 3)
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
Mô hình ResNet
--------------
.. raw:: html
Hai tầng đầu tiên của ResNet giống hai tầng đầu tiên của GoogLeNet: tầng
tích chập :math:`7\times 7` với 64 kênh đầu ra và sải bước 2, theo sau
bởi tầng gộp cực đại :math:`3 \times 3` với sải bước 2. Sự khác biệt là
trong ResNet, mỗi tầng tích chập theo sau bởi tầng chuẩn hóa theo batch.
.. code:: python
net = nn.Sequential()
net.add(nn.Conv2D(64, kernel_size=7, strides=2, padding=3),
nn.BatchNorm(), nn.Activation('relu'),
nn.MaxPool2D(pool_size=3, strides=2, padding=1))
.. raw:: html
GoogLeNet sử dụng bốn mô-đun được tạo thành từ các khối Inception.
ResNet sử dụng bốn mô-đun được tạo thành từ các khối phần dư có cùng số
kênh đầu ra. Mô-đun đầu tiên có số kênh bằng số kênh đầu vào. Vì trước
đó đã sử dụng tầng gộp cực đại với sải bước 2, nên không cần phải giảm
chiều cao và chiều rộng ở mô-đun này. Trong các mô-đun sau, khối phần dư
đầu tiên nhân đôi số kênh, đồng thời giảm một nửa chiều cao và chiều
rộng.
.. raw:: html
Bây giờ ta sẽ lập trình mô-đun này. Chú ý rằng mô-đun đầu tiên được xử
lý khác một chút.
.. code:: python
def resnet_block(num_channels, num_residuals, first_block=False):
blk = nn.Sequential()
for i in range(num_residuals):
if i == 0 and not first_block:
blk.add(Residual(num_channels, use_1x1conv=True, strides=2))
else:
blk.add(Residual(num_channels))
return blk
.. raw:: html
Sau đó, chúng ta thêm các khối phần dư vào ResNet. Ở đây, mỗi mô-đun có
hai khối phần dư.
.. code:: python
net.add(resnet_block(64, 2, first_block=True),
resnet_block(128, 2),
resnet_block(256, 2),
resnet_block(512, 2))
.. raw:: html
.. raw:: html
.. raw:: html
Cuối cùng, giống như GoogLeNet, ta thêm một tầng gộp trung bình toàn cục
và một tầng kết nối đầy đủ.
.. code:: python
net.add(nn.GlobalAvgPool2D(), nn.Dense(10))
.. raw:: html
Có 4 tầng tích chập trong mỗi mô-đun (không tính tầng tích chập
:math:`1 \times 1`). Cộng thêm tầng tích chập đầu tiên và tầng kết nối
đầy đủ cuối cùng, mô hình có tổng cộng 18 tầng. Do đó, mô hình này
thường được gọi là ResNet-18. Có thể thay đổi số kênh và các khối phần
dư trong mô-đun để tạo ra các mô hình ResNet khác nhau, ví dụ mô hình
152 tầng của ResNet-152. Mặc dù có kiến trúc lõi tương tự như GoogLeNet,
cấu trúc của ResNet đơn giản và dễ sửa đổi hơn. Tất cả các yếu tố này
dẫn đến sự phổ cập nhanh chóng và rộng rãi của ResNet.
:numref:`fig_ResNetFull` là sơ đồ đầy đủ của ResNet-18.
.. raw:: html
.. _fig_ResNetFull:
.. figure:: ../img/ResNetFull.svg
ResNet-18
.. raw:: html
Trước khi huấn luyện, hãy quan sát thay đổi của kích thước đầu vào qua
các mô-đun khác nhau trong ResNet. Như trong tất cả các kiến trúc trước,
độ phân giải giảm trong khi số lượng kênh tăng đến khi tầng gộp trung
bình toàn cục tổng hợp tất cả các đặc trưng.
.. code:: python
X = np.random.uniform(size=(1, 1, 224, 224))
net.initialize()
for layer in net:
X = layer(X)
print(layer.name, 'output shape:\t', X.shape)
.. parsed-literal::
:class: output
conv5 output shape: (1, 64, 112, 112)
batchnorm4 output shape: (1, 64, 112, 112)
relu0 output shape: (1, 64, 112, 112)
pool0 output shape: (1, 64, 56, 56)
sequential1 output shape: (1, 64, 56, 56)
sequential2 output shape: (1, 128, 28, 28)
sequential3 output shape: (1, 256, 14, 14)
sequential4 output shape: (1, 512, 7, 7)
pool1 output shape: (1, 512, 1, 1)
dense0 output shape: (1, 10)
.. raw:: html
.. raw:: html
.. raw:: html
Thu thập dữ liệu và Huấn luyện
------------------------------
.. raw:: html
Giống như các phần trước, chúng ta huấn luyện ResNet trên bộ dữ liệu
Fashion-MNIST. Thay đổi duy nhất là giảm tốc độ học lại do kiến trúc
mạng phức tạp hơn.
.. code:: python
lr, num_epochs, batch_size = 0.05, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr)
.. parsed-literal::
:class: output
loss 0.024, train acc 0.993, test acc 0.929
1884.4 examples/sec on gpu(0)
.. figure:: output_resnet_vn_ea6188_17_1.svg
.. raw:: html
Tóm tắt
-------
.. raw:: html
- Khối phần dư cho phép tham số hóa đến hàm đồng nhất
:math:`f(\mathbf{x}) = \mathbf{x}`.
- Thêm các khối phần dư làm tăng độ phức tạp của hàm số theo một cách
chủ đích.
- Chúng ta có thể huấn luyện hiệu quả mạng nơ-ron sâu nhờ khối phần dư
chuyển dữ liệu liên tầng.
- ResNet có ảnh hưởng lớn đến thiết kế sau này của các mạng nơ-ron sâu,
cả tích chập và tuần tự.
.. raw:: html
Bài tập
-------
.. raw:: html
1. Tham khảo Bảng 1 trong :cite:`He.Zhang.Ren.ea.2016` để lập trình
các biến thể khác nhau.
2. Đối với các mạng sâu hơn, ResNet giới thiệu kiến trúc “thắt cổ chai”
để giảm độ phức tạp của mô hình. Hãy thử lập trình kiến trúc đó.
3. Trong các phiên bản sau của ResNet, tác giả đã thay đổi kiến trúc
“tích chập, chuẩn hóa theo batch, và hàm kích hoạt” thành “chuẩn hóa
theo batch, hàm kích hoạt, và tích chập”. Hãy tự lập trình kiến trúc
này. Xem hình 1 trong :cite:`He.Zhang.Ren.ea.2016*1` để biết chi
tiết.
4. Chứng minh rằng nếu :math:`\mathbf{x}` được tạo ra bởi ReLU thì khối
ResNet sẽ bao gồm hàm số đồng nhất.
5. Tại sao không thể tăng không giới hạn độ phức tạp của các hàm số,
ngay cả với các lớp hàm lồng nhau?
.. raw:: html
.. raw:: html
Thảo luận
---------
- `Tiếng Anh `__
- `Tiếng Việt `__
Những người thực hiện
---------------------
Bản dịch trong trang này được thực hiện bởi:
- Đoàn Võ Duy Thanh
- Nguyễn Văn Quang
- Nguyễn Cảnh Thướng
- Lê Khắc Hồng Phúc
- Nguyễn Văn Cường
- Nguyễn Đình Nam
- Phạm Minh Đức
- Phạm Hồng Vinh