目录
GoogLeNet —— 含并行连结的网络
卷积层超参数
Inception块:小学生才做选择,我全要!
为什么要用Inception块?
GoogLeNet架构
详细展开每个Stage
Stage 1 & 2
Stage 3
Stage 4 & 5
Inception 有各种后续变种
总结
代码实现
Inception Block
逐步实现每个Stage
GoogLeNet —— 含并行连结的网络
L大写用来致敬LeNet,但其实它跟LeNet什么关系都没有哈哈哈。
Just公司名字放上去然后玩个梗致敬以下。
我们上篇提到的NiN严重影响了这个网络的设计,所以还没看上篇博客的可以先去学习一下NiN的思想。指路:秃姐学AI系列之:NiN + 代码实现-CSDN博客
卷积层超参数
这篇论文的一个重点是解决了什么样大小的卷积核最合适的问题。 毕竟,以前流行的网络使用小到1×1,大到11×11的卷积核。
卷积层:1x1?3x3?5x5?
MaxPooling?MeanPooling?
MLP:1x1?
本文的一个观点是,有时使用不同大小的卷积核组合是有利的。
本篇博客介绍的是一个稍微简化的GoogLeNet版本:省略了一些为稳定训练而添加的特殊特性,现在有了更好的训练方法,这些特性不是必要的。
Inception块:小学生才做选择,我全要!
这很可能得名于电影《盗梦空间》(Inception),因为电影中的一句话“我们需要走得更深”(“We need to go deeper”)
四个路径(Copy)从不同层面抽取信息,然后在输出通道维合并
四条路分别使用不同窗口大小的卷积层或池化层来抽取特征
每个层都会通过padding来保持输入输出同高宽
最后在输出的时候,沿着通道数的方向将四个通道的所有特征叠(concat)在一起。
- 即高宽不变,变得只有通道数
中间的两条路径在输入上执行1×1卷积,为了减少通道数,从而降低模型的复杂性。
我们以输入数据:192 * 28 * 28 来观察在模型中通道数是怎么变化的
5x5 卷积更贵,所以我们可以发现 5x5 之前那个卷积给参数压缩到一个更小的状态
我们可以发现:上图中,白色的Conv基本是用来改变通道数的,蓝色的层用来抽取特征信息(1x1用来抽取通道信息,3x3、5x5、最大池化用用来抽取空间信息)
可能有同学会有疑问,这个数值是哪里来的?或者是怎么确定的?其实作者也没说怎么来的,但是我们通道数相加可以发现,最后输出通道数为256。
或者可以理解成,我们需要把更多数的通道数留给效果更好的路:
- 比如上面的 3x3 一条路拥有一半的通道数,计算量也不大,效果也好;
- 剩下的一般通道里面,又分了一半给 1x1 的卷积,不看空间卷积,只看通道卷积;
- 剩下的1/4就两条通道对半分了
这个思路可以借鉴,但是其实具体是用什么数值这个东西确实是调出来的hahahaha
为什么要用Inception块?
跟单个 3x3 或 5x5 卷积层比,Inception块有更少的参数个数和计算复杂度
所以通过上表我们可以看出。Inception块不但可以让我们在更多维度抽取特征,还让我们计算量减小了很多
GoogLeNet架构
5段,9个Inception块
- Stage1、2都没有用到Inception块,我们可以看到Stage 2添加的是我们刚刚上面分析的,作者他们可能认为最重要的中间那一条路,也就是 3x3 那条卷积。
这里的Stage定义是:只要把 高宽减半、通道数两倍 就合起来称为一个stage
- Stage3 用 2 个Inception块(不改变高宽,只改变通道数)+ 3x3的最大池化(stride = 2)来降低高宽。
- Stage4 更厉害了,用了 5 个Inception块 + MaxPooling。
- Stage5 使用 2 个Inception块 + 全局的平均池化
- 最后通过一个全连接层来映射到你需要的分类数。
所以我们开头为什么说 GoogLeNet 借鉴了 NiN 的思路?
我们可以看到 GoogLeNet 用了大量的 1x1 Conv,其实也是拿它当MLP在使用
详细展开每个Stage
Stage 1 & 2
更小的宽口,更多的通道
- 从AlexNet的第一层 11x11Conv变成 7x7
- Stage2也从 5x5Conv 变成 3x3
- AlexNet 高宽 降得是比较狠的,GoogLeNet没有降得这么狠,还是用padding缓冲了以下,缓慢下降
虽然 高宽 降得没有AlexNet那么块,但是GoogLeNet也是通过前两个 Stage 很快的把高宽从 224x224 降到了 28x28,然后把通道数拉上去,为后面的计算提供可控性(减少计算量)
Stage 3
两个Inception Block
- 整个数据在穿过两个Inception Block的特征通道数变化是:
- 192 -> 256 -> 480
- 第一个Inception Block我们上面已经讲过了,四条线的通道数分配分别是:
- 1/4、1/2、1/8、1/8
- 第二个Inception Block我们发现 虽然第二天线 3x3Conv的通道数增加了,但是其实比例没有拿到一半,反而 5x5Conv扩大到原来的近三倍的样子,1x1Conv 和 MaxPool 都是扩大到原来的两倍的样子
完全没有规律可言......同一条路没有,不同路之间也没太有....基本GOOGLE就是机器多,然后用机器做了个架构搜索hahaha
所以导致 GoogLeNet 这个论文很难复现,有那么多超参数
Stage 4 & 5
Stage 4 的 5 个 Inception Block 只有 开头 和 结尾的 Block 进行了通道数的增加
最后输出一个长为 1024 的向量
和 VGG 很像,只不过 Block 用的是 Inception,最后输出的 1024 维度也比 VGG的 512 大很多
Inception 有各种后续变种
我们介绍的是v1,v1这个版本几乎没被人使用过,现在使用较多的是v3
- Inception-BN(v2)——使用 batch normalization
- Inception-v3——修改了 Inception 块,尝试了很多不同的思想:
- 替换 5x5 为多个 3x3 卷积层
- 替换 5x5 为 1x7 和 7x1 卷积层
- 先在行上面看看空间信息,而不看列
- 再在列上面看看空间信息,而不看行
- 替换 3x3 为 1x3 和 3x1 卷积层
- 前面讲的都是串行的,这个常识直接改成了并行
- 更深
虽然 v3 这个东西很诡异,但实际上效果还挺好的hahaha(当然是在当年)。但是v3比较耗内存!耗内存多,计算慢,但在当年还是精度挺好的一个网络
- Inception-v4——使用残差连接
总结
-
Inception 块相当于一个有4条路径的子网络。它通过不同窗口形状的卷积层和最大汇聚层来并行抽取不同信息,并使用1×1卷积层减少每像素级别上的通道维数从而降低模型复杂度。
-
主要优点:模型参数小、计算复杂度低
-
-
GoogLeNet使用了 9 个 Inception 块与其他层(卷积层、全连接层)串联起来。其中 Inception 块的通道数分配之比是在 ImageNet 数据集上通过大量的实验得来的。
-
是第一个达到上百层的网络
-
当然 GoogLeNet 也不是纯欻以下升到 100 层,升一百直到 ResNet 出现才可以,在 GoogLeNet 还是要加并行那些层才可以达到上百层。
-
GoogLeNet和它的后继者们一度是ImageNet上最有效的模型之一:它以较低的计算复杂度提供了类似的测试精度。
但是GoogLeNet其实不咋受欢迎,因为网络太奇怪啦。乱七八糟的超参数也不知道怎么来的。
代码实现
Inception Block
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2lclass Inception(nn.Module):# c1--c4是每条路径的输出通道数# **kwargs:将除了前面显式列出的参数外的其他参数,以dict结构进行接受def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):super(Inception, self).__init__(**kwargs)# 线路1,单1x1卷积层self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)# 线路2,1x1卷积层后接3x3卷积层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,1x1卷积层后接5x5卷积层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,3x3最大汇聚层后接1x1卷积层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)
逐步实现每个Stage
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))
b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),nn.ReLU(),nn.Conv2d(64, 192, kernel_size=3, padding=1),nn.ReLU(),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),nn.AdaptiveAvgPool2d((1,1)),nn.Flatten())net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10))
对着网络架构图抄就行了
有一个思路可以注意一下:虽然每个Stage里面都乱七八糟的,但是每个 Stage 都用一个 Sequential 包住,打印网络看起来其实是很清晰的:
这里数据维度就不展示了,因为为了适应我的数据集我把网络维度改了