Google Inception Net是一个大家族,包括Inception V1、Inception V2、Inception V3和Inception V4,而且都取得了不错的效果。
Inception V1
Inception V1有22层深,相比于AlexNet的8层和VGGNet的19层都要深,但是计算量却小了很多,只有AlexNet参数量的1/12,却可以达到优胜于AlexNet的准确率。其降低参数量的目的有两点:
- 参数越多规模越庞大,需要提供模型学习的数据量就越大
- 参数越多,耗费的计算资源越大
Inception V1参数量少但效果好有以下原因:
- 模型层数能力更深,表达能力更强
- 去除了最后的全连接层,用全局平均池化层(将图片尺寸变为1×1)
- Inception V1使用了精心设计的
Inception Module
提高了参数的利用率关于Inception V1的设计借鉴了NIN的思想,就如同大网络中的一个小网络,通过反复堆叠形成一个大网路。一般来说卷积层要提升表达能力,主要依靠增加输出通道数,但副作用是计算量大和过拟合,因为每一个输出通道对应一个滤波器,同一个滤波器共享参数,只能提取一类特征,因此一个输出通道只能做一种特征处理。而NIN的MLPConv拥有更强大的能力,允许在输出通道之间组合信息,其基本等效于普通卷积层再连接1×1的卷积层和ReLU激活函数。
Inception Module
Inception Module的基本结构包括四个分支:
- 第一个分支,对输入进行1×1的卷积,它可以夸通道组织信息,提高网络的表达能力,同时可以对输出通道升维和降维操作,降低计算量成本。
- 第二个分支,先使用了1×1卷积,然后连接3×3卷积,相当于两次特征变换。
- 第三个分支,先是1×1的卷积,然后连接5×5的卷积。
- 第四个分支,先使用3×3最大池化层后直接使用1×1卷积。
最终将这四个分支通过一个聚合操作合并(在输出通道数这个维度上聚合)。
Inception Module可以让网络的深度和宽度高效率地扩充,提升准确率却不至于过拟合。人脑神经元的连接是稀疏的,Inception Net的主要目标就是找到最优的稀疏结构单元(Inception Module)。
Hebbian原理
表明一起发射的神经元会连接在一起,所以应该把相关性高的一簇神经元节点连接在一起,在普通的数据集中,可能需要对神经元节点聚类,但是图片数据中临近区域的数据相关性,可以直接通过卷积操作连接在一起。所以1×1的的卷积就可以很自然地把这些相关性很高的、在同一个空间位置但在不同通道的特征连接在一起。
在整个网络中,使用了多个堆叠的Inception Module,我们希望靠后的Inception Module可以捕捉更高阶的抽象特征,因此越靠后的Inception Module的卷积的空间集中度应该逐渐降低,这样可以捕获更大面积的特征,因此越靠后,Inception Module中的3×3和5×5卷积核的占比应该更多。
Inception V2
Inception V2学习了VGGNet,使用两个3×3的卷积核代替5×5的卷积核,还提出了BN(Batch Normalization)
,是一种非常有效的正则化方法。其思想是BN用于神经网络某层时,会对每一个mini-batch数据的内部进行标准化处理,使输出规范化到N(0, 1)的正态分布,减少了内部神经元分布的改变。
传统的深度神经网络再训练时,每一层的输出都在变化,导致训练变得困难,只能用一个很小的学习速率解决问题,而使用了BN之后,学习速率可以增大很多倍。
Inception V3
Inception V3引入了Factoriztion into small convolutions
的思想,将一个较大的二维卷积拆成两个较小的一维卷积,比如将3×3卷积拆成1×3和3×1的两个卷积,即节约了参数又加速运算减轻了过拟合,同时增加了一层非线性扩展模型表达能力。
Inception V3优化了Inception Module的结构,在Inception Molule中使用了分支,还在分支中使用了分支,也就是Network In Network in Network
。
Inception V4
相比于Inception V3,Inception V4主要结合了ResNet
,这个接下来学习了再总结。
Inception V3的结构比较复杂,所以使用了tf.contrib.slim辅助设计这个网络,通过使用contrib.slim中的一些功能和组件可以大大减少设计的代码量。
以下是每一段的输出尺寸:
InceptionV3/InceptionV3/Mixed_5b/concat [32, 35, 35, 256]
InceptionV3/InceptionV3/Mixed_5c/concat [32, 35, 35, 288]
InceptionV3/InceptionV3/Mixed_5d/concat [32, 35, 35, 288]
InceptionV3/InceptionV3/Mixed_6a/concat [32, 17, 17, 768]
InceptionV3/InceptionV3/Mixed_6b/concat [32, 17, 17, 768]
InceptionV3/InceptionV3/Mixed_6c/concat [32, 17, 17, 768]
InceptionV3/InceptionV3/Mixed_6d/concat [32, 17, 17, 768]
InceptionV3/InceptionV3/Mixed_6e/concat [32, 17, 17, 768]
InceptionV3/InceptionV3/Mixed_7a/concat [32, 8, 8, 1280]
InceptionV3/InceptionV3/Mixed_7b/concat [32, 8, 8, 2048]
InceptionV3/InceptionV3/Mixed_7c/concat [32, 8, 8, 2048]
InceptionV3/AuxLogits/SpatialSqueeze [32, 1000]
InceptionV3/Logits/SpatialSqueeze [32, 1000]
由于代码比较长,这里只放了第一个Inception模块的实现,具体实现现点这里:
with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d],
stride=1, padding='SAME'):
# 第一个Inception模块的第一个Inception Model
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)
print_activations(net)
# 第一个Inception模块的第二个Inception Model
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_0b_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)
print_activations(net)
# 第一个Inception模块的第三个Inception Model
with tf.variable_scope('Mixed_5d'):
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_0b_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)
print_activations(net)
总结
- Factorization into small convolutions很有效,可以降低参数量、减轻过拟合,增加网络非线性的表达能力。
- 卷积网络从输入到输出,应该让图片尺寸逐减小,输出通道数逐渐增加,即让空间结构简化,将空间信息转化为高阶抽象的特征信息。
- Inception Module用多个分支提取不同抽象程度的高阶特征的思路很有效,可以丰富网络的表达能力。