基于 Pytorch 解读 GoogLeNet 现代卷积神经网络
1 概述
GoogLeNet关键点:保证算力情况下增大宽度和深度
宽度:利用Inception结构同时执行多个网络结构
深度:利用辅助分类器防止梯度消失
多尺度训练和预测
适用于多种计算机视觉任务
多尺度训练对全卷积网络有效,一般设置几种不同尺度的图片,训练时每隔一定iterations随机选取一种尺度训练。这样训练出来的模型鲁棒性强,其可以接受任意大小的图片作为输入,使用尺度小的图片测试速度会快些,但准确度低,用尺度大的图片测试速度慢,但是准确度高。
例如:训练的时候,把图片缩放到不同大小输入给同一个网络,网络就能看到不一样大小范围的内容,然后就叫做多尺度训练;好处是可以适应不同尺度的输入,泛化性好。把一个特征层用由不同感受野的卷积核组成的网络层(例如SPP)处理,这层网络的同一个像素位置就能看到不同范围的上层特征,就叫做多尺度特征融合;好处是能考虑到不同范围的空间特征上下文(例如头发下面一般会有一张脸)。把一个网络中不同深度的层做融合:浅层感受野小,分辨率大,能够处理并保存小尺度的几何特征;高层感受野大,分辨率小,能够处理并保存大尺度的语义特征。这也叫做多尺度特征融合;好处是能够将语义特征和几何特征进行融合(例如这块区域是头发,低分辨率图上的分界线一般是线状特征和非线状特征的边界)。
下面根据论文的顺序介绍该模型:
1、比赛成绩:
ILSVRC14分类任务第一(classification)、检测任务第一(detection)。GoogLeNet和VGGNet同时期,根据VGGNet的论文,单模型VGGNet更好,但七个模型集成之后GoogLeNet更好
2、背景介绍:
GoogLeNet是一个基于Hebbian法则和多尺度处理构建的网络结构,共有22层,相较于当时两年前的AlexNet参数少了12倍,但精确率提高了很多;不仅能用于分类任务,还可以用于检测任务。
在目标检测领域中,最大的提升并不是来自于使用更大更深的网络,而是将传统CV方法和更深的网络结构结合在一起,比如R-CNN:先利用低层语义信息找到bbox,再用CNN进行分类。
由于移动设备和嵌入式计算的发展,算法的效率即精确率和内存占用变得很重要,GoogLeNet考虑到了这一点,并权衡了这两者的关系。一个好的模型应当不仅有学术性,而且还能应用于真实世界中。
Inception结构的想法来源:
之前的研究中有人利用多个不同尺寸的Gabor滤波器处理多尺度问题(其实这个想法是来源于灵长类动物的视觉皮质的神经科学模型),但模型层数太少,而GoogLeNet将Inception重复很多次以此实现22层的网络结构
1 x 1卷积的想法来源于NiN
3、动机和高层次的考虑:
将网络设计得更宽、更深的问题:
容易过拟合
计算资源有限且越大的网络利用率未必高(如参数很多0)
为了解决这两个问题,需要将FC层或卷积层转换成稀疏连接的结构(这一点除了是模仿生物以外,有坚实的理论基础),但使用稀疏连接的结构会导致硬件效率变低(更均匀的网络结构、卷积核和更大的batch size才能使计算效率更高),可以将稀疏矩阵合并成相对稠密的子矩阵,以此解决稀疏矩阵相乘的问题。
2 版本
GoogLeNet Incepetion V1
GoogLeNet Inception V2
GoogLeNet凭借其优秀的表现,得到了很多研究人员的学习和使用,因此Google团队又对其进行了进一步发掘改进,产生了升级版本的GoogLeNet。这一节介绍的版本记为V2,文章为:《Rethinking the Inception Architecture for Computer Vision》。
用nx1卷积来代替大卷积核,这里设定n=7来应对17x17大小的feature map。该结构被正式用在GoogLeNet V2中。
3 含并行连结的网络(GoogLeNet)
3.1 Inception块
在GoogLeNet中,基本的卷积块被称为Inception块(Inception block)。这很可能得名于电影《盗梦空间》(Inception),因为电影中的一句话“我们需要走得更深”(“We need to go deeper”)。
Inception块由四条并行路径组成。
前三条路径使用窗口大小为 、 和 的卷积层,从不同空间大小中提取信息。
中间的两条路径在输入上执行 卷积,以减少通道数,从而降低模型的复杂性。
第四条路径使用 最大汇聚层,然后使用 卷积层来改变通道数。
这四条路径都使用合适的填充来使输入与输出的高和宽一致,最后我们将每条线路的输出在通道维度上连结,并构成Inception块的输出。在Inception块中,通常调整的超参数是每层输出通道的数量。
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
class Inception(nn.Module):
# `c1`--`c4` 是每条路径的输出通道数
def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
super(Inception, self).__init__(**kwargs)
# 线路1,单1 x 1卷积层
self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
# 线路2,1 x 1卷积层后接3 x 3卷积层
self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
# 线路3,1 x 1卷积层后接5 x 5卷积层
self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)
self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
# 线路4,3 x 3最大汇聚层后接1 x 1卷积层
self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)
def forward(self, x):
p1 = F.relu(self.p1_1(x))
p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
p4 = F.relu(self.p4_2(self.p4_1(x)))
# 在通道维度上连结输出
return torch.cat((p1, p2, p3, p4), dim=1)
那么为什么GoogLeNet这个网络如此有效呢?首先我们考虑一下滤波器(filter)的组合,它们可以用各种滤波器尺寸探索图像,这意味着不同大小的滤波器可以有效地识别不同范围的图像细节。同时,我们可以为不同的滤波器分配不同数量的参数。
3.2 结构
GoogLeNet 一共使用 9 个Inception块和全局平均汇聚层的堆叠来生成其估计值。Inception块之间的最大汇聚层可降低维度。
第一个模块类似于 AlexNet 和 LeNet,Inception块的栈从VGG继承,全局平均汇聚层避免了在最后使用全连接层。
现在,我们逐一实现GoogLeNet的每个模块。第一个模块使用 64 个通道、 卷积层。
b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
第二个模块使用两个卷积层:第一个卷积层是 64个通道、 卷积层;第二个卷积层使用将通道数量增加三倍的 卷积层。
这对应于 Inception 块中的第二条路径。
b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
nn.ReLU(),
nn.Conv2d(64, 192, kernel_size=3, padding=1),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
第三个模块串联两个完整的Inception块。
第一个 Inception 块的输出通道数为 ,四个路径之间的输出通道数量比为 。
第二个和第三个路径首先将输入通道的数量分别减少到 和 ,然后连接第二个卷积层。第二个 Inception 块的输出通道数增加到 ,四个路径之间的输出通道数量比为 。
第二条和第三条路径首先将输入通道的数量分别减少到 和 。
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))
第四模块更加复杂,
它串联了5个Inception块,其输出通道数分别是 、 、 、 和 。
这些路径的通道数分配和第三模块中的类似,首先是含 卷积层的第二条路径输出最多通道,其次是仅含 卷积层的第一条路径,之后是含 卷积层的第三条路径和含 最大汇聚层的第四条路径。
其中第二、第三条路径都会先按比例减小通道数。
这些比例在各个 Inception 块中都略有不同。
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))
第五模块包含输出通道数为 和 的两个Inception块。
其中每条路径通道数的分配思路和第三、第四模块中的一致,只是在具体数值上有所不同。
需要注意的是,第五模块的后面紧跟输出层,该模块同 NiN 一样使用全局平均汇聚层,将每个通道的高和宽变成1。
最后我们将输出变成二维数组,再接上一个输出个数为标签类别数的全连接层。
b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
Inception(832, 384, (192, 384), (48, 128), 128),
nn.AdaptiveAvgPool2d((1,1)),
nn.Flatten())
net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10))
X = torch.rand(size=(1, 1, 96, 96))
for layer in net:
X = layer(X)
print(layer.__class__.__name__,'output shape:\t', X.shape)
nn.AdaptiveAvgPool2d((1,1)),也做个一个降维度哦!
4 训练
使用 Fashion-MNIST 数据集来训练我们的模型。在训练之前,我们将图片转换为 $96 \times 96$ 分辨率。
lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
5 总结
Inception 块相当于一个有4条路径的子网络。它通过不同窗口形状的卷积层和最大汇聚层来并行抽取信息,并使用 卷积层减少每像素级别上的通道维数从而降低模型复杂度。
GoogLeNet将多个设计精细的Inception块与其他层(卷积层、全连接层)串联起来。其中Inception块的通道数分配之比是在 ImageNet 数据集上通过大量的实验得来的。
参考
[1].https://zh-v2.d2l.ai/index.html
博客地址:lixiang.blog.csdn.net
点击下方阅读原文加入社区会员