GoogLeNet 是 google 推出的基于 Inception 模块的深度神经网络模型,在 2014 年的 ImageNet 竞赛中夺得了冠军,在随后的两年中一直在改进,形成了 Inception V2、Inception V3、Inception V4 等版本。我们会用一系列文章,分别对这些模型做介绍。本篇文章先介绍最早版本的 GoogLeNet。
论文名称:Going deeper with convolutions
开源代码:conan7882/GoogLeNet-Inception
1. 为什么要提出 Inception
一般来说,提升网络性能最直接的办法就是增加网络深度和宽度,但一味地增加,会带来诸多问题:
1)参数太多,如果训练数据集有限,很容易产生过拟合;
2)网络越大、参数越多,计算复杂度越大,难以应用;
3)网络越深,容易出现梯度弥散问题(梯度越往后穿越容易消失),难以优化模型。
我们希望在增加网络深度和宽度的同时减少参数,为了减少参数,自然就想到将全连接变成稀疏连接。但是在实现上,全连接变成稀疏连接后实际计算量并不会有质的提升,因为大部分硬件是针对密集矩阵计算优化的,稀疏矩阵虽然数据量少,但是计算所消耗的时间却很难减少。在这种需求和形势下,Google 研究人员提出了 Inception 的方法。
2. 什么是 Inception
Inception 就是把多个卷积或池化操作,放在一起组装成一个网络模块,设计神经网络时以模块为单位去组装整个网络结构。模块如下图所示
在未使用这种方式的网络里,我们一层往往只使用一种操作,比如卷积或者池化,而且卷积操作的卷积核尺寸也是固定大小的。但是,在实际情况下,在不同尺度的图片里,需要不同大小的卷积核,这样才能使性能最好,或者或,对于同一张图片,不同尺寸的卷积核的表现效果是不一样的,因为他们的感受野不同。所以,我们希望让网络自己去选择,Inception 便能够满足这样的需求,一个 Inception 模块中并列提供多种卷积核的操作,网络在训练的过程中通过调节参数自己去选择使用,同时,由于网络中都需要池化操作,所以此处也把池化层并列加入网络中。
3. 实际中需要什么样的 Inception
我们在上面提供了一种 Inception 的结构,但是这个结构存在很多问题,是不能够直接使用的。首要问题就是参数太多,导致特征图厚度太大。为了解决这个问题,作者在其中加入了 1X1 的卷积核,改进后的 Inception 结构如下图
这样做有两个好处,首先是大大减少了参数量,其次,增加的 1X1 卷积后面也会跟着有非线性激励,这样同时也能够提升网络的表达能力。
4. 整体网络结构设计
GoogLeNet 的整体网络结构如下图所示
对上图说明如下:
1)GoogLeNet 采用了模块化的结构(Inception 结构),方便增添和修改;
2)网络最后采用了 average pooling(平均池化)来代替全连接层,该想法来自 NIN(Network in Network),事实证明这样可以将准确率提高 0.6%。
3)虽然移除了全连接,但是网络中依然使用了 Dropout ;
4)为了避免梯度消失,网络额外增加了 2 个辅助的 softmax 用于向前传导梯度(辅助分类器)
对于前三点都很好理解,下面我们重点看一下第 4 点。这里的辅助分类器只是在训练时使用,在正常预测时会被去掉。辅助分类器促进了更稳定的学习和更好的收敛,往往在接近训练结束时,辅助分支网络开始超越没有任何分支的网络的准确性,达到了更高的水平。
5、实现代码
'''GoogLeNet with PyTorch.'''
import torch
import torch.nn as nn
import torch.nn.functional as F
# 编写卷积+bn+relu模块
class BasicConv2d(nn.Module):
def __init__(self, in_channels, out_channals, **kwargs):
super(BasicConv2d, self).__init__()
self.conv = nn.Conv2d(in_channels, out_channals, **kwargs)
self.bn = nn.BatchNorm2d(out_channals)
def forward(self, x):
x = self.conv(x)
x = self.bn(x)
return F.relu(x)
# 编写Inception模块
class Inception(nn.Module):
def __init__(self, in_planes,
n1x1, n3x3red, n3x3, n5x5red, n5x5, pool_planes):
super(Inception, self).__init__()
# 1x1 conv branch
self.b1 = BasicConv2d(in_planes, n1x1, kernel_size=1)
# 1x1 conv -> 3x3 conv branch
self.b2_1x1_a = BasicConv2d(in_planes, n3x3red,
kernel_size=1)
self.b2_3x3_b = BasicConv2d(n3x3red, n3x3,
kernel_size=3, padding=1)
# 1x1 conv -> 3x3 conv -> 3x3 conv branch
self.b3_1x1_a = BasicConv2d(in_planes, n5x5red,
kernel_size=1)
self.b3_3x3_b = BasicConv2d(n5x5red, n5x5,
kernel_size=3, padding=1)
self.b3_3x3_c = BasicConv2d(n5x5, n5x5,
kernel_size=3, padding=1)
# 3x3 pool -> 1x1 conv branch
self.b4_pool = nn.MaxPool2d(3, stride=1, padding=1)
self.b4_1x1 = BasicConv2d(in_planes, pool_planes,
kernel_size=1)
def forward(self, x):
y1 = self.b1(x)
y2 = self.b2_3x3_b(self.b2_1x1_a(x))
y3 = self.b3_3x3_c(self.b3_3x3_b(self.b3_1x1_a(x)))
y4 = self.b4_1x1(self.b4_pool(x))
# y的维度为[batch_size, out_channels, C_out,L_out]
# 合并不同卷积下的特征图
return torch.cat([y1, y2, y3, y4], 1)
class GoogLeNet(nn.Module):
def __init__(self):
super(GoogLeNet, self).__init__()
self.pre_layers = BasicConv2d(3, 192,
kernel_size=3, padding=1)
self.a3 = Inception(192, 64, 96, 128, 16, 32, 32)
self.b3 = Inception(256, 128, 128, 192, 32, 96, 64)
self.maxpool = nn.MaxPool2d(3, stride=2, padding=1)
self.a4 = Inception(480, 192, 96, 208, 16, 48, 64)
self.b4 = Inception(512, 160, 112, 224, 24, 64, 64)
self.c4 = Inception(512, 128, 128, 256, 24, 64, 64)
self.d4 = Inception(512, 112, 144, 288, 32, 64, 64)
self.e4 = Inception(528, 256, 160, 320, 32, 128, 128)
self.a5 = Inception(832, 256, 160, 320, 32, 128, 128)
self.b5 = Inception(832, 384, 192, 384, 48, 128, 128)
self.avgpool = nn.AvgPool2d(8, stride=1)
self.linear = nn.Linear(1024, 10)
def forward(self, x):
out = self.pre_layers(x)
out = self.a3(out)
out = self.b3(out)
out = self.maxpool(out)
out = self.a4(out)
out = self.b4(out)
out = self.c4(out)
out = self.d4(out)
out = self.e4(out)
out = self.maxpool(out)
out = self.a5(out)
out = self.b5(out)
out = self.avgpool(out)
out = out.view(out.size(0), -1)
out = self.linear(out)
return out
def test():
net = GoogLeNet()
x = torch.randn(1,3,32,32)
y = net(x)
print(y.size())
test()
至此,整个 GoogLeNet 网络便介绍完毕了。
https://zhuanlan.zhihu.com/p/73857137