卷积神经网络之GoogLeNet(附完整代码)

2014年,GoogLeNet获得了第一名、VGG获得了第二名

(1)参数太多,如果训练数据集有限,很容易产生过拟合;--比如VGG
(2)网络越大、参数越多,计算复杂度越大,难以应用;
(3)网络越深,容易出现梯度弥散问题(梯度越往后穿越容易消失),难以优化模型。

为了减少参数,自然就想到将全连接变成稀疏连接,但是在实现上,全连接变成稀疏连接后实际计算量并不会有质的提升,,因为大部分硬件是针对密集矩阵计算优化的,稀疏矩阵虽然数据量少,但是计算所消耗的时间却很难减少。

【问题来了】什么是Inception呢?

一、Inception V1 原始结构

image

该结构将CNN中常用的卷积(1x1,3x3,5x5)、池化操作(3x3)堆叠在一起(卷积、池化后的尺寸相同,将通道相加),一方面增加了网络的宽度,另一方面也增加了网络对尺度的适应性。
网络卷积层中的网络能够提取输入的每一个细节信息,同时5x5的滤波器也能够覆盖大部分接受层的的输入。还可以进行一个池化操作,以减少空间大小,降低过度拟合。在这些层之上,在每一个卷积层后都要做一个ReLU操作,以增加网络的非线性特征。然而这个Inception原始版本,所有的卷积核都在上一层的所有输出上来做,而那个5x5的卷积核所需的计算量就太大了,造成了特征图的厚度很大,为了避免这种情况,在3x3前、5x5前、max pooling后分别加上了1x1的卷积核,以起到了降低特征图厚度的作用,这也就形成了Inception v1的网络结构,如下图所示:

image

1x1的卷积核有什么用呢?
1x1卷积的主要目的是为了减少维度,还用于修正线性激活(ReLU)。比如,上一层的输出为100x100x128,经过具有256个通道的5x5卷积层之后(stride=1,pad=2),输出数据为100x100x256,其中,卷积层的参数为128x5x5x256= 819200。而假如上一层输出先经过具有32个通道的1x1卷积层,再经过具有256个输出的5x5卷积层,那么输出数据仍为为100x100x256,但卷积参数量已经减少为128x1x1x32 + 32x5x5x256= 204800,大约减少了4倍。

基于Inception构建了GoogLeNet的网络结构如下(共22层):


image

对上图说明如下:
(1)GoogLeNet采用了模块化的结构(Inception结构),方便增添和修改;
(2)网络最后采用了average pooling(平均池化)来代替全连接层,该想法来自NIN(Network in Network),事实证明这样可以将准确率提高0.6%。但是,实际在最后还是加了一个全连接层,主要是为了方便对输出进行灵活调整;
(3)虽然移除了全连接,但是网络中依然使用了Dropout ;
(4)为了避免梯度消失,网络额外增加了2个辅助的softmax用于向前传导梯度(辅助分类器)。辅助分类器是将中间某一层的输出用作分类,并按一个较小的权重(0.3)加到最终分类结果中,这样相当于做了模型融合,同时给网络增加了反向传播的梯度信号,也提供了额外的正则化,对于整个网络的训练很有裨益。而在实际测试的时候,这两个额外的softmax会被去掉。

GoogLeNet的网络结构图细节如下:


image

注:上表中的“#3x3 reduce”,“#5x5 reduce”表示在3x3,5x5卷积操作之前使用了1x1卷积的数量。

GoogLeNet网络结构明细表解析如下:
0、输入
原始输入图像为224x224x3,且都进行了零均值化的预处理操作(图像每个像素减去均值)。
1、第一层(卷积层)
使用7x7的卷积核(滑动步长2,padding为3),64通道,输出为112x112x64,卷积后进行ReLU操作
经过3x3的max pooling(步长为2),输出为((112 - 3+1)/2)+1=56,即56x56x64,再进行ReLU操作
2、第二层(卷积层)
使用3x3的卷积核(滑动步长为1,padding为1),192通道,输出为56x56x192,卷积后进行ReLU操作
经过3x3的max pooling(步长为2),输出为((56 - 3+1)/2)+1=28,即28x28x192,再进行ReLU操作
3a、第三层(Inception 3a层)
分为四个分支,采用不同尺度的卷积核来进行处理
(1)64个1x1的卷积核,然后RuLU,输出28x28x64
(2)96个1x1的卷积核,作为3x3卷积核之前的降维,变成28x28x96,然后进行ReLU计算,再进行128个3x3的卷积(padding为1),输出28x28x128
(3)16个1x1的卷积核,作为5x5卷积核之前的降维,变成28x28x16,进行ReLU计算后,再进行32个5x5的卷积(padding为2),输出28x28x32
(4)pool层,使用3x3的核(padding为1),输出28x28x192,然后进行32个1x1的卷积,输出28x28x32。
将四个结果进行连接,对这四部分输出结果的第三维并联,即64+128+32+32=256,最终输出28x28x256
3b、第三层(Inception 3b层)
(1)128个1x1的卷积核,然后RuLU,输出28x28x128
(2)128个1x1的卷积核,作为3x3卷积核之前的降维,变成28x28x128,进行ReLU,再进行192个3x3的卷积(padding为1),输出28x28x192
(3)32个1x1的卷积核,作为5x5卷积核之前的降维,变成28x28x32,进行ReLU计算后,再进行96个5x5的卷积(padding为2),输出28x28x96
(4)pool层,使用3x3的核(padding为1),输出28x28x256,然后进行64个1x1的卷积,输出28x28x64。
将四个结果进行连接,对这四部分输出结果的第三维并联,即128+192+96+64=480,最终输出输出为28x28x480

第四层(4a,4b,4c,4d,4e)、第五层(5a,5b)……,与3a、3b类似,在此就不再重复。

从GoogLeNet的实验结果来看,效果很明显,差错率比MSRA、VGG等模型都要低,对比结果如下表所示:
image
# -- encoding:utf-8 --
"""
Create on 19/6/26 20:12
"""

import tensorflow as tf


class GoogleNet(object):
    def __init__(self):
        with tf.variable_scope("net", initializer=tf.random_normal_initializer(0.0, 0.001)):
            with tf.variable_scope("Input"):
                input_x = tf.placeholder(dtype=tf.float32, shape=[None, 28, 28, 1])
                # 由于GoogleNet网络要求输入大小为224 * 224 *  3的图像,所以做一个转换。 channel不做转换了。
                net = tf.image.resize_images(images=input_x, size=(224, 224))

            net = self.__conv2d('conv1', net, 64, 7, 2)
            net = self.__max_pool('pool2', net, 3, 2)
            net = self.__conv2d('conv3', net, 192, 3, 1)
            net = self.__conv2d('conv4', net, 192, 3, 1)
            net = self.__max_pool('pool5', net, 3, 2)
            net = self.__inception('inception6', net, 64, 96, 128, 16, 32, 32)
            net = self.__inception('inception7', net, 64, 96, 128, 16, 32, 32)
            net = self.__inception('inception8', net, 128, 128, 192, 32, 96, 64)
            net = self.__inception('inception9', net, 128, 128, 192, 32, 96, 64)
            net = self.__max_pool('pool10', net, 3, 2)
            net = self.__inception('inception11', net, 192, 96, 208, 16, 48, 64)
            net = self.__inception('inception12', net, 192, 96, 208, 16, 48, 64)
            net = self.__inception('inception13', net, 160, 112, 224, 24, 64, 64)
            net = self.__inception('inception14', net, 160, 112, 224, 24, 64, 64)
            net = self.__inception('inception15', net, 128, 128, 256, 24, 64, 64)
            net = self.__inception('inception16', net, 128, 128, 256, 24, 64, 64)
            net = self.__inception('inception17', net, 112, 144, 288, 32, 64, 64)
            net = self.__inception('inception18', net, 112, 144, 288, 32, 64, 64)
            net = self.__inception('inception19', net, 256, 160, 320, 32, 128, 128)
            net = self.__inception('inception20', net, 256, 160, 320, 32, 128, 128)
            net = self.__max_pool('pool21', net, 3, 2)
            net = self.__inception('inception22', net, 256, 160, 320, 32, 128, 128)
            net = self.__inception('inception23', net, 256, 160, 320, 32, 128, 128)
            net = self.__inception('inception24', net, 384, 192, 384, 48, 128, 128)
            net = self.__inception('inception25', net, 384, 192, 384, 48, 128, 128)
            net = self.__avg_pool('pool26', net, 7, 1, padding='VALID')
            net = tf.nn.dropout(net, keep_prob=0.4)
            shape = net.get_shape()
            net = tf.reshape(net, shape=[-1, shape[1] * shape[2] * shape[3]])
            net = self.__fc('fc27', net, 10)
            logits = tf.nn.softmax(net)

            self.logits = logits

    def __fc(self, name, net, units, with_activation=True):
        with tf.variable_scope(name):
            input_units = net.get_shape()[-1]
            w = tf.get_variable('w', shape=[input_units, units])
            b = tf.get_variable('b', shape=[units])
            net = tf.add(tf.matmul(net, w), b)
            if with_activation:
                net = tf.nn.relu(net)
            return net

    def __conv2d(self, name, net, output_channels, window_size, stride_size, padding='SAME', with_activation=True):
        with tf.variable_scope(name):
            input_channels = net.get_shape()[-1]
            filter = tf.get_variable('w', shape=[window_size, window_size, input_channels, output_channels])
            bias = tf.get_variable('b', shape=[output_channels])
            net = tf.nn.bias_add(tf.nn.conv2d(input=net, filter=filter,
                                              strides=[1, stride_size, stride_size, 1],
                                              padding=padding), bias)
            if with_activation:
                net = tf.nn.relu(net)
            return net

    def __max_pool(self, name, net, window_size, stride_size, padding='SAME'):
        with tf.variable_scope(name):
            net = tf.nn.max_pool(net, ksize=[1, window_size, window_size, 1],
                                 strides=[1, stride_size, stride_size, 1],
                                 padding=padding)
            return net

    def __avg_pool(self, name, net, window_size, stride_size, padding='SAME'):
        with tf.variable_scope(name):
            net = tf.nn.avg_pool(net, ksize=[1, window_size, window_size, 1],
                                 strides=[1, stride_size, stride_size, 1],
                                 padding=padding)
            return net

    def __inception(self, name, net,
                    branch1_output_channels,
                    branch2_reduce_output_channels, branch2_output_channels,
                    branch3_reduce_output_channels, branch3_output_channels,
                    branch4_output_channels):
        with tf.variable_scope(name):
            with tf.variable_scope("branch_1"):
                # 1. 第一个分支
                net1 = self.__conv2d('conv1', net, branch1_output_channels, 1, 1)
            with tf.variable_scope("branch_2"):
                # 2. 第二个分支
                tmp_net = self.__conv2d('conv1', net, branch2_reduce_output_channels, 1, 1)
                net2 = self.__conv2d('conv2', tmp_net, branch2_output_channels, 3, 1)
            with tf.variable_scope("branch_3"):
                # 3. 第三个分支
                tmp_net = self.__conv2d('conv1', net, branch3_reduce_output_channels, 1, 1)
                net3 = self.__conv2d('conv2', tmp_net, branch3_output_channels, 5, 1)
            with tf.variable_scope("branch_4"):
                # 4. 第四个分支
                tmp_net = self.__max_pool('pool1', net, 3, 1)
                net4 = self.__conv2d('conv1', tmp_net, branch4_output_channels, 1, 1)
            with tf.variable_scope("Concat"):
                net = tf.concat([net1, net2, net3, net4], axis=-1)
            return net


def train():
    # TODO: 损失函数、图的运行过程等代码,自己完善,我不写了。
    with tf.Graph().as_default():
        net = GoogleNet()
        writer = tf.summary.FileWriter(logdir='./models/graph/01', graph=tf.get_default_graph())
        writer.close()


if __name__ == '__main__':
    train()

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容