一、introduction
在上一篇博客中提到了2014年分类比赛的第二名VGG,今天来自己动手搭建当年比赛的冠军GoogLeNet网络。tips:注意该网络中google的L是大写的,据说是为了致敬当年的LeNet。
很巧的是,这两个卷积神经网络的特点都是加深了网络层数,而GoogLeNet在此基础上提出了Inception网络结构,这是一个稀疏网络结构,但是能够产生稠密的数据,既能增加神经网络表现,又能保证计算资源的使用效率。
从2014年2016年,Inception经历了Inception v1、Inception v2、Inception v3、Inception v4这四个版本,本文主要介绍最原始的GoogLeNet网络结构。
二、Inception 结构
在介绍GoogLeNet网络之前,我们必须讲到Inception 结构。上图是原始版本的Inception,而下图是Inception v1的结构。由于原始版本的Inception中所有卷积核都在上一层的输出上来进行,而3x3、5x5的卷积核所需的计算量太大,会造成计算资源的浪费。在 NIN 模型中与1*1卷积层等效的 MLPConv 既能跨通道组织信息,提高网络的表达能力,同时可以对输出有效进行降维,因为GoogLeNet模型也借鉴了这种思想,在3x3前、5x5前、max pooling后分别加上1x1的卷积核,在不损失模型特征表示能力的前提下,尽量减少 filters 的数量,达到降低模型复杂度的目的。这也就形成了Inception v1的网络结构。
Inception v1结构的具体实现代码如下:
class Inception(nn.Module):
#c1-c2为每条线路里的层的输出通道数
def __init__(self, in_c, c1, c2, c3, c4):
super(Inception, self).__init__()
#线路1,单个1*1的卷积层
self.p1_1 = nn.Conv2d(in_c, c1, kernel_size=1)
#线路2, 1*1卷积层后接3*3卷积层
self.p2_1 = nn.Conv2d(in_c, c2[0], kernel_size=1)
self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
#线路3, 1*1卷积层后接5*5卷积层
self.p3_1 = nn.Conv2d(in_c, c3[0], kernel_size=1)
self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
#线路4, 3*3最大池化层后接 1*1卷积层
self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
self.p4_2 = nn.Conv2d(in_c, c4, kernel_size=1)
def forward(self, x):
p1 = nn.functional.relu(self.p1_1(x))
p2 = nn.functional.relu(self.p2_2(nn.functional.relu(self.p2_1(x))))
p3 = nn.functional.relu(self.p3_2(nn.functional.relu(self.p3_1(x))))
p4 = nn.functional.relu(self.p4_2(self.p4_1(x)))
return torch.cat((p1, p2, p3, p4), dim=1) #在通道维上连结输出
其中p1-p4分别是inception的四个分支,参数in_c表示输入的通道数,c1-c4表示每个分支的输出通道数。每个卷积层后接上relu激活函数。
三、GoogLeNet网络结构
网上比较流行的网络结构如上图所示,在进行代码实现时我们通过torch.nn.Sequential容器来堆叠实现其网络结构。
import torch
from torch import nn
#输入通道数和类别数
in_channels = 3
classes=40
class Flatten(nn.Module):
def __init__(self):
super(Flatten, self).__init__()
def forward(self, input):
input = nn.functional.dropout(input, p=0.4)
return input.view(input.size(0), -1)
#全局平均池化层
class GlobalAvgPool2d(nn.Module):
def __init__(self):
super(GlobalAvgPool2d, self).__init__()
def forward(self, x):
return nn.functional.avg_pool2d(x, kernel_size=x.size()[2:])
b1 = nn.Sequential(
nn.Conv2d(in_channels, 64, kernel_size=7, stride=2, padding=3),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
b2 = nn.Sequential(
nn.Conv2d(64, 64, kernel_size=1),
nn.Conv2d(64, 192, kernel_size=3, padding=1),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
b3 = nn.Sequential(
Inception(192, 64, (96, 128), (16, 32), 32),
Inception(256, 128, (128, 192), (32, 96), 64),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
b4 = nn.Sequential(
Inception(480, 192, (96, 208), (16, 48), 64),
Inception(512, 160, (112, 224), (24, 64), 64),
Inception(512, 128, (128, 256), (24, 64), 64),
Inception(512, 112, (144, 288), (32, 64), 64),
Inception(528, 256, (160, 320), (32, 128), 128),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
b5 = nn.Sequential(
Inception(832, 256, (160, 320), (32, 128), 128),
Inception(832, 384, (192, 384), (48, 128), 128),
GlobalAvgPool2d()
)
net = nn.Sequential(
b1, b2, b3, b4, b5,
Flatten(),
nn.Linear(1024, classes)
)
四、Inception V1-V4
此部分转载于https://blog.csdn.net/hejin_some/article/details/78636586
Inception V1——构建了1x1、3x3、5x5的 conv 和3x3的 pooling 的分支网络,同时使用1X1卷积 和全局平均池化,扩宽卷积层网络宽度,增加了网络对尺度的适应性;
Inception V2——提出了 Batch Normalization,代替 Dropout 和 LRN,其正则化的效果让大型卷积网络的训练速度加快很多倍,同时收敛后的分类准确率也可以得到大幅提高,同时学习 VGG 使用两个3´3的卷积核代替5´5的卷积核,在降低参数量同时提高网络学习能力;
Inception V3——引入了 Factorization,将一个较大的二维卷积拆成两个较小的一维卷积,比如将3´3卷积拆成1´3卷积和3´1卷积,一方面节约了大量参数,加速运算并减轻了过拟合,同时增加了一层非线性扩展模型表达能力,除了在 Inception Module 中使用分支,还在分支中使用了分支(Network In Network In Network);
Inception V4——研究了 Inception Module 结合 Residual Connection,结合 ResNet 可以极大地加速训练,同时极大提升性能,在构建 Inception-ResNet 网络同时,还设计了一个更深更优化的 Inception v4 模型,能达到相媲美的性能。
这里还有一个关于GoogLeNet及其发展历程的详细讲解
https://my.oschina.net/u/876354/blog/1637819
五、writing in the end
不得不说GoogLeNet网络和Inception 结构设计得十分精妙,其中很多方法和思想也一直沿用至今。比如1X1的卷积层,BN层,使用多个小卷积核替代大的卷积核等等,在未来动手搭建自己的神经网络时也可以试试这些方法,或许能带来准确率的提升。