社区计算机视觉课程文档

GoogLeNet

Hugging Face's logo
加入 Hugging Face 社区

并获得增强型文档体验

开始

GoogLeNet

本章将介绍一种名为 GoogLeNet 的卷积架构。

概述

Inception 架构是一种专为计算机视觉任务(如分类和检测)而设计的卷积神经网络 (CNN),以其效率著称。它包含不到 700 万个参数,比其前身更加紧凑,比 AlexNet 小 9 倍,比 VGG16 小 22 倍。这种架构在 ImageNet 2014 挑战赛中获得了认可,Google 对其的改编版本被称为 GoogLeNet(向 LeNet 致敬),在性能方面树立了新的基准,同时与以前领先的方法相比,使用的参数更少。

架构创新

在 Inception 架构出现之前,像 AlexNet 和 VGG 这样的模型已经证明了更深层网络结构的优势。然而,更深层的网络通常需要更多的计算步骤,并会导致过度拟合和梯度消失问题。Inception 架构提供了解决方案,它能够以减少的浮点参数数量来训练复杂的 CNN。

Inception “网络中的网络” 模块

在之前的网络(如 AlexNet 或 VGG)中,基本模块是卷积层本身。然而,Lin 等人于 2013 年提出了“网络中的网络”的概念,他们认为单个卷积不一定是正确的基本构建块。它应该更复杂。因此,受到这一概念的启发,Inception 模型的作者决定采用一个更复杂的构建块,称为 Inception 模块,以著名的电影“盗梦空间”(梦境中的梦境)命名。

Inception 模块坚持在多个尺度上应用不同内核大小的卷积滤波器来提取特征。对于任何输入特征图,它都会并行地应用1×11 \times 1卷积、3x3 卷积和 5x5 卷积。除了卷积之外,还应用了最大池化操作。所有四种操作都以保持相同空间维度的方式进行填充和步长。这些特征被连接起来,形成下一阶段的输入。参见图 1。

inception_naive

图 1:原始 Inception 模块

我们可以看到,在多个尺度上应用多个卷积(使用更大的内核尺寸,如 5x5)会极大地增加参数数量。随着输入特征大小(通道大小)的增加,这个问题会更加突出。因此,当我们深化网络,堆叠这些“Inception 模块”时,计算量会急剧增加。简单的解决方案是在计算需求似乎增加的地方减少特征数量。高计算量的主要痛点是卷积层。在 3x3 和 5x5 卷积之前,通过一个计算量不大的1×11 \times 1卷积来减少特征维度。让我们用一个例子来看一下。

我们要将S×S×128 S \times S \times 128 的特征图转换为S×S×256 S \times S \times 256 通过 5x5 卷积。参数数量(不包括偏差)为 5*5*128*256 = 819,200。但是,如果我们先通过1×11 \times 1卷积将特征维度减少到 64,那么参数数量(不包括偏差)为 1×1×128×64+5×5×64×256=8,192+409,600=417,792 1\times 1\times 128\times 64 + 5\times 5\times 64\times 256 = 8,192 + 409,600 = 417,792 . 这意味着参数数量减少了近一半!

我们还想在与输出特征图连接之前减少最大池化的输出特征。 所以,我们再加一个1×1 1\times 1 卷积层在最大池化层之后。 我们还在每个卷积层之后添加一个 ReLU 激活函数,以提高模块的非线性度和复杂度。 参见图 2。1×1 1\times 1 图 2:Inception 模块

inception_reduced

此外,由于在多个尺度上并行地进行卷积运算,我们可以在不深入网络的情况下确保更多操作,从而有效地缓解梯度消失问题。

平均池化

在之前的网络(如 AlexNet 或 VGG)中,最后一层是几个全连接层。 这些全连接层由于其大量的单元数量,贡献了网络中大部分参数。 例如,VGG16 的参数中有 89% 位于最后三个全连接层中。 AlexNet 的参数中有 95% 位于最后三个全连接层中。 这种需求可归因于这样一个前提,即卷积层不一定足够复杂。

然而,有了 Inception 块,我们不需要全连接层,只需沿着空间维度进行简单的平均池化就足够了。 这种方法也源于 Network in Network 论文。 然而,GoogLeNet 包含一个全连接层。 他们报告说 top-1 精度提高了 0.6%。

GoogLeNet 中全连接层仅占参数的 15%。

辅助分类器

随着计算节省型

1×1 1 \times 1 卷积层的引入和用平均池化层替换多个全连接层,该网络的参数显著减少,这意味着我们可以添加更多层并深入网络。 然而,堆叠层会导致梯度消失问题,即梯度在反向传播到网络的初始层时会越来越小,并接近于零。该论文引入了辅助分类器,从中间层的输出分支出几个小型分类器,并将这些分类器的损失添加到总损失中(权重较低)。 这确保了靠近输入的层也能接收足够大的梯度。

辅助分类器包含以下内容:

一个平均池化层,其

  • 过滤器大小为5×5 5 \times 5 ,步长为 3。
  • 一个卷积层的引入和用平均池化层替换多个全连接层,该网络的参数显著减少,这意味着我们可以添加更多层并深入网络。 然而,堆叠层会导致梯度消失问题,即梯度在反向传播到网络的初始层时会越来越小,并接近于零。具有 128 个滤波器的卷积层,用于降维和修正线性激活。
  • 一个具有 1024 个单元的全连接层,以及修正线性激活函数。
  • 一个 dropout 层,其丢弃输出的比例为 70%。
  • 一个线性层,使用 softmax 损失作为分类器。

在推理时,这些辅助分类器会被移除。 然而,使用辅助分类器带来的收益很小(0.5%)。

googlenet_aux_clf

图 3:辅助分类器

架构 - GoogLeNet

GoogLeNet 的完整架构如下图所示。 所有卷积层,包括 Inception 块内部的卷积层,都使用 ReLU 激活函数。 它从两个卷积层和最大池化块开始。 之后是一个包含两个 Inception 模块(3a 和 3b)和一个最大池化层的块。 然后是一个包含 5 个 Inception 块(4a、4b、4c、4d、4e)和一个最大池化层的块。 从 4a 和 4d 的输出中提取辅助分类器。 接着是两个 Inception 块(5a 和 5b)。 之后,使用一个平均池化层和一个包含 128 个单元的全连接层。

googlenet_arch 图 4:完整的 GoogLeNet 架构

代码

import torch
import torch.nn as nn


class BaseConv2d(nn.Module):
    def __init__(self, in_channels, out_channels, **kwargs):
        super(BaseConv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, **kwargs)
        self.relu = nn.ReLU()

    def forward(self, x):
        x = self.conv(x)
        x = self.relu(x)
        return x


class InceptionModule(nn.Module):
    def __init__(self, in_channels, n1x1, n3x3red, n3x3, n5x5red, n5x5, pool_proj):
        super(InceptionModule, self).__init__()

        self.b1 = BaseConv2d(in_channels, n1x1, kernel_size=1)

        self.b2 = nn.Sequential(
            BaseConv2d(in_channels, n3x3red, kernel_size=1),
            BaseConv2d(n3x3red, n3x3, kernel_size=3, padding=1),
        )

        self.b3 = nn.Sequential(
            BaseConv2d(in_channels, n5x5red, kernel_size=1),
            BaseConv2d(n5x5red, n5x5, kernel_size=5, padding=2),
        )

        self.b4 = nn.Sequential(
            nn.MaxPool2d(3, stride=1, padding=1),
            BaseConv2d(in_channels, pool_proj, kernel_size=1),
        )

    def forward(self, x):
        y1 = self.b1(x)
        y2 = self.b2(x)
        y3 = self.b3(x)
        y4 = self.b4(x)
        return torch.cat([y1, y2, y3, y4], 1)


class AuxiliaryClassifier(nn.Module):
    def __init__(self, in_channels, num_classes, dropout=0.7):
        super(AuxiliaryClassifier, self).__init__()
        self.pool = nn.AvgPool2d(5, stride=3)
        self.conv = BaseConv2d(in_channels, 128, kernel_size=1)
        self.relu = nn.ReLU(True)
        self.flatten = nn.Flatten()
        self.fc1 = nn.Linear(2048, 1024)
        self.dropout = nn.Dropout(dropout)
        self.fc2 = nn.Linear(1024, num_classes)

    def forward(self, x):
        x = self.pool(x)
        x = self.conv(x)
        x = self.flatten(x)
        x = self.fc1(x)
        x = self.relu(x)
        x = self.dropout(x)
        x = self.fc2(x)
        return x


class GoogLeNet(nn.Module):
    def __init__(self, use_aux=True):
        super(GoogLeNet, self).__init__()

        self.use_aux = use_aux
        ## block 1
        self.conv1 = BaseConv2d(3, 64, kernel_size=7, stride=2, padding=3)
        self.lrn1 = nn.LocalResponseNorm(5, alpha=0.0001, beta=0.75)
        self.maxpool1 = nn.MaxPool2d(3, stride=2, padding=1)

        ## block 2
        self.conv2 = BaseConv2d(64, 64, kernel_size=1)
        self.conv3 = BaseConv2d(64, 192, kernel_size=3, padding=1)
        self.lrn2 = nn.LocalResponseNorm(5, alpha=0.0001, beta=0.75)
        self.maxpool2 = nn.MaxPool2d(3, stride=2, padding=1)

        ## block 3
        self.inception3a = InceptionModule(192, 64, 96, 128, 16, 32, 32)
        self.inception3b = InceptionModule(256, 128, 128, 192, 32, 96, 64)
        self.maxpool3 = nn.MaxPool2d(3, stride=2, padding=1)

        ## block 4
        self.inception4a = InceptionModule(480, 192, 96, 208, 16, 48, 64)
        self.inception4b = InceptionModule(512, 160, 112, 224, 24, 64, 64)
        self.inception4c = InceptionModule(512, 128, 128, 256, 24, 64, 64)
        self.inception4d = InceptionModule(512, 112, 144, 288, 32, 64, 64)
        self.inception4e = InceptionModule(528, 256, 160, 320, 32, 128, 128)
        self.maxpool4 = nn.MaxPool2d(3, stride=2, padding=1)

        ## block 5
        self.inception5a = InceptionModule(832, 256, 160, 320, 32, 128, 128)
        self.inception5b = InceptionModule(832, 384, 192, 384, 48, 128, 128)

        ## auxiliary classifier
        if self.use_aux:
            self.aux1 = AuxiliaryClassifier(512, 1000)
            self.aux2 = AuxiliaryClassifier(528, 1000)

        ## block 6
        self.avgpool = nn.AvgPool2d(7, stride=1)
        self.dropout = nn.Dropout(0.4)
        self.fc = nn.Linear(1024, 1000)

    def forward(self, x):
        ## block 1
        x = self.conv1(x)
        x = self.maxpool1(x)
        x = self.lrn1(x)

        ## block 2
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.lrn2(x)
        x = self.maxpool2(x)

        ## block 3
        x = self.inception3a(x)
        x = self.inception3b(x)
        x = self.maxpool3(x)

        ## block 4
        x = self.inception4a(x)
        if self.use_aux:
            aux1 = self.aux1(x)
        x = self.inception4b(x)
        x = self.inception4c(x)
        x = self.inception4d(x)
        if self.use_aux:
            aux2 = self.aux2(x)
        x = self.inception4e(x)
        x = self.maxpool4(x)

        ## block 5
        x = self.inception5a(x)
        x = self.inception5b(x)

        ## block 6
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.dropout(x)
        x = self.fc(x)

        if self.use_aux:
            return x, aux1, aux2
        else:
            return x
< > 在 GitHub 上更新