Inception Module 是Inception Net中精心设计的模块。是大网络中的小网络。沿用了Network In Network的设计思路。不过更近一步的增加了分支网络。
Inception Module的基本结构(Inception V1):
4个分支最后通过一个{聚合操作}合并(在输出通道这个维度上聚合)。
人脑神经元的连接是稀疏的,其实卷积神经网络就是稀疏的连接。一个filter,共享参数,两层之间的连接节点,只有filter大小的参数集合。是稀疏的。反过来说,全连接就是完全密度连接。
Inception Module 的设计思念就是收到Hebbian原理的启发。Hebbian原理:神经反射活动的持续与重复会导致神经元连接稳定的持久提升,当两个神经元细胞A和B距离很近,并且A参与了对B重复、持续的兴奋,那么某些代谢变化会导致A将作为能使B兴奋的细胞。简单来说,A持续参与对B的兴奋刺激,生物上A就和B连接起来,A兴奋B就跟着兴奋。(Cells that fire together, wire together),学习过程中的刺激会使神经元间的突触强度增加。那么一个思想运用到神经网络的设计中就是:上一层高度相关的节点聚类,并将聚类出来的每一个cluster连接到一起。
Inception Module中,通常1x1卷积的比例最高,3x3和5x5卷积稍低。而在整个网络中,会有多个堆叠的Inception Module,希望靠后的Inception Module可以捕捉更高阶的抽象特征,因此靠后的Inception Module的卷积的空间集中度应该逐渐降低,这样可以捕捉到更大面积的特征。因此,越靠后的Inception Module中,3x3和5x5的占比应该更多。
Inception Net有22层深,除了最后一层的输出。其中间节点的分类效果也很好。因此在Inception Net中,还使用到了辅助分类结点(auxiliary classifiers),将中间某一层的输出用作分类,并按一个较小的权重(0.3)加到最终的分类结果中。这就相当于做了模型融合,同时给网络增加了反向传播的梯度信号,也提供了额外的正则化。
Inception V2学习了VGGNet的思想,用两个3x3的卷积来代替5x5的大卷积(能减少参数并减轻过拟合),还提出了BN(Batch Normalization)。BN是一种非常有效的正则化方法,在用于神经网络的某层时,会对每一个mini-batch数据的内部进行标准化处理,使输出规范到(0,1)的正态分布,减少了internal covariance shift(内部神经元分布的改变)。
Inception V3则在两个方面做出改造:1. 引入了 Factorization into small convolution的思想,将一个较大的二维卷积拆成两个较小的一维卷积,比如7x7会被拆为两个(1x7,7x1),或者将3x3拆分为(1x3,3x1)。另一方面V3优化了Inception Module的结构,Module中有35,17,8三种不同结构。这些Module只在网络的后部出现,前面还是普通的卷积层。并且还在分支中使用了分支,可以说是network in network in network。
Inception V4结合了ResNet的思想,结构更为复杂。
不过这里只准备实现V3版本。V3一共有42层深。
类型 | kernel size / stride(or comment) | 输入尺寸 |
---|---|---|
conv | 3x3 / 2 | 299x299x3 |
conv | 3x3 / 1 | 149x149x32 |
conv | 3x3 / 1 | 147x147x32 |
pool | 3x3 / 2 | 147x147x64 |
conv | 3x3 / 1 | 73x73x64 |
conv | 3x3 / 2 | 71x71x80 |
conv | 3x3 / 1 | 35x35x192 |
inception module | 3个module | 35x35x288 |
inception module | 5个module | 17x17x768 |
inception module | 3个module | 8x8x1280 |
pool | 8x8 | 8x8x2048 |
linear | logits | 1x1x2048 |
softmax | 分类输出 | 1x1x1000 |
In [1]:
import tensorflow as tf
import tensorflow.contrib.slim as slim
In [2]:
# 用于产生截断的正态分布
trunc_normal = lambda stddev: tf.truncated_normal_initializer(0.0, stddev)
用于生成网络中经常用到的函数默认参数。比如卷积的激活函数、权重初始化方式、标准化器等。设置L2正则的weight_decay默认值为0.00004,标准差stddev默认值为0.1,参数batch_norm_var_collection默认值为moving_vars。接下来,定义batch normalization的参数字典,定义起衰减系数decay为0.997,epsilon为0.001,update_collections为tf.GraphKeys.UPDATE_OPS,然后字典variables_collections中beta和gamma均设置为None,moving_mean和moving_variance均设置为前面的batch_norm_var_collection。
而slim.arg_scope,这是一个非常有用的工具。可以给函数的参数自动赋予某些默认值。
In [4]:
def inception_v3_arg_scope(
weight_decay=0.00004,
stddev = 0.1,
batch_norm_var_collection='moving_vars'):
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],
}
}
with slim.arg_scope([slim.conv2d, slim.fully_connected],
weights_regularizer=slim.l2_regularizer(weight_decay)):
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
接下来定义函数inception_v3_base,它可以生成V3网络的卷积部分。参数inputs为输入的图片数据的tensor,scope为包含了函数默认参数的环境。
定义了一个字典表end_points,用来保存某些关键节点供之后使用。接着再使用slim.arg_scope对slim.conv2d,slim.max_pool2d和slim_avg_pool2d这三个函数设置默认值,将stride设为1,padding设为valid。
在设定默认参数的scope下,开始定义Inception V3网络结构。一开始是非Incption Module的卷积层,这里直接使用slim.conv2d来创建卷积层。
slim.conv2d:
按照表格结构一步步搭建出网络。因为已经在slim.arg_scope中设定一些默认值,比如padding或者stride的大小,所以接下来创建的时候有些参数就可以不用明文写出来,除非有所更改。这就给搭建网络带来很多方便也能减少代码量。
第一个Inception模块组包含了三个结构类似的Inception Module.
第一个Incetion Module -- Mixed_5b中有4个分支,从Branch_0到Branch_3:
第一个分支为64输出通道的1x1卷积;
第二个分支为48输出通道的1x1卷积,连接64输出通道的5x5卷积;
第三个分支为64输出通道的1x1卷积,再连接连续2个有96输出通道的3x3卷积;
第四个分支为3x3平均池化,连接1个32输出通道的1x1卷积.
完成以上四个分支,再利用tf.concat将四个分支合并起来,其实就是将输出通道合并起来:64+64+96+32=256.
接下来构建第二个inception module -- Mixed_5c. 这里依然使用之前的默认参数,同样是4个分支,唯一不同的是第4个分支最后接的是64输出通道的1x1卷积,而此前是32个输出通道.那么最终的尺寸就是: 64+64+96+64=288.
接下来是第三个inception module -- Mixed_5d.这个就和上一个一样.
第2个inception module模块组是一个非常大的模块组,包含了5个inception module,其中第2个到第5个inception module的结构非常类似.
其中第一个inception module名称为 Mixed_6a,它包含了3个分支.
第1个分支是一个384输出通道的3x3卷积,这个分支通道书一下就超过了之前的通道数之和.不过步长为2,所以图片是被压缩了,且padding设定为VALID,所以图片尺寸缩小为17x17;
第2个分支有三层,分别是一个64输出通道1x1卷积和两个96输出通道的3x3卷积.需要注意的是,最后一层的步长为2,padding同样设定为VALID,因此图片也被压缩,最终输出是17x17x96;
第3个分支是一个3x3最大池化层,步长同样为2,padding设定为VALID,因此tensor的尺寸为17x17x256.
In [ ]:
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'):
net = slim.conv2d(inputs, 32, [3, 3], stride=2, scope='Conv2d_1a_3x3')
net = slim.conv2d(net, 32, [3, 3], scope='Conv2d_2a_3x3')
net = slim.conv2d(net, 64, [3, 3], padding='SAME', scope='Conv2d_2b_3x3')
net = slim.max_pool2d(net, [3, 3], stride=2, scope='MaxPool_3a_3x3')
net = slim.conv2d(net, 80, [1, 1], scope='Conv2d_3b_1x1')
net = slim.conv2d(net, 192, [3, 3], scope='Conv2d_4a_3x3')
net = slim.max_pool2d(net, [3, 3], stride=2, scope='MaxPool_5a_3x3')
with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d],
stride=1, padding='SAME'):
# 第一个inception module name_scope设定为Mixed_5b
with tf.variable_scope('Mixed_5b'):
with tf.variable_scope('Branch_0'):
branch_0 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1')
with tf.variable_scope('Branch_1'):
branch_1 = slim.conv2d(net, 48, [1, 1], scope='Conv2d_0a_1x1')
branch_1 = slim.conv2d(branch_1, 64, [5, 5], scope='Conv2d_0b_5x5')
with tf.variable_scope('Branch_2'):
branch_2 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1')
branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0b_3x3')
branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0c_3x3')
with tf.variable_scope('Branch_3'):
branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')
branch_3 = slim.conv2d(branch_3, 32, [1, 1], scope='Conv2d_0b_1x1')
net = tf.concat([branch_0, branch_1, branch_2, branch_3], 3)
# 第二个inception module name_scope设定为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_1x1')
with tf.variable_scope('Branch_1'):
branch_1 = slim.conv2d(net, 48, [1, 1], scope='Conv2d_0a_1x1')
branch_1 = slim.conv2d(branch_1, 64, [5, 5], scope='Conv2d_0b_5x5')
with tf.variable_scope('Branch_2'):
branch_2 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1')
branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0b_3x3')
branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0c_3x3')
with tf.variable_scope('Branch_3'):
branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')
branch_3 = slim.conv2d(branch_3, 64, [1, 1], scope='Conv2d_0b_1x1')
net = tf.concat([branch_0, branch_1, branch_2, branch_3], 3)
# 第三个inception module name_scope设定为Mixed_5d
with tf.variable_scope('Mixed_5c'):
with tf.variable_scope('Branch_0'):
branch_0 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1')
with tf.variable_scope('Branch_1'):
branch_1 = slim.conv2d(net, 48, [1, 1], scope='Conv2d_0a_1x1')
branch_1 = slim.conv2d(branch_1, 64, [5, 5], scope='Conv2d_0b_5x5')
with tf.variable_scope('Branch_2'):
branch_2 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1')
branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0b_3x3')
branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0c_3x3')
with tf.variable_scope('Branch_3'):
branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')
branch_3 = slim.conv2d(branch_3, 64, [1, 1], scope='Conv2d_0b_1x1')
net = tf.concat([branch_0, branch_1, branch_2, branch_3], 3)
# 第二个模块组:第一个inception module
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_1x1')
with tf.variable_scope('Branch_1'):
branch_1 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_1a_1x1')
branch_1 = slim.conv2d(branch_1, 96, [3, 3], scope='Conv2_1b_3x3')
branch_1 = slim.conv2d(branch_1, 96, [3, 3], stride=2,
padding='VALID', scope='Conv2d_1c_1x1')
with tf.variable_scope('Branch_2'):
branch_2 = slim.max_pool2d(net, [3, 3], stride=2, padding='VALID', scope='Conv2d_1a_3x3')
net = tf.concat([branch_0, branch_1, branch_2], 3)