4.7 NIN网络#

4.6节内容中,我们详细介绍了一种可复用的网络模型VGG,它通过一个个小的网络块来堆叠形成整个网络结构。在接下来的这节内容中,我们将向大家介绍另外一种也是基于这种“块”思想的网络模型——网络中的网络(Network in Network, NiN)[1]。

4.7.1 NIN动机#

网络中的网络是新加坡国立大学2014年于ICLR会议上所提出的一种模型。论文作者认为,传统的卷积神经网络都是通过卷积和池化操作来提取特征并且使用全连接层进行分类,一方面传统的卷积操作本质上也仅仅只是一种泛化的线性模型,它并不足以提取得到更加高级且抽象的特征,尤其是当下游任务中特征类别是非线性可分的情况;另一方面全连接层的参数量较大导致模型过于复杂,容易使得模型出现过拟合的现象。

因此,作者提出了另外一种微型网络块来代替卷积操作并以此来构建整个网络模型,同时也摒弃了全连接层直接通过全局平均池化来完成最后的维度转换过程,以此来提高模型的特征表达能力和泛化性能。

4.7.2 NIN结构#

在正式介绍整个NIN的网络结构前我们先弄清楚其中的两个核心部分:多层感知机卷积(Multilayer Perceptron Convolution)和全局平均池化(Global Average Pooling)。在理解了这两部分内容之后,整个网络结构就非常容易理解了。

1. 多层感知机卷积

如图4-39所示,左侧为原始的卷积操作,右侧为论文中所提出的多层感知机卷积操作。相较于普通的卷积结构,多层感知机卷积结构是在原有的卷积基础上又增加了一个多层感知机来进一步提高模型的特征表达能力。

图 4-39. 线性卷积与多层感知机卷积对比结构图

在多层感知机卷积结构中,卷积操作后的两个全连接层并不是传统意义上的全连接层,而是采用了卷积核大小为$1\times1$的卷积操作进行代替。因为$1\times1$的卷积操作相当于卷积核在执行卷积的过程中对不同通道上同一位置处的特征值进行了一次线性组合,这类似于传统全连接层的计算方式。同时,使用$1\times1$的卷积操作一方面能够根据训练得到的权重参数来确定每个特征通道的重要性占比(这也类似于注意力机制的思想)并融合形成一个通道,使得模型具有跨特征图交互的能力;另一方面也能够很方便地与前后层的卷积操作进行转换。最后,在多层感知机卷积结构中,每个卷积层之后都通过ReLU激活函数进行了一次非线性变换。

2. 全局平均池化操作

在传统的卷积神经网络中,卷积结构均被视为一个特征提取器,在对原始输入进行深度特征提取后通常会通过多个全连接层来完成最后的分类任务,而这种做法的弊端便是引入了大量的权重参数并且模型容易出现过拟合现象。在论文中,作者提出通过平均池化操作来解决这一个问题。具体地,在最后一个卷积层操作中为每个分类类别均生成一个特征图,然后取整个特征图的平均值来作为对应类别的置信度预测输出,这样既降低了模型的参数量同时也充分利用了特征图空间信息。因此,这也就意味着如果下游任务具有$K$个分类类别,那么模型最后的卷积输出一定是有$K$个通道数。

3. 整体网络结构

在介绍完多层感知机卷积块和全局平均池化这两项技术后再来看整个NIN的网络结构就比较清晰了。

图 4-40. NIN网络结构图

如图4-40所示便是NINI网络模型的结构图,可以明显地发现整个网络是由多个多层感知机卷积块所构成,而最右侧则是进行全局平均池化并通过Softmax层完成分类。

4.7.3 NIN实现#

在介绍完整个网络结构后我们再来看如何通过PyTorch进行实现。根据4.7.2节内容的介绍,我们需要先实现最基础的多层感知机卷积块,然后再以此为基础来构建整个网络模型。以下完整示例代码可以参见Code/Chapter04/C06_NIN/文件。

1. 辅助模块

首先需要实现一个辅助函数来完成多层感知机卷积块的计算过程,实现代码如下所示:

1 def nin_block(in_chs, out_chs=None, k_size=5, s=1, p=2):
2     nin_seq = nn.Sequential(
3         nn.Conv2d(in_chs, out_chs[0], k_size, s, p),
4         nn.ReLU(inplace=True),
5         nn.Conv2d(out_chs[0], out_chs[1], 1, 1),
6         nn.ReLU(inplace=True),
7         nn.Conv2d(out_chs[1], out_chs[2], 1, 1),
8         nn.ReLU(inplace=True))
9     return nin_seq

在上述代码中,第1行用于传入对应块的超参数信息,其中in_chs表示第1个卷积层的输入通道数,out_chs为一个列表表示3个卷积层各自的输出通道数,sp分别表示第1个卷积层的步长和填充数。第3~4行为多层感知机卷积块中的第1个正常卷积层。第5~8行则是对应的两个$1\times1$卷积操作。

2. 前向传播

基于上面实现的辅助模块,整个NIN网络模型的前向传播过程实现代码如下所示:

 1 class NIN(nn.Module):
 2     def __init__(self, init_weights=True):
 3         super(NIN, self).__init__()
 4         self.nin = nn.Sequential(
 5             nin_block(in_chs=3, out_chs=[192, 160, 96], k_size=5, s=1, p=2),
 6             nn.MaxPool2d(kernel_size=3, stride=2), nn.Dropout(0.5),
 7             nin_block(in_chs=96, out_chs=[192, 192, 192], k_size=5, s=1, p=2),
 8             nn.MaxPool2d(kernel_size=3, stride=2), nn.Dropout(0.5),
 9             nin_block(in_chs=192, out_chs=[192, 192, 10], k_size=3, s=1, p=1),
10             nn.AdaptiveAvgPool2d(output_size=1), nn.Flatten())
11 
12     def forward(self, x, labels=None):
13         logits = self.nin(x)
14         if labels is not None:
15             loss_fct = nn.CrossEntropyLoss(reduction='mean')
16             loss = loss_fct(logits, labels)
17             return loss, logits
18         else:
19             return logits

在上述代码中,第5、7行分别是前两个多层感知机卷积块,采用了$[5\times5]$的卷积核并通过填充保持了每次输出形状的大小不变。第9行则是最后一个多层感知机卷积块的输出,可以看出这里是按照一个10分类任务来进行的超参数设定。第10行是先进行非线性变换,然后再进行全局平均池化,最后拉伸成一个向量作为每个类别对应的置信度,其中nn.AdaptiveAvgPool2d是根据给定的输出形状来进行平均池化,而当output_size=1时则是对应全局平均池化。第12~19行便是根据相应的条件来返回预测置信度或样本损失值。

以输入形状为[1, 3, 224, 224]的图片为例,上述NIN模型第5~10行对应每一层的输出形状为:

1 网络层: Sequential, 输出形状: torch.Size([1, 96, 224, 224])
2 网络层: MaxPool2d, 输出形状: torch.Size([1, 96, 111, 111])
3 网络层: Dropout, 输出形状: torch.Size([1, 96, 111, 111])
4 网络层: Sequential, 输出形状: torch.Size([1, 192, 111, 111])
5 网络层: MaxPool2d, 输出形状: torch.Size([1, 192, 55, 55])
6 网络层: Dropout, 输出形状: torch.Size([1, 192, 55, 55])
7 网络层: Sequential, 输出形状: torch.Size([1, 10, 55, 55])
8 网络层: AdaptiveAvgPool2d, 输出形状: torch.Size([1, 10, 1, 1])
9 网络层: Flatten, 输出形状: torch.Size([1, 10])

3. 模型训练

在这里我们将继续使用4.6.3节中介绍到的CIFAR10数据集,所以不再对相关内容进行赘述。在前面各项工作都准备完毕之后便可以进一步实现模型的训练过程。不过由于这部分代码与4.6.3节中的训练代码基本上一样,仅仅只需要将网络模型实例化的语句改为model = NIN()即可,因此这里也不再赘述,各位读者直接参考源码即可。

最后,在对网络模型进行训练时将会得到类似如下的输出结果:

1 Epochs[1/60]--batch[0/391]--Acc: 0.0781--loss: 2.5644
2 Epochs[1/60]--batch[50/391]--Acc: 0.2109--loss: 2.1791
3 Epochs[1/60]--batch[100/391]--Acc: 0.3047--loss: 1.9268
4 ...
5 Epochs[1/60]--Acc on test 0.3912
6 Epochs[59/60]--Acc on test 0.8669
7 Epochs[60/60]--Acc on test 0.853

4.7.4 小结#

在本节内容中,我们首先介绍了NIN模型的提出动机;然后详细介绍了论文中所提出来的多层感知机卷积模块和全局平均池化的思想原理;最后我们还以CIFAR10数据集为例进行了实验。在下一节内容中,我们将开始介绍卷积网络中的第5个经典模型GoogLeNet。

引用#

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

[2]Network In Network architecture: The beginning of Inception http://teleported.in/posts/network-in-network/#mlpconv

[3]https://openreview.net/forum?id=ylE6yojDR5yqX

[4]https://zh.d2l.ai/

[5]https://arxiv.org/abs/1311.2901

[6]https://github.com/tflearn/tflearn/blob/master/examples/images/network_in_network.py

[7]https://github.com/mavenlin/cuda-convnet/blob/master/NIN/cifar-10_def