从 L e n e t 5 Lenet 5 Lenet5 开始,传统的卷积网络都是堆叠卷积层(也会接归一化层)、池化层然后接一个或者多个全连接层,研究人员基于这种模式设计了很多网络,它们在MNIST、CIFAR、ImageNet 数据集上都取得了很好的结果,对于较大较深的网络,则使用Dropout来缓解过拟合问题。
一般来说,提高网络的深度(增加网络层数)和宽度(增加每一层神经元的个数),是提高网络性能的最直接方式,但是由此带来的问题是:
参数增多,特别是带标签的数据有限的情况下,很容易造成过拟合。而一些数据的标记工作可能需要“专家”来完成,代价也会很昂贵,论文中举了一个ImageNet的数据标注例子,如下图
没有专业的知识,你可能很难分清两条狗的详细类型
计算量急剧增大
增加网络深度,会造成计算量剧增,例如将两个卷积层连接在一起,第一层卷积使用 128 128 128个大小为 3 ∗ 3 ∗ 64 3*3*64 3∗3∗64的卷积核,下一层卷积使用 256 256 256个大小为 3 ∗ 3 ∗ 128 3*3*128 3∗3∗128的卷积核,此时网络性能可能有所提升,但是参数量增加了 4 4 4倍,如果这些新增的网络层利用率较低(大部分权重值趋于 0 0 0),则会造成计算资源的严重浪费。因此在计算资源有限的情况下,不能为了提高网络性能而一味地增加网络深度或者宽度。
为了解决这两个问题,作者认为需要引入稀疏性,包括将全连接转换成稀疏连接,以及在卷积内部也实现稀疏连接。引入稀疏性的原因是:
生物学的神经网络本身就具有稀疏性
Arora等人的研究表明,如果数据集的概率分布可以用一个大的、稀疏的深度神经网络来表示,那么可以通过分析激活值的相关性统计以及将输出值相关性高的神经元聚合在一起的方式,逐层分析出该最优的网络结构
Hebbian 理论:neurons that fire together, wire together,即如果两个神经元经常被同时激活,则这两个神经元的相关性极高。反之,如果两个神经元经常被分开激活,则两个神经元的相关性极低。
关于该理论在人工神经网络引入稀疏性的理论支撑在于:
如果两个神经元经常被同时激活,则这两个神经元的相关性极高,在人工神经网络中则表现为两个神经元的连接权重增大
如果两个神经元经常被分开激活,则两个神经元的相关性极低,在人工神经网络中则表现为两个神经元的连接权重很小,甚至两个神经元的连接毫无意义(即权重为 0 0 0),这种无意义的神经元连接,不需要连接两个神经元,也就由此引入了稀疏性
然而,引入稀疏性带来的问题是,当前的计算设备在处理非均匀的稀疏数据结构时非常低效,即使运算量减少100倍,查询和cache misses的开销很大,计算效率依然低下。 另外,由于数值库利用CPU和GPU的硬件特性对稠密矩阵计算进行的很多优化,使得计算设备处理稠密数据和稀疏数据的性能差距更大。
现在矛盾的点是,我们既想引入稀疏性,又想使用稠密矩阵的高速计算性能。
如何引入稀疏性?
我们一般通过卷积来实现稀疏性。然而卷积是对前一层patches(每次进行卷积计算时,卷积核覆盖输入图的区域大小)的稠密连接,这句话我的理解是:
卷积既有稀疏性,也能利用稠密计算的性能
卷积过程有稠密连接,言外之意,应该考虑将这个稠密连接也转换成稀疏连接
为了让卷积过程也引入稀疏性,传统的卷积网络在特征空间中使用随机稀疏连接表,比如 L e N e t 5 LeNet5 LeNet5的结构中就采用了这种方式,其论文(Gradient-Based Learning Applied to Do cument Recognition) 提到,在 L e N e t 5 LeNet5 LeNet5网络结构中, S 2 S2 S2层(大小为 14 ∗ 14 ∗ 6 14*14*6 14∗14∗6)经过卷积得到 C 3 C3 C3层(大小为 10 ∗ 10 ∗ 16 10*10*16 10∗10∗16),采用了如下图所示的稀疏连接表(如 C 3 C3 C3的前 4 4 4个特征(通道)图来自 S 2 S2 S2层不同但连续的三个特征卷积得来)
为了能使得卷积使用稠密矩阵的计算特性, A l e x N e t AlexNet AlexNet 又移除了特征空间的稀疏连接。那么问题来了,我们能否在滤波器层面上也使用稀疏模型,并使用稠密矩阵进行计算?答案是肯定的,正是我们等下要介绍的 I n c e p t i o n Inception Inception模块。另外作者在这里还提到,大量关于稀疏矩阵运算的文献(如On two-dimensional sparse matrix par-titioning: Models, methods, and a recipe)认为可以将稀疏矩阵聚类为相对密集的子矩阵来提高计算稀疏矩阵的运行效率。
总之,我们最终的目的,就是找到一个基于卷积网络的最优稀疏模块,且该模块可以通过密集组件进行构建。
最终,提出了 I n c e p t i o n Inception Inception模块的简单版本,如下图所示:
模型有以下特点:
这种模型的存在的问题是:
运算量巨大
假设输入图的大小为 28 ∗ 28 ∗ 192 28*28*192 28∗28∗192,Inception模块的几个卷积配置为: 64 64 64个 1 ∗ 1 ∗ 192 1*1*192 1∗1∗192的卷积核、 128 128 128个 3 ∗ 3 ∗ 192 3*3*192 3∗3∗192的卷积核、 32 32 32个 5 ∗ 5 ∗ 192 5*5*192 5∗5∗192的卷积核进行卷积。 大卷积核的计算量也会很大,我们以 5 ∗ 5 5*5 5∗5卷积为例,计算其运算次数,使用了 32 32 32个 5 ∗ 5 ∗ 192 5*5*192 5∗5∗192的卷积核进行卷积,将得到 28 ∗ 28 ∗ 32 28*28*32 28∗28∗32的特征图,共有 28 ∗ 28 ∗ 32 = 25088 28*28*32=25088 28∗28∗32=25088个特征值,每个特征值需要执行 5 ∗ 5 ∗ 192 = 4800 5*5*192=4800 5∗5∗192=4800次乘法,总共需要 计算 25088 ∗ 4800 = 120422400 25088*4800=120422400 25088∗4800=120422400次乘法。 这里我们值计算乘法运算次数,不计算加法次数。
由于最大池化不改变输入图的通道大小,执行完卷积核池化之后需要将特征图沿通道方向拼接在一起,随着Inception模块的堆叠,模块输出的特征图通道数越来越大,导致计算崩溃。
因此对该模块的结构进行了优化,如下图所示
改进后的结构在每次卷积之前,最大池化之后加入 1 ∗ 1 1*1 1∗1的卷积。还是以假设输入图的大小为 28 ∗ 28 ∗ 192 28*28*192 28∗28∗192,Inception模块的几个卷积配置为: 64 64 64个 1 ∗ 1 1*1 1∗1的卷积核、 128 128 128个 3 ∗ 3 3*3 3∗3的卷积核、 32 32 32个 5 ∗ 5 5*5 5∗5的卷积核进行卷积为例。在 5 ∗ 5 5*5 5∗5的卷积之前,添加 16 16 16个 1 ∗ 1 1*1 1∗1的卷积核,将得到 28 ∗ 28 ∗ 16 28*28*16 28∗28∗16的特征图,此时的的乘法计算次数为 28 ∗ 28 ∗ 16 ∗ 1 ∗ 1 ∗ 192 = 2408448 28*28*16*1*1*192=2408448 28∗28∗16∗1∗1∗192=2408448, 之后需要使用 32 32 32个 5 ∗ 5 5*5 5∗5的卷积核对 28 ∗ 28 ∗ 16 28*28*16 28∗28∗16的特征图进行卷积,将得到 28 ∗ 28 ∗ 32 28*28*32 28∗28∗32的特征图,此时需要的乘法计算量为 28 ∗ 28 ∗ 32 ∗ 5 ∗ 5 ∗ 16 = 10035200 28*28*32*5*5*16=10035200 28∗28∗32∗5∗5∗16=10035200,总共的乘法计算量为 10035200 + 2408448 = 12443648 10035200+2408448 = 12443648 10035200+2408448=12443648,如果不添加 1 ∗ 1 1*1 1∗1卷积其乘法计算量为 120422400 120422400 120422400, 添加之后计算量仅为之前的 1 10 \frac{1}{10} 101
利用Inception模块构建GoogLeNet,参数设置如下表所示
其完整的网络结构如下图所示:
#!/usr/bin/python3
# @Time : 2021/04/01 10:07
# @Author :
# @File : GoogLeNet
# @Software: PyCharm
# @Description : 使用TensorFlow实现GoogLeNet网络结构
import tensorflow as tf
from tensorflow.keras.layers import Layer,Conv2D,MaxPool2D,Dropout,Activation,Flatten,Dense,AvgPool2D,concatenate,Softmax,Input
# 定义Inception 模块类
class Inception(Layer,):
def __init__(self, cov1_1,cov_reduce3_3,cov3_3,cov_reduce5_5,cov5_5,pool_proj, **kwargs):
super(Inception, self).__init__(**kwargs)
self.branch1 = Conv2D(filters=cov1_1,kernel_size=1,activation='relu')
self.branch2 = tf.keras.Sequential([
Conv2D(filters=cov_reduce3_3,kernel_size=1,activation='relu'),
Conv2D(filters=cov3_3,kernel_size=3,padding='same',activation='relu')
])
self.branch3 = tf.keras.Sequential([
Conv2D(filters=cov_reduce5_5,kernel_size=1,activation='relu'),
Conv2D(filters=cov5_5,kernel_size=5,padding='same',activation='relu')
])
self.branch4 = tf.keras.Sequential([
MaxPool2D(pool_size=3,strides=1,padding='same'),
Conv2D(filters=pool_proj,kernel_size=1,activation='relu')
])
def call(self,inputs):
branch1 = self.branch1(inputs)
branch2 = self.branch2(inputs)
branch3 = self.branch3(inputs)
branch4 = self.branch4(inputs)
outputs = concatenate([branch1,branch2,branch3,branch4])
return outputs
# 定义辅助分类器
class AuxiliaryClassifier(Layer,):
def __init__(self, **kwargs):
super(AuxiliaryClassifier, self).__init__(**kwargs)
self.avgpool = AvgPool2D(pool_size=5,strides=3)
self.conv = Conv2D(filters=128,kernel_size=1,activation='relu')
self.flatten = Flatten()
self.fc1 = Dense(units=1024,activation="relu")
self.dropout = Dropout(rate=0.7)
self.fc2 = Dense(units=1000,activation="softmax")
def call(self,inputs):
x = self.avgpool(inputs)
x = self.conv(x)
x = self.flatten(x)
x = self.fc1(x)
x = self.dropout(x)
outputs = self.fc2(x)
return outputs
def GoogLeNet(model_name='GoogLeNet',im_height=224,im_width=224,class_num=1000):
# inputs = tf.keras.Input(shape=(im_height,im_width,3),name='Inputs')
inputs = Input(shape=(im_height,im_width,3),name='Inputs')
# input [224,224,3] --> [112,112,64]
x = Conv2D(filters=64,kernel_size=7,strides=2,padding='same',activation='relu',name='convolution-1')(inputs)
# [112,112,64] --> [56,56,64]
x = MaxPool2D(pool_size=3,strides=2,padding='same',name='max-pool-1')(x)
# [56,56,64] --> [56,56,192]
x = Conv2D(filters=192,kernel_size=3,strides=1,padding='same',activation='relu',name='convolution-2')(x)
# [56,56,192] --> [28,28,192]
x = MaxPool2D(pool_size=3,strides=2,padding='same',name='max-pool-2')(x)
# [28,28,192] --> [28, 28, 256]
x = Inception(64,96,128,16,32,32,name='inception-3a')(x)
# [28, 28, 256] --> [28, 28, 480]
x = Inception(128,128,192,32,96,64,name='inception-3b')(x)
# [28, 28, 480] --> [14, 14, 480]
x = MaxPool2D(pool_size=3,strides=2,padding='same',name='max-pool-3')(x)
# [14, 14, 480] --> [14, 14, 512]
x = Inception(192,96,208,16,48,64,name='inception-4a')(x)
# [14, 14, 512] --> [1000]
outputs1 = AuxiliaryClassifier(name='AuxiliaryClassifier-1')(x)
# [14, 14, 512] --> [14, 14, 512]
x = Inception(160,112,224,24,64,64,name='inception-4b')(x)
# [14, 14, 512] --> [14, 14, 512]
x = Inception(128,128,256,24,64,64,name='inception-4c')(x)
# [14, 14, 512] --> [14, 14, 528]
x = Inception(112,144,288,32,64,64,name='inception-4d')(x)
# [14, 14, 528] --> [1000,]
outputs2 = AuxiliaryClassifier(name='AuxiliaryClassifier-2')(x)
# [14, 14, 528] --> [14, 14, 832]
x = Inception(256,160,320,32,128,128,name='inception-4e')(x)
# [14, 14, 832] --> [7, 7, 832]
x = MaxPool2D(pool_size=3,strides=2,padding='same',name='max-pool-4')(x)
# [7, 7, 832] --> [7, 7, 832]
x = Inception(256,160,320,32,128,128,name='inception-5a')(x)
# [7, 7, 832] --> [7, 7, 1024]
x = Inception(384,192,384,48,128,128,name='inception-5b')(x)
# [7, 7, 1024] --> [1, 1, 1024]
x = AvgPool2D(pool_size=7,strides=1,name='avg-pool')(x)
# [1, 1, 1024] --> [1024,]
x = Flatten(name='flatten')(x)
x = Dropout(rate=0.4,name='dropout')(x)
x = Dense(units=1000,name='linear')(x)
outputs = Softmax(name='softmax')(x)
model = tf.keras.models.Model(inputs=inputs,outputs=[outputs1,outputs2,outputs],name='GoogLeNet')
return model
model = GoogLeNet()
model.summary()
模型摘要如下
Model: "GoogLeNet"
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
Inputs (InputLayer) [(None, 224, 224, 3) 0
__________________________________________________________________________________________________
convolution-1 (Conv2D) (None, 112, 112, 64) 9472 Inputs[0][0]
__________________________________________________________________________________________________
max-pool-1 (MaxPooling2D) (None, 56, 56, 64) 0 convolution-1[0][0]
__________________________________________________________________________________________________
convolution-2 (Conv2D) (None, 56, 56, 192) 110784 max-pool-1[0][0]
__________________________________________________________________________________________________
max-pool-2 (MaxPooling2D) (None, 28, 28, 192) 0 convolution-2[0][0]
__________________________________________________________________________________________________
inception-3a (Inception) (None, 28, 28, 256) 163696 max-pool-2[0][0]
__________________________________________________________________________________________________
inception-3b (Inception) (None, 28, 28, 480) 388736 inception-3a[0][0]
__________________________________________________________________________________________________
max-pool-3 (MaxPooling2D) (None, 14, 14, 480) 0 inception-3b[0][0]
__________________________________________________________________________________________________
inception-4a (Inception) (None, 14, 14, 512) 376176 max-pool-3[0][0]
__________________________________________________________________________________________________
inception-4b (Inception) (None, 14, 14, 512) 449160 inception-4a[0][0]
__________________________________________________________________________________________________
inception-4c (Inception) (None, 14, 14, 512) 510104 inception-4b[0][0]
__________________________________________________________________________________________________
inception-4d (Inception) (None, 14, 14, 528) 605376 inception-4c[0][0]
__________________________________________________________________________________________________
inception-4e (Inception) (None, 14, 14, 832) 868352 inception-4d[0][0]
__________________________________________________________________________________________________
max-pool-4 (MaxPooling2D) (None, 7, 7, 832) 0 inception-4e[0][0]
__________________________________________________________________________________________________
inception-5a (Inception) (None, 7, 7, 832) 1043456 max-pool-4[0][0]
__________________________________________________________________________________________________
inception-5b (Inception) (None, 7, 7, 1024) 1444080 inception-5a[0][0]
__________________________________________________________________________________________________
avg-pool (AveragePooling2D) (None, 1, 1, 1024) 0 inception-5b[0][0]
__________________________________________________________________________________________________
flatten (Flatten) (None, 1024) 0 avg-pool[0][0]
__________________________________________________________________________________________________
dropout (Dropout) (None, 1024) 0 flatten[0][0]
__________________________________________________________________________________________________
linear (Dense) (None, 1000) 1025000 dropout[0][0]
__________________________________________________________________________________________________
AuxiliaryClassifier-1 (Auxiliar (None, 1000) 3188840 inception-4a[0][0]
__________________________________________________________________________________________________
AuxiliaryClassifier-2 (Auxiliar (None, 1000) 3190888 inception-4d[0][0]
__________________________________________________________________________________________________
softmax (Softmax) (None, 1000) 0 linear[0][0]
==================================================================================================
Total params: 13,374,120
Trainable params: 13,374,120
Non-trainable params: 0