> 文档中心 > GoogLeNet网络详解与模型搭建

GoogLeNet网络详解与模型搭建

文章目录

    • 1 模型介绍
    • 2 GoogLeNet详解
      • Inception模块
      • 辅助分类器
    • 3 GoogLeNet网络结构
    • 4 Pytorch模型搭建代码

1 模型介绍

GoogLeNet是2014年Christian Szegedy等人在2014年大规模视觉挑战赛(ILSVRC-2014)上使用的一种全新卷积神经网络结构,并以6.65%的错误率力压VGGNet等模型取得了ILSVRC-2014在分类任务上的冠军,于2015年在CVPR发表了论文《Going Deeper with Convolutions》。在这之前的AlexNet、VGG等结构都是通过增大网络的深度(层数)来获得更好的训练效果,但层数的增加会带来很多负作用,比如overfitting、梯度消失、梯度爆炸等,GoogLeNet则做了更加大胆的网络结构尝试,Inception的提出则从另一种角度来提升训练结果:能更高效的利用计算资源,在相同的计算量下能提取到更多的特征,从而提升训练结果,采用了Inception结构的GoogLeNet深度只有22层,其参数约为AlexNet的1/12,是同时期VGGNet的1/3。

GoogLeNet是谷歌(Google)提出的深度网络结构,为什么不叫“GoogleNet”,而叫“GoogLeNet”,是为了向经典模型“LeNet”致敬

2 GoogLeNet详解

下面给出了GoogLeNet架构的缩略图,更详细以及带标注的图放在文末。相比于以前的卷积神经网络结构,除了在深度上进行了延伸,还对网络的宽度进行了扩展,整个网络由许多块状子网络的堆叠而成,这个子网络即Inception模块。

在这里插入图片描述

首先说说该模型的亮点:

  • 采用了模块化的设计(stem, stacked inception module, axuiliary function和classifier),方便层的添加与修改。
    • Stem部分:论文指出Inception module要在网络中间使用的效果比较好,因此网络前半部分依旧使用传统的卷积层代替
    • 辅助函数(Axuiliary Function):从信息流动的角度看梯度消失,因为是梯度信息在BP过程中能量衰减,无法到达浅层区域,因此在中间开个口子,加个辅助损失函数直接为浅层
    • Classifier部分:从VGGNet以及NIN的论文中可知,fc层具有大量层数,因此用average pooling替代fc,减少参数数量防止过拟合。在softmax前的fc之间加入dropout,p=0.7,进一步防止过拟合。
  • 使用1x1的卷积核进行降维以及映射处理 (虽然VGG网络中也有,但该论文介绍的更详细)。
  • 引入了Inception结构(融合不同尺度的特征信息)。
  • 丢弃全连接层,使用平均池化(average pooling)层,大大减少模型参数。
  • 为了避免梯度消失,网络额外增加了2个辅助的softmax用于向前传导梯度(辅助分类器)。辅助分类器是将中间某一层的输出用作分类,并按一个较小的权重(0.3)加到最终分类结果中,这样相当于做了模型融合,同时给网络增加了反向传播的梯度信号,也提供了额外的正则化,对于整个网络的训练很有裨益。而在实际测试的时候,这两个额外的softmax会被去掉。

Inception模块

GoogLeNet中使用的Inception模块被命名为Inception v1,实际上在2014-2016年间,Google团队不断地对GoogLeNet进行改进的过程中形成了Inception v1-v4和Xception结构,具体有关于inception结构的详细介绍,可以参考博主的另一篇博文详解Inception结构:从Inception v1到Xception。

在这里插入图片描述

左图是GoogleNet作者设计的初始inception结构(native inception),其想法是用多个不同类型的卷积核( 1 × 1 1\times1 1×1 3 × 3 3\times3 3×3 5 × 5 5\times5 5×5 3 × 3 P o o l 3\times3Pool 3×3Pool)堆叠在一起(卷积、池化后的尺寸相同,将通道相加)代替一个3x3的小卷积核,好处是可以使提取出来的特征具有多样化,并且特征之间的co-relationship不会很大,最后用把feature map都concatenate起来使网络做得很宽,然后堆叠Inception Module将网络变深。但仅仅简单这么做会使一层的计算量爆炸式增长

native inception中所有的卷积核都在上一层的所有输出上来做,而那个5x5的卷积核所需的计算量就太大了,造成了特征图的厚度很大,为了避免这种情况,在3x3前、5x5前、max pooling后分别加上了1x1的卷积核,以起到了降低特征图厚度的作用,这也就形成了Inception v1的网络结构(右图)。

假设input feature map的size为 28 × 28 × 256 28\times28\times256 28×28×256,output feature map的size为 28 × 28 × 480 28\times28\times480 28×28×480,则native Inception Module的计算量有854M。计算过程如下

在这里插入图片描述

从上图可以看出,计算量主要来自高维卷积核的卷积操作,因而在每一个卷积前先使用 1 × 1 1\times1 1×1卷积核将输入图片的feature map维度先降低,进行信息压缩,在使用3x3卷积核进行特征提取运算,相同情况下,Inception v1的计算量仅为358M。

在这里插入图片描述

Inception结构总共有4个分支,输入的feature map并行的通过这四个分支得到四个输出,然后在在将这四个输出在深度维度(channel维度)进行拼接(concate)得到我们的最终输出(注意,为了让四个分支的输出能够在深度方向进行拼接,必须保证四个分支输出的特征矩阵高度和宽度都相同),因此inception结构的参数为:

  • branch1: C o n v 1 × 1 Conv 1\times1 Conv1×1, stride=1
  • branch2: C o n v 3 × 3 Conv 3\times3 Conv3×3, stride=1, padding=1
  • branch3: C o n v 5 × 5 Conv 5\times5 Conv5×5, stride=1, padding=2
  • branch4: M a x P o o l 3 × 3 MaxPool 3\times3 MaxPool3×3, stride=1, padding=1

GoogLeNet中使用了9个Inception v1 module,分别被命名为inception(3a)、inception(3b)、inception(4a)、inception(4b)、inception(4c)、inception(4d)、inception(4e)、inception(5a)、inception(5b)。

辅助分类器

GoogLeNet网络结构中有深层和浅层2个分类器,两个辅助分类器结构是一模一样的,其组成如下图所示,这两个辅助分类器的输入分别来自Inception(4a)和Inception(4d)。
在这里插入图片描述

辅助分类器的第一层是一个平均池化下采样层,池化核大小为5x5,stride=3;第二层是卷积层,卷积核大小为1x1,stride=1,卷积核个数是128;第三层是全连接层,节点个数是1024;第四层是全连接层,节点个数是1000(对应分类的类别个数)。

在模型训练时的损失函数按照: L o s s = L 0 + 0.3 ∗ L 1 + 0.3 ∗ L 2 Loss=L_0+0.3*L_1+0.3*L_2 Loss=L0+0.3L1+0.3L2 L 0 L_0 L0是最后的分类损失。在测试阶段则去掉辅助分类器,只记最终的分类损失。

3 GoogLeNet网络结构

在这里插入图片描述

每个卷积层的卷积核个数如何确定呢,下面是原论文中给出的参数列表,对于我们搭建的Inception模块,所需要使用到参数有#1x1, #3x3reduce, #3x3, #5x5reduce, #5x5, poolproj,这6个参数,分别对应着所使用的卷积核个数。
在这里插入图片描述注:上表中的“#3x3 reduce”,“#5x5 reduce”表示在3x3,5x5卷积操作之前使用了1x1卷积的数量。

4 Pytorch模型搭建代码

根据GoogLeNet网络结构图和配置表格,利用Pytorch可以搭建模型代码。
注:本代码参考了Pytorch官方实现的GooLeNet,其实现中:由于LRN层对训练结果影响不大,故代码中去除了LRN层;为了方便修改输出分类类别及迁移学习,softmax层前依然采用了全连接层。

import torchimport torch.nn as nnimport torch.nn.functional as Fclass GoogLeNet(nn.Module):    def __init__(self, num_classes=1000, aux_logits=True, init_weights=False): super().__init__() self.aux_logits = aux_logits self.conv1 = BasicConv2d(3, 64, kernel_size=7, stride=2, padding=3) self.pool1 = nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True) self.conv2 = BasicConv2d(64, 64, kernel_size=1) self.conv3 = BasicConv2d(64, 192, kernel_size=3, padding=1) self.pool2 = nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True) self.inception3a = Inception(192, 64, 96, 128, 16, 32, 32)  self.inception3b = Inception(256, 128, 128, 192, 32, 96, 64) self.pool3 = nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True) self.inception4a = Inception(480, 192, 96, 208, 16, 48, 64)  self.inception4b = Inception(512, 160, 112, 224, 24, 64, 64) self.inception4c = Inception(512, 128, 128, 256, 24, 64, 64) self.inception4d = Inception(512, 112, 144, 288, 32, 64, 64)  self.inception4e = Inception(528, 256, 160, 320, 32, 128, 128) self.pool4 = nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True)  self.inception5a = Inception(832, 256, 160, 320, 32, 128, 128) self.inception5b = Inception(832, 384, 192, 384, 48, 128, 128) if aux_logits:     self.aux1 = InceptionAux(512, num_classes)     self.aux2 = InceptionAux(528, num_classes) self.avgpool = nn.AdaptiveAvgPool2d(output_size=(1, 1)) self.dropout = nn.Dropout(p=0.2) self.fc = nn.Linear(in_features=1024, out_features=num_classes) if init_weights:     self._init_weights()    def forward(self, x): x = self.conv1(x)  # [None, 3, 224, 224] -> [None, 64, 112, 112] x = self.pool1(x)  # [None, 64, 112, 112] -> [None, 64, 56, 56] x = self.conv2(x) x = self.conv3(x)  # [None, 64, 112, 112] -> [None, 192, 56, 56] x = self.pool2(x)  # [None, 192, 56, 56] -> [None, 192, 28, 28] x = self.inception3a(x) # [None, 192, 28, 28] -> [None, 256, 28, 28] x = self.inception3b(x)  # [None, 256, 28, 28] -> [None, 480, 28, 28] x = self.pool3(x)  # [None, 480, 28, 28] -> [None, 480, 14, 14] x = self.inception4a(x) # [None, 480, 14, 14] -> [None, 512, 14, 14] if self.training and self.aux_logits:  # eval mode discards this layer     aux1 = self.aux1(x) x = self.inception4b(x) x = self.inception4c(x) x = self.inception4d(x) # [None, 512, 14, 14] -> [None, 528, 14, 14] if self.training and self.aux_logits:     aux2 = self.aux2(x) x = self.inception4e(x)  # [None, 528, 14, 14] -> [None, 832, 14, 14] x = self.pool4(x) # [None, 832, 14, 14] -> [None, 832, 7, 7] x = self.inception5a(x) x = self.inception5b(x)  # [None, 832, 7, 7] -> [None, 1024, 7, 7] x = self.avgpool(x) x = torch.flatten(x, start_dim=1) x = self.dropout(x) x = self.fc(x) if self.training and self.aux_logits:     return x, aux2, aux1 return x    def _init_weights(self): for m in self.modules():     if isinstance(m, nn.Conv2d):  nn.init.kaiming_uniform_(m.weight, mode='fan_out', nonlinearity='leaky_relu')  if m.bias is not None:      nn.init.constant_(m.bias, 0)     elif isinstance(m, nn.Linear):  nn.init.constant_(m.weight, 0.01)  nn.init.constant_(m.bias, 0)class BasicConv2d(nn.Module):    def __init__(self, in_channels, out_channels, **kwargs): super().__init__() self.conv = nn.Conv2d(in_channels, out_channels, bias=False, **kwargs) self.bn = nn.BatchNorm2d(num_features=out_channels, eps=0.001)    def forward(self, x): x = self.conv(x) x = self.bn(x) return F.relu(x, inplace=True)class Inception(nn.Module):    def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj): super().__init__() self.branch1 = BasicConv2d(in_channels, ch1x1, kernel_size=1) self.branch2 = nn.Sequential(     BasicConv2d(in_channels, ch3x3red, kernel_size=1),     BasicConv2d(ch3x3red, ch3x3, kernel_size=3, padding=1) ) self.branch3 = nn.Sequential(     BasicConv2d(in_channels, ch5x5red, kernel_size=1),     BasicConv2d(ch5x5red, ch5x5, kernel_size=5, padding=2) ) self.branch4 = nn.Sequential(     nn.MaxPool2d(kernel_size=3, stride=1, padding=1),     BasicConv2d(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 torch.cat(outputs, dim=1)class InceptionAux(nn.Module):    def __init__(self, in_channels, num_classes): super().__init__() # self.avgpool = nn.AvgPool2d(kernel_size=5, stride=3) self.avgpool = nn.AdaptiveAvgPool2d(output_size=(4, 4)) self.conv = BasicConv2d(in_channels, 128, kernel_size=1)  # output size [batch, 128, 4, 4] self.fc1 = nn.Linear(2048, 1024) self.fc2 = nn.Linear(1024, num_classes)    def forward(self, x): # aux1: N x 512 x 14 x 14, aux2: N x 528 x 14 x 14 x = self.avgpool(x) # aux1: N x 512 x 4 x 4, aux2: N x 528 x 4 x 4 x = self.conv(x) # N x 128 x 4 x 4 x = torch.flatten(x, start_dim=1) x = F.dropout(x, p=0.5, training=self.training) x = self.fc1(x) x = F.relu(x, inplace=True) x = F.dropout(x, p=0.5, training=self.training) x = self.fc2(x) return x