Inception Module 是Inception Net中精心设计的模块。是大网络中的小网络。沿用了Network In Network的设计思路。不过更近一步的增加了分支网络。

Inception Module的基本结构(Inception V1):

  1. 第一个分支对输入进行1x1卷积,1x1卷积是非常优秀的结构,可以跨通道组织信息,提高网络的表达能力,同时可以对输出通道升维或者降维;
  2. 第二分支先使用了1x1卷积,然后连接3x3卷积,相当于进行了两次特征变化
  3. 第三分支先使用了1x1卷积,然后做5x5卷积
  4. 最后一个分支则是先进行3x3最大池化,然后进行1x1卷积

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:

  1. 输入的tensor
  2. 输出的管道数outchannel
  3. 卷积核尺寸filter_size
  4. 步长stride
  5. 对其方式padding

按照表格结构一步步搭建出网络。因为已经在slim.arg_scope中设定一些默认值,比如padding或者stride的大小,所以接下来创建的时候有些参数就可以不用明文写出来,除非有所更改。这就给搭建网络带来很多方便也能减少代码量。

Inception Module Group 1

第一个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.这个就和上一个一样.

Inception Module Group 2

第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)