社区计算机视觉课程文档
GoogLeNet
并获得增强的文档体验
开始使用
GoogLeNet
在本章中,我们将学习一种名为 GoogleNet 的卷积架构。
概述
Inception 架构是一种卷积神经网络 (CNN),专为计算机视觉任务(如分类和检测)而设计,因其高效性而脱颖而出。它包含不到 700 万个参数,并且比其前身显着更紧凑,比 AlexNet 小 9 倍,比 VGG16 小 22 倍。该架构在 ImageNet 2014 挑战赛中获得认可,谷歌的改编版 GoogLeNet(向 LeNet 致敬)树立了新的性能基准,同时与以前的领先方法相比,使用的参数更少。
架构创新
在 Inception 架构出现之前,AlexNet 和 VGG 等模型证明了更深层网络结构的优势。然而,更深的网络通常需要更多的计算步骤,并可能导致过拟合和梯度消失等问题。Inception 架构提供了一种解决方案,能够训练具有减少的浮点参数计数的复杂 CNN。
Inception “网络中的网络”模块
在 AlexNet 或 VGG 等之前的网络中,基本块是卷积层本身。然而,Lin 等人在 2013 年提出了网络中的网络的概念,认为单个卷积不一定是正确的基本构建块。它应该更复杂。因此,受此启发,Inception 模型作者决定采用更复杂的构建块,称为 Inception 模块,恰如其分地以著名电影“盗梦空间”(梦中梦)命名。
Inception 模块坚持应用不同内核大小的卷积滤波器,以便在多个尺度上进行特征提取。对于任何输入特征图,它应用一个卷积、一个 3x3 卷积和一个 5x5 卷积并行。除了卷积之外,还应用了最大池化操作。所有四个操作都具有填充和步幅,以便具有相同的空间维度。这些特征被连接起来,形成下一阶段的输入。参见图 1。
图 1:朴素 Inception 模块
正如我们所见,应用多个尺度和更大内核尺寸(如 5x5)的多个卷积会急剧增加参数数量。当输入特征尺寸(通道尺寸)增加时,这个问题会变得更加突出。因此,当我们深入网络堆叠这些“Inception 模块”时,计算量将急剧增加。简单的解决方案是在计算要求似乎增加的任何地方减少特征数量。高计算量的主要痛点是卷积层。特征维度通过计算成本较低的卷积在 3x3 和 5x5 卷积之前进行缩减。让我们通过一个例子来看看。
我们想要转换一个特征图,从到通过 5x5 卷积。参数数量(不包括偏差)为 5*5*128*256 = 819,200。但是,如果我们首先通过卷积将特征维度减少到 64,则参数数量(不包括偏差)为。这意味着参数数量减少了近一半!
我们还希望在与输出特征图连接之前减少最大池化的输出特征。因此,我们在最大池化层之后添加了一个卷积。我们还在每个卷积之后添加了 ReLU 激活,从而增加了模块的非线性和复杂性。参见图 2。
图 2:Inception 模块
此外,由于多尺度卷积的并行操作,我们确保了更多的操作,而无需深入网络,从而有效地缓解了梯度消失问题。
平均池化
在 AlexNet 或 VGG 等之前的网络中,最后一层将是几个全连接层。这些全连接层由于其单元数量庞大,将贡献网络中的大部分参数。例如,VGG16 的 89% 的参数在最后三个全连接层中。AlexNet 中 95% 的参数在最后的全连接层中。这种需求可以归因于卷积层不一定足够复杂的前提。
然而,有了 Inception 块,我们不需要全连接层,并且沿空间维度的简单平均池化应该就足够了。这也是从网络中的网络论文中得出的。然而,GoogLeNet 包含一个全连接层。他们报告 top-1 准确率提高了 0.6%。
GoogLeNet 只有 15% 的参数在全连接层中。
辅助分类器
随着节省计算量的卷积以及用平均池化替换多个全连接层,该网络的参数显着减少,这意味着我们可以添加更多层并深入网络。然而,堆叠层会导致梯度消失问题,即梯度在反向传播到网络的初始层时变得越来越小并接近于零。
该论文介绍了辅助分类器 - 从中间层的层中分支出来几个小的分类器,并将这些分类器的损失添加到总损失中(权重较小)。这确保了靠近输入的层也接收到相当大的梯度。
辅助分类器包括
- 一个平均池化层,具有滤波器大小和步幅 3。
- 一个卷积,具有 128 个滤波器,用于降维和修正线性激活。
- 一个全连接层,具有 1024 个单元和修正线性激活。
- 一个 dropout 层,dropout 比率为 70%。
- 一个线性层,具有 softmax 损失作为分类器。
这些辅助分类器在推理时被移除。然而,使用辅助分类器获得的收益极小 (0.5%)。
图 3:辅助分类器
架构 - GoogLeNet
下图显示了 GoogLeNet 的完整架构。所有卷积,包括 inception 块内部的卷积,都使用 ReLU 激活。它从两个卷积和最大池化块开始。紧随其后的是两个 inception 模块(3a 和 3b)和一个最大池化块。之后是 5 个 inception 块(4a、4b、4c、4d、4e)和一个最大池化。辅助分类器从 4a 和 4d 的输出中取出。接下来是两个 inception 块(5a 和 5b)。在此之后,使用平均池化和一个 128 单元的全连接层。
图 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 = nn.Sequential(
nn.Conv2d(in_channels, n1x1, kernel_size=1),
nn.ReLU(True),
)
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