.. _sec_googlenet:
Mạng có kết nối song song (GoogLeNet)
=====================================
Năm 2014, *GoogLeNet* giành chiến thắng trong ImageNet Challenge, đề
xuất một cấu trúc kết hợp các thế mạnh của Nin và mô hình của các khối
lặp lại :cite:`Szegedy.Liu.Jia.ea.2015`. Một trọng tâm của bài báo là
giải quyết câu hỏi về hạt nhân có kích thước nào là tốt nhất. Rốt cuộc,
các mạng phổ biến trước đây sử dụng các lựa chọn nhỏ tới
:math:`1 \times 1` và lớn tới :math:`11 \times 11`. Một cái nhìn sâu sắc
trong bài báo này là đôi khi nó có thể thuận lợi để sử dụng một sự kết
hợp của các hạt nhân có kích thước đa dạng. Trong phần này, chúng tôi sẽ
giới thiệu GoogLeNet, trình bày một phiên bản đơn giản hóa một chút của
mô hình gốc: chúng tôi bỏ qua một vài tính năng đặc biệt đã được thêm
vào để ổn định đào tạo nhưng bây giờ không cần thiết với các thuật toán
đào tạo tốt hơn có sẵn.
Chống\*\* Bắt nối
-----------------
Khối phức tạp cơ bản trong GoogLeNet được gọi là khối *Inception *, có
thể được đặt tên do một trích dẫn từ bộ phim *Inception* (“Chúng ta cần
phải đi sâu hơn”), đã đưa ra một meme virus.
.. _fig_inception:
.. figure:: ../img/inception.svg
Structure of the Inception block.
Như được mô tả trong :numref:`fig_inception`, khối khởi đầu bao gồm
bốn con đường song song. Ba đường dẫn đầu tiên sử dụng các lớp phức tạp
với kích thước cửa sổ :math:`1\times 1`, :math:`3\times 3` và
:math:`5\times 5` để trích xuất thông tin từ các kích thước không gian
khác nhau. Hai đường dẫn giữa thực hiện sự phức tạp :math:`1\times 1`
trên đầu vào để giảm số lượng kênh, giảm độ phức tạp của mô hình. Đường
dẫn thứ tư sử dụng một lớp tổng hợp tối đa :math:`3\times 3`, tiếp theo
là một lớp ghép :math:`1\times 1` để thay đổi số kênh. Bốn đường dẫn đều
sử dụng đệm thích hợp để cung cấp cho đầu vào và đầu ra cùng chiều cao
và chiều rộng. Cuối cùng, các đầu ra dọc theo mỗi đường dẫn được nối dọc
theo kích thước kênh và bao gồm đầu ra của khối. Các siêu tham số được
điều chỉnh chung của khối Inception là số kênh đầu ra trên mỗi lớp.
.. raw:: html
.. raw:: html
.. code:: python
from mxnet import np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l
npx.set_np()
class Inception(nn.Block):
# `c1`--`c4` are the number of output channels for each path
def __init__(self, c1, c2, c3, c4, **kwargs):
super(Inception, self).__init__(**kwargs)
# Path 1 is a single 1 x 1 convolutional layer
self.p1_1 = nn.Conv2D(c1, kernel_size=1, activation='relu')
# Path 2 is a 1 x 1 convolutional layer followed by a 3 x 3
# convolutional layer
self.p2_1 = nn.Conv2D(c2[0], kernel_size=1, activation='relu')
self.p2_2 = nn.Conv2D(c2[1], kernel_size=3, padding=1,
activation='relu')
# Path 3 is a 1 x 1 convolutional layer followed by a 5 x 5
# convolutional layer
self.p3_1 = nn.Conv2D(c3[0], kernel_size=1, activation='relu')
self.p3_2 = nn.Conv2D(c3[1], kernel_size=5, padding=2,
activation='relu')
# Path 4 is a 3 x 3 maximum pooling layer followed by a 1 x 1
# convolutional layer
self.p4_1 = nn.MaxPool2D(pool_size=3, strides=1, padding=1)
self.p4_2 = nn.Conv2D(c4, kernel_size=1, activation='relu')
def forward(self, x):
p1 = self.p1_1(x)
p2 = self.p2_2(self.p2_1(x))
p3 = self.p3_2(self.p3_1(x))
p4 = self.p4_2(self.p4_1(x))
# Concatenate the outputs on the channel dimension
return np.concatenate((p1, p2, p3, p4), axis=1)
.. raw:: html
.. raw:: html
.. code:: python
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
class Inception(nn.Module):
# `c1`--`c4` are the number of output channels for each path
def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
super(Inception, self).__init__(**kwargs)
# Path 1 is a single 1 x 1 convolutional layer
self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
# Path 2 is a 1 x 1 convolutional layer followed by a 3 x 3
# convolutional layer
self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
# Path 3 is a 1 x 1 convolutional layer followed by a 5 x 5
# convolutional layer
self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)
self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
# Path 4 is a 3 x 3 maximum pooling layer followed by a 1 x 1
# convolutional layer
self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)
def forward(self, x):
p1 = F.relu(self.p1_1(x))
p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
p4 = F.relu(self.p4_2(self.p4_1(x)))
# Concatenate the outputs on the channel dimension
return torch.cat((p1, p2, p3, p4), dim=1)
.. raw:: html
.. raw:: html
.. code:: python
import tensorflow as tf
from d2l import tensorflow as d2l
class Inception(tf.keras.Model):
# `c1`--`c4` are the number of output channels for each path
def __init__(self, c1, c2, c3, c4):
super().__init__()
# Path 1 is a single 1 x 1 convolutional layer
self.p1_1 = tf.keras.layers.Conv2D(c1, 1, activation='relu')
# Path 2 is a 1 x 1 convolutional layer followed by a 3 x 3
# convolutional layer
self.p2_1 = tf.keras.layers.Conv2D(c2[0], 1, activation='relu')
self.p2_2 = tf.keras.layers.Conv2D(c2[1], 3, padding='same',
activation='relu')
# Path 3 is a 1 x 1 convolutional layer followed by a 5 x 5
# convolutional layer
self.p3_1 = tf.keras.layers.Conv2D(c3[0], 1, activation='relu')
self.p3_2 = tf.keras.layers.Conv2D(c3[1], 5, padding='same',
activation='relu')
# Path 4 is a 3 x 3 maximum pooling layer followed by a 1 x 1
# convolutional layer
self.p4_1 = tf.keras.layers.MaxPool2D(3, 1, padding='same')
self.p4_2 = tf.keras.layers.Conv2D(c4, 1, activation='relu')
def call(self, x):
p1 = self.p1_1(x)
p2 = self.p2_2(self.p2_1(x))
p3 = self.p3_2(self.p3_1(x))
p4 = self.p4_2(self.p4_1(x))
# Concatenate the outputs on the channel dimension
return tf.keras.layers.Concatenate()([p1, p2, p3, p4])
.. raw:: html
.. raw:: html
Để đạt được một số trực giác về lý do tại sao mạng này hoạt động rất
tốt, hãy xem xét sự kết hợp của các bộ lọc. Họ khám phá hình ảnh trong
một loạt các kích cỡ bộ lọc. Điều này có nghĩa là các chi tiết ở các
phạm vi khác nhau có thể được nhận dạng hiệu quả bởi các bộ lọc có kích
thước khác nhau. Đồng thời, chúng ta có thể phân bổ lượng tham số khác
nhau cho các bộ lọc khác nhau.
Mô hình GoogLeNet
-----------------
Như thể hiện trong :numref:`fig_inception_full`, GoogLeNet sử dụng một
chồng tổng cộng 9 khối khởi đầu và tổng hợp trung bình toàn cầu để tạo
ra ước tính của nó. Tổng hợp tối đa giữa các khối khởi đầu làm giảm kích
thước. mô-đun đầu tiên tương tự như AlexNet và LeNet. Ngăn xếp các khối
được kế thừa từ VGG và tổng hợp trung bình toàn cầu tránh được một chồng
các lớp được kết nối hoàn toàn ở cuối.
.. _fig_inception_full:
.. figure:: ../img/inception-full.svg
The GoogLeNet architecture.
Bây giờ chúng ta có thể thực hiện GoogLeNet từng mảnh. mô-đun đầu tiên
sử dụng lớp ghép :math:`7\times 7` 64 kênh.
.. raw:: html
.. raw:: html
.. code:: python
b1 = nn.Sequential()
b1.add(nn.Conv2D(64, kernel_size=7, strides=2, padding=3, activation='relu'),
nn.MaxPool2D(pool_size=3, strides=2, padding=1))
.. raw:: html
.. raw:: html
.. code:: python
b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
.. raw:: html
.. raw:: html
.. code:: python
def b1():
return tf.keras.models.Sequential([
tf.keras.layers.Conv2D(64, 7, strides=2, padding='same',
activation='relu'),
tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')])
.. raw:: html
.. raw:: html
mô-đun thứ hai sử dụng hai lớp phức tạp: thứ nhất, một lớp ghép
:math:`1\times 1` 64 kênh, sau đó là một lớp ghép :math:`3\times 3` tăng
gấp ba lần số kênh. Điều này tương ứng với đường dẫn thứ hai trong khối
Inception.
.. raw:: html
.. raw:: html
.. code:: python
b2 = nn.Sequential()
b2.add(nn.Conv2D(64, kernel_size=1, activation='relu'),
nn.Conv2D(192, kernel_size=3, padding=1, activation='relu'),
nn.MaxPool2D(pool_size=3, strides=2, padding=1))
.. raw:: html
.. raw:: html
.. code:: python
b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
nn.ReLU(),
nn.Conv2d(64, 192, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
.. raw:: html
.. raw:: html
.. code:: python
def b2():
return tf.keras.Sequential([
tf.keras.layers.Conv2D(64, 1, activation='relu'),
tf.keras.layers.Conv2D(192, 3, padding='same', activation='relu'),
tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')])
.. raw:: html
.. raw:: html
mô-đun thứ ba kết nối hai khối Inception hoàn chỉnh trong chuỗi. Số kênh
đầu ra của khối Inception đầu tiên là :math:`64+128+32+32=256` và tỷ lệ
số kênh đầu ra giữa bốn đường dẫn là :math:`64:128:32:32=2:4:1:1`. Các
đường dẫn thứ hai và thứ ba đầu tiên làm giảm số lượng kênh đầu vào
xuống :math:`96/192=1/2` và :math:`16/192=1/12`, và sau đó kết nối lớp
phức tạp thứ hai. Số lượng kênh đầu ra của khối Inception thứ hai được
tăng lên :math:`128+192+96+64=480` và tỷ lệ số kênh đầu ra trong bốn
đường dẫn là :math:`128:192:96:64 = 4:6:3:2`. Các đường dẫn thứ hai và
thứ ba đầu tiên làm giảm số lượng kênh đầu vào xuống :math:`128/256=1/2`
và :math:`32/256=1/8`, tương ứng.
.. raw:: html
.. raw:: html
.. code:: python
b3 = nn.Sequential()
b3.add(Inception(64, (96, 128), (16, 32), 32),
Inception(128, (128, 192), (32, 96), 64),
nn.MaxPool2D(pool_size=3, strides=2, padding=1))
.. raw:: html
.. raw:: html
.. code:: python
b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32),
Inception(256, 128, (128, 192), (32, 96), 64),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
.. raw:: html
.. raw:: html
.. code:: python
def b3():
return tf.keras.models.Sequential([
Inception(64, (96, 128), (16, 32), 32),
Inception(128, (128, 192), (32, 96), 64),
tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')])
.. raw:: html
.. raw:: html
mô-đun thứ tư phức tạp hơn. Nó kết nối năm khối Inception trong loạt, và
họ có :math:`192+208+48+64=512`, :math:`160+224+64+64=512`,
:math:`128+256+64+64=512`, :math:`112+288+64+64=528`, và
:math:`256+320+128+128=832` kênh đầu ra, tương ứng. Số lượng kênh được
gán cho các đường dẫn này tương tự như trong mô-đun thứ ba: đường dẫn
thứ hai với lớp cuộn :math:`3\times 3` xuất ra số lượng kênh lớn nhất,
tiếp theo là đường dẫn đầu tiên chỉ có lớp ghép :math:`1\times 1`, đường
dẫn thứ ba với lớp biên độ :math:`5\times 5` và con đường thứ tư với lớp
tổng hợp tối đa :math:`3\times 3`. Các đường dẫn thứ hai và thứ ba trước
tiên sẽ giảm số lượng kênh theo tỷ lệ. Các tỷ lệ này hơi khác nhau trong
các khối Inception khác nhau.
.. raw:: html
.. raw:: html
.. code:: python
b4 = nn.Sequential()
b4.add(Inception(192, (96, 208), (16, 48), 64),
Inception(160, (112, 224), (24, 64), 64),
Inception(128, (128, 256), (24, 64), 64),
Inception(112, (144, 288), (32, 64), 64),
Inception(256, (160, 320), (32, 128), 128),
nn.MaxPool2D(pool_size=3, strides=2, padding=1))
.. raw:: html
.. raw:: html
.. code:: python
b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64),
Inception(512, 160, (112, 224), (24, 64), 64),
Inception(512, 128, (128, 256), (24, 64), 64),
Inception(512, 112, (144, 288), (32, 64), 64),
Inception(528, 256, (160, 320), (32, 128), 128),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
.. raw:: html
.. raw:: html
.. code:: python
def b4():
return tf.keras.Sequential([
Inception(192, (96, 208), (16, 48), 64),
Inception(160, (112, 224), (24, 64), 64),
Inception(128, (128, 256), (24, 64), 64),
Inception(112, (144, 288), (32, 64), 64),
Inception(256, (160, 320), (32, 128), 128),
tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')])
.. raw:: html
.. raw:: html
mô-đun thứ năm có hai khối Inception với các kênh đầu ra
:math:`256+320+128+128=832` và :math:`384+384+128+128=1024`. Số lượng
kênh được gán cho mỗi đường dẫn giống như trong các mô-đun thứ ba và thứ
tư, nhưng khác nhau về các giá trị cụ thể. Cần lưu ý rằng khối thứ năm
được theo sau bởi lớp đầu ra. Khối này sử dụng lớp tổng hợp trung bình
toàn cầu để thay đổi chiều cao và chiều rộng của mỗi kênh thành 1, giống
như trong Nin. Cuối cùng, chúng ta biến đầu ra thành một mảng hai chiều
theo sau là một lớp kết nối hoàn toàn có số lượng đầu ra là số lớp nhãn.
.. raw:: html
.. raw:: html
.. code:: python
b5 = nn.Sequential()
b5.add(Inception(256, (160, 320), (32, 128), 128),
Inception(384, (192, 384), (48, 128), 128),
nn.GlobalAvgPool2D())
net = nn.Sequential()
net.add(b1, b2, b3, b4, b5, nn.Dense(10))
.. raw:: html
.. raw:: html
.. code:: python
b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
Inception(832, 384, (192, 384), (48, 128), 128),
nn.AdaptiveAvgPool2d((1,1)),
nn.Flatten())
net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10))
.. raw:: html
.. raw:: html
.. code:: python
def b5():
return tf.keras.Sequential([
Inception(256, (160, 320), (32, 128), 128),
Inception(384, (192, 384), (48, 128), 128),
tf.keras.layers.GlobalAvgPool2D(),
tf.keras.layers.Flatten()
])
# 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
def net():
return tf.keras.Sequential([b1(), b2(), b3(), b4(), b5(),
tf.keras.layers.Dense(10)])
.. raw:: html
.. raw:: html
Mô hình GoogLeNet phức tạp về mặt tính toán, vì vậy không dễ dàng để sửa
đổi số lượng kênh như trong VGG. Để có thời gian đào tạo hợp lý về
Fashion-MNIST, chúng tôi giảm chiều cao và chiều rộng đầu vào từ 224
xuống 96. Điều này đơn giản hóa việc tính toán. Những thay đổi về hình
dạng của đầu ra giữa các mô-đun khác nhau được thể hiện dưới đây.
.. raw:: html
.. raw:: html
.. code:: python
X = np.random.uniform(size=(1, 1, 96, 96))
net.initialize()
for layer in net:
X = layer(X)
print(layer.name, 'output shape:\t', X.shape)
.. parsed-literal::
:class: output
sequential0 output shape: (1, 64, 24, 24)
sequential1 output shape: (1, 192, 12, 12)
sequential2 output shape: (1, 480, 6, 6)
sequential3 output shape: (1, 832, 3, 3)
sequential4 output shape: (1, 1024, 1, 1)
dense0 output shape: (1, 10)
.. raw:: html
.. raw:: html
.. code:: python
X = torch.rand(size=(1, 1, 96, 96))
for layer in net:
X = layer(X)
print(layer.__class__.__name__,'output shape:\t', X.shape)
.. parsed-literal::
:class: output
Sequential output shape: torch.Size([1, 64, 24, 24])
Sequential output shape: torch.Size([1, 192, 12, 12])
Sequential output shape: torch.Size([1, 480, 6, 6])
Sequential output shape: torch.Size([1, 832, 3, 3])
Sequential output shape: torch.Size([1, 1024])
Linear output shape: torch.Size([1, 10])
.. raw:: html
.. raw:: html
.. code:: python
X = tf.random.uniform(shape=(1, 96, 96, 1))
for layer in net().layers:
X = layer(X)
print(layer.__class__.__name__, 'output shape:\t', X.shape)
.. parsed-literal::
:class: output
Sequential output shape: (1, 24, 24, 64)
Sequential output shape: (1, 12, 12, 192)
Sequential output shape: (1, 6, 6, 480)
Sequential output shape: (1, 3, 3, 832)
Sequential output shape: (1, 1024)
Dense output shape: (1, 10)
.. raw:: html
.. raw:: html
Đào tạo
-------
Như trước đây, chúng tôi đào tạo mô hình của mình bằng cách sử dụng bộ
dữ liệu Fashion-MNIST. Chúng tôi chuyển đổi nó thành độ phân giải
:math:`96 \times 96` pixel trước khi gọi quy trình đào tạo.
.. raw:: html
.. raw:: html
.. code:: python
lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
.. parsed-literal::
:class: output
loss 0.242, train acc 0.908, test acc 0.900
2251.6 examples/sec on gpu(0)
.. figure:: output_googlenet_83a8b4_87_1.svg
.. raw:: html
.. raw:: html
.. code:: python
lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
.. parsed-literal::
:class: output
loss 0.260, train acc 0.900, test acc 0.891
3459.1 examples/sec on cuda:0
.. figure:: output_googlenet_83a8b4_90_1.svg
.. raw:: html
.. raw:: html
.. code:: python
lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
.. parsed-literal::
:class: output
loss 0.234, train acc 0.912, test acc 0.900
3525.3 examples/sec on /GPU:0
.. parsed-literal::
:class: output
.. figure:: output_googlenet_83a8b4_93_2.svg
.. raw:: html
.. raw:: html
Tóm tắt
-------
- Khối Inception tương đương với một mạng con với bốn đường dẫn. Nó
trích xuất thông tin song song thông qua các lớp phức tạp của các
hình dạng cửa sổ khác nhau và các lớp tổng hợp tối đa.
:math:`1 \times 1` sự covolutions làm giảm kích thước kênh ở mức mỗi
pixel. Tổng hợp tối đa làm giảm độ phân giải.
- GoogLeNet kết nối nhiều khối Inception được thiết kế tốt với các lớp
khác trong loạt. Tỷ lệ số kênh được gán trong khối Inception thu được
thông qua một số lượng lớn các thí nghiệm trên tập dữ liệu ImageNet.
- GoogLeNet, cũng như các phiên bản thành công của nó, là một trong
những mô hình hiệu quả nhất trên ImageNet, cung cấp độ chính xác thử
nghiệm tương tự với độ phức tạp tính toán thấp hơn.
Bài tập
-------
1. Có một số lần lặp lại của GoogLeNet. Cố gắng thực hiện và chạy chúng.
Một số trong số họ bao gồm những điều sau đây:
- Thêm một lớp chuẩn hóa hàng loạt :cite:`Ioffe.Szegedy.2015`, như
được mô tả sau trong :numref:`sec_batch_norm`.
- Thực hiện các điều chỉnh đối với khối Inception
:cite:`Szegedy.Vanhoucke.Ioffe.ea.2016`.
- Sử dụng làm mịn nhãn cho mô hình regarization
:cite:`Szegedy.Vanhoucke.Ioffe.ea.2016`.
- Bao gồm nó trong kết nối còn lại
:cite:`Szegedy.Ioffe.Vanhoucke.ea.2017`, như được mô tả sau
trong :numref:`sec_resnet`.
2. Kích thước hình ảnh tối thiểu để GoogLeNet hoạt động là bao nhiêu?
3. So sánh kích thước tham số mô hình của AlexNet, VGG và Nin với
GoogLeNet. Làm thế nào để hai kiến trúc mạng sau làm giảm đáng kể
kích thước tham số mô hình?
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html