zoukankan      html  css  js  c++  java
  • AlexNet、VGG、NIN、GoogLeNet、ResNet

    本文主要简要介绍以下经典网络结构:

    • AlexNet
    • VGG
    • NIN
    • GoogLeNet
    • ResNet
    • DenseNet
    • MobileNet
    • ShuffleNet

     

    AlexNet:多层不同大小的卷积层+全连接

    参考链接:
    Netscope
    深度学习卷积神经网络-AlexNet
    AlexNet神经网络结构 - 牧野的博客 - CSDN博客

     

     

    AlexNet 结构示意图

     

    conv1 阶段
    输入数据:227×227×3
    卷积核:11×11×3;步长:4;数量(也就是输出个数):96
    卷积后数据:55×55×96 (原图N×N,卷积核大小n×n,卷积步长大于1为k,输出维度是(N-n)/k+1)
    relu1后的数据:55×55×96
    Max pool1的核:3×3,步长:2
    Max pool1后的数据:27×27×96
    norm1:local_size=5 (LRN(Local Response Normalization) 局部响应归一化)
    最后的输出:27×27×96

     

    conv2 阶段
    输入数据:27×27×96
    卷积核:5×5;步长:1;数量(也就是输出个数):256
    卷积后数据:27×27×256 (做了Same padding(相同补白),使得卷积后图像大小不变。)
    relu2后的数据:27×27×256
    Max pool2的核:3×3,步长:2
    Max pool2后的数据:13×13×256 ((27-3)/2+1=13 )
    norm2:local_size=5 (LRN(Local Response Normalization) 局部响应归一化)
    最后的输出:13×13×256

     

    conv2中使用了same padding,保持了卷积后图像的宽高不缩小。

     

     

    conv3 阶段
    输入数据:13×13×256
    卷积核:3×3;步长:1;数量(也就是输出个数):384
    卷积后数据:13×13×384 (做了Same padding(相同补白),使得卷积后图像大小不变。)
    relu3后的数据:13×13×384
    最后的输出:13×13×384

     

    conv3层没有Max pool层和norm层

     

    conv4 阶段
    输入数据:13×13×384
    卷积核:3×3;步长:1;数量(也就是输出个数):384
    卷积后数据:13×13×384 (做了Same padding(相同补白),使得卷积后图像大小不变。)
    relu4后的数据:13×13×384
    最后的输出:13×13×384

     

    conv4层也没有Max pool层和norm层

     

    conv5 阶段
    输入数据:13×13×384
    卷积核:3×3;步长:1;数量(也就是输出个数):256
    卷积后数据:13×13×256 (做了Same padding(相同补白),使得卷积后图像大小不变。)
    relu5后的数据:13×13×256
    Max pool5的核:3×3,步长:2
    Max pool2后的数据:6×6×256 ((13-3)/2+1=6 )
    最后的输出:6×6×256

     

    conv5层有Max pool,没有norm层

     

    fc6 阶段
    输入数据:6×6×256
    全连接输出:4096×1
    relu6后的数据:4096×1
    drop out6后数据:4096×1
    最后的输出:4096×1

     

    fc7 阶段
    输入数据:4096×1
    全连接输出:4096×1
    relu7后的数据:4096×1
    drop out7后数据:4096×1
    最后的输出:4096×1

     

    fc8阶段
    输入数据:4096×1
    全连接输出:1000
    fc8输出一千种分类的概率。

     

    整体来看,AlexNet的卷积核从11到5再到3不断变小,而feature map也通过重叠式max pool在第1、2、5层折半式缩小,到第5个卷积层后,图像特征已经提炼得足够充分,便用两个全连接层和一个softmax层组合得出最终的分类结果。

     

    AlexNet相对于前辈们有以下改进:

     

    1、AlexNet采用了Relu激活函数:ReLU(x) = max(x,0)

     

    2、AlexNet另一个创新是LRN(Local Response Normalization) 局部响应归一化,LRN模拟神经生物学上一个叫做 侧抑制(lateral inhibitio)的功能,侧抑制指的是被激活的神经元会抑制相邻的神经元。LRN局部响应归一化借鉴侧抑制的思想实现局部抑制,使得响应比较大的值相对更大,提高了模型的泛化能力。LRN只对数据相邻区域做归一化处理,不改变数据的大小和维度。

    侧抑制参考链接:常见的视觉现象 - 壹心理

     

    3、AlexNet还应用了Overlapping(重叠池化),重叠池化就是池化操作在部分像素上有重合。池化核大小是n×n,步长是k,如果k=n,则是正常池化,如果 k<n, 则是重叠池化。官方文档中说明,重叠池化的运用减少了top-5和top-1错误率的0.4%和0.3%。重叠池化有避免过拟合的作用。

     

    4、AlexNet在fc6、fc7全连接层引入了drop out的功能。dropout是指在深度学习网络的训练过程中,对于神经网络单元,按照一定的概率(AlexNet是50%,这种情况下随机生成的网络结构最多)将其暂时从网络中丢弃(保留其权值),不再对前向和反向传输的数据响应。注意是暂时,对于随机梯度下降来说,由于是随机丢弃,故而相当于每一个mini-batch都在训练不同的网络,drop out可以有效防止模型过拟合,让网络泛化能力更强,同时由于减少了网络复杂度,加快了运算速度。还有一种观点认为drop out有效的原因是对样本增加来噪声,变相增加了训练样本。

     

    5、数据增强:在数据处理这部分作者提到过将每张图片处理为256××256的大小,但网络结构图中的输入却为224××224,这是因为作者在256××256大小的图片上使用了一个224××224的滑动窗口,将每个滑动窗口中的内容作为输入,这样就能将整个数据集扩大到原来的(256−224)×(256−224)=1024(256−224)×(256−224)=1024倍

     

    AlexNet TensorFlow 实现:

    from datetime import datetime
    import math
    import time
    import tensorflow as tf
    
    
    batch_size=32
    num_batches=100
    
    def print_activations(t):
        print(t.op.name, ' ', t.get_shape().as_list())
    
    
    def inference(images):
        parameters = []
        # conv1
        with tf.name_scope('conv1') as scope:
            kernel = tf.Variable(tf.truncated_normal([11, 11, 3, 64], dtype=tf.float32,
                                                     stddev=1e-1), name='weights')
            conv = tf.nn.conv2d(images, kernel, [1, 4, 4, 1], padding='SAME')
            biases = tf.Variable(tf.constant(0.0, shape=[64], dtype=tf.float32),
                                 trainable=True, name='biases')
            bias = tf.nn.bias_add(conv, biases)
            conv1 = tf.nn.relu(bias, name=scope)
            print_activations(conv1)
            parameters += [kernel, biases]
    
    
      # pool1
        lrn1 = tf.nn.lrn(conv1, 4, bias=1.0, alpha=0.001 / 9.0, 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_activations(pool1)
    
      # conv2
        with tf.name_scope('conv2') as scope:
            kernel = tf.Variable(tf.truncated_normal([5, 5, 64, 192], dtype=tf.float32,
                                                     stddev=1e-1), name='weights')
            conv = tf.nn.conv2d(pool1, kernel, [1, 1, 1, 1], padding='SAME')
            biases = tf.Variable(tf.constant(0.0, shape=[192], dtype=tf.float32),
                                 trainable=True, name='biases')
            bias = tf.nn.bias_add(conv, biases)
            conv2 = tf.nn.relu(bias, name=scope)
            parameters += [kernel, biases]
        print_activations(conv2)
    
      # pool2
        lrn2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75, name='lrn2')
        pool2 = tf.nn.max_pool(lrn2,
                               ksize=[1, 3, 3, 1],
                               strides=[1, 2, 2, 1],
                               padding='VALID',
                               name='pool2')
        print_activations(pool2)
    
      # conv3
        with tf.name_scope('conv3') as scope:
            kernel = tf.Variable(tf.truncated_normal([3, 3, 192, 384],
                                                     dtype=tf.float32,
                                                     stddev=1e-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)
            conv3 = tf.nn.relu(bias, name=scope)
            parameters += [kernel, biases]
            print_activations(conv3)
    
      # conv4
        with tf.name_scope('conv4') as scope:
            kernel = tf.Variable(tf.truncated_normal([3, 3, 384, 256],
                                                     dtype=tf.float32,
                                                     stddev=1e-1), name='weights')
            conv = tf.nn.conv2d(conv3, 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)
            conv4 = tf.nn.relu(bias, name=scope)
            parameters += [kernel, biases]
            print_activations(conv4)
    
      # conv5
        with tf.name_scope('conv5') as scope:
            kernel = tf.Variable(tf.truncated_normal([3, 3, 256, 256],
                                                     dtype=tf.float32,
                                                     stddev=1e-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)
            conv5 = tf.nn.relu(bias, name=scope)
            parameters += [kernel, biases]
            print_activations(conv5)
    
      # pool5
        pool5 = tf.nn.max_pool(conv5,
                               ksize=[1, 3, 3, 1],
                               strides=[1, 2, 2, 1],
                               padding='VALID',
                               name='pool5')
        print_activations(pool5)
    
        return pool5, parameters
    
    
    def time_tensorflow_run(session, target, info_string):
        num_steps_burn_in = 10
        total_duration = 0.0
        total_duration_squared = 0.0
        for i in range(num_batches + num_steps_burn_in):
            start_time = time.time()
            _ = session.run(target)
            duration = time.time() - start_time
            if i >= num_steps_burn_in:
                if not i % 10:
                    print ('%s: step %d, duration = %.3f' %
                           (datetime.now(), i - num_steps_burn_in, duration))
                total_duration += duration
                total_duration_squared += duration * duration
        mn = total_duration / num_batches
        vr = total_duration_squared / num_batches - mn * mn
        sd = math.sqrt(vr)
        print ('%s: %s across %d steps, %.3f +/- %.3f sec / batch' %
               (datetime.now(), info_string, num_batches, mn, sd))
    
    
    
    def run_benchmark():
    
            images = tf.Variable(tf.random_normal([batch_size,
                                               image_size,
                                               image_size, 3],
                                              dtype=tf.float32,
                                              stddev=1e-1))
    
        # Build a Graph that computes the logits predictions from the
        # inference model.
            pool5, parameters = inference(images)
    
        # Build an initialization operation.
            init = tf.global_variables_initializer()
    
        # Start running operations on the Graph.
            config = tf.ConfigProto()
            config.gpu_options.allocator_type = 'BFC'
            sess = tf.Session(config=config)
            sess.run(init)
    
        # Run the forward benchmark.
            time_tensorflow_run(sess, pool5, "Forward")
    
        # Add a simple objective so we can calculate the backward pass.
            objective = tf.nn.l2_loss(pool5)
        # Compute the gradient with respect to all the parameters.
            grad = tf.gradients(objective, parameters)
        # Run the backward benchmark.
            time_tensorflow_run(sess, grad, "Forward-backward")
    
    
    run_benchmark()

    VGG (16layers):多层(更多)同样大小的卷积层+全连接层

    不得不说VGG是个很可爱的神经网络结构,它的成功说明了网络结构还是深的好。它使用的卷积全部为3x3,Pad=1,步长为1,也就是说,卷积不会改变输出大小,而改变输出大小这件事就交给了2x2,步长为2 的max pool,也就是说每通过一个 max pool,卷积的尺寸都会折半。

     

    参考链接:NetscopeVGG网络结构分析 - Double_V的博客 - CSDN博客

     

    VVG的结构如图:

     

    VGG与Alexnet相比,具有如下改进几点:

    1、去掉了LRN层,作者发现深度网络中LRN的作用并不明显,干脆取消了

    2、采用更小的卷积核-3x3,Alexnet中使用了更大的卷积核,比如有7x7的,因此VGG相对于Alexnet而言,参数量更少

    3、池化核变小,VGG中的池化核是2x2,stride为2,Alexnet池化核是3x3,步长为2

     

    另外,VGG可以看做Hebbian Principle的一个应用,即在神经网络中越高层的网络应该越稀疏,同时表现力更强。这样可以避免过深的神经网络计算量过大,容易过拟合的问题。

    NIN:卷积层+用1X1层替代全连接层

    NIN结构(右边)与AlexNet、VGG(左边)的区别:

    稍加计算我们就会发现,VGG、AlexNet的绝大多数参数都集中于最后几个全连接层上,然而全连接层这玩意不仅线性强,参数多,还容易过拟合,NIN便使用1x1的卷积层创造性地解决了这个问题,利用多个“普通卷积层+1x1的卷积层”的嵌套,不仅可以达到良好的效果,而且大大降低了参数。

     

    GoogLeNet

    参考链接:

    Netscope

    【深度学习】论文导读:GoogLeNet模型,Inception结构网络简化(Going deeper with convolutions)

    GoogLeNet学习心得 - 静悟生慧 - 博客园

    GoogLeNet的心路历程(二)

     

    受到NIN的启发,GoogleNet提出了inception结构:

     

    可以看出这个结构很大程度上借鉴了NIN的思想,大量使用1x1的卷积层,同时也有创新,一个inception同时使用多个不同尺寸的卷积层,以一种结构化的方式来捕捉不同尺寸的信息,很大程度地降低了参数量和计算量。

     

    而GoogleNet可以看做多个inception的叠加:

     

    旁边的两个softmax,是训练时为了避免上述梯度消失问题,模型训练好后就拿掉。

     

    GoogLeNet也可以看做Hebbian Principle的应用:进入第一个inception前,feature map为 56x56,经过两个inception后,缩小为28x28,经过7个inception后变成14x14,经过9个inception后为7x7。最后通过7x7的average pool变成1x1。

     

    ResNet:VGG+残差结构

    ResNet取得了5项第一,并又一次刷新了CNN模型在ImageNet上的历史:

     

    为何ResNet如此牛逼?因为它足够深!但越深的网络越难以训练。如下图,深层网络表象竟然还不如浅层网络的好。

    参考链接:为什么深层神经网络难以训练 - BinChasing的博客 - CSDN博客

     

    其实用的是一种很直觉的方法。想想看,深层网络表现不如浅层网络是没有道理的,一个56层的网络,我只用前20层,后面36层不干活,最起码性能应该达到和一个20层网络的同等水平吧。所以,肯定有方法使得更深层的网络达到或者超过浅层网络的效果。

     

    那ResNet是如何解决这个问题的呢?它采用了一种“短路”的结构:

    假定原来的网络结构需要学习得到函数 H(x),那么不妨让原始信号 x 接到输出部分,并修改需要学习的函数为F(x)=H(x)-x,便可得到同样的效果。

     

    那这样的结构有什么好处呢?

    通过这样的方式,原始信号可以跳过一部分网络层,直接在更深的网络层传递。从直觉上来看,深层神经网络之所以难以训练,就是因为原始信号x在网络层中传递时,越来越失真(即梯度不稳定),而这种“短路”结构使得原始信号直接传入神经网络的深层,避免了信号失真,这样一来便极大地加快了神经网络训练时的效率:

     

    作者就设计实验来证明自己的观点。首先构建了一个18层和一个34层的plain网络,即将所有层进行简单的铺叠,然后构建了一个18层和一个34层的residual网络,仅仅是在plain上插入了shortcut,而且这两个网络的参数量、计算量相同,并且和之前有很好效果的VGG-19相比,计算量要小很多。(36亿FLOPs VS 196亿FLOPs,FLOPs即每秒浮点运算次数。)这也是作者反复强调的地方,也是这个模型最大的优势所在。

     

    不同结构的实验结果:

    模型构建好后进行实验,在plain上观测到明显的退化现象,而且ResNet上不仅没有退化,34层网络的效果反而比18层的更好,而且不仅如此,ResNet的收敛速度比plain的要快得多。

     

    参考链接:ResNet论文笔记 - XlyPb - CSDN博客

     

    DenseNet:用通道维上连结代替直接与原信号相加的ResNet

    与 ResNet 的主要区别在于,DenseNet 里模块B的输出不是像 ResNet 那样和模块A的输出相加,而是在通道维上连结。这样模块A的输出可以直接传入模块B后面的层。在这个设计里,模块A直接跟模块B后面的所有层连接在了一起。这也是它被称为“稠密连接”的原因。

     

     

    MobileNets:同样的卷积层,更少的参数

    MobileNet,正如其名,这是一个非常简单快速并且准确率也不错的CNN网络结构,它大大减少了网络层的参数数量,使得网络的前向传播和后向传播的运算量大幅减少,最终成为了一个效率极高的CNN网络。

     

    其主要的创新如下图所示:

    左边为传统卷积层,右边为MobileNet的卷积层

    这个图是什么意思呢?比如输入图片维度是11 × 11 × 3,标准卷积为3 × 3 × 3 ×16(假设stride为2,padding为1),那么可以得到输出为6 × 6 × 16的输出结果。现在输入图片不变,如果先通过一个维度是3 × 3 × 1 × 3的深度卷积(输入是3通道,这里有3个卷积核,对应着进行计算,理解成for循环),得到6 × 6 × 3的中间输出,然后再通过一个维度是1 × 1 × 3 ×16的1 ×1卷积,同样得到输出为6 × 6 × 16。

     

    这样下来,和传统卷积层结构对比如下:

    • DK为输入图片的长宽
    • M为通道数
    • DF为卷积宽度
    • N为输出卷积层厚度

     

    参考链接:论文简要翻译论文原文

     

    更进一步的,虽然MobileNet模型架构确实小又低延时,但是应用啥的可能需要更小更快的,所以介绍下一个简单的参数 [公式] ,叫它宽乘法器,它角色扮演就是让每一层网络瘦一定的比例,例如输入层 [公式] 就变为 [公式] ,输出层 [公式] 就变为 [公式] 。

    这个时候深可分解网络开销就变为:

    其中 [公式] 属于0~1中间,这个计算开销和参数数量接近 [公式] 的减少。应用了 [公式] 后模型需要再训练的。

     

    另外还有一个参数,从输入图片到每一层特征上都用这个分辨率乘法器 [公式] 数都用,原理啥的跟宽乘法器差不多,直接公式看,减少了开销。

     

    ShuffleNets:Group convolution+Channel Shuffle

    ShuffleNet是Face++提出的一种轻量化网络结构,主要思路是使用Group convolution和Channel shuffle改进ResNet,可以看作是ResNet的压缩版本。

     

    图14展示了ShuffleNet的结构,其中(a)就是加入BatchNorm的ResNet bottleneck结构,而(b)和(c)是加入Group convolution和Channel Shuffle的ShuffleNet的结构。

    图14 ShuffleNet

    那么ShuffleNet为何要这样做?既然是轻量化网络,我们还是来算算计算量。

    假设输入feature为 [公式] , 所有的[公式] 卷积数为 [公式] , [公式] Depthwise卷积数为 [公式],Group convolution都分为 [公式] 组。图14 (a)和(b)的网络乘法计算量:

    • 图8(a) ResNet bottleneck: [公式]
    • 图8(b) ShuffleNet stride=1结构: [公式]

    相比原始的ResNet缩小了超级多的计算量。所以ShuffleNet相当于保留ResNet结构,同时又压低计算量的改进版。

    这里解释下为何要做Channel Shuffle操作:

    ShuffleNet的本质是将卷积运算限制在每个Group内,这样模型的计算量取得了显著的下降。然而导致模型的信息流限制在各个Group内,组与组之间没有信息交换,如图15,这会影响模型的表示能力。因此,需要引入组间信息交换的机制,即Channel Shuffle操作。同时Channel Shuffle是可导的,可以实现end-to-end一次性训练网络。

     

    参考链接:
    轻量化网络ShuffleNet MobileNet v1/v2 解析
    为移动 AI 而生——旷视(Face++)最新成果 ShuffleNet 全面解读

     

    资源链接:
    多种网络结构TensorFlow实现:xiaohu2015/DeepLearning_tutorials
  • 相关阅读:
    穷人思维与富人思维
    纯真IP库读取文件
    memcached 分布式 一致性hash算法demo
    纯CSS画的基本图形(矩形、圆形、三角形、多边形、爱心、八卦等)
    IPhone在横屏字体变大解决办法-webkit-text-size-adjust
    js设计模式---阅读笔记002--接口
    js设计模式---阅读笔记001--富有表现力的js
    关于js继承
    js正则表达式中test,exec,match方法的区别说明
    js获取自定义的属性值
  • 原文地址:https://www.cnblogs.com/hanhao970620/p/12616200.html
Copyright © 2011-2022 走看看