网络中的网络(NiN)

前几节介绍的 LeNet、AlexNet 和 VGG 在设计上的共同之处是:先以由卷积层构成的模块充分抽取空间特征,再以由全连接层构成的模块来输出分类结果。其中,AlexNet 和 VGG 对 LeNet 的改进主要在于如何对这两个模块加宽和加深。这一节我们介绍网络中的网络(NiN)[1]。它提出了另外一个思路,即串联多个由卷积层和“全连接”层构成的小网络来构建一个深层网络。

NiN 块

我们知道,卷积层的输入和输出通常是四维数组(样本,通道,高,宽),而全连接层的输入和输出则通常是二维数组(样本,特征)。如果想在全连接层后再接上卷积层,则需要将全连接层的输出变换为四维。回忆在“多输入通道和多输出通道”一节里介绍的 \(1\times 1\) 卷积层。它可以看成全连接层:其中空间维度(高和宽)上的每个元素相当于样本,通道相当于特征。因此,NiN 使用 \(1\times 1\) 卷积层来替代全连接层,从而使空间信息能够自然传递到后面的层中去。图 5.7 对比了 NiN 同 AlexNet 和 VGG 等网络在结构上的主要区别。

左图是AlexNet和VGG的网络结构,右图是NiN的网络结构。

左图是AlexNet和VGG的网络结构,右图是NiN的网络结构。

NiN 块是 NiN 中的基础块。它由一个卷积层加两个充当全连接层的 \(1\times 1\) 卷积层串联而成。其中第一个卷积层的超参数可以自行设置,而第二和第三个卷积层的超参数一般是固定的。

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

def nin_block(num_channels, kernel_size, strides, padding):
    blk = nn.Sequential()
    blk.add(nn.Conv2D(num_channels, kernel_size,
                      strides, padding, activation='relu'),
            nn.Conv2D(num_channels, kernel_size=1, activation='relu'),
            nn.Conv2D(num_channels, kernel_size=1, activation='relu'))
    return blk

NiN 模型

NiN 是在 AlexNet 问世不久后提出的。它们的卷积层设定有类似之处。NiN 使用卷积窗口形状分别为 \(11\times 11\)\(5\times 5\)\(3\times 3\) 的卷积层,相应的输出通道数也与 AlexNet 中的一致。每个 NiN 块后接一个步幅为 2、窗口形状为 \(3\times 3\) 的最大池化层。

除了使用 NiN 块以外,NiN 还有一个设计与 AlexNet 有显著不同:NiN 去掉了 AlexNet 最后的三个全连接层。取而代之的,NiN 使用了输出通道数等于标签类别数的 NiN 块,然后使用全局平均池化层对每个通道中所有元素求平均并直接用于分类。这里的全局平均池化层即窗口形状等于输入空间维形状的平均池化层。NiN 的这个设计的好处是可以显著减小模型参数尺寸,从而很好地缓解过拟合。然而,该设计有时会造成模型训练时间的增加。

In [2]:
net = nn.Sequential()
net.add(nin_block(96, kernel_size=11, strides=4, padding=0),
        nn.MaxPool2D(pool_size=3, strides=2),
        nin_block(256, kernel_size=5, strides=1, padding=2),
        nn.MaxPool2D(pool_size=3, strides=2),
        nin_block(384, kernel_size=3, strides=1, padding=1),
        nn.MaxPool2D(pool_size=3, strides=2), nn.Dropout(0.5),
        # 标签类别数是 10。
        nin_block(10, kernel_size=3, strides=1, padding=1),
        # 全局平均池化层将窗口形状自动设置成输入的高和宽。
        nn.GlobalAvgPool2D(),
        # 将四维的输出转成二维的输出,其形状为(批量大小,10)。
        nn.Flatten())

我们构建一个数据样本来查看每一层的输出形状。

In [3]:
X = nd.random.uniform(shape=(1, 1, 224, 224))
net.initialize()
for layer in net:
    X = layer(X)
    print(layer.name, 'output shape:\t', X.shape)
sequential1 output shape:        (1, 96, 54, 54)
pool0 output shape:      (1, 96, 26, 26)
sequential2 output shape:        (1, 256, 26, 26)
pool1 output shape:      (1, 256, 12, 12)
sequential3 output shape:        (1, 384, 12, 12)
pool2 output shape:      (1, 384, 5, 5)
dropout0 output shape:   (1, 384, 5, 5)
sequential4 output shape:        (1, 10, 5, 5)
pool3 output shape:      (1, 10, 1, 1)
flatten0 output shape:   (1, 10)

获取数据并训练

我们依然使用 Fashion-MNIST 数据集训练模型。NiN 的训练与 AlexNet 和 VGG 的类似,但一般使用更大的学习率。

In [4]:
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=224)
gb.train_ch5(net, train_iter, test_iter, batch_size, trainer, ctx, num_epochs)
training on gpu(0)
epoch 1, loss 2.2422, train acc 0.194, test acc 0.313, time 26.2 sec
epoch 2, loss 1.4701, train acc 0.475, test acc 0.705, time 24.4 sec
epoch 3, loss 0.7122, train acc 0.743, test acc 0.784, time 24.8 sec
epoch 4, loss 0.5493, train acc 0.801, test acc 0.833, time 25.4 sec
epoch 5, loss 0.4818, train acc 0.823, test acc 0.842, time 24.9 sec

小结

  • NiN 重复使用由卷积层和代替全连接层的 \(1\times 1\) 卷积层构成的 NiN 块来构建深层网络。
  • NiN 去除了容易造成过拟合的全连接输出层,而是将其替换成输出通道数等于标签类别数的 NiN 块和全局平均池化层。
  • NiN 以上的设计思想影响了后面一系列卷积神经网络的设计。

练习

  • 调节超参数,提高分类准确率。
  • 为什么 NiN 块里要有两个 \(1\times 1\) 卷积层?去除其中的一个,观察并分析实验现象。

扫码直达讨论区

参考文献

[1] Lin, M., Chen, Q., & Yan, S. (2013). Network in network. arXiv preprint arXiv:1312.4400.