前言
继续学习霹雳大神的神经网络讲解视频
更新不易,希望大家可以去看原视频支持up主霹雳吧啦Wz
GoogLeNet网络详解
使用pytorch搭建GoogLeNet网络
本博文记载的是基于Pytorch框架的GoogLeNet网络
GoogLeNet网络结构详解与模型的搭建
简单介绍GoogLeNet网络
GoogLeNet网络在2014年被谷歌团队提出 LeNet大写是为了致敬LeNet
网络亮点:
- 引入了Inception结构(融合不同尺度的特征信息)
- 使用1 x 1的卷积核进行降维以及映射处理
- 添加两个辅助分类器帮助训练
// AlexNet和VGG都只有一个输出层 GoogLeNet有三个输出层(其中有两个辅助分类层)
- 丢弃全连接测层,使用平均池化层(大大减少模型参数)
模型结构
结构分析
input
卷积层
最大下采样层
LocalRespNorm层
卷积层
卷积层
LocalRespNorm层
最大下采样层
Inception层(3a)
lnception(3b)
最大池化层
Inception(4a)
黄色部分是辅助分类器1
softmax激活函数进行输出
Inception模型
初始的版本
将上一层的输出同时输入到四个分支中进行处理 处理之后将我们得到的这四个分支的特征矩阵按照深度进行拼接 得到我们的输出特征矩阵
注意:每个分支得到的特征矩阵的高和宽必须相同 否则无法沿深度方向进行拼接
优化后的版本
Inception结构加上一个降维的功能
用到了三个1 x 1的卷积层 进行降维
1x1的卷积核如何起到降维的作用呢?
这样子使用1x1的卷积核 目的是为了减少特征矩阵的深度,从而减少我们的参数个数 ,也就减少了我们的计算量
辅助分类器
两个辅助分类器的结构一样
**第一层是我们的平均下采样操作,池化核大小是5*5 ,步距是等于3 **
第一个辅助分类器是来自于我们的inception4a的输出
inception4a的输出矩阵的大小是14x14x512
第二个辅助分类器是来自于我们的inception4d的输出
inception4d的输出矩阵的大小是14x14x528
两个分类器的输出矩阵高度与宽度相同 但是深度不一样
计算一下AveragePool的输出
根据out(size)=in(size)- F(size)+2 P / S+1
可以计算得到out = (14 -5 )/ 3 + 1 = 4
而平均下采样层又不会改变输入矩阵的深度
所以第一个输出分类器AveragePool层的输出是4x4x512
所以第二个输出分类器AveragePool层的输出是4x4x528
采用128个卷积核大小为1*1的卷积层进行卷积处理,目的是为了降低维度,并且使用的是ReLU激活函数
采用节点个数为1024的全连接层,同样使用ReLU激活函数
全连接层与全连接层之间使用dropout函数,以70%的比例,随机失活神经元
采用节点个数为1000的全连接层,这里的节点个数是对应我们的类别个数!!!
通过softmax函数得到我们的概率分布
理解参数
表格数据从上到下 一一对应GoogLeNet的每一层结构的数据
LocalRespNorm层起到的作用不大 可以舍弃掉
辅助分类器的参数前面有详细讲解
池化层的参数表示是类似5x5+2(V) 表示的是5x5的大小 2的步长
而卷积层参数表示是5x5+2(S) 表示的是5x5的大小 2的步长
注意最后avg pool与全连接层直接还有一个dropout函数 是以40%的概率随机失活神经元
inception结构的对应 如下图所示
model.py代码解读
创建GoogLeNet网络之前先创建几个模板文件
首先是我们的BasicConv2d
因为我们在构建我们的卷积层的时候通常是将我们的卷积和我们的ReLU激活函数绑定在一起
所以创建一个常用的class BasicConv2d
定义BasicConv2d
class BasicConv2d(nn.Module):#继承于nn.Module
def __init__(self, in_channels, out_channels, **kwargs):#参数有输出特征层的深度 输出矩阵特征层的深度
super(BasicConv2d, self).__init__()
self.conv = nn.Conv2d(in_channels, out_channels, **kwargs)#定义卷积层
#输入特征矩阵深度就是我in_channels,卷积核的个数就是我们输出特征矩阵的深度
self.relu = nn.ReLU(inplace=True)#定义ReLU激活函数
def forward(self, x):#定义一个正向传播过程
x = self.conv(x)
x = self.relu(x)
return x
定义Inception结构
class Inception(nn.Module):#继承自nn.Module
def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj):
#参数分析:输入特征矩阵的深度,ch1x1代表#1x1 ch3x3red代表#3x3 reduce
#ch5x5red代表#5x5 reduce ch5x5代表#5x5 pool_pro代表pool proj
super(Inception, self).__init__()
self.branch1 = BasicConv2d(in_channels, ch1x1, kernel_size=1)
#分支1 使用BasicConv2d模板(我们输入特征矩阵的深度,采用卷积核的个数,卷积核大小,步距是1是默认的)
self.branch2 = nn.Sequential(
BasicConv2d(in_channels, ch3x3red, kernel_size=1),
BasicConv2d(ch3x3red, ch3x3, kernel_size=3, padding=1) # 保证输出大小等于输入大小
)
#分支2使用nn.Sequential函数
#使用BasicConv2d(输入特征矩阵的深度,采用卷积核的个数,卷积核大小是1x1,步距是1是默认的)
#使用BasicConv2d模板(输入特征矩阵的深度这时候就是ch3x3red是上一层输出矩阵的深度,采用卷积核的个数,卷积核大小为3x3,步距是1是默认的)
#为了保证每个分支所输出的高度和宽度相同,所以必须设置padding为1
#output_size=(input_size-3+2*1)/1+1=input_size
self.branch3 = nn.Sequential(
BasicConv2d(in_channels, ch5x5red, kernel_size=1),
BasicConv2d(ch5x5red, ch5x5, kernel_size=5, padding=2) # 保证输出大小等于输入大小
)
#分支3 使用nn.Sequential函数
#使用BasicConv2d(输入特征矩阵的深度,采用卷积核的个数,卷积核大小是1x1,步距是1是默认的)
#使用BasicConv2d模板(输入特征矩阵的深度这时候就是ch5x5red是上一层输出矩阵的深度,采用卷积核的个数,卷积核大小为5x5,步距是1是默认的)
#为了保证每个分支所输出的高度和宽度相同,所以必须设置padding为2
#output_size=(input_size-5+2*2)/1+1=input_size
self.branch4 = nn.Sequential(
nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
BasicConv2d(in_channels, pool_proj, kernel_size=1)
)
#分支4 使用nn.Sequential函数
#使用nn.MaxPool2d(卷积核大小为3x3,步距是1,padding是1)
#为了保证每个分支所输出的高度和宽度相同,stride设置为1,设置padding为1
#使用BasicConv2d模板(输入矩阵的深度,采用卷积核的个数是pool_proj,卷积核大小为1x1)
def forward(self, x): #定义正向传播过程
#将我们的特征矩阵分别输入到branch1、branch2.。。。 分别得到这四个分支所对应的输出
branch1 = self.branch1(x)
branch2 = self.branch2(x)
branch3 = self.branch3(x)
branch4 = self.branch4(x)
outputs = [branch1, branch2, branch3, branch4] #将我们的的输出放入一个列表当中
return torch.cat(outputs, 1)#使用一个torch.cat函数对我们的四个分支的输出进行合并,第一个参数是输出特征的列表,需要合并的一个维度,我们序呀在需要在深度方向上进行合并
#tensor的参数是(batch,channel,高度,宽度)
定义辅助分类器
class InceptionAux(nn.Module):#定义自nn.Module这个父类
def __init__(self, in_channels