tensorflow学习笔记——AlexNet

  2012年,Alex Krizhevsky、Ilya Sutskever在多伦多大学Geoff Hinton的实验室设计出了一个深层的卷积神经网络AlexNet,夺得了2012年ImageNet LSVRC的冠军,且准确率远超第二名(top5错误率为15.3%,第二名为26.2%),引发了很大的轰动。AlexNet能够说是具备历史意义的一个网络结构,在此以前,深度学习已经沉寂了很长时间,自2012年AlexNet诞生以后,后面的ImageNet冠军都是用卷积神经网络(CNN)来作的,而且层次愈来愈深,使得CNN成为在图像识别分类的核心算法模型,带来了深度学习的大爆发。html

  下面首先学习AlexNet网络的创新点,而后在学习AlexNet的网络结构,最后在用代码实现AlexNet。git

1,AlexNet网络的创新点

  AlexNet将LeNet的思想发扬光大,把CNN的基本原理应用到了很深很宽的网络中。AlexNet主要使用到的新技术点以下:算法

  (1)成功使用ReLU做为CNN的激活函数,并验证其效果在较深的网络超过了Sigmoid,成功解决了Sigmoid在网络较深时的梯度弥散问题。虽然ReLU激活函数在好久以前就被提出了,但直到AlexNet的出现才将其发扬光大。服务器

  在最初的感知机模型中,输入和输出的关系以下:网络

   虽然只是单纯的线性关系,这样的网络结构有很大的局限性:即便用不少这样结构的网络层叠加,其输出和输入仍然是线性关系,没法处理有非线性关系的输入输出。所以,对每一个神经元的输出作个非线性的转换也就是,将上面的加权求和的结果输入到一个非线性函数,也就是激活函数中。这样,因为激活函数的引入,多个网络层的叠加就再也不是单纯的线性变换,而是具备更强的表现能力。session

  在网络层较少时,Sigmoid函数的特性可以很好的知足激活函数的做用:它把一个实数压缩至0到1之间,当输入的数字很是大的时候,结果会接近1,;当输入很是大的负数时,则会获得接近0的结果。这种特性,可以很好的模拟神经元在受刺激后,是否被激活向后传递信息(输出为0,几乎不被激活;输出为1,彻底被激活)。Sigmoid函数一个很大的问题就是梯度饱和。观察Sigmoid函数的曲线,当输入的数字较大(或较小)时,其函数值趋于不变,其导数变得很是的小。这样在层数不少的网络结构中,进行反向传播时,因为不少个很小的Sigmoid导数累成,致使其结果趋于0,权值更新较慢。架构

  (2)训练时使用Dropout随机忽略一部分神经元,以免模型过拟合。Dropout虽有单独的论文论述,可是AlexNet将其实用化,经过实践证明了它的效果。在AlexNet中主要是最后几个全链接层使用了Dropout。app

  Dropout应该是AlexNet网络中一个很大的创新,如今神经网络中的必备结构之一。Dropout也能够看作是一种模型组合,每次生成的网络结构都不同,经过组合多个模型的方式可以有效地减小过拟合。Dropout只须要两倍的训练时间便可实现模型组合(相似去平均)的效果。很是高效。dom

  (3)在CNN中使用重叠的最大池化层。此前CNN中广泛使用平均池化,AlexNet所有使用最大池化,避免平均池化的模糊化效果,而且AlexNet中提出让步长比池化核的尺寸小,这样池化层的输出之间会有重叠和覆盖,提高了特征的丰富性。机器学习

  通常的池化(pooling)是不重叠的,池化区域的窗口大小与步长相等,以下图所示:

   在AlexNet中使用的池化(pooling)倒是可重叠的,也就是说,在池化的时候,每次移动的步长小于池化的窗口长度。

  AlexNet池化的大小为3*3的正方形,每次池化移动步长为2,这样就会出现重叠。重叠池化能够避免过拟合,这个策略共享了0.3%的Top-5错误率。

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

  LRN层,全称 Local  Response Normalization(局部响应归一化)。核心思想就是利用临近的数据作归一化,这个策略贡献了1.2%的Top-5错误率。

  ReLU具备良好性质:当输入为正,其导数为1,有效的避免神经元中止学习,也就是死掉。

  在神经生物学有一个概念叫作“侧抑制”(lateral inhibitio),指的是被激活的神经元抑制相邻神经元。归一化(normalization)的目的是“抑制”,局部归一化就是借鉴了“侧抑制”的思想来实现局部抑制,尤为当使用ReLU时这种“侧抑制”很管用,由于ReLU的响应结果是无界的(能够很是大),因此须要归一化。使用局部归一化的方案有助于增长泛化能力。

  在神经网络中,咱们用激活函数将神经元的输出作一个非线性映射,但tanh和sigmoid这些传统的激活函数的值域都是有范围的,可是ReLU激活函数获得的值域没有一个区间,因此要对ReLU获得的结果进行归一化。也就是Local Response Normalization。局部响应归一化的方法以下图的公式:

    表明的时ReLU 在第 i 个 kernel 的(x,y)位置的输出, n表示的是 的邻居个数。N表示该 kernel 的总数量, 表示的时 LRN的结果。ReLU输出的结果和他周围必定范围的邻居作一个局部的归一化,怎么理解呢?我以为这里有些相似于咱们的最大最小归一化,假设有一个向量 X = [X_1, X_2, ....X_n] 那么将全部的数归一化到 0~1之间的归一化规则是:

   上面那个公式有着相似的功能,只不过稍微负载一些,首先运算略微复杂,其次还有一些其余的参数 alpah, beta, k。

   咱们看看上图,每个矩阵表示的是一个卷积核生成的feature map,全部的pixel已经通过的了ReLU 激活函数,如今咱们都要对具体的 pixel进行局部的归一化。假设绿色箭头指向的是第 i 个 kernel对应的map,其他的四个蓝色箭头是它周围的邻居 kernel层对应的map,假设矩阵中间的绿色的 pixel 的位置为(x,  y),那么咱们须要提取出来进行局部归一化的数据就是周围邻居 kernel 对应的 map(x, y) 位置的 pixel 的值。也就是上面式子的 ,而后把这些邻居 pixel 的值平方再加和。乘以一个稀疏 alpha 再加上一个常数 k ,而后 beta 次幂,就算分母,分子就是第 i 个 kernel 对应的 map 的(x, y)位置的 pixel的值。这样理解后我以为不是那么的复杂了。

  关键是参数 alpha,beta,k 如何肯定,论文中说是在验证集中肯定,最终肯定的结果为:

  (5)使用CUDA加速神经卷积网络的训练,利用GPU强大的并行计算能力,处理神经网络训练时大量的矩阵运算。AlexNet 使用了两块 GTX 580 GPU 进行训练,单个 CTX 580 只有3GB显存,这限制了可训练的网络的最大规模。所以做者将AlexNet分布在两个GPU上,在每一个GPU的显存中存储一半的神经元的参数。所以GPU之间通讯方便,能够互相访问显存,而不须要经过主机内存,因此同时使用多块GPU也是很是高效的。同时,AlexNet的设计让GPU之间的通讯只在网络的某些层进行,控制了通讯的性能损耗。

  (6)数据加强,随机的从256*256的原始图像中截取224*224大小的区域(以及水平翻转的镜像),至关于增长了(256-224)2*2=2048倍的数据量。若是没有数据加强,仅靠原始的数据量,参数众多的CNN会陷入过拟合中,使用了数据加强后能够大大减轻过拟合,提升泛化能力。进行预测时,则是取图片的四个角加中间共5个位置,并进行左右翻转,一共得到10张图片,对他们进行预测并对10次结果求均值。同时,AlexNet论文中提到了会对图像的RGB数据进行PCA处理,并对主成分作一个标准差为0.1的高斯扰动,增长了一些噪声,这个Trick会让错误率再下降1%。

  有一种观点认为神经网络是靠数据喂出来的,若是可以增长训练数据,提供海量数据进行训练,则可以有效提高算法的准确率,由于这样能够避免过拟合,从而能够进一步增大、加深网络结构。而当训练数据有限时,能够经过一些变换从已有的训练数据集中生成一些新的数据,以快速地扩充训练数据。
  其中,最简单、通用的图像数据变形的方式:水平翻转图像,从原始图像中随机裁剪、平移变换,颜色、光照变换。

  AlexNet在训练时候,在数据扩充(data  augmentation)这样处理:

  • (1)随机裁剪,对256×256的图片进行随机裁剪到224×224,而后进行水平翻转,至关于将样本数量增长了((256-224)^2)×2=2048倍;
  • (2)测试的时候,对左上、右上、左下、右下、中间分别作了5次裁剪,而后翻转,共10个裁剪,以后对结果求平均。做者说,若是不作随机裁剪,大网络基本上都过拟合;
  • (3)对RGB空间作PCA(主成分分析),而后对主成分作一个(0, 0.1)的高斯扰动,也就是对颜色、光照做变换,结果使错误率又降低了1%

  (7) Overlapping Pooling(重叠池化)

  在传统的CNN中,卷积以后通常链接一个池化层,这个池化运算是没有重叠的池化运算。更加确切的说,池化能够被看做是由间隔为 s 的池化网络构成的,将 z*z 的池化单元的中心放在这个网络上以完成池化操做,若是 s = z ,那么这个操做就是传统的 CNN 池化操做,若是 s < z ,就是重叠池化操做,本文就是使用这个方法。

   通常的池化层由于没有重叠,因此pool_size 和 stride通常是相等的,例如8×8的一个图像,若是池化层的尺寸是2×2 ,那么通过池化后的操做获得的图像是 4×4大小,这种设置叫作不覆盖的池化操做,若是 stride < pool_size, 那么就会产生覆盖的池化操做,这种有点相似于convolutional化的操做,这样能够获得更准确的结果。在top-1,和top-5中使用覆盖的池化操做分别将error rate下降了0.4%和0.3%。论文中说,在训练模型过程当中,覆盖的池化层更不容易过拟合。

2,AlexNet网络的网络结构

  整个AlexNet有8个须要训练参数的层(不包括池化层和LRN层),前5层为卷积层,后3层为全链接层,以下图所示,ALexNet最后一层是有1000类输出的Softmax层用做分类。LRN层出如今第一个及第二个卷积层后,而最大池化层出如今两个LRN层及最后一个卷积层后,ReLU激活函数则应用在这8层每一层的后面。由于AlexNet训练时使用了两块GPU,所以这个结构图中很多组件被拆为两部分。如今咱们GPU的显存能够放下所有的模型参数,所以只考虑一块GPU的状况便可。

   AlexNet 每层的超参数如上图所示,其中输入的图片尺寸为224*224,第一个卷积层使用了较大的卷积核尺寸11*11,步长为4,有96个卷积核;紧接着一个LRN层;而后是一个3*3的最大池化层,步长为2,。这以后的卷积核尺寸都比较小,都是5*5 或者3*3 的大小,而且步长都为1,即会扫描全图全部像素;而最大池化层依然保持为3*3,而且步长为2。咱们会发现一个比较有意思的现象,在前几个卷积层,虽然计算量很大,但参数量很小,都在1M 左右甚至更小,只占AlexNet 总参数量的很小一部分。这就是卷积层有用的地方,能够经过较小的参数量提取有效的特征,而若是前几层直接使用全链接层,那么参数量和计算量将成为天文数字。虽然每个卷积层占整个网络的参数量的1%都不到,可是若是去掉任何一个卷积层,都会使网络的分类性能大幅的降低。

  (注意:这个224,其实通过计算(224-11)/4=54.75,而不是论文中的55*55,而是227*227,因此假如使用224*224,就让padding=2)

  其实下图更能让人理解AlexNet网络结构:

   AlexNet 每层的超参数及参数数量:

   那下面对照着图,咱们再详细分析一波这个网络:

  其实论文中是使用两块GPU来处理的,这就致使了第一个AlexNet的网络结构是分开的,那后面咱们用一个架构网络来分析,如今也都9012年了,科技更发达了,因此使用一块GPU彻底能够运算,咱们下面来详细分析一下这8层网络,包括5层卷积,3层全链接。(论文中提到过,五层卷积,去掉任意一层都会使结果很差,因此这个网络的深度彷佛比较重要。)

2.1 卷积C1

  该层的处理流程是:卷积——》  ReLU ——》池化 ——》 归一化

  • 卷积:输入的是227*227,使用96个11*11*3的卷积核,获得的FeatureMap为55*55*96
  • ReLU:将卷积层输出的FeatureMap 输入到ReLU函数中。
  • 池化:使用3*3 步长为2的池化单位(重叠池化,步长小于池化单元的宽度),输出为27*27*96((55-3)/2+1=27)
  • 局部响应归一化:使用k =2,n=5,alpha=10-4, beta=0.75进行局部归一化,输出仍然是27*27*96,输出分为两组,每组为27*27*48。

  卷积后的图形大小是怎么样的呢?

  width = (227 + 2 * padding - kerne_size)/ stride + 1 = 55

  height = (227 + 2 * padding - kerne_size)/ stride + 1 = 55

  dimention = 96

2.2 卷积层C2

  该层的处理流程是:卷积——》  ReLU ——》池化 ——》 归一化

  • 卷积:输入的是2组27*27*48,使用2组,每组128个尺寸为5*5*48的卷积核,并作了边缘填充 padding=2,卷积的步长为1,则输出的FeatureMap为2组,每组的大小为 27*27*128,((27 + 2*2-5)/1+1=27)
  • ReLU:将卷积层输出的FeatureMap 输入到ReLU函数中。
  • 池化:使用3*3 步长为2的池化单位(重叠池化,步长小于池化单元的宽度),池化后图像的尺寸为(27-3)/2+1=12,输出为13*13*256
  • 局部响应归一化:使用k =2,n=5,alpha=10-4, beta=0.75进行局部归一化,输出仍然是13*13*256,输出分为两组,每组为13*13*128。

2.3 卷积层C3

该层的处理流程是: 卷积——》ReLU

  • 卷积,输入是,13*13*256,使用2组共384尺寸为3*3*256的卷积核,作了边缘填充padding=1,卷积的步长为1,则输出的FeatureMap 为13*13*384

  • ReLU,将卷积层输出的FeatureMap输入到ReLU函数中

2.4 卷积层C4

该层的处理流程是: 卷积——》ReLU

  • 卷积,输入是13*13*384,分为两组,每组13*13*192,作了边缘填充padding=1,卷积的步长为1,则输出的Featuremap为13*13*384

  • ReLU,将卷积层输出的FeatureMap输入到ReLU函数中

2.5 卷积层C5

  该层的处理流程是:卷积——》  ReLU ——》池化 

  • 卷积:输入的是13*13*384,分为两组,每组13*13*192,使用两组,每组为128,尺寸为3*3*192的卷积核,作了边缘填充padding=1,卷积的步长为1,则输出的FeatureMap为13*13*256
  • ReLU:将卷积层输出的FeatureMap 输入到ReLU函数中。
  • 池化:使用3*3 步长为2的池化单位(重叠池化,步长小于池化单元的宽度),池化后图像的尺寸为(13-3)/2+1=12,输出为6*6*256

2.6 全链接层FC6

流程为:(卷积)全链接——》  ReLU ——》 Dropout

  • 卷积(全链接):输入为6*6*256,该层有4096个卷积核,每一个卷积核的大小为6*6*256.因为卷积核的尺寸恰好与待处理特征图(输入)的尺寸相同,即卷积核中的每一个系数只与特征图(输入)尺寸的一个像素值相乘,一一对应,所以,该层被称为全链接层。因为卷积核与特征图的尺寸相同,卷积运算后只有一个值,所以,卷积后的像素层尺寸为 4096*1*1,即有4096个神经元。
  • ReLU:这4096个运算结果经过ReLU激活函数生成4096个值
  • Dropout:抑制过拟合,随机的断开某些神经元的链接或者是不激活某些神经元

2.7 全链接层FC7

流程为:全链接——》  ReLU ——》 Dropout

  • 全链接:输入为4096的向量
  • ReLU:这4096个运算结果经过ReLU激活函数生成4096个值
  • Dropout:抑制过拟合,随机的断开某些神经元的链接或者是不激活某些神经元

2.8 输出层

   第七层的神经元的个数为 4096,第八层最终输出 softmax为1000个。

3,AlexNet 参数数量

  卷积层的参数 = 卷积核的数量 * 卷积核 + 偏置

  • C1:96个11*11*3的卷积核,96*11*11*3 + 96 = 34848
  • C2:2组,每组128个5*5*48的卷积核,(128*5*5*48+128)*2=307456
  • C3:384个3*3*256的卷积核,3*3*256*384+384=885120
  • C4:2组,每组192个3*3*192的卷积核,(3*3*192*192+192)*2=663936 
  • C5:2组,每组128个3*3*192的卷积核,(3*3*192*128+128)*2=442624
  • FC6:4096个6*6*256的卷积核,6*6*256*4096+4096=37752832
  • FC7:4096*4096+4096=16781312
  • output:4096+1000=4096000

  卷积层 C2,C4,C5中的卷积核只和位于同一GPU的上一层的FeatureMap相连。从上面能够看出,参数大多数集中在全链接层,在卷积层因为权值共享,权值参数较少。

4,AlexNet网络的TensorFlow实现

   由于使用 ImageNet数据集训练一个完整的AlexNet耗时很是长,所以下面学习的AlexNet的实现将不涉及实际数据的训练。咱们会创建一个完整的ALexNet卷积神经网络,而后对它每一个batch的前馈计算(forward)和反馈计算(backward)的速度进行测试。这里使用随机图片数据来计算每轮前馈,反馈的平均耗时。固然也能够下载ImageNet数据并使用AlexNet网络完成训练,并在测试集上进行测试。

   首先,定义一个用来显示网络每一层结构的函数 print_actications,展现每个卷积层或池化层输出 tensor 的尺寸。这个函数通常接受一个 tensor做为输入,并显示其名称(t.op.name)和 tensor尺寸(t.get_shape.as_list())。

# 定义一个用来显示网络每一层结构的函数 print_actications 展现每个卷积层或者输出tensor的尺寸
# 此函数接受一个tensor做为输入,并显示其名称(t.op.name)和tensor尺寸
def print_activations(t):
    print(t.op.name, ' ', t.get_shape().as_list())

  接下来设计AlexNet的网络结构,咱们先定义函数 inference,它接受 images做为输入,返回最后一层 pool5(第5个池化层)及 paramenters(AlexNet 中全部须要训练的模型参数)。这个 inference 函数将会很大,包括多个卷积和池化层,所以下面将拆分学习。

  首先是第一个卷积层 conv1,这里使用 TensorFlow 中的 name_scope,经过 with.tf.name_scope('conv1') as scope 能够将 scope 内生成的 Variable 自动命名为 conv1/xxx,便于区分不一样卷积层之间的组件。而后定义第一个卷积层,和以前同样使用 tf.truncated_normal 截断的正态分布函数(标准差为0.1)初始化卷积核的参数 kernel。卷积核尺寸为11*11,颜色通道为 3 ,卷积核数量为64.准确好了 kernel ,再使用 tf.nn.conv2d 对输入 images 完成卷积操做,咱们将 strides 步长设置为4*4(即在图片上每4*4 区域只取样一次,横向间隔是4,纵向间隔也为4,每次取样的卷积核大小都是11*11),padding模式设为SAME。将卷积层的biases所有初始化为0,再使用 tf.nn.bias_add 将 conv 和 biases 加起来,并使用激活函数 tf.nn.relu 对结果进行非线性处理。最后使用 print_actations 将这一层最后输出的 tensor conv1 的结构打印出来,并将这一层可训练的参数 kernel,biases 添加到 parameters中。

def inference(images):
    parameters = []

    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]
        

  在第一个卷积层后再添加LRN层和最大池化层。先使用 tf.nn.lrn 对前面输出的 tensor conv1 进行 LRN 处理,这里使用的 depth_radius 设为4,bias设为1,alpha为0.001/9,beta为0.75,基本都是AlexNet 的论文中的推荐值。不过目前除了 AlexNet,其余经典的卷积神经网络模型都放弃了 LRN (主要是效果不明显),而咱们使用 LRN 也会让前馈,反馈的速度大大降低(总体速度降到1/3),你们能够支柱选择是否使用 LRN。下面使用 tf.nn.max_pool 对前面的输出  lrn1 进行最大池化处理,这里的池化尺寸为 3*3,即将3*3大小的像素块降为 1*1 的像素,取样的步长为 2*2,padding 模式设为 VALID,即取样时不能超过边框,不像SAME模式那样能够填充边界外的点。最后将输出结果 pool1 的结构打印出来。

lrn1 = tf.nn.lrn(conv1, 4, bias=1.0, 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_activations(pool1)

  接下来设计第二个卷积层,大部分步骤和第一个卷积层相同,只有几个参数不一样。主要区别在于咱们的卷积核尺寸是 5*5,输入通道数(即上一层输出通道数,也就是上一层卷积核数量)为64,卷积核数量为192。同时,卷积的步长也所有设为1,即扫描全图像素。

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)

  接下来对第二个卷积层的输出  conv2 进行处理,一样先作 LRN处理,再进行最大池化处理,参数和以前彻底同样,这里再也不赘述了。

# 接下来对第二个卷积层的输出 conv2 进行处理,一样是作了LRN处理,再进行最大池化处理
lrn2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001/9, 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)

  接下来建立第三个卷积层,基本结构和前面两个相似,也只是参数不一样。这一层的卷积核尺寸为3*3,输入的通道数为192,卷积核数量继续扩大为384,一样卷积的步长所有为1,其余地方和前面的保持一致。

# 第三个卷积层
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)

  第四个卷积层和以前也相似,这一层的卷积核尺寸为3*3,输入通道数为384,可是卷积核数量降为256。

# 第四个卷积层,核尺寸为3*3,输入通道数为384, 可是卷积核数量下降为256
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)

  最后的第五个卷积层一样是3*3 大小的卷积核,输入通道数为256,卷积核数量也为256。

# 第五个卷积层一样是3*3大小的卷积核,输入通道数为256, 卷积核数量也是256
    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。至此,inference函数就完成了,它能够建立 AlexNet 的卷积部分。在正式使用 AlexNet来训练或预测时,还须要添加3个全链接层,隐含节点数分别为 4096,4096和1000。因为最后三个全链接层的计算量很小,就没放到计算速度测评中,他们对计算耗时的影响很是小。你们在正式使用 AlexNet时须要自行添加这三个全链接层,全链接层在TensorFlow中的实现方法在以前已经学习过了,这里再也不赘述。

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

  接下来实现一个评估 AlexNet每轮计算时间的函数 time_tensorflow_run。这个函数的第一个输入是 TensorFlow的Session,第二个变量是须要评测的运算算子,第三个变量是测试的名称。先定义预测轮数  num_steps_burn_in=10,它的做用是给程序热身,头几轮迭代有显存加载,cache命中等问题所以能够跳过,咱们只靠量10轮迭代以后的计算时间。同时,也记录总时间 total_duration 和平方和 total_duration_squared 用以计算方差。

def time_tensorflow_run(session, target, info_string):
    num_steps_burn_in = 10
    total_duration = 0.0  # 记录总时间
    total_duration_squared = 0.0  # 记录平方和total_duration_squared用于计算方差

  咱们进行  num_batches + num_steps_burn_in 次迭代计算,使用 time.time() 记录时间,每次迭代经过 session.run(target)执行。在初始热身的 num_steps_burn_in 次迭代后,每10轮迭代显示当前迭代所须要的时间。同时每轮将 total_duration 和 total_duration_dquared累加,以便后面每轮耗时的均值和标准差。

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: stpe %d, duration=%.3f'%(datetime.now(),
                                                i-num_steps_burn_in, duration))
            total_duration += duration
            total_duration_squared += duration * duration

  在循环结束后,计算每轮迭代的平均耗时 mn 和 标准差  sd,最后将结果显示出来。这样就完成了计算每轮迭代耗时的评测函数 time_tensorflow_run。

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))

  接下来是主函数  run_benchmark。首先是要 with  tf.Graph().as_default() 定义默认的 Graph 方便后面使用。如前面所说,咱们这里不使用  ImageNet 数据集来训练,只使用随机图片数据测试前馈和反馈计算的耗时。咱们使用 tf.random_normal 函数构造正态分布(标准差为0.1)的随机 tensor ,第一个维度是 batch_size,即每轮迭代的样本数,第二个和第三个维度是图片的尺寸 image_size = 224,第四个维度是图片的颜色通道数。接下来,使用前面定义的  inference 函数构建整个 AlexNet网络,获得最后一个池化层的输出 pool5 和网络中须要训练的参数的集合  parameters。接下来,咱们使用 tf.Session()  建立新的 Session 并经过 tf.global_variables_initializer() 初始化全部参数。

def run_benchmark():
    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=1e-1))
        pool5, parameters = inference(images)

        init = tf.global_variables_initializer()
        sess = tf.Session()
        sess.run(init)

  下面进行 AlexNet的 forward 计算的测评,这里直接使用 time_tensorflow_run 统计运算时间,传入的 target 就是 pool5,即卷积网络最后一个池化层的输出。而后进行 backward 即训练过程的评测,这里和 forward 计算有些不一样,咱们须要给最后的输出 pool5 设置一个优化目标 loss。咱们使用 tf.nn.l2_loss 计算 pool5的loss,再使用 tf.gradients求相对于 loss 的全部模型参数的梯度,这样就模拟了一个训练的额过程。固然,训练时还有一个根据梯度更新参数的过程,不过这个计算量很小,就不统计在评测程序里了。最后咱们使用 time_tensorflow_run 统计 backward 的运行时间,这里的  target 就是求整个网络梯度 grad 的操做。 

time_tensorflow_run(sess, 'pool5', 'Forward')

objective = tf.nn.l2_loss(pool5)
grad = tf.gradients(objective, parameters)
time_tensorflow_run(sess, grad, 'Forward-backward')

  最后执行主函数:

if __name__ == '__main__':
    run_benchmark()

  程序显示的结果有三段,首先是 AlexNet的网络结构,能够看到咱们定义的 5个卷积层中第1 个,第二个和第五个卷积后面还链接着池化层,另外每一层输出 tensor 的尺寸也显示出来了。

conv1   [32, 56, 56, 64]
pool1   [32, 27, 27, 64]
conv2   [32, 27, 27, 192]
pool2   [32, 13, 13, 192]
conv3   [32, 13, 13, 384]
conv4   [32, 13, 13, 256]
conv5   [32, 13, 13, 256]
pool5   [32, 6, 6, 256]

  而后显示的是 forward 计算的时间。我是在服务器上运行,配置了GPU,因此在LRN层时每轮迭代时间大约为0.010s;去除LRN层后,每轮的迭代时间大约0.030s,速度也快了3倍多。由于 LRN层对最终准确率的影响不是很大,因此你们能够考虑是否使用 LRN。

2019-09-10 18:03:08.871731: stpe 0, duration=0.010
2019-09-10 18:03:08.969771: stpe 10, duration=0.010
2019-09-10 18:03:09.066901: stpe 20, duration=0.010
2019-09-10 18:03:09.163687: stpe 30, duration=0.010
2019-09-10 18:03:09.260273: stpe 40, duration=0.010
2019-09-10 18:03:09.356837: stpe 50, duration=0.010
2019-09-10 18:03:09.453185: stpe 60, duration=0.010
2019-09-10 18:03:09.549359: stpe 70, duration=0.010
2019-09-10 18:03:09.645590: stpe 80, duration=0.010
2019-09-10 18:03:09.741800: stpe 90, duration=0.010
2019-09-10 18:03:09.828454: Forward across 100 steps, 0.001 +/- 0.003 sec / batch

  而后就是显示的 backward 运算的时间。在使用 LRN 层时,每轮的迭代时间为 0.031s,在去除LRN层后,每轮迭代时间约为0.025s,速度也快了3倍多。另外能够发现不管是否有 LRN 层,咱们 backward 运算的耗时大约是 forward 耗时的三倍。

2019-09-10 18:03:10.404082: stpe 0, duration=0.031
2019-09-10 18:03:10.712611: stpe 10, duration=0.031
2019-09-10 18:03:11.020057: stpe 20, duration=0.031
2019-09-10 18:03:11.326645: stpe 30, duration=0.031
2019-09-10 18:03:11.632946: stpe 40, duration=0.030
2019-09-10 18:03:11.940875: stpe 50, duration=0.031
2019-09-10 18:03:12.249667: stpe 60, duration=0.031
2019-09-10 18:03:12.556174: stpe 70, duration=0.031
2019-09-10 18:03:12.863977: stpe 80, duration=0.031
2019-09-10 18:03:13.172933: stpe 90, duration=0.031
2019-09-10 18:03:13.451794: Forward-backward across 100 steps, 0.003 +/- 0.009 sec / batch

  CNN的训练过程(即backward计算)一般都比较耗时,并且不想预测过程(即 forward计算),训练一般须要过不少遍数据,进行大量的迭代。所以应用CNN的主要瓶颈仍是在训练,用 CNN 作预测问题不大。

  至此,AlexNet的TensorFlow实现和运算时间评测就完成了。AlexNet为卷积神经网络和深度学习正名,以绝对优点拿下ILSVRC 2012 冠军,引发学术界的极大关注,为复兴神经网络作出来很大的贡献。ALexNet 在 ILSVRC 数据集上可达到 16.4% 的错误率(你们能够自行下载数据集测试,可是要注意 batch_size 可能要设为1 才能复现论文中的结果),其中用到的许多网络结构和 Trick 给深度学习的发展带来了深入地影响。固然,咱们决不能忽略 ImageNet 数据集给深度学习带来的 贡献。训练深度卷积神经网络,必须拥有一个像ImageNet这样超大的数据集才能避免过拟合,发展深度学习的优点。能够说,传统机器学习模型适合学习一个小型数据集,可是对于大型数据集,咱们须要有更大的学习容量(Learning Capacity)的模型,即深度学习模型。

完整代码以下:

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):
    #  """Run the computation to obtain the target tensor and print timing stats.
    #
    #  Args:
    #    session: the TensorFlow session to run the computation under.
    #    target: the target Tensor that is passed to the session's run() function.
    #    info_string: a string summarizing this run, to be printed with the stats.
    #
    #  Returns:
    #    None
    #  """
    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():
    #  """Run the benchmark on AlexNet."""
    with tf.Graph().as_default():
        # Generate some dummy images.
        image_size = 224
        # Note that our padding definition is slightly different the cuda-convnet.
        # In order to force the model to start with the same activations sizes,
        # we add 3 to the image_size and employ VALID padding above.
        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")


if __name__ == '__main__':
    run_benchmark()

 

5,Keras实现AlexNet网络

  下面粘贴的是网友在Keras上实现的AlexNet网络代码。

  因为AlexNet是使用两块显卡进行训练的,其网络结构的实际是分组进行的。而且,在C2,C4,C5上其卷积核只和上一层的同一个GPU上的卷积核相连。 对于单显卡来讲,并不适用,本文基于Keras的实现,忽略其关于双显卡的的结构,而且将局部归一化操做换成了BN。其网络结构以下

  利用Keras实现AlexNet:

from keras.models import Sequential
from keras.layers import Dense, Flatten, Dropout
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.utils.np_utils import to_categorical
import numpy as np
seed = 7
np.random.seed(seed)

# 建立模型序列
model = Sequential()
#第一层卷积网络,使用96个卷积核,大小为11x11步长为4, 要求输入的图片为227x227, 3个通道,不加边,激活函数使用relu
model.add(Conv2D(96, (11, 11), strides=(1, 1), input_shape=(28, 28, 1), padding='same', activation='relu',
                 kernel_initializer='uniform'))
# 池化层
model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
# 第二层加边使用256个5x5的卷积核,加边,激活函数为relu
model.add(Conv2D(256, (5, 5), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
#使用池化层,步长为2
model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
# 第三层卷积,大小为3x3的卷积核使用384个
model.add(Conv2D(384, (3, 3), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
# 第四层卷积,同第三层
model.add(Conv2D(384, (3, 3), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
# 第五层卷积使用的卷积核为256个,其余同上
model.add(Conv2D(256, (3, 3), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))

model.add(Flatten())
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
model.summary()
参考来源:(https://blog.csdn.net/qq_41559533/article/details/83718778 )

  

  本文是学习AlexNet网络的笔记,参考了《tensorflow实战》这本书中关于AlexNet的章节,写的很是好,因此在此作了笔记,侵删。

参考文献:https://www.jianshu.com/p/00a53eb5f4b3

https://blog.csdn.net/luoluonuoyasuolong/article/details/81750190

https://www.cnblogs.com/wangguchangqing/p/10333370.html

https://my.oschina.net/u/876354/blog/1633143

相关文章
相关标签/搜索