Inception

Inception 是谷歌于 2014 年提出的一个网络结构,并为谷歌团队夺取了 ILSVRC 2014 图像分类项目的冠军。 Inception 单个模型可以在测试集上达到 10.07% 的 top-5 错误率。通过模型集成 1008 个模型,整体的 top 5 错误率可以进一步降低为 6.67%,比 VGGNet 取得的 7.3% 还低了一点。

参加比赛所用的网络是 Inception 的一个具体形式,称为 GoogLeNet。由于 Inception 后续又有所升级,因而 GoogLeNet 也称为 Inveption v1。

原始论文:Going Deeper with Convolutions

Inception 模块

Inception 最大的创新在于所提出的 Inception 模块。 我们可以看到,从 LeNet 到 VGGNet,卷积网络的整体架构并没有本质的变化,都是一系列卷积层、非线性层、池化层的交替叠加,并在最后连接几层全连接层用于分类。 它们之间的不同仅在于深度、卷积核的大小等方面。

从 AlexNet 到 VGGNet,一系列实验都已经证明,提升卷积网络的深度有助于提高图像分类的准确率。 但模型深度的增加也是有代价的,即计算量的增加,还有参数的增加以及随之而来的过拟合问题。

Inception 的工作尝试使用一种“稀疏”的网络,同时降低计算量与参数个数。 整体而言,Inception 用一种相对复杂的模块替代了原有的卷积层, 使得在输入输出分辨率和通道数不变的情况下,在参数空间和特征空间同时产生一定的稀疏性。

Inception 模块的具体结构如下图 b 所示。

利用特征的稀疏性降低模型参数和计算量

为了更好理解 Inception 的动机和原理,让我们先考虑一个卷积层中的参数和计算量。

假设这个卷积层有 CoutC_\text{out} 个大小为 $K\times K$ 的卷积核,卷积层的输入为 CinC_\text{in} 通道,空间分辨率为 $H\text{in} \times W\text{in}$ 。

那么这个卷积层包含参数的个数为:

Nparams=CinCoutK2N_{\text{params}} = C_\text{in} C_\text{out} K^2

完成这个卷积层的计算所需要的乘加次数为:

Nmadd=CoutHoutWout×K2CinHinWinCin CoutK2S2HinWinS2Nparams\begin{align} N_\text{madd} &= C_\text{out} H_\text{out} W_\text{out} \times K^2 C_\text{in} \\ & \approx H_\text{in} W_\text{in} C_\text{in}\ C_\text{out} \frac{K^2 }{S^2} \\ & \approx \frac{H_\text{in} W_\text{in}}{S^2} N_\text{params} \end{align}

其中 SS 为卷积的步长。

从上面两个等式我们可以看出,参数个数和计算量都正比于输入输出的通道数,且正比于卷积核边长的平方。

从这个两个指标来看,使用大的卷积核显然是不经济的。

VGGNet 采用叠加多个 3x3 的卷积层,在控制感受野不变的情况下,降低卷积核的参数总量。

Inception 的作者从一篇理论工作和一个神经生物学原理受到启发,他们认为,仅仅使用 1x1 的卷积核对前一层产生的特征进行组合,即对同一位置不同通道的神经元的输出进行线性组合,就可以有效地产生大部分高质量的高层次特征。 换句话说,即便使用 3x3 或 5x5 的卷积核,当网络被训练到较好的性能时,一些卷积核中边缘位置的参数也应该接近于 0。

在这种启发下,我们就可以直接将 CoutC_\text{out} 个卷积核中的一部分卷积核人为设置为 1x1 的大小。

将这些卷积核作用在输入特征图上,将可以产生一系列通道的输出特征图,再与其余 K×KK\times K 卷积核的输出按通道堆叠,产生完整的输出,如下图所示。

这样,在输入输出分辨率不变、感受野不变的情况下,可学习的参数总数和计算量都大大减少。这相当于借助先验信息对网络进行了一定的正则化处理。

Inception 模块正是基于这种考量而设计的,每个 Inception 模块使用 4 种方式根据输入特征计算输出特征,分别是 1x1、3x3、5x5 的卷积和一个 3x3 的 maxpooling,四种操作的步长都为 1。 最终的输出是四种特征在通道上的叠加。

利用通道融合降低模型参数和计算量

Inception 借助稀疏性降低了,但 3x3 和 5x5 的卷积计算仍然会占据大量的参数和计算量。 由于使用 1x1 的卷积核已经大大降低了卷积核中的参数冗余,要进一步降低参数和计算量,只能从通道数入手。

从这个角度出发,Inception 模块在每个 3x3 和 5x5 的子卷积层前增加了一个 1x1 的卷积层。 这个 1x1 的卷积层并不改变 3x3 或 5x5 的卷积层的感受野,但可以降低参数和计算量。

如果 1x1 卷积层的通道数为 Cmid<CinC_\text{mid} \lt C_{in},那么两层所包含的参数量为

Nparams=Cmid(Cin+K2Cout)K2CoutCinN'_\text{params} = C_\text{mid}(C_\text{in} + K^2C_\text{out}) \lessapprox K^2C_\text{out}C_\text{in}

在 Inception 模块中, 3x3 卷积核的通道数为输入通道数的 1/4 左右,而 5x5 和 pooling 的通道则仅有输入通道的 1/8 左右,大幅抵消了卷积核变大导致的参数和计算量增多。同时增加 1x1 的卷积层还引入了额外的一层非线性映射。

使用这两种方法,Inception 模块的计算速度大约是普通卷积层的 3 到 10 倍。

GoogLeNet 的整体结构

GoogLeNet 堆叠了 9 层 Inception 模块,每个 Inception 模块包含 2 层(虽然卷积核形状不统一),加上输入的 3 个卷积层和输出的 1 个全连接层总共有 22 层网络,如下图所示:

作为比赛模型,GoogLeNet 还是用了许多辅助性的训练和测试方法来进一步提高图像分类的精度。例如在中间层分出额外的监督分支,并使用数据增强以及集成学习方法。

Inception 的后续改进

(待完善)

《Rethinking the Inception Architecture for Computer Vision》提出了 Inception v2 和 v3 结构,但似乎在命名上有些混乱。

  • Inception v2: 加了 BN,在网络的开始部分用三个 3x3 替换 7x7,中间部分的 Inception 里面用两个 3x3 替换 5x5,高层的 Inception 里面进一步用 1x7 和 7x1 的叠加进一步替换 3x3 ,总共 42 层。

  • Inception v3:在 v2 的基础上,给辅助分类器加入 BN,再配合 label smoothing、RMSProp 等训练技巧。

《Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning》给出了 v4 模型。

Pytorch 实现

torchvision.model 模块提供了 GoogLeNet(Inception v1) 及后续各个版本模型的实现。 源码

其中,GoogLeNet 的主体结构是若干 Inception 模块的堆叠,并不难理解。我们重点来看一下 Inception 模块的实现。

Inception 模块将 4 中不同的卷积实现在 4 个branch模块中。在前传时,这些模块独立应用自己的卷积配置计算出对应的特征图,再通过torch.cat(*, dim=1)的方式按通道拼接在一。torch 遵循 NCHW 的维度顺序,因此 dim=1 即对应通道维度。

class Inception(nn.Module):

    def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj,
                 conv_block=None):
        super(Inception, self).__init__()
        if conv_block is None:
            conv_block = BasicConv2d
        self.branch1 = conv_block(in_channels, ch1x1, kernel_size=1)

        self.branch2 = nn.Sequential(
            conv_block(in_channels, ch3x3red, kernel_size=1),
            conv_block(ch3x3red, ch3x3, kernel_size=3, padding=1)
        )

        self.branch3 = nn.Sequential(
            conv_block(in_channels, ch5x5red, kernel_size=1),
            # Here, kernel_size=3 instead of kernel_size=5 is a known bug.
            # Please see https://github.com/pytorch/vision/issues/906 for details.
            conv_block(ch5x5red, ch5x5, kernel_size=3, padding=1)
        )

        self.branch4 = nn.Sequential(
            nn.MaxPool2d(kernel_size=3, stride=1, padding=1, ceil_mode=True),
            conv_block(in_channels, pool_proj, kernel_size=1)
        )

    def _forward(self, x):
        branch1 = self.branch1(x)
        branch2 = self.branch2(x)
        branch3 = self.branch3(x)
        branch4 = self.branch4(x)

        outputs = [branch1, branch2, branch3, branch4]
        return outputs

    def forward(self, x):
        outputs = self._forward(x)
        return torch.cat(outputs, 1)

杂项

不难看出 GoogLeNet 是 Google 和 LeNet 这两个名称的组合。 而 Inception 这个名字则取自电影《盗梦空间》(英文为 inception),因为当时流行的网络迷因 "We Need To Go Deeper" 似乎与计算机视觉学界所流行的“更深的神经网络”不约而合[1][出处]。

参考文献

Last updated