0. 前言
GoogLeNet是Google开发的一个卷积神经网络模型,获得了ILSVRC2014的冠军。GoogLeNet增加了模型的宽度和深度,它的深度有22层,但是参数却之后500万个,AlexNet是它的12倍,VGGNet的参数是它的3倍。从模型结果来看,GoogLeNet的性能更加优越,更适合内存和计算资源有限的场景。之所以取名为GoogLeNet,是为了向LeNet致敬。
论文原文:Going Deeper with Convolutions
1. Motivation
当前深度学习能提高性能的方法主要有两个:
- 增加深度 - 隐藏层的数量;
- 增加宽度-每一层单元的数量;
但是这样直接增大模型的规模(size)会存在一些问题:
【问题1】:模型规模越大,代表参数越多,这使得模型容易过拟合;
【问题2】:增加网络规模意味着需要更多的计算资源。
解决办法:将全连接变成稀疏连接。
但是在实现上,全连接变成稀疏连接后实际计算量并不会有质的提升,因为大部分硬件是针对密集矩阵计算优化的,稀疏矩阵虽然数据量少,但是计算所消耗的时间却很难减少(主要是lookups和cache misses的开销大)。
那么,有没有一种方法既能保持网络结构的稀疏性,又能利用密集矩阵的高计算性能。大量的文献表明可以将稀疏矩阵聚类为较为密集的子矩阵来提高计算性能,就如人类的大脑是可以看做是神经元的重复堆积,因此,GoogLeNet团队提出了Inception网络结构,就是构造一种“基础神经元”结构,来搭建一个稀疏性、高计算性能的网络结构。
在《Provable bounds for learning some deep representations》中提到“如果用大而稀疏的CNN来表示数据集的概率分布,那么可以通过逐层分析最后一层的Activation和具有高度相关的输出的聚类神经的相关统计数据来构建最优的网络拓补结构。”,尽管没有严格的数学证明,但是这很符合Hebbian principle的说法,这为网络架构的设计提供了有力的理论保障!
所谓的Hebbian principle是指:如果两个神经元常常同时产生动作电位,或者说同时激动(fire),这两个神经元之间的连接就会变强,反之则变弱(neurons that fire together, wire together)。
2. Inception结构
基于上面的分析,Inception结构主要思想就是找出卷积视觉网络中最优的局部稀疏结构是如何被现成的密集组件逼近和覆盖的。最基础的Inception架构就是上一层的输入分别经过1X1,3X3和5X5卷积核和一个3X3的池化核,然后再将结果组合起来作为下一层的输出:

为了避免对齐的问题,模型将卷积核限制在1X1,3X3和5X5中,这只是为了方便,而不是出于必要性。经过这些卷积核后会得到相同大小的特征图,然后将他们在通道上进行组合,作为下一层的输出。加入池化核是因为池化能提高额外的性能。
但是上面这个模型存在一个问题:随着层数的加深,特征图的通道数越来越多,这时候如果使用5X5的卷积核,将会产生大量的参数。 因此,在进行3X3和5X5卷积之前,先使用1X1的卷积核进行维度缩减,然后再进行卷积:

同时,在每一个卷积层后都需要使用ReLU来增加网络的非线性特征。
3. GoogLeNet-InceptionV1
网络的主要参数为:

其中#1X1,#3X3, #5X5是三种卷积核的数量,#3X3 reduce和#5X5 reduce是两者之前1X1卷积核的数量, pool proj是经过3X3池化核之后的1X1卷积核的数量。
整体结构示意图为:

【说明】
- 包括Inception模块的所有卷积,都用了修正线性单元(ReLU);
- 网络的感受野大小是224x224,采用RGB彩色通道,且减去均值;
- 网络中间的层次生成的特征会非常有区分性,给这些层增加一些辅助分类器。这些分类器以小卷积网络的形式放在Inception(4b)和Inception(4e)的输出上。在训练过程中,损失会根据折扣后的权重(折扣权重为0.3)叠加到总损失中。而在实际测试的时候,这两个额外的softmax会被去掉。
- dropout的概率是0.7。
4. InceptionV1的tensorflow实现
在GoogLeNet中最重要的部分就是实现Inception模块,我这里写了一个函数用于生成这样的一个模块,其基本思路就是将传入的特征图进行四次不同的运算(进入4个分支),运算的结果在通道这个维度上进行叠加,然后输出给下一层:
import tflearn
from tflearn.layers.core import input_data, dropout, fully_connected
from tflearn.layers.conv import conv_2d, max_pool_2d, avg_pool_2d
from tflearn.layers.estimator import regression
import tflearn.datasets.oxflower17 as oxflower17
import tensorflow as tf
import numpy as np
import matplotlib.image as mpimg
def Inception_model(network, para):
'''
定义Inception模块
:param network: 网络参数
:param para: 相关参数
:return:
'''
for i in range(1, para['maxlayer']+1):
# branch1 : 1*1
branch1 = conv_2d(network, para[i][0], 1, strides=1)
# branch2: 1*1 -> 3*3
branch2 = conv_2d(network, para[i][1][0], 1, strides=1)
branch2 = conv_2d(branch2, para[i][1][1], 3, strides=1)
# branch3: 1*1 -> 5*5
branch3 = conv_2d(network, para[i][2][0], 1, strides=1)
branch3 = conv_2d(branch3, para[i][2][1], 5, strides=1)
# branch4: max_pool -> 1*1
branch4 = max_pool_2d(network, 3, strides=1)
branch4 = conv_2d(branch4, para[i][3], 1, strides=1)
# 按通道数合并, 第三个维度
network = tf.concat([branch1, branch2, branch3, branch4], 3)
if para['is_maxpool']:
network = max_pool_2d(network, para['pool_para'][0], strides=para['pool_para'][1])
else:
network = avg_pool_2d(network, para['pool_para'][0], strides=para['pool_para'][1])
return network
有了上面的这个Inception模块,我们就可以很容易的搭建GoogLeNet:
def GoogLeNet():
# 定义输入层
network = input_data(shape=[None, 227, 227, 3])
# 第1层卷积
network = conv_2d(network, 64, 7, strides=2, activation='relu')
network = max_pool_2d(network, 3, strides=2)
# 第2层卷积
network = conv_2d(network, 64, 3, strides=2, activation='relu')
network = max_pool_2d(network, 3, strides=2)
# inception模块3a, 3b
para = {1: [64, [96, 128], [16, 32], 32], # [1*1, [1*1, 3*3], [1*1, 3*3], pool-1*1]
2: [128, [128, 192], [32, 96], 64],
'maxlayer': 2,
'is_maxpool': True,
'pool_para': [3, 2]}
network = Inception_model(network, para)
# inception模块4a, 4b, 4c, 4d, 4e
para = {1: [192, [96, 208], [16, 48], 64],
2: [160, [112, 224], [24, 64], 64],
3: [128, [128, 256], [24, 64], 64],
4: [112, [144, 288], [32, 64], 64],
5: [256, [160, 320], [32, 128], 128],
'maxlayer': 5,
'is_maxpool': True,
'pool_para': [3, 2]
}
network = Inception_model(network, para)
# inception模块5a, 5b
para = {1: [256, [160, 320], [32, 128], 128],
2: [384, [192, 384], [48, 128], 128],
'maxlayer': 2,
'is_maxpool': False,
'pool_para': [7, 1]
}
network = Inception_model(network, para)
# dropout, 概率为70%
network = dropout(network, 0.7)
# 最后一层为linear层[在ILSVRC中为1000, 这里使用的oxflowers数据集,类别为17]
network = fully_connected(network, 17, activation='softmax')
return network
完整代码如下:
import tflearn
from tflearn.layers.core import input_data, dropout, fully_connected
from tflearn.layers.conv import conv_2d, max_pool_2d, avg_pool_2d
from tflearn.layers.estimator import regression
import tflearn.datasets.oxflower17 as oxflower17
import tensorflow as tf
import numpy as np
import matplotlib.image as mpimg
def loadData():
X, Y = oxflower17.load_data(dirname='17flowers/', one_hot=True, resize_pics=(227, 227))
return X, Y
def Inception_model(network, para):
'''
定义Inception模块
:param network: 网络参数
:param para: 相关参数
:return:
'''
for i in range(1, para['maxlayer']+1):
# branch1 : 1*1
branch1 = conv_2d(network, para[i][0], 1, strides=1)
# branch2: 1*1 -> 3*3
branch2 = conv_2d(network, para[i][1][0], 1, strides=1)
branch2 = conv_2d(branch2, para[i][1][1], 3, strides=1)
# branch3: 1*1 -> 5*5
branch3 = conv_2d(network, para[i][2][0], 1, strides=1)
branch3 = conv_2d(branch3, para[i][2][1], 5, strides=1)
# branch4: max_pool -> 1*1
branch4 = max_pool_2d(network, 3, strides=1)
branch4 = conv_2d(branch4, para[i][3], 1, strides=1)
# 按通道数合并, 第三个维度
network = tf.concat([branch1, branch2, branch3, branch4], 3)
if para['is_maxpool']:
network = max_pool_2d(network, para['pool_para'][0], strides=para['pool_para'][1])
else:
network = avg_pool_2d(network, para['pool_para'][0], strides=para['pool_para'][1])
return network
def GoogLeNet():
# 定义输入层
network = input_data(shape=[None, 227, 227, 3])
# 第1层卷积
network = conv_2d(network, 64, 7, strides=2, activation='relu')
network = max_pool_2d(network, 3, strides=2)
# 第2层卷积
network = conv_2d(network, 64, 3, strides=2, activation='relu')
network = max_pool_2d(network, 3, strides=2)
# inception模块3a, 3b
para = {1: [64, [96, 128], [16, 32], 32], # [1*1, [1*1, 3*3], [1*1, 3*3], pool-1*1]
2: [128, [128, 192], [32, 96], 64],
'maxlayer': 2,
'is_maxpool': True,
'pool_para': [3, 2]}
network = Inception_model(network, para)
# inception模块4a, 4b, 4c, 4d, 4e
para = {1: [192, [96, 208], [16, 48], 64],
2: [160, [112, 224], [24, 64], 64],
3: [128, [128, 256], [24, 64], 64],
4: [112, [144, 288], [32, 64], 64],
5: [256, [160, 320], [32, 128], 128],
'maxlayer': 5,
'is_maxpool': True,
'pool_para': [3, 2]
}
network = Inception_model(network, para)
# inception模块5a, 5b
para = {1: [256, [160, 320], [32, 128], 128],
2: [384, [192, 384], [48, 128], 128],
'maxlayer': 2,
'is_maxpool': False,
'pool_para': [7, 1]
}
network = Inception_model(network, para)
# dropout, 概率为70%
network = dropout(network, 0.7)
# 最后一层为linear层[在ILSVRC中为1000, 这里使用的oxflowers数据集,类别为17]
network = fully_connected(network, 17, activation='softmax')
return network
def main():
# 加载数据
X, Y = loadData()
# 获取参数
network = GoogLeNet()
network = regression(network, optimizer='momentum')
print('-'*15, 'Start training... ', '-'*15)
# Start training
model = tflearn.DNN(network, checkpoint_path='model_GoogLeNet/', max_checkpoints=5, tensorboard_verbose=0, tensorboard_dir='logs/')
model.fit(X, Y, n_epoch=100, validation_set=0.1, shuffle=True, show_metric=True, batch_size=32, snapshot_step=200, snapshot_epoch=False, run_id='googlenet_oxflowers17')
model.save('E:/python_project/CNN/07-CNN/model_file/')
if __name__ == '__main__':
main()
程序运行完成之后,可以在tensorboard中查看网络拓扑:

可以看到跟论文中的一样。
【一点感想:】
- 不得不说,GoogLeNet训练的速度确实比AlexNet和VGGNet要快;
- 另一方面,作者也说了,虽然这个模型在计算机视觉领域取得了成功,但是他的成功是否归因于结构的指导原则还有待商榷,需要更多的分析和验证。
总之,目前已经看了三篇CNN的文章AlexNet、VGGNet和GoogLeNet,但是总是觉得作者在描述他们的模型的时候,都是尽量的描述其结构的合理性,都是从果到因这样一个逻辑顺序,但是为什么是这样,这里面的一些数学原理貌似都没有详细的说明。这一方面说明了当前的计算机视觉方面的模型或者架构都是以结果为导向的,结果好就行,至于为什么结果好,还有待研究。这也是神经网络作为一种黑盒模型难以被理解的地方。另一方面也说明要在深度学习领域要有所建树必须要持续的学习、需要一些灵感还有就是坚持不懈的实验!