《动手学深度学习》
Table Of Contents
《动手学深度学习》
Table Of Contents

含并行连结的网络(GoogLeNet)

在 2014 年的 ImageNet 图像识别挑战赛中,一个名叫 GoogLeNet 的网络结构大放异彩 [1]。它虽然在名字上向 LeNet 致敬,但在网络结构上已经很难看到 LeNet 的影子。GoogLeNet 吸收了 NiN 中网络串联网络的思想,并在此基础上做了很大改进。在随后的几年里,研究人员对 GoogLeNet 进行了数次改进,本节将介绍这个模型系列的第一个版本。

Inception 块

GoogLeNet 中的基础卷积块叫做 Inception 块,得名于同名电影《盗梦空间》(Inception)。与上一节介绍的 NiN 块相比,这个基础块在结构上更加复杂。

Inception块的结构。

Inception块的结构。

由图 5.8 可以看出,Inception 块里有四条并行的线路。前三条线路使用窗口大小分别是 \(1\times 1\)\(3\times 3\)\(5\times 5\) 的卷积层来抽取不同空间尺寸下的信息。其中中间两个线路会对输入先做 \(1\times 1\) 卷积来减少输入通道数,以降低模型复杂度。第四条线路则使用 \(3\times 3\) 最大池化层,后接 \(1\times 1\) 卷积层来改变通道数。四条线路都使用了合适的填充来使得输入输出高和宽一致。最后我们将每条线路的输出在通道维上连结,并输入到接下来的层中去。

Inception 块中可以自定义的超参数是每个层的输出通道数,我们以此来控制模型复杂度。

In [1]:
import gluonbook as gb
from mxnet import gluon, init, nd
from mxnet.gluon import nn

class Inception(nn.Block):
    # c1 - c4 为每条线路里的层的输出通道数。
    def __init__(self, c1, c2, c3, c4, **kwargs):
        super(Inception, self).__init__(**kwargs)
        # 线路 1,单 1 x 1 卷积层。
        self.p1_1 = nn.Conv2D(c1, kernel_size=1, activation='relu')
        # 线路 2,1 x 1 卷积层后接 3 x 3 卷积层。
        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')
        # 线路 3,1 x 1 卷积层后接 5 x 5 卷积层。
        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')
        # 线路 4,3 x 3 最大池化层后接 1 x 1 卷积层。
        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))
        return nd.concat(p1, p2, p3, p4, dim=1)  # 在通道维上连结输出。

GoogLeNet 模型

GoogLeNet 跟 VGG 一样,在主体卷积部分中使用五个模块(block),每个模块之间使用步幅为 2 的 \(3\times 3\) 最大池化层来减小输出高宽。第一模块使用一个 64 通道的 \(7\times 7\) 卷积层。

In [2]:
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))

第二模块使用两个卷积层:首先是 64 通道的 \(1\times 1\) 卷积层,然后是将通道增大 3 倍的 \(3\times 3\) 卷积层。它对应 Inception 块中的第二条线路。

In [3]:
b2 = nn.Sequential()
b2.add(nn.Conv2D(64, kernel_size=1),
       nn.Conv2D(192, kernel_size=3, padding=1),
       nn.MaxPool2D(pool_size=3, strides=2, padding=1))

第三模块串联两个完整的 Inception 块。第一个 Inception 块的输出通道数为 \(64+128+32+32=256\),其中四条线路的输出通道数比例为 \(64:128:32:32=2:4:1:1\)。其中第二、第三条线路先分别将输入通道数减小至 \(96/192=1/2\)\(16/192=1/12\) 后,再接上第二层卷积层。第二个 Inception 块输出通道数增至 \(128+192+96+64=480\),每条线路的输出通道数之比为 \(128:192:96:64 = 4:6:3:2\)。其中第二、第三条线路先分别将输入通道数减小至 \(128/256=1/2\)\(32/256=1/8\)

In [4]:
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))

第四模块更加复杂。它串联了五个 Inception 块,其输出通道数分别是 \(192+208+48+64=512\)\(160+224+64+64=512\)\(128+256+64+64=512\)\(112+288+64+64=528\)\(256+320+128+128=832\)。这些线路的通道数分配和第三模块中的类似:含 \(3\times 3\) 卷积层的第二条线路输出最多通道,其次是仅含 \(1\times 1\) 卷积层的第一条线路,之后是含 \(5\times 5\) 卷积层的第三条线路和含 \(3\times 3\) 最大池化层的第四条线路。其中第二、第三条线路都会先按比例减小通道数。这些比例在各个 Inception 块中都略有不同。

In [5]:
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))

第五模块有输出通道数为 \(256+320+128+128=832\)\(384+384+128+128=1024\) 的两个 Inception 块。其中每条线路的通道数分配思路和第三、第四模块中的一致,只是在具体数值上有所不同。需要注意的是,第五模块的后面紧跟输出层,该模块同 NiN 一样使用全局平均池化层来将每个通道的高和宽变成 1。最后我们将输出变成二维数组后接上一个输出个数为标签类数的全连接层。

In [6]:
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))

GoogLeNet 模型的计算复杂,而且不如 VGG 那样便于修改通道数。本节里我们将输入的高和宽从 224 降到 96 来简化计算。下面演示各个模块之间的输出的形状变化。

In [7]:
X = nd.random.uniform(shape=(1, 1, 96, 96))
net.initialize()
for layer in net:
    X = layer(X)
    print(layer.name, 'output shape:\t', X.shape)
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)

获取数据并训练

我们使用高和宽均为 96 像素的图像来训练 GoogLeNet 模型。训练使用的图像依然来自 Fashion-MNIST 数据集。

In [8]:
lr, num_epochs, batch_size, ctx = 0.1, 5, 128, gb.try_gpu()
net.initialize(force_reinit=True, ctx=ctx, init=init.Xavier())
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': lr})
train_iter, test_iter = gb.load_data_fashion_mnist(batch_size, resize=96)
gb.train_ch5(net, train_iter, test_iter, batch_size, trainer, ctx, num_epochs)
training on gpu(0)
epoch 1, loss 1.6208, train acc 0.396, test acc 0.743, time 79.2 sec
epoch 2, loss 0.7383, train acc 0.728, test acc 0.826, time 70.7 sec
epoch 3, loss 0.4378, train acc 0.836, test acc 0.861, time 70.1 sec
epoch 4, loss 0.3786, train acc 0.857, test acc 0.863, time 70.1 sec
epoch 5, loss 0.3326, train acc 0.874, test acc 0.886, time 70.2 sec

小结

  • Inception 块相当于一个有四条线路的子网络。它通过不同窗口形状的卷积层和最大池化层来并行抽取信息,并使用 \(1\times 1\) 卷积层减少通道数从而减小模型复杂度。
  • GoogLeNet 将多个设计精细的 Inception 块和其他层串联起来。其中 Inception 块的通道数分配之比是在 ImageNet 数据集上通过大量的实验得来的。
  • GoogLeNet 和它的后继者们一度是 ImageNet 上最高效的模型之一:在类似的测试精度下,它们的计算复杂度往往更低。

练习

  • GoogLeNet 有数个后续版本。尝试实现并运行它们,观察实验结果。这些后续版本包括加入批量归一化层(后面章节将介绍)[2]、对 Inception 块做调整 [3] 和加入残差连接(后面章节将介绍)[4]。
  • 对比 AlexNet、VGG 和 NiN、GoogLeNet 的模型参数尺寸。为什么后两个网络可以显著减小模型参数尺寸?

扫码直达讨论区

image0

参考文献

[1] Szegedy, C., Liu, W., Jia, Y., Sermanet, P., Reed, S., & Anguelov, D. & Rabinovich, A.(2015). Going deeper with convolutions. In Proceedings of the IEEE conference on computer vision and pattern recognition (pp. 1-9).

[2] Ioffe, S., & Szegedy, C. (2015). Batch normalization: Accelerating deep network training by reducing internal covariate shift. arXiv preprint arXiv:1502.03167.

[3] Szegedy, C., Vanhoucke, V., Ioffe, S., Shlens, J., & Wojna, Z. (2016). Rethinking the inception architecture for computer vision. In Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (pp. 2818-2826).

[4] Szegedy, C., Ioffe, S., Vanhoucke, V., & Alemi, A. A. (2017, February). Inception-v4, inception-resnet and the impact of residual connections on learning. In Proceedings of the AAAI Conference on Artificial Intelligence (Vol. 4, p. 12).