"""Inception V1 最大的特点是控制了计算量和参数量的同时,获得了非常好的分类性能——top-5错误率6.67%
Inception V1 有22层深,比AlexNet的8层或者VGGNet的19层还要更深,但参数量仅为AlexNet(6000万)的1/12。
Inception V1 降低参数量的目的有两点:第一,参数越多模型越庞大,需要提供模型学习的数据量就越大;第二,参数越多,耗费的计算资源也会更大。
Inception V1 参数少但效果好的原因除了模型层数更深,表达能力更强外,还有两点: 一、用全局平均池化层取代全连接层。二、其精心设计的Inception Module提高了参数的利用效率。
NIN主要是级联的卷积层和MLPConv层.一般来说卷积层要提升表达能力,主要依靠增加输出通道数,但副作用是计算量增大和过拟合。
每一个输出通道对应一个滤波器,同一个滤波器共享参数,只能提取一类特征。而NIN中的MLPConv允许在输出通道间组合信息,因此效果明显。
可以说,MLPConv基本等效于普通卷积层后再连接1*1的卷积和RELU激活函数。1*1的卷积是一个非常优秀的结构,它可以跨通道组织信息,提高网络的表达能力,同时可以
对输出通道升维和降维。(低成本的跨通道的特征变换,计算量比3*3小很多,1*1卷积的性价比很高,用很小的计算量就能增加一层特征变换和非线性化)
Inception Module中包含了3种不同尺寸的卷积(1*1,3*3,5*5 convolutions)和一个最大池化,增加了网络对不同尺度的适应性,这一部分和Multi-Scale的思想类似。
Inception V1的论文中指出,Inception Module可以让网络的深度和宽度高效率的扩充,提升准确率且不至于过拟合。
Inception Net的主要目标就是找到最优的稀疏结构单元,即(Inception Module),论文中提到其稀疏结构基于Hebbian原理:Cells that fire together, wire together.学习过程中的刺激会使神经元间的突触强度增加。
从神经网络的角度来看即相关性高的一簇神经元节点应该被连接在一起。在普通的数据集中,这可能需要对神经元节点聚类,但是在图片数据中,天然的就是临近区域的数据相关性高,因此相邻的像素点被卷积操作连接在一起。因此一个1*1的卷积就可以很自然地把这些相关性很高的,在同一个空间位置但是不同通道的特征连接在一起,这就是为什么1*1卷积这么频繁地被应用到Inception Net中的原因。
而在整个网络中,会有多个堆叠的Inception Module,我们希望靠后的Inception Module可以捕捉更高阶地抽象特征,因此靠后的Inception Module的卷积的空间集中度应该逐渐降低,这样可以捕获更大面积的特征。因此,越靠后的Inception Module中,3*3和5*5这两个大面积的卷积核的占比(输出通道数)应该更多。
Inception Net有22层深,除了最后一层的输出,其中间节点的分类效果也很好,因此,在 Inception Net中,还使用到了辅助分类节点(auxiliary classifiers),即将中间某一层的输出用作分类,并按一个较小的权重(0.3)加到最终分类结果中。这样相当于做了模型融合,同时给网络增加了反向传播的梯度信号,也提供了额外的正则化,对于整个 Inception Net的训练很有裨益。
Google Inception Net V3 相对比较复杂,所以这里使用tf.contrib.slim辅助设计这个网络。contrib.slim中的一些功能和组件可以大大减少设计Inception Net的代码量,我们只需要少量代码即可构建好有42层深的Inception V3
"""
import tensorflow as tf
slim= tf.contrib.slim
trunc_normal= lamda stddev: tf.truncated_normal_initializer(0.0,stddev) #产生截断的正太分布
#定义函数inception_v3_arg_scope,用来生成函数的 默认参数!,比如卷积的激活函数,权重初始化方式,标准化器等。
def inception_v3_arg_scope(weight_decay=0.00004, #l2正则
stddev=0.1,
batch_norm_var_collection='moving_vars'):
#定义batch normalization的参数字典
batch_norm_params ={
'decay':0.9997, #衰减系数
'epsilon': 0.001
'updates_collections':tf.GraphKeys.UPDATE_OPS,
'variables_collections':{
'beta':None,
'gamma':None,
'moving_mean':[batch_norm_var_collection], # batch_norm_var_collection='moving_vars'):
'moving_variance':[batch_norm_var_collection],
}
}
#使用slim.arg_scope,它可以给函数的参数自动赋予某些默认值。
with slim.arg_scope([slim.conv2d, slim.fully_connected],
weights_regularizer= slim.l2_regularizer(weight_decay)):
#接下来,嵌套一个slim.arg_scope, 定义好了slim.conv2d中的各种默认参数
with slim.arg_scope(
[slim.conv2d],
weights_initializer= tf.truncated_normal_initializer(stddev=stddev),
activation_fn = tf.nn.relu,
normalizer_fn= slim.batch_norm,
normalizer_params= batch_norm_params) as sc:
return sc
#最后返回定义好的scope
#定义函数inception_v3_base, 可生成 Inception V3网络的卷积部分
# 5个卷积层,2个池化层,实现了对输入图片数据的尺寸压缩,并对图片特征进行了抽象。
def inception_v3_base(inputs,scope=None):
end_points ={} #定义一个字典表,用来保存某些关键节点供之后使用
with tf.variable_scope(scope,'InceptionV3',[inputs]):
with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d],
stride=1, padding='VALID'):
#接下来定义Inception V3的网络结构,首先是前面的非Inception Module的卷积层。这里直接使用slim.conv2d创建卷积层。(slim.arg_scope)
net= slim.conv2d(inputs,32,[3,3],stride=2,scope='Conv2d_1a_3*3')
net= slim.conv2d(net,32,[3,3],scope='Conv2d_2a_3*3')
net= slim.conv2d(net,64,[3,3],padding='SAME',
scope='Conv2d_2b_3*3')
net= slim.max_pool2d(net,[3,3],stride=2,scope='MaxPool_3a_3*3')
net= slim.conv2d(net,80,[1,1],scope='Conv2d_3b_1*1')
net= slim.conv2d(net,192,[3,3],scope='Conv2d_4a_3*3')
net= slim.max_pool2d(net,[3,3],stride=2,scope='MaxPool_5a_3*3')
""" Inception V3的网络结构
类型 kernel尺寸/步长(或注释) 输入尺寸
卷积 3*3 /2 299*299*3
卷积 3*3 /1 149*149*32
卷积 3*3 /1 147*147*32
池化 3*3 /2 147*147*64
卷积 3*3 /1 73*73*64
卷积 3*3 /2 71*71*80
卷积 3*3 /1 35*35*192
Inception模块组 3个Inception Module 35*35*288
Inception模块组 5个Inception Module 17*17*768
Inception模块组 3个Inception Module 8*8*1280
池化 8*8 8*8*2048
线性 logits 1*1*2048
Softmax 分类输出 1*1*1000
"""
"""
回忆一下Inception V3的网络结构(4 2层),首先是5个卷积层和2个池化层交替的普通结构,然后是三个连接的Inception模块组,各自分别有多个Inception Module,在前面的几个普通的非Inception Module的卷积层中,主要使用了3*3的小卷积核,这是借鉴了VGGNet的结构。另外可以看到,除了第一个卷积层步长为2,其余的卷积层步长均为1,而池化层则是尺寸为3*3、步长为2的重叠最大池化,这是AlexNet中使用过的结构。网络的输入尺寸为299*299*3,在经历3个步长为2的层之后,尺寸最后缩小为35*35*192,空间尺寸大大降低,但是输出通道数增加了很多。这部分代码一共有5个卷积层,2个池化层,实现了对输入图片数据的尺寸压缩,并对图片特征进行了抽象。
设计Inception Net的一个重要原则是,图片尺寸是不断缩小的从299*299通过5个步长为2的卷积层或池化层后,缩小为8*8, 同时,输出通道数持续增加。从一开始的3(RGB三色)到2048.
从这里可以看出,每一层卷积、池化、Inception模块组的目的都是将空间结构简化,同时将空间信息转化为高阶抽象的特征信息,即将空间维度转为通道的维度。
这一过程同时也使每层输出tensor的总size持续下降,降低了计算量。
Inception Module('Mixed_5b,c,d,'Mixed_6a,b~e,'Mixed_7a,b,c)的规律:一般情况下有四个分支('Branch_0','Branch_1' 2, 3),
第一个分支一般是1*1卷积,第二个分支一般是1*1的卷积再接分解后(factorized)的1*n和n*1卷积,第三个分支和第二个分支类似,但是一般更深一些,
第四个分支一般具有最大池化或平均池化。因此, Inception Module是通过组合比较简单的特征抽象(分支一),比较复杂的特征抽象(分支2和分支3)和一个简化结构的池化层(分支4),
一共四种不同程度的特征抽象和变换来有选择的保留不同层次的高阶特征,这样可以最大程度的丰富网络的表达能力。
使用了slim及slim.arg_scope,我们一行代码就可以定义好一个卷积层,相比之前AlexNet的实现中使用好几行代码定义一个卷积层,或是VGGNet中专门写一个函数来定义卷积层,都更加方便。
"""
#接下来就将是三个连接的Inception模块组,各自分别有多个Inception Module(结构非常类似,但存在一些细节不同)
#第一个Inception模块组 包含了三个结构类似的 Inception Module。第一个 Inception Module名称为 Mixed_5b, 有四个分支,从Branch_0到Branch_3
with slim.arg_scope([slim.sonv2d, slim.max_pool2d, slim.avg_pool2d], stride=1, padding='SAME'):
#用slim.arg_scope()设置所有Inception模块组的默认参数。
with tf.variable_scope('Mixed_5b'): #设置这个Inception Module的variable_scope名称为Mixed_5.
with tf.variable_scope('Branch_0'):
branch_0 = slim.conv2d(net, 64, [1,1], scope='Conv2d_0a_1*1')
with tf.variable_scope('Branch_1'):
branvh_1 = slim.conv2d(net, 48, [1,1], scope='Conv2d_0a_1*1')
branch_1 = slim.conv2d(branch_1, 64, [5, 5], scope='Conv2d_0b_5*5')
with tf.variable_scope('Branch_2'):
branch_2 = slim.conv2d(net, 64, [1,1], scope='Conv2d_0a_1*1')
branch_2 = slim.conv2d(branch_2, 96, [3,3], scope='Conv2d_0b_3*3')
branch_2 = slim.conv2d(branch_2, 96, [3,3], scope='Conv2d_0c_3*3')
with tf.variable_scope('Branch_3'):
branch_3 = slim.avg_pool2d(net, [3,3], scope='AvgPool_0a_3*3') #第4个分支为3*3的平均池化,连接有32个输出通道的1*1卷积
branch_3 = slim.conv2d(branch_3, 32, [1,1], scope='Conv2d_ob_1*1')
net = tf.concat([branch_0, branch_1, branch_2, branch_3], 3)
#最后使用tf.concat将4个分支的输出合并在一起(在第3个维度合并,即输出通道上合并,生成这个Inception Module的最终输出)
#因为这里所有层的步长均为1,并且padding模式为SAME,所以图片的尺寸并不会缩小,依然维持在35*35.
#不过通道数增加了,4个分支的输出通道数之和为64+64+96+32=256,即最终输出的tensor尺寸为35*35*256
""" with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d],stride=1,padding='SAME'):
with tf.variable_scope('Mixed_5b'):
with tf.variable_scope('Branch_0'):
branch_0= slim.conv2d(net,64,[1,1],scope='Conv2d_0a_1*1')
with tf.variable_scope('Branch_1'):
branch_1= slim.conv2d(net,48,[1,1],scope='Conv2d_0a_1*1')
branch_1= slim.conv2d(branch_1,64,[5,5], scope='Conv2d_0b_5*5')
with tf.variable_scope('Branch_2'):
branch_2= slim.conv2d(net,64,[1,1],scope='Conv2d_0a_1*1')
branch_2= slim.conv2d(branch_2,96,[3,3], scope='Conv2d_0b_3*3')
branch_2= slim.conv2d(branch_2,96,[3,3], scope='Conv2d_0c_3*3')
with tf.variable_scope('Branch_3'):
branch_3= slim.avg_pool2d(net,[3,3], scope='AvgPool_0a_3*3')
branch_3= slim.conv2d(branch_3,32,[1,1],scope='Conv2d_0b_1*1')
net= tf.concat([branch_0,branch_1,branch_2,branch_3],3)
"""
#接下来是第一个Inception模块组的第二个Inception Module---Mixed_5c
with tf.variable_scope('Mixed_5c'):
with tf.variable_scope('Branch_0'):
branch_0= slim.conv2d(net,64,[1,1], scope='Conv2d_0a_1*1')
with tf.variable_scope('Branch_1'):
branch_1= slim.conv2d(net,48,[1,1], scope='Conv2d_0b_1*1')
branch_1= slim.conv2d(branch_1,64,[5,5], scope='Conv2d_0c_5*5')
with tf.variable_scope('Branch_2'):
branch_2= slim.conv2d(net,64,[1,1], scope='Conv2d_0a_1*1')
branch_2= slim.conv2d(branch_2,96,[3,3], scope='Conv2d_0b_3*3')
branch_2= slim.conv2d(branch_2,96,[3,3], scope='Conv2d_0c_3*3')
with tf.variable_scope('Branch_3'):
branch_3= slim.avg_pool2d(net,[3,3], scope='AvgPool_0a_3*3')
branch_3= slim.conv2d(branch_3,64,[1,1],scope='Conv2d_0b_1*1')
net= tf.concat([branch_0,branch_1,branch_2,branch_3],3)
#而第一个Inception模块组的第三个Inception Module----Mixed_5d和上一个Inception Module完全相同,四个分支的结构,参数一模一样,输出tensor的尺寸也为35*35*288
with tf.variable_scope('Mixed_5d'):
with tf.variable_scope('Branch_0'):
branch_0= slim.conv2d(net,64,[1,1], scope='Conv2d_0a_1*1')
with tf.variable_scope('Branch_1'):
branch_1= slim.conv2d(net,48,[1,1], scope='Conv2d_0b_1*1')
branch_1= slim.conv2d(branch_1,64,[5,5], scope='Conv2d_0c_5*5')
with tf.variable_scope('Branch_2'):
branch_2= slim.conv2d(net,64,[1,1], scope='Conv2d_0a_1*1')
branch_2= slim.conv2d(branch_2,96,[3,3], scope='Conv2d_0b_3*3')
branch_2= slim.conv2d(branch_2,96,[3,3], scope='Conv2d_0c_3*3')
with tf.variable_scope('Branch_3'):
branch_3= slim.avg_pool2d(net,[3,3], scope='AvgPool_0a_3*3')
branch_3= slim.conv2d(branch_3,64,[1,1],scope='Conv2d_0b_1*1')
net= tf.concat([branch_0,branch_1,branch_2,branch_3],3)
#第2个Inception模块组包含了5个Inception Module,第二个到第五个Inception Module结构非常类似
#第2个Inception模块组的第1个Inception Module----Mixed_6a
with tf.variable_scope('Mixed_6a'):
with tf.variable_scope('Branch_0'):
branch_0= slim.conv2d(net,384,[3,3],stride=2,padding='VALID',scope='Conv2d_1a_1*1')
with tf.variable_scope('Branch_1'):
branch_1= slim.conv2d(net,64,[1,1],scope='Conv2d_0a_1*1')
branch_1= slim.conv2d(branch_1,96 [3,3],scope='Conv2d_0b_3*3')
branch_1= slim.conv2d(branch_1,96,[3,3],stride=2,padding='VALID',scope='Conv2d_1a_1*1')
with tf.variable_scope('Branch_2'):
branch_2= slim.max_pool2d(net,[3,3],stride=2,padding='VALID',scope='MaxPool_1a_3*3')
net= tf.concat([branch_0,branch_1,branch_2],3)
#第2个Inception模块组的第2个Inception Module----Mixed_6b (有四个分支)
#Factorization into small convolutions思想: 串联的1*7卷积和7*1卷积相当于合成了一个7*7卷积,不过参数量大大减少了(只有后者的2/7),并减轻了过拟合。
#同时多了一个激活函数增强了非线性特征变换。
with tf.variable_scope('Mixed_6b'):
with tf.variable_scope('Branch_0'):
branch_0= slim.conv2d(net, 192,[1,1], scope='Conv2d_0a_1*1')
with tf.variable_scope('Branch_1'):
branch_1= slim.conv2d(net, 128,[1,1], scope='Conv2d_0a_1*1')
branch_1= slim.conv2d(branch_1,128,[1,7],scope='Conv2d_0b_1*7')
brabch_1= slim.conv2d(branch_1,192,[7,1],scope='Conv2d_0c_7*1')
with tf.variable_scope('Branch_2'):
branch_2= slim.conv2d(net,128,[1,1],scope='Conv2d_0a_1*1')
branch_2= slim.cov2d(branch_2,128,[7,1],scope='Conv2d_0b_7*1')
branch_2= slim.cov2d(branch_2,128,[1,7],scope='Conv2d_0c_1*7')
branch_2= slim.cov2d(branch_2,128,[7,1],scope='Conv2d_0d_7*1')
branch_2= slim.cov2d(branch_2,192,[1,7],scope='Conv2d_0e_1*7')
with tf.variable_scope('Branch_3'):
branch_3= slim.avg_pool2d(net,[3,3], scope='AvgPool_0a_3*3')
branch_3= slim.conv2d(branch_3,192,[1,1],scope='Conv2d_0b_1*1')
net= tf.concat([branch_0,branch_1,branch_2,branch_3],3)
#然后是第2个Inception模块组的第3个Inception Module----Mixed_6c (有四个分支)
#需要注意的是,我们的网络每经过一个Inception Module,即使输出tensor尺寸不变,但是特征都相当于被重新精炼了一遍,其中丰富的卷积和非线性化对提升网络性能帮助很大。
with tf.variable_scope('Mixed_6c'):
with tf.variable_scope('Branch_0'):
branch_0= slim.conv2d(net, 192,[1,1], scope='Conv2d_0a_1*1')
with tf.variable_scope('Branch_1'):
branch_1= slim.conv2d(net, 160,[1,1], scope='Conv2d_0a_1*1')
branch_1= slim.conv2d(branch_1,160,[1,7],scope='Conv2d_0b_1*7')
brabch_1= slim.conv2d(branch_1,192,[7,1],scope='Conv2d_0c_7*1')
with tf.variable_scope('Branch_2'):
branch_2= slim.conv2d(net,160,[1,1],scope='Conv2d_0a_1*1')
branch_2= slim.cov2d(branch_2,160,[7,1],scope='Conv2d_0b_7*1')
branch_2= slim.cov2d(branch_2,160,[1,7],scope='Conv2d_0c_1*7')
branch_2= slim.cov2d(branch_2,160,[7,1],scope='Conv2d_0d_7*1')
branch_2= slim.cov2d(branch_2,192,[1,7],scope='Conv2d_0e_1*7')
with tf.variable_scope('Branch_3'):
branch_3= slim.avg_pool2d(net,[3,3], scope='AvgPool_0a_3*3')
branch_3= slim.conv2d(branch_3,192,[1,1],scope='Conv2d_0b_1*1')
net= tf.concat([branch_0,branch_1,branch_2,branch_3],3)
#Mixed_6d和前面的Mixed_6c完全一致,目的同样是通过 Inception Module精心设计的结构增加卷积和非线性,提炼特征。
with tf.variable_scope('Mixed_6d'):
with tf.variable_scope('Branch_0'):
branch_0= slim.conv2d(net, 192,[1,1], scope='Conv2d_0a_1*1')
with tf.variable_scope('Branch_1'):
branch_1= slim.conv2d(net, 160,[1,1], scope='Conv2d_0a_1*1')
branch_1= slim.conv2d(branch_1,160,[1,7],scope='Conv2d_0b_1*7')
brabch_1= slim.conv2d(branch_1,192,[7,1],scope='Conv2d_0c_7*1')
with tf.variable_scope('Branch_2'):
branch_2= slim.conv2d(net,160,[1,1],scope='Conv2d_0a_1*1')
branch_2= slim.cov2d(branch_2,160,[7,1],scope='Conv2d_0b_7*1')
branch_2= slim.cov2d(branch_2,160,[1,7],scope='Conv2d_0c_1*7')
branch_2= slim.cov2d(branch_2,160,[7,1],scope='Conv2d_0d_7*1')
branch_2= slim.cov2d(branch_2,192,[1,7],scope='Conv2d_0e_1*7')
with tf.variable_scope('Branch_3'):
branch_3= slim.avg_pool2d(net,[3,3], scope='AvgPool_0a_3*3')
branch_3= slim.conv2d(branch_3,192,[1,1],scope='Conv2d_0b_1*1')
net= tf.concat([branch_0,branch_1,branch_2,branch_3],3)
# Mixed_6e也和前面两个Inception Module完全一致,这是第2个Inception模块组的最后一个Inception Module,
# 我们将Mixed_6e存储于end_points中,作为Auxiliary Classifier 辅助模型的分类
with tf.variable_scope('Mixed_6e'):
with tf.variable_scope('Branch_0'):
branch_0= slim.conv2d(net, 192,[1,1], scope='Conv2d_0a_1*1')
with tf.variable_scope('Branch_1'):
branch_1= slim.conv2d(net, 160,[1,1], scope='Conv2d_0a_1*1')
branch_1= slim.conv2d(branch_1,160,[1,7],scope='Conv2d_0b_1*7')
brabch_1= slim.conv2d(branch_1,192,[7,1],scope='Conv2d_0c_7*1')
with tf.variable_scope('Branch_2'):
branch_2= slim.conv2d(net,160,[1,1],scope='Conv2d_0a_1*1')
branch_2= slim.cov2d(branch_2,160,[7,1],scope='Conv2d_0b_7*1')
branch_2= slim.cov2d(branch_2,160,[1,7],scope='Conv2d_0c_1*7')
branch_2= slim.cov2d(branch_2,160,[7,1],scope='Conv2d_0d_7*1')
branch_2= slim.cov2d(branch_2,192,[1,7],scope='Conv2d_0e_1*7')
with tf.variable_scope('Branch_3'):
branch_3= slim.avg_pool2d(net,[3,3], scope='AvgPool_0a_3*3')
branch_3= slim.conv2d(branch_3,192,[1,1],scope='Conv2d_0b_1*1')
net= tf.concat([branch_0,branch_1,branch_2,branch_3],3)
end_points['Mixed_6e'] = net
#第三个Inception模块组包含了3个Inception Module
#注意池化层不会对输出通道产生改变,输出的tensor尺寸为8*8*(320+192+768)=8*8*1280
#从这个Inception Module开始,输出的图片尺寸又被缩小了,同时通道数也增加了,tensor的总size在持续下降中。
with tf.variable_scope('Mixed_7a'):
with tf.variable_scope('Branch_0'):
branch_0 = slim.conv2d(net,192,[1,1], scope= 'Conv2d_0a_1*1')
branch_0 = slim.conv2d(branch_0,320,[3,3],stride= 2, padding= 'VALID',scope='Conv2d_1a_3*3')
with tf.variable_scope('Branch_1'):
branch_1 = slim.conv2d(net,192,[1,1],scope= 'Conv2d_0a_1*1')
branch_1 = slim.conv2d(branch_1,192,[1,7],scope= 'Conv2d_0b_1*7')
branch_1 = slim.conv2d(branch_1,192,[7,1],scope= 'Conv2d_0c_7*1')
branch_1 = slim.conv2d(branch_1,192,[3,3],scope= 'Conv2d_1a_3*3')
with tf.variable_scope('Branch_2'):
branch_2 = slim.max_pool2d(net,[3,3],stride=2,padding='VALID',scope='MaxPool_1a_3*3')
net= tf.concat([branch_0,branch_1,branch_2],3)
#接下来是第3个Inception模块组的第2个Inception Module,,它有四个分支,8*8*(320+768+768+192)=8*8*2048
with tf.variable_scope('Mixed_7b'):
with tf.variable_scope('Branch_0'):
branch_0= slim.conv2d(net,320,[1,1],scope= 'Conv2d_0a_1*1')
with tf.variable_scope('Branch_1'):
branch_1= slim.conv2d(net,384,[1,1],scope= 'Conv2d_0a_1*1')
branch_1= tf.concat([
slim.conv2d(branch_1,384,[1,3],scope= 'Conv2d_0b_1*3')
slim.conv2d(branch_1,384,[3,1],scope= 'Conv2d_0b_3*1')
])
with tf.variable_scope('Branch_2'):
branch_2= slim.conv2d(net,448,[1,1],scope= 'Conv2d_0a_1*1')
branch_2= slim.conv2d(branch_2,384,[3,3],scope= 'Conv2d_0b_3*3')
branch_2= tf.concat([
slim.conv2d(branch_2, 384,[1,3], scope= 'Conv2d_0c_1*3')
slim.conv2d(branch_2, 384,[3,1], scope= 'Conv2d_0c_3*1')
])
with tf.variable_scope('Branch_3'):
branch_3= slim.avg_pool2d(net, [3,3],scope= 'AvgPool_0a_3*3')
branch_3= slim.cinv2d(branch_3,192,[1,1], scope='Conv2d_0b_1*1')
net= tf.concat([branch_0,branch_1,branch_2,branch_3],3)
# Mixed_7c是第3个Inception模块组的最后一个Inception Module,不过它和前面的Mixed_7b是完全一致的,输出的tensor也是8*8*2048
# 最后我们返回这个Inception Module的结果,作为inception_v3_base函数的最终输出。
with tf.variable_scope('Mixed_7c'):
with tf.variable_scope('Branch_0'):
branch_0= slim.conv2d(net,320,[1,1],scope= 'Conv2d_0a_1*1')
with tf.variable_scope('Branch_1'):
branch_1= slim.conv2d(net,384,[1,1],scope= 'Conv2d_0a_1*1')
branch_1= tf.concat([
slim.conv2d(branch_1,384,[1,3],scope= 'Conv2d_0b_1*3')
slim.conv2d(branch_1,384,[3,1],scope= 'Conv2d_0b_3*1')
])
with tf.variable_scope('Branch_2'):
branch_2= slim.conv2d(net,448,[1,1],scope= 'Conv2d_0a_1*1')
branch_2= slim.conv2d(branch_2,384,[3,3],scope= 'Conv2d_0b_3*3')
branch_2= tf.concat([
slim.conv2d(branch_2, 384,[1,3], scope= 'Conv2d_0c_1*3')
slim.conv2d(branch_2, 384,[3,1], scope= 'Conv2d_0c_3*1')
])
with tf.variable_scope('Branch_3'):
branch_3= slim.avg_pool2d(net, [3,3],scope= 'AvgPool_0a_3*3')
branch_3= slim.cinv2d(branch_3,192,[1,1], scope='Conv2d_0b_1*1')
net= tf.concat([branch_0,branch_1,branch_2,branch_3],3)
return net, end_points
#接下来,我们来实现Inception V3网络的最后一部分————全局平均池化、Softmax和Auxiliary Logits.
def inception_v3(inputs,
num_classes=1000, #最后需要分类的数量,这里默认的1000是ILSVRC比赛数据集的种类数
is_training=True, #只有在训练时,Batch Normalization和Dropout才会被启用
dropout_keep_prob=0.8,
prediction_fn= slim.softmax, #最后用来进行分类的函数
spatial_squeeze=True, #spatial_squeeze:是否对输出进行squeeze操作(即去除维度为1的维度,比如5*3*1转为5*3)
reuse=None, #是否会对网络和Variable进行重复使用
scope= 'InceptionV3'): #scope包含了函数默认参数的环境
with tf.variable_scope(scope, 'InceptionV3',[inputs, num_classes], reuse=reuse) as scope: #首先使用tf.variable_scope定义网络的的name和reuse等参数的默认值
with slim.arg_scope([slim.batch_norm, slim.dropout], #然后使用slim.arg_scope定义Batch Normalization和Dropout的is_training的默认值
is_training=is_training): #最后,使用前面定义好的inception_v3_base构筑整个网络的卷积部分,拿到最后一层的输出net和重要节点的字典表end_points.
net,end_points= inception_v3_base(inputs,scope=scope)
# Auxiliary Logits作为辅助分类的节点,对分类结果预测有很大帮助。
with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d ],#先使用slim.arg_scope将卷积、最大池化,平均池化的默认步长设为1,默认padding设为SAME。
stride=1, padding='SAME'):
aux_logits = end_points['Mixed_6e'] #然后通过end_points取到Mixed_6e,并在Mixed_6e之后再接一个5*5的平均池化。。。。
with tf.variable_scope('AuxLogits'):
aux_logits = slim.avg_pool2d( aux_logits, [5,5], stride= 3, padding= 'VALID', scope='AvgPool_1a_5*5')
aux_logits = slim.conv2d( aux_logits, 128, [1,1],scope='Conv2d_1b_1*1')
aux_logits = slim.conv2d( aux_logits, 768,[5,5], weights_initializer=trunc_normal(0.01), padding='VALID',scope='Conv2d_2a_5*5')
aux_logits = slim.conv2d( aux_logits, num_classes, [1,1],activation_fn=None,
normalizer_fn= None, weights_initializer= trunc_normal(0.001),
scope='Conv2d_2b_1*1') #输出变为了1*1*1000
if spatial_squeeze:
aux_logits = tf.squeeze(aux_logits,[1,2], name='SpatialSqueeze') #使用tf.squeeze函数消除tenor中前两个为1的维度。
end_points['AuxLogits'] = aux_logits #最后将辅助分类节点的输出aux_logits储存到字典表end_points中。
#下面处理正常的分类预测的逻辑
with tf.variable_scope('Logits'):
net = slim.avg_pool2d(net,[8,8], padding='VALID', scope='AvgPool_1a_8*8') #我们直接对Mixed_7c即最后一个卷积层的输出进行一个8*8全局平均池化,这样输出tensor的尺寸就变为了1*1*2048
net = slim.dropout(net, keep_prob= dropout_keep_prob, scope= 'Dropout_1b')
end_points['PreLogits'] = net
#接着连接一个输出通道数为1000的1*1卷积,激活函数和规范化函数设为空。
logits = slim.conv2d(net, num_classes, [1,1], activation_fn= None, normalizer_fn= None, scope='Conv2d_1c_1*1')
if spatial_squeeze:
logits = tf.squeeze(logits,[1,2],name='SpatialSqueeze')
end_points['Logits'] =logits
end_points['Predictions'] = prediction_fn(logits, scope='Predictions') ## prediction_fn= slim.softmax, #最后用来进行分类的函数
return logits, end_points
#至此,整个Inception V3网络的构建就完成了,下面对Inception V3进行运算性能测试,time_tensorflow_run函数
#图片尺寸设置为299*299,并用tf.random_uniform生成随机图片数据作为input,接着我们使用slim.arg_scope加载前面定义好的inception_v3_arg_scope(最开头定义的),在这个scope中包含了Batch Normalization的默认参数,以及激活函数和参数初始化方式的默认值。
#然后在这个arg_scope下,调用Inception_V3函数,并传入inputs,获取logits和end_points,
batch_size = 32
height, width= 299,299
inputs= tf.random_uniform((batch_size, height, width.3))
with slim.arg_scope( inception_v3_arg_scope()):
logits, end_points = inception_v3(inputs, is_training=False)
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
num_batches=100 #我们设置测试的batch数量为100.
time_tensorflow_run(sess,logits,"Forward") #测试Inception V3网络的forward性能
Google Inception Net
最新推荐文章于 2021-03-18 11:32:55 发布