赞
踩
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”致敬
下面给出了GoogLeNet架构的缩略图,更详细以及带标注的图放在文末。相比于以前的卷积神经网络结构,除了在深度上进行了延伸,还对网络的宽度进行了扩展,整个网络由许多块状子网络的堆叠而成,这个子网络即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结构的参数为:
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.3∗L1+0.3∗L2, L 0 L_0 L0是最后的分类损失。在测试阶段则去掉辅助分类器,只记最终的分类损失。
每个卷积层的卷积核个数如何确定呢,下面是原论文中给出的参数列表,对于我们搭建的Inception模块,所需要使用到参数有#1x1, #3x3reduce, #3x3, #5x5reduce, #5x5, poolproj,这6个参数,分别对应着所使用的卷积核个数。
注:上表中的“#3x3 reduce”,“#5x5 reduce”表示在3x3,5x5卷积操作之前使用了1x1卷积的数量。
根据GoogLeNet网络结构图和配置表格,利用Pytorch可以搭建模型代码。
注:本代码参考了Pytorch官方实现的GooLeNet,其实现中:由于LRN层对训练结果影响不大,故代码中去除了LRN层;为了方便修改输出分类类别及迁移学习,softmax层前依然采用了全连接层。
import torch import torch.nn as nn import torch.nn.functional as F class 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。