卷积神经网络之AlexNet

作者: Ice_spring | 来源:发表于2019-08-05 22:11 被阅读9次

    AlexNet网络结构剖析

    2012年AlexNet在ImageNet大赛上一举夺冠,展现了深度CNN在图像任务上的惊人表现,掀起了CNN研究的热潮,深度学习自此引爆,在AlexNet之前,深度学习已经沉寂了很久。该网络是以论文的第一作者 Alex Krizhevsky 的名字命名的,另外两位合著者是 Ilya Sutskever 和 Geoffery Hinton。
    该网络结构如下(为了讲清楚AlexNet结构,找了不同的示意图):

    原论文AlexNet AlexNet2 AlexNet3

    需要注意的是,论文发表时的GPU运算能力达不到该网络的训练需求,所以使用的是两个GPU并行,中间为了提高模型效果使用了不少交互相关的Trick。我们下面只关注网络结构,不关注两个GPU处理之间的交互。

    下面对该结构做详细介绍,数据预处理首先将所有图像都处理为256×256×3的图像,随后AlexNet通过随机采样的方式从中选取一张227×227×3的图片作为输入(原论文AlexNet结构图示写的是224×224×3,AlexNet2是吴恩达老师讲义中的图示,经计算确实应该是227)。
    注:以下卷积操作由于卷积核通道数和上一层次输出FeatureMap个数相同,故没有写进Kernel中,Kernel的最后一个参数是卷积核个数。

    • 第一个卷积层Kernel=(11,11,96),Padding=0,Stride= 4,因此输出大小为\frac{(227-11+2×0)}{4}+1=55,将输出经过Relu激活层后,进行Local Response Normalization(LRN局部响应归一化),之后经过一个3×3的Maxpooling,Stride=2,Padding=0,于是输出大小为\frac{(55-3+2×0)}{2}+1=27
    • 第二个卷积层,Kernel=(5,5,256),Padding=2,Stride=1,于是输出为\frac{(27-5+2×2)}{1}+1=27,接着Relu,LRN,之后经过一个3×3的Maxpooling,Stride=2,Padding=0,于是输出大小为\frac{(27-3+2×0)}{2}+1=13
    • 第三个卷积层,Kernel=(3,3,384),Padding=1,Stride=1,输出为\frac{(13-3+2×1)}{1}+1=13,接着经过Relu层;
    • 第四个卷积层和第三层操作一样;
    • 第五个卷积层,Kernel=(3,3,256),Padding=1,Stride=1,输出为\frac{(13-3+2×1)}{1}+1=13,Relu,之后经过一个3×3的Maxpooling,Stride=2,Padding=0,于是输出大小为\frac{(13-3+2×0)}{2}+1=6;-
    • 全连接层1,这一层实际上是4096个(6,6,256)的Kernel,只是正好可以看做拉直后的全连接,接着依次经过Relu,Dropout;
    • 全连接层2,4096,依次经过Relu,Dropout。
    • 全连接层3,输出1000个结果,Softmax给出分类。

    在原论文的双GPU并行计算结构下,卷积层 Conv2,Conv4,Conv5中的卷积核只和位于同一GPU的上一层的FeatureMap相连,于是需要训练的参数数量是:

    卷积层的参数 = 卷积核的数量 * 卷积核 + 偏置
    Conv1: 96个11×11×3的卷积核,96×11×11×3+96=34848
    Conv2: 2组,每组128个5×5×48的卷积核,(128×5×5×48+128)×2=307456
    Conv3: 384个3×3×256的卷积核,3×3×256×384+384=885120
    Conv4: 2组,每组192个3×3×192的卷积核,(3×3×192×192+192)×2=663936
    Conv5: 2组,每组128个3×3×192的卷积核,(3×3×192×128+128)×2=442624
    FuulC6: 4096个6×6×256的卷积核,6×6×256×4096+4096=37752832
    FuulC7: 4096∗4096+4096=16781312
    output(FuulC8): 4096∗1000=4096000

    而实际如果Conv2,Conv4,Conv5也合在一块计算的话,这3个层次中相应的参数数量会翻倍。
    不过,从上面也可以看出,参数大多数集中在全连接层,在卷积层由于局部连接和权值共享,权值参数相对较少。

    AlexNet的特点及其解释

    AlexNet首次在CNN中成功应用了ReLU和Dropout等Trick,同时也使用了GPU进行运算加速。

    • 激活函数ReLu:
      在最初,Sigmoid和Tanh函数最常用的激活函数。在网络层数较少时,Sigmoid函数的特性能够很好地向后传递信息。但它有一个很大的问题就是梯度饱和(梯度消失)。
      \sigma(x)=\frac{1}{1+e^{-x}}
    sigmoid

    所谓梯度饱和就是当输入的数字很大(或很小)时,其导数值接近于0。这样在深层次网络结构中进行反向传播时,由于链式法则,很多个很小的sigmoid导数相乘,导致其结果趋于0,权值更新将非常缓慢。
    而ReLU是一个分段线性函数:
    ReLu=max(x,0)

    ReLu

    相比于Sigmoid不仅运算更快,且导数是恒定值,一般不会发生严重的梯度饱和。另外Relu会使一部分神经元的输出为0,这样就形成了网络的稀疏性,减少了参数的相互依赖,从而一定程度抑制了过拟合。

    • Dropout
      对Dropout可以有以下几个层面的解释。
      组合解释:每次的Dropout都相当于训练了一个子网络,最终结果是这些子网络的组合;
      动机解释:Dropout的使用降低了参数之间的依赖性,减弱了神经元之间相互依赖协作的工作模式,从而提高了神经元的独立学习能力,增加了模型泛化能力;
      数据解释:对于Dropout后的训练结果总能找到与之对应的样本,这相当于数据增强。

    AlexNet逻辑的TensorFlow实现

    由于是用个人的PC跑程序,相当于只有一个运算中心,所以不可能实现论文最原始的网络了,我们只实现AlexNet的核心逻辑。另外,后来的实验发现LRN会让前向传播和反向传播的速度降低,但最终对模型效果提升却不明显,所以只有AlexNet用LRN,其之后的模型都放弃了。这里实现的网络中虽然加入了LRN,但为了训练速度可以自行删除LRN过程:

    # -*- coding:utf-8 -*-
    import tensorflow as tf
    import time
    import math
    from datetime import datetime
     
    batch_size=32
    num_batch=100
    keep_prob=0.5
    def print_architecture(t):
        
        print(t.op.name," ",t.get_shape().as_list()) 
    def inference(images):
        '''构建网络'''
        parameters=[]  #储存参数
     
        with tf.name_scope('conv1') as scope:
     
            kernel=tf.Variable(tf.truncated_normal([11,11,3,96],
                               dtype=tf.float32,stddev=0.1),name="weights")
            conv=tf.nn.conv2d(images,kernel,[1,4,4,1],padding='SAME')
            biases=tf.Variable(tf.constant(0.0, shape=[96],  dtype=tf.float32),
                               trainable=True,name="biases")
            bias=tf.nn.bias_add(conv,biases) # w*x+b
            conv1=tf.nn.relu(bias,name=scope) # reLu
            print_architecture(conv1)
            parameters +=[kernel,biases]
     
            #添加LRN层和max_pool层
            lrn1=tf.nn.lrn(conv1,depth_radius=4,bias=1,alpha=0.001/9,beta=0.75,name="lrn1")
            pool1=tf.nn.max_pool(lrn1,ksize=[1,3,3,1],strides=[1,2,2,1],
                                 padding="VALID",name="pool1")
            print_architecture(pool1)
     
        with tf.name_scope('conv2') as scope:
            kernel = tf.Variable(tf.truncated_normal([5, 5, 96, 256],
                                                     dtype=tf.float32, stddev=0.1), name="weights")
            conv = tf.nn.conv2d(pool1, kernel, [1, 1, 1, 1], padding='SAME')
            biases = tf.Variable(tf.constant(0.0, shape=[256], dtype=tf.float32),
                                 trainable=True, name="biases")
            bias = tf.nn.bias_add(conv, biases)  # w*x+b
            conv2 = tf.nn.relu(bias, name=scope)  # reLu
            parameters += [kernel, biases]
            # 添加LRN层和max_pool层
            lrn2 = tf.nn.lrn(conv2, depth_radius=4, bias=1, alpha=0.001 / 9, beta=0.75, name="lrn1")
            pool2 = tf.nn.max_pool(lrn2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                                   padding="VALID", name="pool2")
            print_architecture(pool2)
     
        with tf.name_scope('conv3') as scope:
            kernel = tf.Variable(tf.truncated_normal([3, 3, 256, 384],
                                                     dtype=tf.float32, stddev=0.1), name="weights")
            conv = tf.nn.conv2d(pool2, kernel, [1, 1, 1, 1], padding='SAME')
            biases = tf.Variable(tf.constant(0.0, shape=[384], dtype=tf.float32),
                                 trainable=True, name="biases")
            bias = tf.nn.bias_add(conv, biases)  # w*x+b
            conv3 = tf.nn.relu(bias, name=scope)  # reLu
            parameters += [kernel, biases]
            print_architecture(conv3)
     
        with tf.name_scope('conv4') as scope:
          
            kernel = tf.Variable(tf.truncated_normal([3, 3, 384, 384],
                                                     dtype=tf.float32, stddev=0.1), name="weights")
            conv = tf.nn.conv2d(conv3, kernel, [1, 1, 1, 1], padding='SAME')
            biases = tf.Variable(tf.constant(0.0, shape=[384], dtype=tf.float32),
                                 trainable=True, name="biases")
            bias = tf.nn.bias_add(conv, biases)  # w*x+b
            conv4 = tf.nn.relu(bias, name=scope)  # reLu
            parameters += [kernel, biases]
            print_architecture(conv4)
     
        with tf.name_scope('conv5') as scope:
            kernel = tf.Variable(tf.truncated_normal([3, 3, 384, 256],
                                                     dtype=tf.float32, stddev=0.1), name="weights")
            conv = tf.nn.conv2d(conv4, kernel, [1, 1, 1, 1], padding='SAME')
            biases = tf.Variable(tf.constant(0.0, shape=[256], dtype=tf.float32),
                                 trainable=True, name="biases")
            bias = tf.nn.bias_add(conv, biases)  # w*x+b
            conv5 = tf.nn.relu(bias, name=scope)  # reLu
            pool5 = tf.nn.max_pool(conv5, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                                   padding="VALID", name="pool5")
            parameters += [kernel, biases]
            print_architecture(pool5)
     
        #全连接层6
        with tf.name_scope('fc6') as scope:
            kernel = tf.Variable(tf.truncated_normal([6*6*256,4096],
                                                     dtype=tf.float32, stddev=0.1), name="weights")
            biases = tf.Variable(tf.constant(0.0, shape=[4096], dtype=tf.float32),
                                 trainable=True, name="biases")
            # 输入数据变换
            flat = tf.reshape(pool5, [-1, 6*6*256] )  # 整形成m*n,列n为7*7*64
            # 进行全连接操作
            fc = tf.nn.relu(tf.matmul(flat, kernel) + biases,name='fc6')
            # 防止过拟合  nn.dropout
            fc6 = tf.nn.dropout(fc, keep_prob)
            parameters += [kernel, biases]
            print_architecture(fc6)
     
        # 全连接层7
        with tf.name_scope('fc7') as scope:
            kernel = tf.Variable(tf.truncated_normal([4096, 4096],
                                                     dtype=tf.float32, stddev=0.1), name="weights")
            biases = tf.Variable(tf.constant(0.0, shape=[4096], dtype=tf.float32),
                                 trainable=True, name="biases")
            # 进行全连接操作
            fc = tf.nn.relu(tf.matmul(fc6, kernel) + biases, name='fc7')
            # 防止过拟合  nn.dropout
            fc7 = tf.nn.dropout(fc, keep_prob)
            parameters += [kernel, biases]
            print_architecture(fc7)
     
        # 全连接层8
        with tf.name_scope('fc8') as scope:
            kernel = tf.Variable(tf.truncated_normal([4096, 1000],
                                                     dtype=tf.float32, stddev=0.1), name="weights")
            biases = tf.Variable(tf.constant(0.0, shape=[1000], dtype=tf.float32),
                                 trainable=True, name="biases")
            # 进行全连接操作
            fc8 = tf.nn.xw_plus_b(fc7, kernel, biases, name='fc8')
            parameters += [kernel, biases]
            print_architecture(fc8)
     
        return fc8,parameters
     
    def time_compute(session,target,info_string):
        num_step_burn_in=10 
        total_duration=0.0   #总时间
        total_duration_squared=0.0
        for i in range(num_batch+num_step_burn_in):
            start_time=time.time()
            _ = session.run(target)
            duration= time.time() -start_time
            if i>= num_step_burn_in:
                if i%10==0: #每迭代10次显示一次duration
                    print("%s: step %d,duration=%.5f "% (datetime.now(),i-num_step_burn_in,duration))
                total_duration += duration
                total_duration_squared += duration *duration
        time_mean=total_duration /num_batch
        time_variance=total_duration_squared / num_batch - time_mean*time_mean
        time_stddev=math.sqrt(time_variance)
        #迭代完成,输出
        print("%s: %s across %d steps,%.3f +/- %.3f sec per batch "%
                  (datetime.now(),info_string,num_batch,time_mean,time_stddev))
     
    def main():
        with tf.Graph().as_default():
            image_size =224
            images=tf.Variable(tf.random_normal([batch_size,image_size,image_size,3],
                                         dtype=tf.float32,stddev=0.1 ) )
            fc8,parameters=inference(images)
     
            init=tf.global_variables_initializer()
            sess=tf.Session()
            sess.run(init)
            time_compute(sess,target=fc8,info_string="Forward")
     
            obj=tf.nn.l2_loss(fc8)
            grad=tf.gradients(obj,parameters)
            time_compute(sess,grad,"Forward-backward")
    if __name__=="__main__":
        main()
    

    原数据集太大,随机生成图片进行测试。

    参考文章1:深入理解AlexNet网络
    参考文章2:再谈AlexNet
    参考文章3:CNN之AlexNet

    相关文章

      网友评论

        本文标题:卷积神经网络之AlexNet

        本文链接:https://www.haomeiwen.com/subject/ynfvdctx.html