.. _sec_vgg:
Mạng sử dụng khối (VGG)
=======================
Trong khi AlexNet đưa ra bằng chứng thực nghiệm rằng CNN sâu có thể đạt
được kết quả tốt, nó không cung cấp một mẫu chung để hướng dẫn các nhà
nghiên cứu tiếp theo trong việc thiết kế các mạng mới. Trong các phần
sau, chúng tôi sẽ giới thiệu một số khái niệm heuristic thường được sử
dụng để thiết kế các mạng sâu.
Tiến bộ trong lĩnh vực này phản ánh rằng trong thiết kế chip nơi các kỹ
sư đã đi từ đặt bóng bán dẫn đến các yếu tố logic đến các khối logic.
Tương tự, thiết kế của các kiến trúc mạng thần kinh đã phát triển dần
dần trừu tượng hơn, với các nhà nghiên cứu chuyển từ suy nghĩ về các tế
bào thần kinh riêng lẻ sang toàn bộ các lớp, và bây giờ đến các khối,
lặp lại các mô hình của các lớp.
Ý tưởng sử dụng các khối đầu tiên xuất hiện từ `Visual Geometry
Group `__ (VGG) tại Đại học Oxford,
trong mạng *VGG* eponymously-name của họ. Thật dễ dàng để thực hiện các
cấu trúc lặp đi lặp lại này trong code với bất kỳ khuôn khổ học sâu hiện
đại nào bằng cách sử dụng các vòng lặp và chương trình con.
.. _subsec_vgg-blocks:
VGG Blocks
----------
Khối xây dựng cơ bản của CNN cổ điển là một chuỗi các như sau: (i) một
lớp tích hợp với đệm để duy trì độ phân giải, (ii) một phi tuyến tính
như một ReLU, (iii) một lớp tổng hợp như một lớp tổng hợp tối đa. Một
khối VGG bao gồm một chuỗi các lớp phức tạp, tiếp theo là một lớp tổng
hợp tối đa để lấy mẫu không gian. Trong giấy VGG gốc
:cite:`Simonyan.Zisserman.2014`, các tác giả đã sử dụng các phức hợp
với :math:`3\times3` hạt nhân với lớp đệm 1 (giữ chiều cao và chiều
rộng) và :math:`2 \times 2` tổng hợp tối đa với sải chân là 2 (giảm một
nửa độ phân giải sau mỗi khối). Trong đoạn code dưới đây, ta định nghĩa
một hàm gọi là ``vgg_block`` để thực hiện một khối VGG.
.. raw:: html
.. raw:: html
Hàm này có hai đối số tương ứng với số lớp phức tạp ``num_convs`` và số
kênh đầu ra ``num_channels``.
.. code:: python
from mxnet import np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l
npx.set_np()
def vgg_block(num_convs, num_channels):
blk = nn.Sequential()
for _ in range(num_convs):
blk.add(nn.Conv2D(num_channels, kernel_size=3,
padding=1, activation='relu'))
blk.add(nn.MaxPool2D(pool_size=2, strides=2))
return blk
.. raw:: html
.. raw:: html
Hàm này có ba đối số tương ứng với số lớp phức tạp ``num_convs``, số
kênh đầu vào ``in_channels`` và số kênh đầu ra ``out_channels``.
.. code:: python
import torch
from torch import nn
from d2l import torch as d2l
def vgg_block(num_convs, in_channels, out_channels):
layers = []
for _ in range(num_convs):
layers.append(nn.Conv2d(in_channels, out_channels,
kernel_size=3, padding=1))
layers.append(nn.ReLU())
in_channels = out_channels
layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
return nn.Sequential(*layers)
.. raw:: html
.. raw:: html
Hàm này có hai đối số tương ứng với số lớp phức tạp ``num_convs`` và số
kênh đầu ra ``num_channels``.
.. code:: python
import tensorflow as tf
from d2l import tensorflow as d2l
def vgg_block(num_convs, num_channels):
blk = tf.keras.models.Sequential()
for _ in range(num_convs):
blk.add(tf.keras.layers.Conv2D(num_channels,kernel_size=3,
padding='same',activation='relu'))
blk.add(tf.keras.layers.MaxPool2D(pool_size=2, strides=2))
return blk
.. raw:: html
.. raw:: html
VGG mạng
--------
Giống như AlexNet và LeNet, Mạng VGG có thể được phân chia thành hai
phần: phần đầu tiên bao gồm chủ yếu là các lớp phức tạp và tập hợp và
thứ hai bao gồm các lớp được kết nối hoàn toàn. Điều này được miêu tả
năm :numref:`fig_vgg`.
.. _fig_vgg:
.. figure:: ../img/vgg.svg
:width: 400px
From AlexNet to VGG that is designed from building blocks.
Phần tích tụ của mạng kết nối một số khối VGG từ :numref:`fig_vgg`
(cũng được định nghĩa trong hàm ``vgg_block``) liên tiếp. Biến sau đây
``conv_arch`` bao gồm một danh sách các tuples (một cho mỗi khối), trong
đó mỗi khối chứa hai giá trị: số lớp phức tạp và số kênh đầu ra, đó
chính xác là các đối số cần thiết để gọi hàm ``vgg_block``. Phần được
kết nối hoàn toàn của mạng VGG giống hệt với phần được bao phủ trong
AlexNet.
Mạng VGG ban đầu có 5 khối phức tạp, trong đó hai khối đầu tiên có một
lớp ghép mỗi lớp và ba khối sau chứa hai lớp ghép mỗi lớp. Khối đầu tiên
có 64 kênh đầu ra và mỗi khối tiếp theo tăng gấp đôi số kênh đầu ra, cho
đến khi con số đó đạt 512. Vì mạng này sử dụng 8 lớp kết nối và 3 lớp
kết nối hoàn toàn nên nó thường được gọi là VGG-11.
.. code:: python
conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))
Mã sau đây thực hiện VGG-11. Đây là một vấn đề đơn giản của việc thực
hiện một for-loop trên ``conv_arch``.
.. raw:: html
.. raw:: html
.. code:: python
def vgg(conv_arch):
net = nn.Sequential()
# The convolutional part
for (num_convs, num_channels) in conv_arch:
net.add(vgg_block(num_convs, num_channels))
# The fully-connected part
net.add(nn.Dense(4096, activation='relu'), nn.Dropout(0.5),
nn.Dense(4096, activation='relu'), nn.Dropout(0.5),
nn.Dense(10))
return net
net = vgg(conv_arch)
.. raw:: html
.. raw:: html
.. code:: python
def vgg(conv_arch):
conv_blks = []
in_channels = 1
# The convolutional part
for (num_convs, out_channels) in conv_arch:
conv_blks.append(vgg_block(num_convs, in_channels, out_channels))
in_channels = out_channels
return nn.Sequential(
*conv_blks, nn.Flatten(),
# The fully-connected part
nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 10))
net = vgg(conv_arch)
.. raw:: html
.. raw:: html
.. code:: python
def vgg(conv_arch):
net = tf.keras.models.Sequential()
# The convulational part
for (num_convs, num_channels) in conv_arch:
net.add(vgg_block(num_convs, num_channels))
# The fully-connected part
net.add(tf.keras.models.Sequential([
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(4096, activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(4096, activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(10)]))
return net
net = vgg(conv_arch)
.. raw:: html
.. raw:: html
Tiếp theo, chúng ta sẽ xây dựng một ví dụ dữ liệu đơn kênh với chiều cao
và chiều rộng từ 224 đến quan sát hình dạng đầu ra của mỗi lớp.
.. raw:: html
.. raw:: html
.. code:: python
net.initialize()
X = np.random.uniform(size=(1, 1, 224, 224))
for blk in net:
X = blk(X)
print(blk.name, 'output shape:\t', X.shape)
.. parsed-literal::
:class: output
sequential1 output shape: (1, 64, 112, 112)
sequential2 output shape: (1, 128, 56, 56)
sequential3 output shape: (1, 256, 28, 28)
sequential4 output shape: (1, 512, 14, 14)
sequential5 output shape: (1, 512, 7, 7)
dense0 output shape: (1, 4096)
dropout0 output shape: (1, 4096)
dense1 output shape: (1, 4096)
dropout1 output shape: (1, 4096)
dense2 output shape: (1, 10)
.. raw:: html
.. raw:: html
.. code:: python
X = torch.randn(size=(1, 1, 224, 224))
for blk in net:
X = blk(X)
print(blk.__class__.__name__,'output shape:\t',X.shape)
.. parsed-literal::
:class: output
Sequential output shape: torch.Size([1, 64, 112, 112])
Sequential output shape: torch.Size([1, 128, 56, 56])
Sequential output shape: torch.Size([1, 256, 28, 28])
Sequential output shape: torch.Size([1, 512, 14, 14])
Sequential output shape: torch.Size([1, 512, 7, 7])
Flatten output shape: torch.Size([1, 25088])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 10])
.. raw:: html
.. raw:: html
.. code:: python
X = tf.random.uniform((1, 224, 224, 1))
for blk in net.layers:
X = blk(X)
print(blk.__class__.__name__,'output shape:\t', X.shape)
.. parsed-literal::
:class: output
Sequential output shape: (1, 112, 112, 64)
Sequential output shape: (1, 56, 56, 128)
Sequential output shape: (1, 28, 28, 256)
Sequential output shape: (1, 14, 14, 512)
Sequential output shape: (1, 7, 7, 512)
Sequential output shape: (1, 10)
.. raw:: html
.. raw:: html
Như bạn có thể thấy, chúng ta giảm một nửa chiều cao và chiều rộng tại
mỗi khối, cuối cùng đạt đến chiều cao và chiều rộng 7 trước khi làm
phẳng các biểu diễn để xử lý bởi phần kết nối hoàn toàn của mạng.
Đào tạo
-------
Vì VGG-11 nặng tính toán hơn AlexNet, chúng tôi xây dựng một mạng với số
lượng kênh nhỏ hơn. Điều này là quá đủ để đào tạo về Fashion-MNIST.
.. raw:: html
.. raw:: html
.. code:: python
ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
net = vgg(small_conv_arch)
.. raw:: html
.. raw:: html
.. code:: python
ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
net = vgg(small_conv_arch)
.. raw:: html
.. raw:: html
.. code:: python
ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
# Recall that this has to be a function that will be passed to
# `d2l.train_ch6()` so that model building/compiling need to be within
# `strategy.scope()` in order to utilize the CPU/GPU devices that we have
net = lambda: vgg(small_conv_arch)
.. raw:: html
.. raw:: html
Ngoài việc sử dụng tốc độ học tập lớn hơn một chút, quá trình model
training cũng tương tự như của AlexNet trong :numref:`sec_alexnet`.
.. raw:: html
.. raw:: html
.. code:: python
lr, num_epochs, batch_size = 0.05, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
.. parsed-literal::
:class: output
loss 0.175, train acc 0.936, test acc 0.923
1765.4 examples/sec on gpu(0)
.. figure:: output_vgg_4a7574_56_1.svg
.. raw:: html
.. raw:: html
.. code:: python
lr, num_epochs, batch_size = 0.05, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
.. parsed-literal::
:class: output
loss 0.177, train acc 0.934, test acc 0.921
2499.7 examples/sec on cuda:0
.. figure:: output_vgg_4a7574_59_1.svg
.. raw:: html
.. raw:: html
.. code:: python
lr, num_epochs, batch_size = 0.05, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
.. parsed-literal::
:class: output
loss 0.176, train acc 0.935, test acc 0.924
2715.0 examples/sec on /GPU:0
.. parsed-literal::
:class: output
.. figure:: output_vgg_4a7574_62_2.svg
.. raw:: html
.. raw:: html
Tóm tắt
-------
- VGG-11 xây dựng một mạng bằng cách sử dụng các khối phức tạp có thể
tái sử dụng. Các mô hình VGG khác nhau có thể được xác định bởi sự
khác biệt về số lượng lớp phức tạp và các kênh đầu ra trong mỗi khối.
- Việc sử dụng các khối dẫn đến các đại diện rất nhỏ gọn của định nghĩa
mạng. Nó cho phép thiết kế hiệu quả các mạng phức tạp.
- Trong giấy VGG của họ, Simonyan và Ziserman đã thử nghiệm với nhiều
kiến trúc khác nhau. Đặc biệt, họ phát hiện ra rằng một số lớp phức
tạp sâu và hẹp (tức là :math:`3 \times 3`) có hiệu quả hơn so với ít
lớp phức tạp rộng hơn.
Bài tập
-------
1. Khi in ra kích thước của các lớp chúng ta chỉ thấy 8 kết quả hơn là
11. Thông tin 3 lớp còn lại đã đi đâu?
2. So với AlexNet, VGG chậm hơn nhiều về mặt tính toán, và nó cũng cần
nhiều bộ nhớ GPU hơn. Phân tích lý do cho việc này.
3. Hãy thử thay đổi chiều cao và chiều rộng của hình ảnh trong
Fashion-MNIST từ 224 đến 96. Điều này có ảnh hưởng gì đến các thí
nghiệm?
4. Tham khảo Bảng 1 trong giấy VGG :cite:`Simonyan.Zisserman.2014` để
xây dựng các mô hình phổ biến khác, chẳng hạn như VGG-16 hoặc VGG-19.
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html