图像识别——AlexNet原理解析及实现

时间:2024-03-17 07:19:34

转载自:https://blog.csdn.net/u012679707/article/details/80793916

                                                                     
               

【深度学习】AlexNet原理解析及实现

    Alex提出的alexnet网络结构模型,在imagenet2012图像分类challenge上赢得了冠军。

    要研究CNN类型DL网络模型在图像分类上的应用,就逃不开研究alexnet,这是CNN在图像分类上的经典模型。

一、Alexnet结构

alexNet为8层深度网络,其中5层卷积层和3层全连接层,不计LRN层和池化层。如下图所示:

    图像识别——AlexNet原理解析及实现

                                                            图 Alexnet结构

详解各层训练参数的计算:

前五层:卷积层

图像识别——AlexNet原理解析及实现

后三层:全连接层

                    图像识别——AlexNet原理解析及实现

整体计算图:

  图像识别——AlexNet原理解析及实现

二、结构分析

        AlexNet每层的超参数如下图所示,其中输入尺寸为227*227,第一个卷积使用较大的核尺寸11*11,步长为4,有96个卷积核;紧接着一层LRN层;然后是最大池化层,核为3*3,步长为2。这之后的卷积层的核尺寸都比较小,5*5或3*3,并且步长为1,即扫描全图所有像素;而最大池化层依然为3*3,步长为2.

        我们可以发现,前几个卷积层的计算量很大,但参数量很小,只占Alexnet总参数的很小一部分。这就是卷积层的优点!通过较小的参数量来提取有效的特征。

        要注意,论文中指出,如果去掉任何一个卷积层,都会使网络的分类性能大幅下降。

            图像识别——AlexNet原理解析及实现

三、AlexNet的新技术点

    AlexNet的新技术点(即大牛论文的contribution),如下:

(1)ReLU作为**函数。

    ReLU为非饱和函数,论文中验证其效果在较深的网络超过了SIgmoid,成功解决了SIgmoid在网络较深时的梯度弥散问题

(2)Dropout避免模型过拟合

    在训练时使用Dropout随机忽略一部分神经元,以避免模型过拟合。在alexnet的最后几个全连接层中使用了Dropout。

(3)重叠的最大池化

    之前的CNN中普遍使用平均池化,而Alexnet全部使用最大池化,避免平均池化的模糊化效果。并且,池化的步长小于核尺寸,这样使得池化层的输出之间会有重叠和覆盖提升了特征的丰富性

(4)提出LRN层

    提出LRN层,对局部神经元的活动创建竞争机制,使得响应较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力。

(5)GPU加速

(6)数据增强

    随机从256*256的原始图像中截取224*224大小的区域(以及水平翻转的镜像),相当于增强了(256-224)*(256-224)*2=2048倍的数据量。使用了数据增强后,减轻过拟合,提升泛化能力。避免因为原始数据量的大小使得参数众多的CNN陷入过拟合中。

四、AlexNet的搭建

    利用tensorflow实现ALexNet,环境为:win10+anaconda+python3+CPU(本人仅利用CPU,未使用GPU加速,所以最终模型训练速度较慢)。

    利用tensorboard可视化ALexNet结构为:

            图像识别——AlexNet原理解析及实现

(1)首先看一下卷积层的搭建:带有LRN和池化层的卷积层

  1.    with tf.name_scope('conv1') as scope:
  2.        """
  3.        images:227*227*3
  4.        kernel: 11*11 *64
  5.        stride:4*4
  6.        padding:name      
  7.        
  8.        #通过with tf.name_scope('conv1') as scope可以将scope内生成的Variable自动命名为conv1/xxx
  9.        便于区分不同卷积层的组建
  10.        
  11.        input: images[227*227*3]
  12.        middle: conv1[55*55*96]
  13.        output: pool1 [27*27*96]
  14.        
  15.        """
  16.        kernel=tf.Variable(tf.truncated_normal([11,11,3,96],
  17.                           dtype=tf.float32,stddev=0.1),name="weights")
  18.        conv=tf.nn.conv2d(images,kernel,[1,4,4,1],padding='SAME')
  19.        biases=tf.Variable(tf.constant(0.0, shape=[96],  dtype=tf.float32),
  20.                           trainable=True,name="biases")
  21.        bias=tf.nn.bias_add(conv,biases) # w*x+b
  22.        conv1=tf.nn.relu(bias,name=scope) # reLu
  23.        print_architecture(conv1)
  24.        parameters +=[kernel,biases]
  25.        #添加LRN层和max_pool层
  26.        """
  27.        LRN会让前馈、反馈的速度大大降低(下降1/3),但最终效果不明显,所以只有ALEXNET用LRN,其他模型都放弃了
  28.        """
  29.        lrn1=tf.nn.lrn(conv1,depth_radius=4,bias=1,alpha=0.001/9,beta=0.75,name="lrn1")
  30.        pool1=tf.nn.max_pool(lrn1,ksize=[1,3,3,1],strides=[1,2,2,1],
  31.                             padding="VALID",name="pool1")
  32.        print_architecture(pool1)
(2)卷积层的搭建:不带有LRN和池化层的卷积层
  1. with tf.name_scope('conv3') as scope:
  2.        """
  3.        input: pool2[13*13*256]
  4.        output: conv3 [13*13*384]
  5.        """
  6.        kernel = tf.Variable(tf.truncated_normal([3, 3, 256, 384],
  7.                                                 dtype=tf.float32, stddev=0.1), name="weights")
  8.        conv = tf.nn.conv2d(pool2, kernel, [1, 1, 1, 1], padding='SAME')
  9.        biases = tf.Variable(tf.constant(0.0, shape=[384], dtype=tf.float32),
  10.                             trainable=True, name="biases")
  11.        bias = tf.nn.bias_add(conv, biases)  # w*x+b
  12.        conv3 = tf.nn.relu(bias, name=scope)  # reLu
  13.        parameters += [kernel, biases]
  14.        print_architecture(conv3)

3)全连接层的搭建

  1. #全连接层6
  2.    with tf.name_scope('fc6') as scope:
  3.        """
  4.        input:pool5 [6*6*256]
  5.        output:fc6 [4096]
  6.        """
  7.        kernel = tf.Variable(tf.truncated_normal([6*6*256,4096],
  8.                                                 dtype=tf.float32, stddev=0.1), name="weights")
  9.        biases = tf.Variable(tf.constant(0.0, shape=[4096], dtype=tf.float32),
  10.                             trainable=True, name="biases")
  11.        # 输入数据变换
  12.        flat = tf.reshape(pool5, [-1, 6*6*256] )  # 整形成m*n,列n为7*7*64
  13.        # 进行全连接操作
  14.        fc = tf.nn.relu(tf.matmul(flat, kernel) + biases,name='fc6')
  15.        # 防止过拟合  nn.dropout
  16.        fc6 = tf.nn.dropout(fc, keep_prob)
  17.        parameters += [kernel, biases]
  18.        print_architecture(fc6)

(4)训练测试:

    因未下载ImageNet数据集(太大),只是简单的测试了一下alexnet的性能。使用的是随机生成的图片来作为训练数据。

  1. def time_compute(session,target,info_string):
  2.    num_step_burn_in=10  #预热轮数,头几轮迭代有显存加载、cache命中等问题可以因此跳过
  3.    total_duration=0.0   #总时间
  4.    total_duration_squared=0.0
  5.    for i in range(num_batch+num_step_burn_in):
  6.        start_time=time.time()
  7.        _ = session.run(target)
  8.        duration= time.time() -start_time
  9.        if i>= num_step_burn_in:
  10.            if i%10==0: #每迭代10次显示一次duration
  11.                print("%s: step %d,duration=%.5f "% (datetime.now(),i-num_step_burn_in,duration))
  12.            total_duration += duration
  13.            total_duration_squared += duration *duration
  14.    time_mean=total_duration /num_batch
  15.    time_variance=total_duration_squared / num_batch - time_mean*time_mean
  16.    time_stddev=math.sqrt(time_variance)
  17.    #迭代完成,输出
  18.    print("%s: %s across %d steps,%.3f +/- %.3f sec per batch "%
  19.              (datetime.now(),info_string,num_batch,time_mean,time_stddev))
  20. def main():
  21.    with tf.Graph().as_default():
  22.        """仅使用随机图片数据 测试前馈和反馈计算的耗时"""
  23.        image_size =224
  24.        images=tf.Variable(tf.random_normal([batch_size,image_size,image_size,3],
  25.                                     dtype=tf.float32,stddev=0.1 ) )
  26.        fc8,parameters=inference(images)
  27.        init=tf.global_variables_initializer()
  28.        sess=tf.Session()
  29.        sess.run(init)
  30.        """
  31.        AlexNet forward 计算的测评
  32.        传入的target:fc8(即最后一层的输出)
  33.        优化目标:loss
  34.        使用tf.gradients求相对于loss的所有模型参数的梯度
  35.        
  36.        
  37.        AlexNet Backward 计算的测评
  38.        target:grad
  39.        
  40.        """
  41.        time_compute(sess,target=fc8,info_string="Forward")
  42.        obj=tf.nn.l2_loss(fc8)
  43.        grad=tf.gradients(obj,parameters)
  44.        time_compute(sess,grad,"Forward-backward")

(5)测试结果:

    结构输出   (注意,32是我设置的batch_size,即训练的图片数量为32)

                图像识别——AlexNet原理解析及实现

    前向预测用时:

图像识别——AlexNet原理解析及实现

    后向训练(学习)用时:

图像识别——AlexNet原理解析及实现

    可以看出后向训练用时比前向推理用时长很多,大概是5倍。


【附录】完整代码

  1. # -*- coding:utf-8 -*-
  2. """
  3. @author:Lisa
  4. @file:alexNet.py
  5. @function:实现Alexnet深度模型
  6. @note:learn from《tensorflow实战》
  7. @time:2018/6/24 0024下午 5:26
  8. """
  9. import tensorflow as tf
  10. import time
  11. import math
  12. from datetime import datetime
  13. batch_size=32
  14. num_batch=100
  15. keep_prob=0.5
  16. def print_architecture(t):
  17.    """print the architecture information of the network,include name and size"""
  18.    print(t.op.name," ",t.get_shape().as_list())
  19. def inference(images):
  20.    """ 构建网络 :5个conv+3个FC"""
  21.    parameters=[]  #储存参数
  22.    with tf.name_scope('conv1') as scope:
  23.        """
  24.        images:227*227*3
  25.        kernel: 11*11 *64
  26.        stride:4*4
  27.        padding:name      
  28.        
  29.        #通过with tf.name_scope('conv1') as scope可以将scope内生成的Variable自动命名为conv1/xxx
  30.        便于区分不同卷积层的组建
  31.        
  32.        input: images[227*227*3]
  33.        middle: conv1[55*55*96]
  34.        output: pool1 [27*27*96]
  35.        
  36.        """
  37.        kernel=tf.Variable(tf.truncated_normal([11,11,3,96],
  38.                           dtype=tf.float32,stddev=0.1),name="weights")
  39.        conv=tf.nn.conv2d(images,kernel,[1,4,4,1],padding='SAME')
  40.        biases=tf.Variable(tf.constant(0.0, shape=[96],  dtype=tf.float32),
  41.                           trainable=True,name="biases")
  42.        bias=tf.nn.bias_add(conv,biases) # w*x+b
  43.        conv1=tf.nn.relu(bias,name=scope) # reLu
  44.        print_architecture(conv1)
  45.        parameters +=[kernel,biases]
  46.        #添加LRN层和max_pool层
  47.        """
  48.        LRN会让前馈、反馈的速度大大降低(下降1/3),但最终效果不明显,所以只有ALEXNET用LRN,其他模型都放弃了
  49.        """
  50.        lrn1=tf.nn.lrn(conv1,depth_radius=4,bias=1,alpha=0.001/9,beta=0.75,name="lrn1")
  51.        pool1=tf.nn.max_pool(lrn1,ksize=[1,3,3,1],strides=[1,2,2,1],
  52.                             padding="VALID",name="pool1")
  53.        print_architecture(pool1)
  54.    with tf.name_scope('conv2') as scope:
  55.        """
  56.        input: pool1[27*27*96]
  57.        middle: conv2[27*27*256]
  58.        output: pool2 [13*13*256]
  59.        """
  60.        kernel = tf.Variable(tf.truncated_normal([5, 5, 96, 256],
  61.                                                 dtype=tf.float32, stddev=0.1), name="weights")
  62.        conv = tf.nn.conv2d(pool1, kernel, [1, 1, 1, 1], padding='SAME')
  63.        biases = tf.Variable(tf.constant(0.0, shape=[256], dtype=tf.float32),
  64.                             trainable=True, name="biases")
  65.        bias = tf.nn.bias_add(conv, biases)  # w*x+b
  66.        conv2 = tf.nn.relu(bias, name=scope)  # reLu
  67.        parameters += [kernel, biases]
  68.        # 添加LRN层和max_pool层
  69.        """
  70.        LRN会让前馈、反馈的速度大大降低(下降1/3),但最终效果不明显,所以只有ALEXNET用LRN,其他模型都放弃了
  71.        """
  72.        lrn2 = tf.nn.lrn(conv2, depth_radius=4, bias=1, alpha=0.001 / 9, beta=0.75, name="lrn1")
  73.        pool2 = tf.nn.max_pool(lrn2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
  74.                               padding="VALID", name="pool2")
  75.        print_architecture(pool2)
  76.    with tf.name_scope('conv3') as scope:
  77.        """
  78.        input: pool2[13*13*256]
  79.        output: conv3 [13*13*384]
  80.        """
  81.        kernel = tf.Variable(tf.truncated_normal([3, 3, 256, 384],
  82.                                                 dtype=tf.float32, stddev=0.1), name="weights")
  83.        conv = tf.nn.conv2d(pool2, kernel, [1, 1, 1, 1], padding='SAME')
  84.        biases = tf.Variable(tf.constant(0.0, shape=[384], dtype=tf.float32),
  85.                             trainable=True, name="biases")
  86.        bias = tf.nn.bias_add(conv, biases)  # w*x+b
  87.        conv3 = tf.nn.relu(bias, name=scope)  # reLu
  88.        parameters += [kernel, biases]
  89.        print_architecture(conv3)
  90.    with tf.name_scope('conv4') as scope:
  91.        """
  92.        input: conv3[13*13*384]
  93.        output: conv4 [13*13*384]
  94.        """
  95.        kernel = tf.Variable(tf.truncated_normal([3, 3, 384, 384],
  96.                                                 dtype=tf.float32, stddev=0.1), name="weights")
  97.        conv = tf.nn.conv2d(conv3, kernel, [1, 1, 1, 1], padding='SAME')
  98.        biases = tf.Variable(tf.constant(0.0, shape=[384], dtype=tf.float32),
  99.                             trainable=True, name="biases")
  100.        bias = tf.nn.bias_add(conv, biases)  # w*x+b
  101.        conv4 = tf.nn.relu(bias, name=scope)  # reLu
  102.        parameters += [kernel, biases]
  103.        print_architecture(conv4)
  104.    with tf.name_scope('conv5') as scope:
  105.        """
  106.        input: conv4[13*13*384]
  107.        output: conv5 [6*6*256]
  108.        """
  109.        kernel = tf.Variable(tf.truncated_normal([3, 3, 384, 256],
  110.                                                 dtype=tf.float32, stddev=0.1), name="weights")
  111.        conv = tf.nn.conv2d(conv4, kernel, [1, 1, 1, 1], padding='SAME')
  112.        biases = tf.Variable(tf.constant(0.0, shape=[256], dtype=tf.float32),
  113.                             trainable=True, name="biases")
  114.        bias = tf.nn.bias_add(conv, biases)  # w*x+b
  115.        conv5 = tf.nn.relu(bias, name=scope)  # reLu
  116.        pool5 = tf.nn.max_pool(conv5, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
  117.                               padding="VALID", name="pool5")
  118.        parameters += [kernel, biases]
  119.        print_architecture(pool5)
  120.    #全连接层6
  121.    with tf.name_scope('fc6') as scope:
  122.        """
  123.        input:pool5 [6*6*256]
  124.        output:fc6 [4096]
  125.        """
  126.        kernel = tf.Variable(tf.truncated_normal([6*6*256,4096],
  127.                                                 dtype=tf.float32, stddev=0.1), name="weights")
  128.        biases = tf.Variable(tf.constant(0.0, shape=[4096], dtype=tf.float32),
  129.                             trainable=True, name="biases")
  130.        # 输入数据变换
  131.        flat = tf.reshape(pool5, [-1, 6*6*256] )  # 整形成m*n,列n为7*7*64
  132.        # 进行全连接操作
  133.        fc = tf.nn.relu(tf.matmul(flat, kernel) + biases,name='fc6')
  134.        # 防止过拟合  nn.dropout
  135.        fc6 = tf.nn.dropout(fc, keep_prob)
  136.        parameters += [kernel, biases]
  137.        print_architecture(fc6)
  138.    # 全连接层7
  139.    with tf.name_scope('fc7') as scope:
  140.        """
  141.        input:fc6 [4096]
  142.        output:fc7 [4096]
  143.        """
  144.        kernel = tf.Variable(tf.truncated_normal([4096, 4096],
  145.                                                 dtype=tf.float32, stddev=0.1), name="weights")
  146.        biases = tf.Variable(tf.constant(0.0, shape=[4096], dtype=tf.float32),
  147.                             trainable=True, name="biases")
  148.        # 进行全连接操作
  149.        fc = tf.nn.relu(tf.matmul(fc6, kernel) + biases, name='fc7')
  150.        # 防止过拟合  nn.dropout
  151.        fc7 = tf.nn.dropout(fc, keep_prob)
  152.        parameters += [kernel, biases]
  153.        print_architecture(fc7)
  154.    # 全连接层8
  155.    with tf.name_scope('fc8') as scope:
  156.        """
  157.        input:fc7 [4096]
  158.        output:fc8 [1000]
  159.        """
  160.        kernel = tf.Variable(tf.truncated_normal([4096, 1000],
  161.                                                 dtype=tf.float32, stddev=0.1), name="weights")
  162.        biases = tf.Variable(tf.constant(0.0, shape=[1000], dtype=tf.float32),
  163.                             trainable=True, name="biases")
  164.        # 进行全连接操作
  165.        fc8 = tf.nn.xw_plus_b(fc7, kernel, biases, name='fc8')
  166.        parameters += [kernel, biases]
  167.        print_architecture(fc8)
  168.    return fc8,parameters
  169. def time_compute(session,target,info_string):
  170.    num_step_burn_in=10  #预热轮数,头几轮迭代有显存加载、cache命中等问题可以因此跳过
  171.    total_duration=0.0   #总时间
  172.    total_duration_squared=0.0
  173.    for i in range(num_batch+num_step_burn_in):
  174.        start_time=time.time()
  175.        _ = session.run(target)
  176.        duration= time.time() -start_time
  177.        if i>= num_step_burn_in:
  178.            if i%10==0: #每迭代10次显示一次duration
  179.                print("%s: step %d,duration=%.5f "% (datetime.now(),i-num_step_burn_in,duration))
  180.            total_duration += duration
  181.            total_duration_squared += duration *duration
  182.    time_mean=total_duration /num_batch
  183.    time_variance=total_duration_squared / num_batch - time_mean*time_mean
  184.    time_stddev=math.sqrt(time_variance)
  185.    #迭代完成,输出
  186.    print("%s: %s across %d steps,%.3f +/- %.3f sec per batch "%
  187.              (datetime.now(),info_string,num_batch,time_mean,time_stddev))
  188. def main():
  189.    with tf.Graph().as_default():
  190.        """仅使用随机图片数据 测试前馈和反馈计算的耗时"""
  191.        image_size =224
  192.        images=tf.Variable(tf.random_normal([batch_size,image_size,image_size,3],
  193.                                     dtype=tf.float32,stddev=0.1 ) )
  194.        fc8,parameters=inference(images)
  195.        init=tf.global_variables_initializer()
  196.        sess=tf.Session()
  197.        sess.run(init)
  198.        """
  199.        AlexNet forward 计算的测评
  200.        传入的target:fc8(即最后一层的输出)
  201.        优化目标:loss
  202.        使用tf.gradients求相对于loss的所有模型参数的梯度
  203.        
  204.        
  205.        AlexNet Backward 计算的测评
  206.        target:grad
  207.        
  208.        """
  209.        time_compute(sess,target=fc8,info_string="Forward")
  210.        obj=tf.nn.l2_loss(fc8)
  211.        grad=tf.gradients(obj,parameters)
  212.        time_compute(sess,grad,"Forward-backward")
  213. if __name__=="__main__":
  214.    main()
------------------------------------------------------         END      ----------------------------------------------------------

参考:

《tensorflow实战》黄文坚(本文内容及代码大多源于此书,感谢!)

大牛论文《ImageNet Classification with Deep Convolutional Neural Networks Alex Krizhevsky

[caffe]深度学习之图像分类模型AlexNet解读  https://blog.csdn.net/sunbaigui/article/details/39938097(参数分析很详细)