目录
- 含并行连结的网络GoogLeNet
- 最好的卷积层超参数
- inception块
- inception 结构
- inception原始结构
- inception + 降维
- 1×1卷积核的降维功能
- GoogLeNet
- 段1&2
- 段3
- 段4&5
- Inception 的各种变种
- Inception V3块,段3
- Inception V3块,段4
- Inception V3块,段5
- 总结
- Inception块代码
- 训练模型
含并行连结的网络GoogLeNet
最好的卷积层超参数
为什么选择1×1,3×3,5×5,Max pooling,AvgPool的卷积层?通道数该怎么选?到底用什么样的卷积比较好?
∴引出inception块,inception不用对此进行选择,是都要。(即:在inception块中抽取的有不同的通道,不同的通道有不同的设计,把想要试的东西都放在里面)
inception块
Inception块是指通过并行使用不同尺寸的卷积核和池化操作来捕捉输入数据的不同尺度信息。这种设计允许模型在同一层内并行地处理数据,通过多个分支(或称为’通道’)来提取特征,每个分支可能包含不同数量的卷积层、不同大小的卷积核以及可能的池化层。
通过将所有这些设计元素(即不同的卷积核大小、卷积层数量、池化操作等)组合在一个Inception块中,模型能够学习并融合来自多个尺度和视角的特征表示。这种‘想要试的东西都放在里面’的策略,实际上是一种高效的特征提取方法,它允许模型自动学习哪些特征对于特定任务是最有用的。
4个路径从不同层面抽取信息,然后在输出通道维合并
input(即:输入)被复制了四份,从四条路径走。
pad1:输入和输出高宽一样
pad2:输入和输出等观
这四条路的输出没有改变高宽(跟输入等同高宽),变的是通道数。将这四个在输出通道上进行合并,并不是图片放在一起。
①白色的卷积用来改变通道数,蓝色的卷积用来抽取信息。
②最左边一条1X1卷积是用来抽取通道信息,其他的3X3卷积用来抽取空间信息。
③输出相同的通道数,5X5比3X3的卷积层参数个数多,3X3比1X1卷积层的参数个数多。
inception 结构
传统的CNN结构如AlexNet、VggNet(下图)都是串联的结构,即将一系列的卷积层和池化层进行串联得到的结构。
inception原始结构
GoogLeNet 提出了一种并联结构,下图是论文中提出的inception原始结构,将特征矩阵同时输入到多个分支进行处理,并将输出的特征矩阵按深度进行拼接,得到最终输出。
inception的作用: 增加网络深度和宽度的同时减少参数。
注意:每个分支所得特征矩阵的高和宽必须相同(通过调整stride和padding),以保证输出特征能在深度上进行拼接。
inception + 降维
在inception的基础上,还可以加上降维功能的结构,如下图所示,在原始 inception 结构的基础上,在分支2,3,4上加入了卷积核大小为1x1的卷积层,目的是为了降维(减小深度),减少模型训练参数,减少计算量。
1×1卷积核的降维功能
同样是对一个深度为512的特征矩阵使用64个大小为5x5的卷积核进行卷积,不使用1x1卷积核进行降维的 话一共需要819200个参数,如果使用1x1卷积核进行降维一共需要50688个参数,明显少了很多。
注:CNN参数个数 = 卷积核尺寸×卷积核深度 × 卷积核组数 = 卷积核尺寸 × 输入特征矩阵深度 × 输出特征矩阵深度
GoogLeNet
段1&2
更小的窗口,更多的通道
大小从224×224➡112×112
公式:(输入大小-卷积核大小+填充*2)// 步幅 +1 = 输出大小
实际计算过程:((224-7)+ 3 * 2 )// 2 + 1= 112
其实可以看stride一般来说数值为多少,就是减多少倍。
段3
段4&5
Inception 的各种变种
Inception V3块,段3
Inception V3块,段4
Inception V3块,段5
总结
Inception块用4条有不同超参数的卷积层和池化层的路来抽取不同的信息。
它的一个主要优点是模型参数小,计算复杂度低。
GoogleNet使用了9个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):
def __init__(self, in_channels, c1, c2, c3, c4, **kwargs): # c1为第一条路的输出通道数、c2为第二条路的输出通道数
super(Inception, self).__init__(**kwargs) # python中*vars代表解包元组,**vars代表解包字典,通过这种语法可以传递不定参数。**kwage是将除了前面显式列出的参数外的其他参数, 以dict结构进行接收.
self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
# 定义第二个卷积层p2_1,它将输入通道数减少到c2[0],卷积核大小为1x1。这是第二条路径的第一步。
self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
# 定义第二个卷积层p2_2,它接收p2_1的输出作为输入,输出通道数为c2[1],卷积核大小为3x3,且使用padding=1以保持输入输出的空间尺寸一致。
self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
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)
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)) # 第一条路的输出
# 计算第二条路径的输出,首先通过1x1卷积减少通道数,然后通过3x3卷积,最后应用ReLU激活函数。
p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
# 计算第四条路径的输出,首先进行最大池化,然后通过1x1卷积,最后应用ReLU激活函数。
p4 = F.relu(self.p4_2(self.p4_1(x)))
return torch.cat((p1, p2, p3, p4), dim=1) # 批量大小的dim为0,通道数的dim为1,以通道数维度进行合并
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.MaxPool2d(kernel_size=3,stride=2,padding=1))
b3 = nn.Sequential(Inception(192,64,(96,128),(18,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))
①在实际的项目当中,我们往往预先只知道的是输入数据和输出数据的大小,而不知道核与步长的大小。
②我们可以手动计算核的大小和步长的值。而自适应(Adaptive)能让我们从这样的计算当中解脱出来,只要我们给定输入数据和输出数据的大小,自适应算法能够自动帮助我们计算核的大小和每次移动的步长。
③相当于我们对核说,我已经给你输入和输出的数据了,你自己适应去吧。你要长多大,你每次要走多远,都由你自己决定,总之最后你的输出符合我的要求就行了。
④比如我们给定输入数据的尺寸是9, 输出数据的尺寸是3,那么自适应算法就能自动帮我们计算出,核的大小是3,每次移动的步长也是3,然后依据这些数据,帮我们创建好池化层。
为了使Fashion-MNIST上的训练短小精悍,我们将输入的高和宽从224降到96。
X = torch.rand(size=(1,1,96,96))
for layer in net:
X = layer(X)
print(layer.__class__.__name__,'output shape:\t',X.shape)
结果:
训练模型
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())
结果: