.. raw:: html
.. raw:: html
.. raw:: html
Mạng Tích chập Kết nối Dày đặc (DenseNet)
=========================================
.. raw:: html
ResNet đã làm thay đổi đáng kể quan điểm về cách tham số hóa các hàm số
trong mạng nơ-ron sâu. Ở một mức độ nào đó, DenseNet có thể được coi là
phiên bản mở rộng hợp lý của ResNet. Để hiểu cách đi đến kết luận đó, ta
cần tìm hiểu một chút lý thuyết. Nhắc lại công thức khai triển Taylor
cho hàm một biến vô hướng như sau
.. math:: f(x) = f(0) + f'(x) x + \frac{1}{2} f''(x) x^2 + \frac{1}{6} f'''(x) x^3 + o(x^3).
.. raw:: html
Phân tách Hàm số
----------------
.. raw:: html
Điểm mấu chốt là khai triển Taylor phân tách hàm số thành các số hạng có
bậc tăng dần. Tương tự, ResNet phân tách các hàm số thành
.. math:: f(\mathbf{x}) = \mathbf{x} + g(\mathbf{x}).
.. raw:: html
Cụ thể, ResNet tách hàm số :math:`f` thành một số hạng tuyến tính đơn
giản và một số hạng phi tuyến phức tạp hơn. Nếu ta muốn tách ra thành
nhiều hơn hai số hạng thì sao? Một giải pháp đã được đề xuất bởi
:cite:`Huang.Liu.Van-Der-Maaten.ea.2017` trong kiến trúc DenseNet.
Kiến trúc này đạt được hiệu suất kỉ lục trên tập dữ liệu ImageNet.
.. raw:: html
.. _fig_densenet_block:
.. figure:: ../img/densenet-block.svg
Sự khác biệt chính giữa ResNet (bên trái) và DenseNet (bên phải)
trong các kết nối xuyên tầng: sử dụng phép cộng và sử dụng phép nối.
.. raw:: html
Như được thể hiện trong :numref:`fig_densenet_block`, điểm khác biệt
chính là DenseNet *nối* đầu ra lại với nhau thay vì cộng lại như ở
ResNet. Kết quả là ta thực hiện một ánh xạ từ :math:`\mathbf{x}` đến các
giá trị của nó sau khi áp dụng một chuỗi các hàm với độ phức tạp tăng
dần.
.. math:: \mathbf{x} \to \left[\mathbf{x}, f_1(\mathbf{x}), f_2(\mathbf{x}, f_1(\mathbf{x})), f_3(\mathbf{x}, f_1(\mathbf{x}), f_2(\mathbf{x}, f_1(\mathbf{x})), \ldots\right].
.. raw:: html
.. raw:: html
.. raw:: html
Cuối cùng, tất cả các hàm số này sẽ được kết hợp trong một Perceptron đa
tầng để giảm số lượng đặc trưng một lần nữa. Lập trình thay đổi này khá
đơn giản — thay vì cộng các số hạng với nhau, ta sẽ nối chúng lại. Cái
tên DenseNet phát sinh từ việc đồ thị phụ thuộc giữa các biến trở nên
khá dày đặc. Tầng cuối cùng của một chuỗi như vậy được kết nối “dày đặc”
tới tất cả các tầng trước đó. Thành phần chính của DenseNet là các khối
dày đặc và các tầng chuyển tiếp. Các khối dày đặc định nghĩa cách các
đầu vào và đầu ra được nối với nhau, trong khi các tầng chuyển tiếp kiểm
soát số lượng kênh sao cho nó không quá lớn. Các kết nối dày đặc được
biểu diễn trong :numref:`fig_densenet`.
.. raw:: html
.. _fig_densenet:
.. figure:: ../img/densenet.svg
Các kết nối dày đặc trong DenseNet
.. raw:: html
.. raw:: html
.. raw:: html
Khối Dày Đặc
------------
.. raw:: html
DenseNet sử dụng kiến trúc “chuẩn hóa theo batch, hàm kích hoạt và phép
tích chập” đã qua sửa đổi của ResNet (xem phần bài tập trong
:numref:`sec_resnet`). Đầu tiên, ta sẽ lập trình kiến trúc này trong
hàm ``conv_block``.
.. code:: python
from d2l import mxnet as d2l
from mxnet import np, npx
from mxnet.gluon import nn
npx.set_np()
def conv_block(num_channels):
blk = nn.Sequential()
blk.add(nn.BatchNorm(),
nn.Activation('relu'),
nn.Conv2D(num_channels, kernel_size=3, padding=1))
return blk
.. raw:: html
Một khối dày đặc bao gồm nhiều khối ``conv_block`` với cùng số lượng
kênh đầu ra. Tuy nhiên, ta sẽ nối đầu vào và đầu ra của từng khối theo
chiều kênh khi tính toán lượt truyền xuôi.
.. code:: python
class DenseBlock(nn.Block):
def __init__(self, num_convs, num_channels, **kwargs):
super(DenseBlock, self).__init__(**kwargs)
self.net = nn.Sequential()
for _ in range(num_convs):
self.net.add(conv_block(num_channels))
def forward(self, X):
for blk in self.net:
Y = blk(X)
# Concatenate the input and output of each block on the channel
# dimension
X = np.concatenate((X, Y), axis=1)
return X
.. raw:: html
Trong ví dụ sau, ta sẽ định nghĩa một khối dày đặc gồm hai khối tích
chập với 10 kênh đầu ra. Với một đầu vào gồm 3 kênh, ta sẽ nhận được một
đầu ra với :math:`3+2\times 10=23` kênh. Số lượng kênh của khối tích
chập kiểm soát sự gia tăng của số lượng kênh đầu ra so với số lượng kênh
đầu vào. Số lượng kênh này còn được gọi là tốc độ tăng trưởng (*growth
rate*).
.. code:: python
blk = DenseBlock(2, 10)
blk.initialize()
X = np.random.uniform(size=(4, 3, 8, 8))
Y = blk(X)
Y.shape
.. parsed-literal::
:class: output
(4, 23, 8, 8)
.. raw:: html
.. raw:: html
.. raw:: html
Tầng Chuyển Tiếp
----------------
.. raw:: html
Mỗi khối dày đặc sẽ làm tăng thêm số lượng kênh. Nhưng việc thêm quá
nhiều kênh sẽ tạo nên một mô hình phức tạp quá mức. Do đó, một tầng
chuyển tiếp sẽ được sử dụng để kiểm soát độ phức tạp của mô hình. Tầng
này dùng một tầng tích chập :math:`1\times 1` để giảm số lượng kênh,
theo sau là một tầng gộp trung bình với sải bước bằng 2 để giảm một nửa
chiều cao và chiều rộng, từ đó giảm độ phức tạp của mô hình hơn nữa.
.. code:: python
def transition_block(num_channels):
blk = nn.Sequential()
blk.add(nn.BatchNorm(), nn.Activation('relu'),
nn.Conv2D(num_channels, kernel_size=1),
nn.AvgPool2D(pool_size=2, strides=2))
return blk
.. raw:: html
Ta sẽ áp dụng một tầng chuyển tiếp với 10 kênh lên đầu ra của khối dày
đặc trong ví dụ trước. Việc này sẽ làm giảm số lượng kênh đầu ra xuống
còn 10, đồng thời làm giảm đi một nửa chiều cao và chiều rộng.
.. code:: python
blk = transition_block(10)
blk.initialize()
blk(Y).shape
.. parsed-literal::
:class: output
(4, 10, 4, 4)
.. raw:: html
.. raw:: html
.. raw:: html
Mô hình DenseNet
----------------
.. raw:: html
Tiếp theo, ta sẽ xây dựng một mô hình DenseNet. Đầu tiên, DenseNet sử
dụng một tầng tích chập và một tầng gộp cực đại như trong ResNet.
.. 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
.. raw:: html
.. raw:: html
Sau đó, tương tự như cách ResNet sử dụng bốn khối phần dư, DenseNet cũng
dùng bốn khối dày đặc. Và cũng giống như ResNet, ta có thể tùy chỉnh số
lượng tầng tích chập được sử dụng trong mỗi khối dày đặc. Ở đây, ta sẽ
đặt số lượng khối tích chập bằng 4 để giống với kiến trúc ResNet-18
trong phần trước. Ngoài ra, ta đặt số lượng kênh (tức tốc độ tăng
trưởng) của các tầng tích chập trong khối dày đặc là 32, vì vậy 128 kênh
sẽ được thêm vào trong mỗi khối dày đặc.
.. raw:: html
Trong ResNet, chiều cao và chiều rộng được giảm sau mỗi khối bằng cách
sử dụng một khối phần dư với sải bước bằng 2. Ở đây, ta sẽ sử dụng tầng
chuyển tiếp để làm giảm đi một nửa chiều cao, chiều rộng và số kênh.
.. code:: python
# Num_channels: the current number of channels
num_channels, growth_rate = 64, 32
num_convs_in_dense_blocks = [4, 4, 4, 4]
for i, num_convs in enumerate(num_convs_in_dense_blocks):
net.add(DenseBlock(num_convs, growth_rate))
# This is the number of output channels in the previous dense block
num_channels += num_convs * growth_rate
# A transition layer that haves the number of channels is added between
# the dense blocks
if i != len(num_convs_in_dense_blocks) - 1:
num_channels //= 2
net.add(transition_block(num_channels))
.. raw:: html
Tương tự như ResNet, một tầng gộp toàn cục và một tầng kết nối đầy đủ sẽ
được thêm vào cuối mạng để tính toán đầu ra.
.. code:: python
net.add(nn.BatchNorm(),
nn.Activation('relu'),
nn.GlobalAvgPool2D(),
nn.Dense(10))
.. raw:: html
.. raw:: html
.. raw:: html
Thu thập dữ liệu và Huấn luyện
------------------------------
.. raw:: html
Trong phần này, vì đang sử dụng một mạng sâu hơn nên để đơn giản hóa
việc tính toán, ta sẽ giảm chiều cao và chiều rộng của đầu vào từ 224
xuống còn 96.
.. code:: python
lr, num_epochs, batch_size = 0.1, 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.144, train acc 0.946, test acc 0.903
1999.9 examples/sec on gpu(0)
.. figure:: output_densenet_vn_76d094_17_1.svg
.. raw:: html
Tóm tắt
-------
.. raw:: html
- Về mặt kết nối xuyên tầng, không giống như trong ResNet khi đầu vào
và đầu ra được cộng lại với nhau, DenseNet nối các đầu vào và đầu ra
theo chiều kênh.
- Các thành phần chính tạo nên DenseNet là các khối dày đặc và các tầng
chuyển tiếp.
- Ta cần kiểm soát kích thước của các chiều khi thiết kế mạng bằng cách
thêm các tầng chuyển tiếp để làm giảm số lượng kênh.
.. raw:: html
Bài tập
-------
.. raw:: html
1. Tại sao ta lại sử dụng phép gộp trung bình thay vì gộp cực đại trong
tầng chuyển tiếp?
2. Một trong những ưu điểm được đề cập trong bài báo DenseNet là kiến
trúc này có số lượng tham số nhỏ hơn so với ResNet. Tại sao lại như
vậy?
3. DenseNet thường bị chỉ trích vì nó tiêu tốn nhiều bộ nhớ.
- Điều này có đúng không? Hãy thử thay đổi kích thước đầu vào thành
:math:`224\times 224` để xem mức tiêu thụ bộ nhớ (GPU) thực tế.
- Hãy tìm các phương án khác để giảm mức tiêu thụ bộ nhớ. Ta cần
thay đổi kiến trúc này như thế nào?
4. Lập trình các phiên bản DenseNet khác nhau được trình bày trong Bảng
1 của :cite:`Huang.Liu.Van-Der-Maaten.ea.2017`.
5. Tại sao ta không cần nối các số hạng nếu ta chỉ quan tâm đến
:math:`\mathbf{x}` và :math:`f(\mathbf{x})` như trong ResNet? Tại sao
ta lại cần làm vậy với nhiều hơn hai tầng trong DenseNet?
6. Thiết kế một mạng kết nối đầy đủ tương tự như DenseNet và áp dụng nó
vào bài toán Dự đoán Giá Nhà.
.. 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 Duy Du
- Nguyễn Văn Cường
- Nguyễn Cảnh Thướng
- Lê Khắc Hồng Phúc
- Phạm Minh Đức
- Phạm Hồng Vinh