tensorflow 实现google Inception Net

Inception 有22层深,但是比alxnet的8层活着vggnet的19层更深,但是参数量更小。他去除了最后的全连接层,用全局拼婚池化来取代(图片尺寸变为1*1),全链接层基本上占据了ALxnet活着vggnet 90%的参数量,而且容易引起过拟合。结构图如下。


其结构反复堆叠在一起形成一个大网络。 

模型图中有四个分枝,最左边的分支对输入进行1*1的卷积,这是NIN中提出的一个重要的结构,1*1的卷积是一个非常优秀的结构,他可以跨通道组织信息,提高网络表达能力,同时对输入通道升维和降维。可以看到inception 的四个分支都用到了1*1的卷积,来进行低成本的特征变化(每个像素都变化了下)。

第二个分支使用了1*1的卷积,然后链接3*3的卷积,相当于进行了两次特征变化。

第三个分支使用了1*1的卷积,然后链接5*5的卷积,也是两次的特征变化。

第四个分支是3*3的最大池化之后使用1*1的卷积。

这四个分支从不同的角度提取了图片的特征,最后通过一个聚合操作合并(在输出通道数这个维度上增加活着聚合)。

人脑的神经元链接是稀疏的,因此研究者认为大型的神经网络应该也是稀疏的。稀疏的网络可以降低参数量,从而对没有很多的样本不存在过拟合的状况。例如卷积神经网络就是稀疏连接的。Inception Net的主要目标就是找到最优的稀疏结构单元(Inception Moudle),这个结构基于Hebbian原理。

Hebbian原理:神经反射活动的持续与重复导致神经元链接稳定性的持久提升。当两个神经元细胞A和B激励很近的时候,并且A参与了对B重复,持续的兴奋。那么某些代谢变化会导致A能够作为B的兴奋细胞。即 “一起发射的神经元会连在一起”,学习过程中的刺激会使神经元之间的突出强度增加。应用中:如果数据集的概率分布可以被一个很大很稀疏的神经网络所表达,那么构筑这个网络的最佳方法是一层一层的构建网络--将上一层高度相关的节点聚类,并将聚类出来的部分链接在一起。如下图所示


一个好的神经网路总是把相关性高的一部分神经元结点(卷积和)链接在一起。在普通的数据集合中,这可能需要对神经元结点聚类,但是在这个图片数据中,天然的就是临近区域的数据相关性高,因此相邻的像素点被卷积操作链接在一起,而我们可能有多个卷积核,在同一空间位置在不同管道的卷积核的输出结果相关性极高。  因此一个1*1的卷积核就可以吧这些相关性很高的,在同一个空间位置,但是不同通道的特征连接在一起。这就是1*1被广泛运用的原因。

整个神经网络中,除了最后一层的输出,其中间节点的分类效果也很好,在Inception中还是用了辅助分类的节点,就是将中间某一层的输出用作分类,并按照一个较小的权重(0.3)加入分类结构中。相当于做了模型融合,同时给网路增加了反向传播信号,也提供了额外的正则化。

Inception V1是比较早的版本,当时使用了SGD训练(梯度下降的三种形式之一)

Inception V2学习了VGGnet,用两个3*3的卷积核代替了5*5的大卷积,并且提出了BN的方法。

Bn是一个非常有效的正则化方法,这里提一下正则化,正则化用于机器学习中的防止过拟合的方式,主要是约束,并且限制要优化的参数,减少变量参数。BN在用于神经网络的某层的时候,会对每一个mini-batch数据的内部进行标准化处理,使输出规范到N(0,1)的正态分布,传统的神经网络在训练的时候,每一层的输入的分布都在变化,导致训练变得苦难,只能用一个很小的学习速率来解决这个问题,如果对每一层bn之后,就可以有效的解决这个问题,学习速率增加,迭代次数减少,训练时间缩短。

另外还有一些其他的调整:增大学习速率并加快学习衰减速度以适用BN规范化后的数据,去除Dropout并减轻L2正则(因为BN已经起到了正则的作用),去除LRN(局部相应归一化层),跟彻底的对训练样本进行shuffle,减小数据增强过程中对数据的光学畸变(因为BN训练的更快,每个样本被训练的次数更少,因此跟真实的样本对训练更有帮助)

Inception V3网络则有两方面的改造,一个是引入 factorization into samll convolutions 的思想,将一个较大的二维卷积拆成两个较小的一维卷积,比如将7*7卷积拆成1*7卷积和7*1卷积核,这样做减少了参数,同时增加了一层线性变化的表达能力(从低维度变成高纬度) 。另一方面优化了Inception Moudle的结构,inception moudle只在网络的后部出现,前部还是普通的卷积层。Inception V3除了在Inception Moudle中使用分支,还在分支中使用分支(8*8的结构中)

Inception V4结合了微软的ResNet而成。代码设计的时候使用tf.contrib.slim辅助设计构建42层深的Inception V3


首先定义一个简单的trunc_normal函数,产生截断的正太分布

import tensorflow as tf 
slim = tf.contrib.slim
trunc_normal = lambda stddev:tf.truncated_normal_initializer(0.0,stddev)

下面定义函数inception_v3_arg_scope,用来产生网络中经常用到的函数的默认参数,比如卷积的激活函数,权重初始化方式,标准化器等。设置L2正则的weight_decay默认值为0.00004,标准差stddev默认值为0.1,参数batch_normal_var_collection默认值为moving_vars.


def inception_v3_arg_scope(weight_decay=0.00004,stddev=0.1,batch_norm_var_collection='moving_vars'):

接下来定义batch_normalization的参数字典,定义其衰减系数decay为0.9997,epsilon为0.001,updates_collections为tf.GraphKeys.UPDATE_OPS,然后字典variables_collections中beta和gammma均设置为None,moving_mean和moving_variance设置为前面的btach_normal_var_collection.

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],'moving_variance':[batch_norm_var_collection],}
}

接下来使用slim.arg_scope,这是个非常有用的工具,他可以给函数的参数自动赋予某些默认值

例如 with slim.arg_scope([slim.conv2d,slim.fully_connected],weights_regularizer=slim.12_regularizer(weight_decay)),会对[slim.conv2d,slim.fully_connected]这两个函数的参数自动赋值,将参数weights_regularizer的值默认设为slim.12_regularizer(weight_decay)使用slim.arg_scope后就不要每次重复设置参数。只需要在修改的地方设置。

with slim.arg_scop([slim.conv2d,slim.fully_connected],weight_regular=slim.12_regularizer(weight_decay))

with slim.arg_scope([slim,conv2d],weight_initializer=tf.truncated_normal_initializer(stddev=stddev),activation_fn=tf.nn.relu,normalize_fn=slim.batch_norm,normalizer_params=batch_norm_params) as sc:
return sc

接下来定义函数inception_v3_base,他可以生成inception_v3的卷积部分,参数input为输入图片的tensor,scoope 包含了函数默认参数的环境,我们定义一个字典表end_points,用来保存某些关键点。输入数据尺寸为299*299*3经过这些层之后变为35*35*192(共5个卷积层,两个池化层)

def inception_v3_base(inputs,scope=None):
    end_points={}
    #创建变量,名称是InceptionV3
    with tf.variable_scope(scope,'InceptionV3',[inputs]):
        #参数默认赋值
        with slim.arg_scope([slim.conv2d,slim.max_pool2d,slim.avg_pool2d],stride=1,padding='VALID'):
            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模块组,这三个Inception模块组各自有多个inception Moudle

            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_ob_1*1')
                    #维度上连接起来
                    net = tf.concat([branch_0,branch_1,branch_2,branch_3],3)

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_ob_1*1')
                    branch_1 = slim.conv2d(branch_1,64,[5,5],scope='Conv_1_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('Bracnch_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)

mined_5d和5c基本上一样

            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_ob_1*1')
                    branch_1 = slim.conv2d(branch_1,64,[5,5],scope='Conv_1_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('Bracnch_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)

然后是mined_6a

            with tf.variable_scope('Mixed_6a'):
                with tf.variable_scope('Branch_0'):
                    branch_0 = slim.conv2d(net,384,[3,3],padding='VALID',scope='Conv2d_0a_1*1')
                with tf.variable_scope('Branch_1'):
                    branch_1 = slim.conv2d(net,64,[1,1],scope='Conv2d_ob_1*1')
                    branch_1 = slim.conv2d(branch_1,96,[3,3],scope='Conv_1_0c_5*5')
                    branch_1 = slim.conv2d(branch_1,96,[3,3],stride=2,padding='VALID',scope='Conv_1_0c_5*5')
                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)

接下来是Inception模块组的第二个Inception Module--Mixed_6b 他有4个分支,第一个分支是一个简单的192输出通道的1*1卷积,第二个分支是由三层卷积层组成,第一层是1*1,第二层是128通道的1*7卷积,第三层是192输出的7*1卷积。第三个分支是有5个卷积层,128输出通道的1*1,128输出通道的7*1卷积,128输出通道的1*7,128*7*1,192*1*7。第四个分支是一个3*3的平均池化,再连接192输出通道的1*1卷积。最后将是个分支合并,这一层输出tensor的尺寸既是17*17*(192+192+192+192)= 17*17*768

先写到这里吧。







阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭