• LeNet-5

 


          LeNet-5网络结构来源于Yan LeCun提出的,原文为《Gradient-based learning applied to document recognition》,论文里使用的是mnist手写数字作为输入数据(32 * 32)进行验证。我们来看一下网络结构。

【深度学习】经典的卷积神经网络(LeNet、AlexNet、VGG)

        LeNet-5一共有8层: 1个输入层+3个卷积层(C1、C3、C5)+2个下采样层(S2、S4)+1个全连接层(F6)+1个输出层,每层有多个feature map(自动提取的多组特征)。

        Input输入层

 cifar10 数据集,每一张图片尺寸:32 * 32

 

        C1 卷积层

 

  •  6个feature_map,卷积核大小 5 * 5 ,feature_map尺寸:28 * 28
  • 每个卷积神经元的参数数目:5 * 5 = 25个和一个bias参数
  • 连接数目:(5*5+1)* 6 *(28*28) = 122,304 
  • 参数共享:每个feature_map内共享参数,∴共(5*5+1)*6 = 156个参数

 

  S2 下采样层(池化层)

 

  • 6个14*14的feature_map,pooling大小 2* 2
  • 每个单元与上一层的feature_map中的一个2*2的滑动窗口连接,不重叠,因此S2每个feature_map大小是C1中feature_map大小的1/4
  • 连接数:(2*2+1)*1*14*14*6 = 5880个
  • 参数共享:每个feature_map内共享参数,有2 * 6 = 12个训练参数

 

  C3 卷积层

 

  这层略微复杂,S2神经元与C3是多对多的关系,比如最简单方式:用S2的所有feature map与C3的所有feature map做全连接(也可以对S2抽样几个feature map出来与C3某个feature map连接),这种全连接方式下:6个S2的feature map使用6个独立的5×5卷积核得到C3中1个feature map(生成每个feature map时对应一个bias),C3中共有16个feature map,所以该层需要学习的参数个数为:(5×5×6+1)×16=2416个,神经元连接数为:2416×8×8=154624个。

 

  S4 下采样层

 

  同S2,如果采用Max Pooling/Mean Pooling,则该层需要学习的参数个数为0个,神经元连接数为:(2×2+1)×16×4×4=1280个。

 

  C5卷积层

 

  类似C3,用S4的所有feature map与C5的所有feature map做全连接,这种全连接方式下:16个S4的feature map使用16个独立的1×1卷积核得到C5中1个feature map(生成每个feature map时对应一个bias),C5中共有120个feature map,所以该层需要学习的参数个数为:(1×1×16+1)×120=2040个,神经元连接数为:2040个。

 

  F6 全连接层

 

  将C5层展开得到4×4×120=1920个节点,并接一个全连接层,考虑bias,该层需要学习的参数和连接个数为:(1920+1)*84=161364个。

 

  输出层

 

  该问题是个10分类问题,所以有10个输出单元,通过softmax做概率归一化,每个分类的输出单元对应84个输入。

 

LeNet-5的Tensorflow实现

   tensorflow版本的LeNet-5版本的可以参照models/tutorials/image/cifar10/(https://github.com/tensorflow/models/tree/master/tutorials/image/cifar10)的步骤来训练,不过这里面的代码包含了很多数据处理、权重衰减以及正则化的一些方法防止过拟合。按照官方写的,batch_size=128时在Tesla K40上迭代10w次需要4小时,准确率能达到86%。不过如果不对数据做处理,直接跑的话,效果应该没有这么好。不过可以仔细借鉴cifar10_inputs.py里的distorted_inouts函数对数据预处理增大数据集的思想,以及cifar10.py里对于权重和偏置的衰减设置等。目前迭代到1w次左右,cost是0.98,acc是78.4%

 

 

 

  •  AlexNet

        AlexNet在ILSVRC-2012的比赛中获得top5错误率15.3%的突破(第二名为26.2%),其原理来源于2012年Alex的论文《ImageNet Classification with Deep Convolutional Neural Networks》,这篇论文是深度学习火爆发展的一个里程碑和分水岭,加上硬件技术的发展,深度学习还会继续火下去。

 

AlexNet网络结构

  由于受限于当时的硬件设备,AlexNet在GPU粒度都做了设计,当时的GTX 580只有3G显存,为了能让模型在大量数据上跑起来,作者使用了两个GPU并行,并对网络结构做了切分,如下:

【深度学习】经典的卷积神经网络(LeNet、AlexNet、VGG)

网络结构

  Input输入层

输入为224×224×3的三通道RGB图像,为方便后续计算,实际操作中通过padding做预处理,把图像变成227×227×3。

  C1卷积层

该层由:卷积操作 + Max Pooling + LRN(后面详细介绍它)组成。 

  • 卷积层:由96个feature map组成,每个feature map由11×11卷积核在stride=4下生成,输出feature map为55×55×48×2,其中55=(227-11)/4+1,48为分在每个GPU上的feature map数,2为GPU个数; 
  • 激活函数:采用ReLU; 
  • Max Pooling:采用stride=2且核大小为3×3(文中实验表明采用2×2的非重叠模式的Max Pooling相对更容易过拟合,在top 1和top 5下的错误率分别高0.4%和0.3%),输出feature map为27×27×48×2,其中27=(55-3)/2+1,48为分在每个GPU上的feature map数,2为GPU个数; 
  • LRN:邻居数设置为5做归一化。 

最终输出数据为归一化后的:27×27×48×2。 

   C2卷积层

该层由:卷积操作 + Max Pooling + LRN组成 

  • 卷积层:由256个feature map组成,每个feature map由5×5卷积核在stride=1下生成,为使输入和卷积输出大小一致,需要做参数为2的padding,输出feature map为27×27×128×2,其中27=(27-5+2×2)/1+1,128为分在每个GPU上的feature map数,2为GPU个数; 
  • 激活函数:采用ReLU; 
  • Max Pooling:采用stride=2且核大小为3×3,输出feature map为13×13×128×2,其中13=(27-3)/2+1,128为分在每个GPU上的feature map数,2为GPU个数; 
  • LRN:邻居数设置为5做归一化。 

最终输出数据为归一化后的:13×13×128×2。

  C3卷积层

该层由:卷积操作 + LRN组成(注意,没有Pooling层) 

  • 输入为13×13×256,因为这一层两个GPU会做通信(途中虚线交叉部分) 
  • 卷积层:之后由384个feature map组成,每个feature map由3×3卷积核在stride=1下生成,为使输入和卷积输出大小一致,需要做参数为1的padding,输出feature map为13×13×192×2,其中13=(13-3+2×1)/1+1,192为分在每个GPU上的feature map数,2为GPU个数; 
  • 激活函数:采用ReLU; 

最终输出数据为归一化后的:13×13×192×2。

  C4卷积层

该层由:卷积操作 + LRN组成(注意,没有Pooling层) 

  • 卷积层:由384个feature map组成,每个feature map由3×3卷积核在stride=1下生成,为使输入和卷积输出大小一致,需要做参数为1的padding,输出feature map为13×13×192×2,其中13=(13-3+2×1)/1+1,192为分在每个GPU上的feature map数,2为GPU个数; 
  • 激活函数:采用ReLU; 

最终输出数据为归一化后的:13×13×192×2。

  C5卷积层

该层由:卷积操作 + Max Pooling组成 

  • 卷积层:由256个feature map组成,每个feature map由3×3卷积核在stride=1下生成,为使输入和卷积输出大小一致,需要做参数为1的padding,输出feature map为13×13×128×2,其中13=(13-3+2×1)/1+1,128为分在每个GPU上的feature map数,2为GPU个数; 
  • 激活函数:采用ReLU; 
  • Max Pooling:采用stride=2且核大小为3×3,输出feature map为6×6×128×2,其中6=(13-3)/2+1,128为分在每个GPU上的feature map数,2为GPU个数. 

最终输出数据为归一化后的:6×6×128×2。

  F6全连接层

该层为全连接层 + Dropout 

  • 使用4096个节点; 
  • 激活函数:采用ReLU; 
  • 采用参数为0.5的Dropout操作 

最终输出数据为4096个神经元节点。

  F7全连接层

该层为全连接层 + Dropout 

  • 使用4096个节点; 
  • 激活函数:采用ReLU; 
  • 采用参数为0.5的Dropout操作 

最终输出为4096个神经元节点。

  输出层

该层为全连接层 + Softmax 

  • 使用1000个输出的Softmax 

最终输出为1000个分类。


 

 

AlexNet的优势

 

  1.使用了ReLu激活函数

 

  ----原始Relu-----

 

  AlexNet引入了ReLU激活函数,这个函数是神经科学家Dayan、Abott在《Theoretical Neuroscience》一书中提出的更精确的激活模型。原始的Relu激活函数(可参见 Hinton论文:《Rectified Linear Units Improve Restricted Boltzmann Machines》)我们比较熟悉,即 $ max(0,x) $ ,这个激活函数把负激活全部清零(模拟上面提到的稀疏性),这种做法在实践中即保留了神经网络的非线性能力,又加快了训练速度。 
        但是这个函数也有缺点:

  • 在原点不可微 

        反向传播的梯度计算中会带来麻烦,所以Charles Dugas等人又提出Softplus来模拟上述ReLu函数(可视作其平滑版): 

$$
\begin{align}
f(x) = log(1+e^{x})\nonumber
\end{align}
$$

实际上它的导数就是一个

$$
\begin{align}
{f}'=\frac{1}{1+e^{-x}}
\end{align}
$$

  • 过稀疏性

        当学习率设置不合理时,即使是一个很大的梯度,在经过ReLu单元并更新参数后该神经元可能永不被激活。


----Leaky ReLu---- 

  为了解决上述过稀疏性导致的大量神经元不被激活的问题,Leaky ReLu被提了出来:  

$$
\begin{align}
f(n) =
\begin{cases}
\alpha x,x< 0 \\
x,x\geq 0
\end{cases}
\end{align}
$$

是人工制定的较小值(如:0.1),它一定程度保留了负激活信息。

   还有很多其他的对于ReLu函数的改进,如Parametric ReLu,Randomized ReLu等,此处就不再展开讲了。

2.Local Response Normalization 局部响应均值

  LRN利用相邻feature map做特征显著化,文中实验表明可以降低错误率,公式如下:

$$
\begin{align}
b_{x,y}^{i}=\frac{a_{x,y}^{i}}{(k+\alpha \cdot \sum_{j=max(0,i-n/2)}^{min(N-1,i+n/2)}(a_{x,y}^{i})^{2})^{\beta }}
\end{align}
$$

公式的直观解释如下:

【深度学习】经典的卷积神经网络(LeNet、AlexNet、VGG)

 由于 $ \alpha $ 都是经过了ReLU的输出,所以一定是大于0的,函数 $ \frac{1}{(k+\alpha \cdot \sum_{ }{x}^{2})^{\beta }} $ ,取文中参数的图像(横坐标为 $  \sum_{ }{x}^{2} $ ):

【深度学习】经典的卷积神经网络(LeNet、AlexNet、VGG)

  • 当 $  \sum_{ }{x}^{2} $ 值较小时,即当前节点和其邻节点输出值差距不明显且大家的输出值都不大,可以认为此时特征间竞争激烈,该函数可以使原本差距不大的输出产生显著性差异且此时函数输出不饱和
  • 当 $  \sum_{ }{x}^{2} $ 值较大时,说明特征本身有显著性差异,但输出值太大容易过拟合,该函数可以令最终输出接近0从而缓解过拟合提高了模型泛化性。

 

 

 

3.Dropout

  Dropout是文章亮点之一,属于提高模型泛化性的方法,操作比较简单,以一定概率随机让某些神经元输出设置为0,既不参与前向传播也不参与反向传播,也可以从正则化角度去看待它。(关于深度学习的正则化年初的时候在公司做过一个分享,下次直接把pdf放出来)

  从模型集成的角度来看:

 【深度学习】经典的卷积神经网络(LeNet、AlexNet、VGG)

无Dropout网络:

【深度学习】经典的卷积神经网络(LeNet、AlexNet、VGG)

有Dropout网络:

【深度学习】经典的卷积神经网络(LeNet、AlexNet、VGG)

 其中 $ P $ n为所在的层。

  它是极端情况下的Bagging,由于在每步训练中,神经元会以某种概率随机被置为无效,相当于是参数共享的新网络结构,每个模型为了使损失降低会尽可能学最“本质”的特征,“本质”可以理解为由更加独立的、和其他神经元相关性弱的、泛化能力强的神经元提取出来的特征;而如果采用类似SGD的方式训练,每步迭代都会选取不同的数据集,这样整个网络相当于是用不同数据集学习的多个模型的集成组合。


 

用Tensorflow实现AlexNet

  1.网络结构

  1 def inference(images):
  2   '''
  3   Alexnet模型
  4   输入:images的tensor
  5   返回:Alexnet的最后一层卷积层
  6   '''
  7   parameters = []
  8   # conv1
  9   with tf.name_scope('conv1') as scope:
 10     kernel = tf.Variable(tf.truncated_normal([11, 11, 3, 64], dtype=tf.float32,
 11                                              stddev=1e-1), name='weights')
 12     conv = tf.nn.conv2d(images, kernel, [1, 4, 4, 1], padding='SAME')
 13     biases = tf.Variable(tf.constant(0.0, shape=[64], dtype=tf.float32),
 14                          trainable=True, name='biases')
 15     bias = tf.nn.bias_add(conv, biases)
 16     conv1 = tf.nn.relu(bias, name=scope)
 17     print_activations(conv1)
 18     parameters += [kernel, biases]
 19 
 20   # lrn1
 21   with tf.name_scope('lrn1') as scope:
 22     lrn1 = tf.nn.local_response_normalization(conv1,
 23                                               alpha=1e-4,
 24                                               beta=0.75,
 25                                               depth_radius=2,
 26                                               bias=2.0)
 27 
 28   # pool1
 29   pool1 = tf.nn.max_pool(lrn1,
 30                          ksize=[1, 3, 3, 1],
 31                          strides=[1, 2, 2, 1],
 32                          padding='VALID',
 33                          name='pool1')
 34   print_activations(pool1)
 35 
 36   # conv2
 37   with tf.name_scope('conv2') as scope:
 38     kernel = tf.Variable(tf.truncated_normal([5, 5, 64, 192], dtype=tf.float32,
 39                                              stddev=1e-1), name='weights')
 40     conv = tf.nn.conv2d(pool1, kernel, [1, 1, 1, 1], padding='SAME')
 41     biases = tf.Variable(tf.constant(0.0, shape=[192], dtype=tf.float32),
 42                          trainable=True, name='biases')
 43     bias = tf.nn.bias_add(conv, biases)
 44     conv2 = tf.nn.relu(bias, name=scope)
 45     parameters += [kernel, biases]
 46   print_activations(conv2)
 47 
 48   # lrn2
 49   with tf.name_scope('lrn2') as scope:
 50     lrn2 = tf.nn.local_response_normalization(conv2,
 51                                               alpha=1e-4,
 52                                               beta=0.75,
 53                                               depth_radius=2,
 54                                               bias=2.0)
 55 
 56   # pool2
 57   pool2 = tf.nn.max_pool(lrn2,
 58                          ksize=[1, 3, 3, 1],
 59                          strides=[1, 2, 2, 1],
 60                          padding='VALID',
 61                          name='pool2')
 62   print_activations(pool2)
 63 
 64   # conv3
 65   with tf.name_scope('conv3') as scope:
 66     kernel = tf.Variable(tf.truncated_normal([3, 3, 192, 384],
 67                                              dtype=tf.float32,
 68                                              stddev=1e-1), name='weights')
 69     conv = tf.nn.conv2d(pool2, kernel, [1, 1, 1, 1], padding='SAME')
 70     biases = tf.Variable(tf.constant(0.0, shape=[384], dtype=tf.float32),
 71                          trainable=True, name='biases')
 72     bias = tf.nn.bias_add(conv, biases)
 73     conv3 = tf.nn.relu(bias, name=scope)
 74     parameters += [kernel, biases]
 75     print_activations(conv3)
 76 
 77   # conv4
 78   with tf.name_scope('conv4') as scope:
 79     kernel = tf.Variable(tf.truncated_normal([3, 3, 384, 256],
 80                                              dtype=tf.float32,
 81                                              stddev=1e-1), name='weights')
 82     conv = tf.nn.conv2d(conv3, kernel, [1, 1, 1, 1], padding='SAME')
 83     biases = tf.Variable(tf.constant(0.0, shape=[256], dtype=tf.float32),
 84                          trainable=True, name='biases')
 85     bias = tf.nn.bias_add(conv, biases)
 86     conv4 = tf.nn.relu(bias, name=scope)
 87     parameters += [kernel, biases]
 88     print_activations(conv4)
 89 
 90   # conv5
 91   with tf.name_scope('conv5') as scope:
 92     kernel = tf.Variable(tf.truncated_normal([3, 3, 256, 256],
 93                                              dtype=tf.float32,
 94                                              stddev=1e-1), name='weights')
 95     conv = tf.nn.conv2d(conv4, kernel, [1, 1, 1, 1], padding='SAME')
 96     biases = tf.Variable(tf.constant(0.0, shape=[256], dtype=tf.float32),
 97                          trainable=True, name='biases')
 98     bias = tf.nn.bias_add(conv, biases)
 99     conv5 = tf.nn.relu(bias, name=scope)
100     parameters += [kernel, biases]
101     print_activations(conv5)
102 
103   # pool5
104   pool5 = tf.nn.max_pool(conv5,
105                          ksize=[1, 3, 3, 1],
106                          strides=[1, 2, 2, 1],
107                          padding='VALID',
108                          name='pool5')
109   print_activations(pool5)
110 
111   return pool5, parameters

完整代码可见:alexnet_tf.py 

 


 

  • Vgg网络结构

        VGGnet是Oxford的Visual Geometry Group的team,在ILSVRC 2014上的主要工作是证明了增加网络的深度能够在一定程度上影响网络最终的性能,如下图,文章通过逐步增加网络深度来提高性能,虽然看起来有一点小暴力,没有特别多取巧的,但是确实有效,很多pretrained的方法就是使用VGG的model(主要是16和19),VGG相对其他的方法,参数空间很大,所以train一个vgg模型通常要花费更长的时间,不过公开的pretrained model让我们很方便的使用,paper中的几种模型如下:

【深度学习】经典的卷积神经网络(LeNet、AlexNet、VGG)

       图中D和E分别为VGG-16和VGG-19,参数分别是138m和144m,是文中两个效果最好的网络结构,VGG网络结构可以看做是AlexNet的加深版,VGG在图像检测中效果很好(如:Faster-RCNN),这种传统结构相对较好的保存了图片的局部位置信息(不像GoogLeNet中引入Inception可能导致位置信息的错乱)。 

  我们来仔细看一下vgg16的网络结构:

 【深度学习】经典的卷积神经网络(LeNet、AlexNet、VGG)

        从图中可以看到,每个卷积层都使用更小的3×3卷积核对图像进行卷积,并把这些小的卷积核排列起来作为一个卷积序列。通俗点来讲就是对原始图像进行3×3卷积,然后再进行3×3卷积,连续使用小的卷积核对图像进行多次卷积。

  在alexnet里我们一开始的时候是用11*11的大卷积核网络,为什么在这里要用3*3的小卷积核来对图像进行卷积呢?并且还是使用连续的小卷积核?VGG一开始提出的时候刚好与LeNet的设计原则相违背,因为LeNet相信大的卷积核能够捕获图像当中相似的特征(权值共享)。AlexNet在浅层网络开始的时候也是使用9×9、11×11卷积核,并且尽量在浅层网络的时候避免使用1×1的卷积核。但是VGG的神奇之处就是在于使用多个3×3卷积核可以模仿较大卷积核那样对图像进行局部感知。后来多个小的卷积核串联这一思想被GoogleNet和ResNet等吸收。

  从图1的实验结果也可以看到,VGG使用多个3x3卷积来对高维特征进行提取。因为如果使用较大的卷积核,参数就会大量地增加、运算时间也会成倍的提升。例如3x3的卷积核只有9个权值参数,使用7*7的卷积核权值参数就会增加到49个。因为缺乏一个模型去对大量的参数进行归一化、约减,或者说是限制大规模的参数出现,因此训练核数更大的卷积网络就变得非常困难了。

  VGG相信如果使用大的卷积核将会造成很大的时间浪费,减少的卷积核能够减少参数,节省运算开销。虽然训练的时间变长了,但是总体来说预测的时间和参数都是减少的了。

 


 

Vgg的优势

   与AlexNet相比:

  • 相同点

    • 整体结构分五层;
    • 除softmax层外,最后几层为全连接层;
    • 五层之间通过max pooling连接。
  • 不同点

    • 使用3×3的小卷积核代替7×7大卷积核,网络构建的比较深;
    • 由于LRN太耗费计算资源,性价比不高,所以被去掉;
    • 采用了更多的feature map,能够提取更多的特征,从而能够做更多特征的组合。 

用Tensorflow实现vgg

  1.网络结构

 1 def inference_op(input_op, keep_prob):
 2     p = []
 3     # 第一块 conv1_1-conv1_2-pool1
 4     conv1_1 = conv_op(input_op, name='conv1_1', kh=3, kw=3,
 5                 n_out = 64, dh = 1, dw = 1, p = p)
 6     conv1_2 = conv_op(conv1_1, name='conv1_2', kh=3, kw=3,
 7                 n_out = 64, dh = 1, dw = 1, p = p)
 8     pool1 = mpool_op(conv1_2, name = 'pool1', kh = 2, kw = 2,
 9                 dw = 2, dh = 2)
10     # 第二块 conv2_1-conv2_2-pool2
11     conv2_1 = conv_op(pool1, name='conv2_1', kh=3, kw=3,
12                 n_out = 128, dh = 1, dw = 1, p = p)
13     conv2_2 = conv_op(conv2_1, name='conv2_2', kh=3, kw=3,
14                 n_out = 128, dh = 1, dw = 1, p = p)
15     pool2 = mpool_op(conv2_2, name = 'pool2', kh = 2, kw = 2,
16                 dw = 2, dh = 2)
17     # 第三块 conv3_1-conv3_2-conv3_3-pool3
18     conv3_1 = conv_op(pool2, name='conv3_1', kh=3, kw=3,
19                 n_out = 256, dh = 1, dw = 1, p = p)
20     conv3_2 = conv_op(conv3_1, name='conv3_2', kh=3, kw=3,
21                 n_out = 256, dh = 1, dw = 1, p = p)
22     conv3_3 = conv_op(conv3_2, name='conv3_3', kh=3, kw=3,
23                 n_out = 256, dh = 1, dw = 1, p = p)
24     pool3 = mpool_op(conv3_3, name = 'pool3', kh = 2, kw = 2,
25                 dw = 2, dh = 2)
26     # 第四块 conv4_1-conv4_2-conv4_3-pool4
27     conv4_1 = conv_op(pool3, name='conv4_1', kh=3, kw=3,
28                 n_out = 512, dh = 1, dw = 1, p = p)
29     conv4_2 = conv_op(conv4_1, name='conv4_2', kh=3, kw=3,
30                 n_out = 512, dh = 1, dw = 1, p = p)
31     conv4_3 = conv_op(conv4_2, name='conv4_3', kh=3, kw=3,
32                 n_out = 512, dh = 1, dw = 1, p = p)
33     pool4 = mpool_op(conv4_3, name = 'pool4', kh = 2, kw = 2,
34                 dw = 2, dh = 2)
35     # 第五块 conv5_1-conv5_2-conv5_3-pool5
36     conv5_1 = conv_op(pool4, name='conv5_1', kh=3, kw=3,
37                 n_out = 512, dh = 1, dw = 1, p = p)
38     conv5_2 = conv_op(conv5_1, name='conv5_2', kh=3, kw=3,
39                 n_out = 512, dh = 1, dw = 1, p = p)
40     conv5_3 = conv_op(conv5_2, name='conv5_3', kh=3, kw=3,
41                 n_out = 512, dh = 1, dw = 1, p = p)
42     pool5 = mpool_op(conv5_3, name = 'pool5', kh = 2, kw = 2,
43                 dw = 2, dh = 2)
44     # 把pool5 ( [7, 7, 512] )  拉成向量
45     shp  = pool5.get_shape()
46     flattened_shape = shp[1].value * shp[2].value * shp[3].value
47     resh1 = tf.reshape(pool5, [-1, flattened_shape], name = 'resh1')
48 
49     # 全连接层1 添加了 Droput来防止过拟合    
50     fc1 = fc_op(resh1, name = 'fc1', n_out = 2048, p = p)
51     fc1_drop = tf.nn.dropout(fc1, keep_prob, name = 'fc1_drop')
52 
53     # 全连接层2 添加了 Droput来防止过拟合    
54     fc2 = fc_op(fc1_drop, name = 'fc2', n_out = 2048, p = p)
55     fc2_drop = tf.nn.dropout(fc2, keep_prob, name = 'fc2_drop')
56 
57     # 全连接层3 加一个softmax求给类别的概率
58     fc3 = fc_op(fc2_drop, name = 'fc3', n_out = 10, p = p)
59     softmax = tf.nn.softmax(fc3)
60     predictions = tf.argmax(softmax, 1)
61     return predictions, softmax, fc3, p

2.训练网络结构

  1 # -*- coding: utf-8 -*-
  2 """
  3 Created by huxiaoman 2017.12.12
  4 vgg_tf.py:训练tensorflow版的vgg16网络,对cifar-10shuju进行分类
  5 """
  6 from datetime import datetime
  7 import math
  8 import time
  9 import tensorflow as tf
 10 import cifar10
 11 
 12 batch_size = 128
 13 num_batches = 200
 14 
 15 # 定义函数对卷积层进行初始化
 16 # input_op : 输入数据 
 17 # name : 该卷积层的名字,用tf.name_scope()来命名
 18 # kh,kw : 分别是卷积核的高和宽
 19 # n_out : 输出通道数
 20 # dh,dw : 步长的高和宽
 21 # p : 是参数列表,存储VGG所用到的参数
 22 # 采用xavier方法对卷积核权值进行初始化
 23 def conv_op(input_op, name, kh, kw, n_out, dh, dw, p):
 24     n_in = input_op.get_shape()[-1].value # 获得输入图像的通道数
 25     with tf.name_scope(name) as scope:
 26         kernel = tf.get_variable(scope+'w',
 27             shape = [kh, kw, n_in, n_out], dtype = tf.float32,
 28             initializer = tf.contrib.layers.xavier_initializer_conv2d())
 29         #  卷积层计算
 30         conv = tf.nn.conv2d(input_op, kernel, (1, dh, dw, 1), padding = 'SAME')
 31         bias_init_val = tf.constant(0.0, shape = [n_out], dtype = tf.float32)
 32         biases = tf.Variable(bias_init_val, trainable = True, name = 'b')
 33         z = tf.nn.bias_add(conv, biases)
 34         activation = tf.nn.relu(z, name = scope)
 35         p += [kernel, biases]
 36         return activation
 37 
 38 # 定义函数对全连接层进行初始化
 39 # input_op : 输入数据
 40 # name : 该全连接层的名字
 41 # n_out : 输出的通道数
 42 # p : 参数列表 
 43 # 初始化方法用 xavier方法
 44 def fc_op(input_op, name, n_out, p):
 45     n_in = input_op.get_shape()[-1].value
 46 
 47     with tf.name_scope(name) as scope:
 48         kernel = tf.get_variable(scope+'w',
 49             shape = [n_in, n_out], dtype = tf.float32,
 50             initializer = tf.contrib.layers.xavier_initializer())
 51         biases = tf.Variable(tf.constant(0.1, shape = [n_out],
 52             dtype = tf.float32), name = 'b')
 53         activation = tf.nn.relu_layer(input_op, kernel,  
 54             biases, name = scope)
 55         p += [kernel, biases]
 56         return activation 
 57 
 58 # 定义函数 创建 maxpool层
 59 # input_op : 输入数据 
 60 # name : 该卷积层的名字,用tf.name_scope()来命名
 61 # kh,kw : 分别是卷积核的高和宽
 62 # dh,dw : 步长的高和宽
 63 def mpool_op(input_op, name, kh, kw, dh, dw):
 64     return tf.nn.max_pool(input_op, ksize = [1,kh,kw,1],
 65         strides = [1, dh, dw, 1], padding = 'SAME', name = name)
 66 
 67 #---------------创建 VGG-16------------------
 68 
 69 def inference_op(input_op, keep_prob):
 70     p = []
 71     # 第一块 conv1_1-conv1_2-pool1
 72     conv1_1 = conv_op(input_op, name='conv1_1', kh=3, kw=3,
 73                 n_out = 64, dh = 1, dw = 1, p = p)
 74     conv1_2 = conv_op(conv1_1, name='conv1_2', kh=3, kw=3,
 75                 n_out = 64, dh = 1, dw = 1, p = p)
 76     pool1 = mpool_op(conv1_2, name = 'pool1', kh = 2, kw = 2,
 77                 dw = 2, dh = 2)
 78     # 第二块 conv2_1-conv2_2-pool2
 79     conv2_1 = conv_op(pool1, name='conv2_1', kh=3, kw=3,
 80                 n_out = 128, dh = 1, dw = 1, p = p)
 81     conv2_2 = conv_op(conv2_1, name='conv2_2', kh=3, kw=3,
 82                 n_out = 128, dh = 1, dw = 1, p = p)
 83     pool2 = mpool_op(conv2_2, name = 'pool2', kh = 2, kw = 2,
 84                 dw = 2, dh = 2)
 85     # 第三块 conv3_1-conv3_2-conv3_3-pool3
 86     conv3_1 = conv_op(pool2, name='conv3_1', kh=3, kw=3,
 87                 n_out = 256, dh = 1, dw = 1, p = p)
 88     conv3_2 = conv_op(conv3_1, name='conv3_2', kh=3, kw=3,
 89                 n_out = 256, dh = 1, dw = 1, p = p)
 90     conv3_3 = conv_op(conv3_2, name='conv3_3', kh=3, kw=3,
 91                 n_out = 256, dh = 1, dw = 1, p = p)
 92     pool3 = mpool_op(conv3_3, name = 'pool3', kh = 2, kw = 2,
 93                 dw = 2, dh = 2)
 94     # 第四块 conv4_1-conv4_2-conv4_3-pool4
 95     conv4_1 = conv_op(pool3, name='conv4_1', kh=3, kw=3,
 96                 n_out = 512, dh = 1, dw = 1, p = p)
 97     conv4_2 = conv_op(conv4_1, name='conv4_2', kh=3, kw=3,
 98                 n_out = 512, dh = 1, dw = 1, p = p)
 99     conv4_3 = conv_op(conv4_2, name='conv4_3', kh=3, kw=3,
100                 n_out = 512, dh = 1, dw = 1, p = p)
101     pool4 = mpool_op(conv4_3, name = 'pool4', kh = 2, kw = 2,
102                 dw = 2, dh = 2)
103     # 第五块 conv5_1-conv5_2-conv5_3-pool5
104     conv5_1 = conv_op(pool4, name='conv5_1', kh=3, kw=3,
105                 n_out = 512, dh = 1, dw = 1, p = p)
106     conv5_2 = conv_op(conv5_1, name='conv5_2', kh=3, kw=3,
107                 n_out = 512, dh = 1, dw = 1, p = p)
108     conv5_3 = conv_op(conv5_2, name='conv5_3', kh=3, kw=3,
109                 n_out = 512, dh = 1, dw = 1, p = p)
110     pool5 = mpool_op(conv5_3, name = 'pool5', kh = 2, kw = 2,
111                 dw = 2, dh = 2)
112     # 把pool5 ( [7, 7, 512] )  拉成向量
113     shp  = pool5.get_shape()
114     flattened_shape = shp[1].value * shp[2].value * shp[3].value
115     resh1 = tf.reshape(pool5, [-1, flattened_shape], name = 'resh1')
116 
117     # 全连接层1 添加了 Droput来防止过拟合    
118     fc1 = fc_op(resh1, name = 'fc1', n_out = 2048, p = p)
119     fc1_drop = tf.nn.dropout(fc1, keep_prob, name = 'fc1_drop')
120 
121     # 全连接层2 添加了 Droput来防止过拟合    
122     fc2 = fc_op(fc1_drop, name = 'fc2', n_out = 2048, p = p)
123     fc2_drop = tf.nn.dropout(fc2, keep_prob, name = 'fc2_drop')
124 
125     # 全连接层3 加一个softmax求给类别的概率
126     fc3 = fc_op(fc2_drop, name = 'fc3', n_out = 10, p = p)
127     softmax = tf.nn.softmax(fc3)
128     predictions = tf.argmax(softmax, 1)
129     return predictions, softmax, fc3, p
130 
131 # 定义评测函数
132 
133 def time_tensorflow_run(session, target, feed, info_string):
134     num_steps_burn_in = 10
135     total_duration = 0.0
136     total_duration_squared = 0.0
137 
138     for i in range(num_batches + num_steps_burn_in):
139         start_time = time.time()
140         _ = session.run(target, feed_dict = feed)
141         duration = time.time() - start_time
142         if i >= num_steps_burn_in:
143             if not i  % 10: 
144                 print('%s: step %d, duration = %.3f' % 
145                     (datetime.now(), i-num_steps_burn_in, duration))
146             total_duration += duration
147             total_duration_squared += duration * duration
148     mean_dur = total_duration / num_batches 
149     var_dur = total_duration_squared / num_batches - mean_dur * mean_dur
150     std_dur = math.sqrt(var_dur)
151     print('%s: %s across %d steps, %.3f +/- %.3f sec / batch' %(datetime.now(), info_string, num_batches, mean_dur, std_dur))
152 
153 
154 def train_vgg16():
155     with tf.Graph().as_default():
156         image_size = 224  # 输入图像尺寸
157         # 生成随机数测试是否能跑通
158         #images = tf.Variable(tf.random_normal([batch_size, image_size, image_size, 3], dtype=tf.float32, stddev=1e-1))
159         with tf.device('/cpu:0'):
160             images, labels = cifar10.distorted_inputs()
161         keep_prob = tf.placeholder(tf.float32)
162         prediction,softmax,fc8,p = inference_op(images,keep_prob)
163         init = tf.global_variables_initializer()
164         sess = tf.Session()
165         sess.run(init)
166         time_tensorflow_run(sess, prediction,{keep_prob:1.0}, "Forward")
167         # 用以模拟训练的过程
168         objective = tf.nn.l2_loss(fc8)  # 给一个loss
169         grad = tf.gradients(objective, p)  # 相对于loss的 所有模型参数的梯度
170         time_tensorflow_run(sess, grad, {keep_prob:0.5},"Forward-backward")
171 
172 
173 
174 
175 if __name__ == '__main__':
176     train_vgg16()

当然,我们也可以用tf.slim来简化一下网络结构

 1 def vgg16(inputs):
 2   with slim.arg_scope([slim.conv2d, slim.fully_connected],
 3                       activation_fn=tf.nn.relu,
 4                       weights_initializer=tf.truncated_normal_initializer(0.0, 0.01),
 5                       weights_regularizer=slim.l2_regularizer(0.0005)):
 6     net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3], scope='conv1')
 7     net = slim.max_pool2d(net, [2, 2], scope='pool1')
 8     net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope='conv2')
 9     net = slim.max_pool2d(net, [2, 2], scope='pool2')
10     net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
11     net = slim.max_pool2d(net, [2, 2], scope='pool3')
12     net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv4')
13     net = slim.max_pool2d(net, [2, 2], scope='pool4')
14     net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv5')
15     net = slim.max_pool2d(net, [2, 2], scope='pool5')
16     net = slim.fully_connected(net, 4096, scope='fc6')
17     net = slim.dropout(net, 0.5, scope='dropout6')
18     net = slim.fully_connected(net, 4096, scope='fc7')
19     net = slim.dropout(net, 0.5, scope='dropout7')
20     net = slim.fully_connected(net, 1000, activation_fn=None, scope='fc8')

小结:

      1.LRN层太耗费计算资源,作用不大,可以舍去。

    2.大卷积核可以学习更大的空间特征,但是需要的参数空间也更多,小卷积核虽然学习的空间特征有限,但所需参数空间更小,多层叠加训练可能效果更好。

    3.越深的网络效果越好,但是要避免梯度消失的问题,选取relu的激活函数、batch_normalization等都可以从一定程度上避免。

  4.小卷积核+深层网络的效果,在迭代相同次数时,比大卷积核+浅层网络效果更好,对于我们自己设计网络时可以有借鉴作用。但是前者的训练时间可能更长,不过可能比后者收敛速度更快,精确度更好。

 

 

 

参考文献

1.LeNet-5论文:《Gradient-based learning applied to document recognition

2.可视化CNN:http://shixialiu.com/publications/cnnvis/demo/

3. https://www.cnblogs.com/charlotte77/p/7906363.html

4.AlexNet: http://www.cs.toronto.edu/~fritz/absps/imagenet.pdf