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

猜你喜欢

转载自www.cnblogs.com/hanhao970620/p/12616200.html