以下部分内容转载自LeNet论文的翻译与CNN三大核心思想的解读卷积神经网络 LeNet-5各层参数详解

相关资料

下面列出的论文都是CNN必读的论文,论文的顺序基本上就是CNN结构演化的历史。

  • LeNet:《Gradient-Based Learning Applied to Document Recognition》 CNN的开山之作,也是手写体识别经典论文。
  • AlexNet:《 ImageNet Classification with Deep Convolutional Neural Networks 》 ILSVRC-2012 大赛冠军,促进CNN的扛鼎之作,AlexNet是CNN发展史上的一个历史性转折,不能不读。
  • Inception V1和V3:《Going Deeper with Convolutions》,《Rethinking the Inception Architecture for Computer Vision》,2014年ImageNet大赛冠军,Inception结构的设计很巧妙
  • VGGNet:《Very Deep Convolutional Networks for Large-Scale Image Recognition》,虽然不是那年ImageNet大赛的冠军(那年的冠军是GoogLeNet),但是VGGNet对后面的ResNet,Inception产生了重要的影响
  • **DeepID2+:《Deeply learned face representations are sparse, selective, and robust》**为什么要推荐这篇论文呢?人脸识别领域,DeepID大名如雷贯耳,与DeepID,DeepID2不同的是,这篇论文并不是单纯讲人脸识别,论文深入分析了CNN的内部结构,试图从理论上解释CNN强大的特征提取能和分类识别能力,这是学者第一次试图去探索CNN的本质属性,看完这篇论文,相信对CNN会有更深入的了解。
  • ResNet:《Deep Residual Learning for Image Recognition》,直接将top5错误率降到了3.57%(GoogLeNet 是6.66%),超越了人眼,文中最大的亮点就是残差块结构的设计。

以下的博客对于学习CNN可以提供很大的帮助,感谢作者的分享

还有一些资料,也非常值得学习

  • CS231n,是斯坦福的一门课程,这里有篇文章整理了相关资源 贺完结!CS231n官方笔记授权翻译总集篇发布
  • 《Deep Learning》 Yoshua Bengio,Ian Goodfellow,Aaron Courville写的一本书
  • 《On the Origin of Deep Learning》,这篇论文对深度学习的发展历史做了一个综述,与CNN相关的第5章:Convolutional Neural Networks and Vision Problems,其中提到了CNN目前遇到的挑战以及机遇。

这里附上一张CNN结构演化历史的图,希望帮助大家更好的了解CNN的发展

aliyun-obser

上面提到的论文和书籍都是可以在网上下载到的,点击下载

LeNet论文翻译

由于LeNet这篇论文篇幅较长,这里只翻译了对理解CNN很关键的第二章的A和B,这部分阐述了CNN的三个重要思想。下面就是第二部分的翻译。

2 用于字符识别的卷积神经网络

使用梯度下降法的多层网络可以从大量的数据中学习复杂的,高纬,非线性的映射,这使得他们成为图像识别任务的首选。在传统的模式识别的模型中,手工设计的特征提取器从图像中提取相关特征清除不相关的信息。分类器可以将这些特征进行分类。全连接的多层网络可以作为分类器。一个更有意思的模式就是尽量依赖特征提取器本身进行学习。对于字符识别,可以将图像作为行向量作为输入输入到网络中。虽然这些任务(比如字符识别)可以使用传统的前向全连接网络完成。但是还存在一些问题。

首先,图像是非常大的,由很多像素组成。具有100个隐藏单元的全连接网络包含成千上万的权重,这么多参数提高了系统的消耗和内存占用,因此需要更大的训练集。但是没有结构的网络的主要缺点是,多于图像或者音频这些应用来说,不具备平移,形变扭曲的不变性。在输入到固定大小输入的网络钱,字符图像的大小必须归一化,并且放在输入的中间,不幸的是,没有哪种预处理能够达到如此完美:由于手写体以字符为归一化单位,会导致每个字符的大小,倾斜,位置存在变化,再加上书写风格的差异,将会导致特征位置的变化,原则上,足够大小的全连接网络可以对这些变化鲁棒,但是,要达到这种目的需要更多的在输入图像不同位置的神经元,这样可以检测到不同的特征,不论他们出现在图像的什么位置。学习这些权值参数需要大量的训练样本去覆盖可能的样本空间,在下面描述的卷积神经网络中,位移不变性(shift invariance)可以通过权值共享实现。

第2点,全连接的网络的另一个缺点就是完全忽略了输入的拓扑结构。在不影响训练的结果的情况下,输入图像可以是任意的顺序。然而,图像具有很强的二维局部结构:空间相邻的像素具有高度相关性。局部相关性对于提取局部特征来说具有巨大优势,因为相邻像素的权值可以分成几类。CNN通过将隐藏结点的感受野限制在局部来提取特征。

A 卷积网络

CNN通过局部感受野(local receptive fields),权值共享(shared weights),下采样(sub-sampling)实现位移,缩放,和形变的不变性(shift,scale,distortion invariance)。一个典型的用于字符识别的网络结构如图2所示,该网络结构称为LeNet-5。输入层输入大小归一化并且字符位于中间的字符图像。每一层的每个神经元(each unit)接受上一层中一组局部领域的神经元的输入(就是局部感受野)。将多个神经元连接为局部感受野的思想可以追溯到60年代的感知机,与Hubel and Wiesel’s在猫的视觉系统中发现的局部感受和方向选择的神经元几乎是同步的(神经网络和神经科学关系密切)。局部感受野在视觉学习神经模型中使用很多次了,使用局部感受野,神经元能够提取边缘,角点等视觉特征,这些特征在下一层中进行结合形成更高层的特征,之前提到,形变和位移会导致显著特征位置的变化,此外图像局部的特征检测器也可以用于整个图像,基于这个特性,我们可以将局部感受野位于图像不同位置的一组神经元设置为相同的权值(这就是权值共享)。每一层中所有的神经元形成一个平面,这个平面中所有神经元共享权值。神经元(unit)的所有输出构成特征图,特征图中所有单元在图像的不同位置执行相同的操作,这样他们可以在输入图像的不同位置检测到同样的特征,一个完整的卷积层由多个特征图组成(使用不同的权值向量),这样每个位置可以提取多种特征。一个具体的示例就是图2 LeNet-5中的第一层,第一层隐藏层中的所有单元形成6个平面,每个是一个特征图。一个特征图中的一个单元对应有25个输入,这25个输入连接到输入层的5x5区域,这个区域就是局部感受野。每个单元有25个输入,因此有25个可训练的参数加上一个偏置。由于特征图中相邻单元以前一层中连续的单元为中心,所以相邻单元的局部感受野是重叠的。比如,LeNet-5中,水平方向连续的单元的感受野存在5行4列的重叠,之前提到过,一个特征图中所有单元共享25个权值和一个偏置,所以他们在输入图像的不同位置检测相同的特征,每一层的其他特征图使用不同的一组权值和偏置,提取不同类型的局部特征。LeNet中,每个输入位置会提取6个不同的特征。特征图的一种实现方式就是使用一个带有感受野的单元,扫面整个图像,并且将每个对应的位置的状态保持在特征图中,这种操作等价于卷积,后面加入一个偏置和一个函数,因此,取名为卷积网络,卷积核就是连接的权重。卷积层的核就是特征图中所有单元使用的一组连接权重。卷积层的一个重要特性是如果输入图像发生了位移,特征图会发生相应的位移,否则特征图保持不变。这个特性是CNN对位移和形变保持鲁棒的基础。

一旦计算出feature map,那么精确的位置就变得不重要了,相对于其他特征的大概位置是才是相关的。比如,我们知道左上方区域有一个水平线段的一个端点,右上方有一个角,下方垂直线段有一个端点,我们就知道这个数字是7。这些特征的精确位置不仅对识别没有帮助,反而不利于识别,因为对于不同的手写体字符,位置会经常变动。在特征图中降低特征位置的精度的方式是降低特征图的空间分辨率,这个可以通过下采样层达到,下采样层通过求局部平均降低特征图的分辨率,并且降低了输出对平移和形变的敏感度。>LeNet-5中的第二个隐藏层就是下采样层。这个层包含了6个特征图,与前一层的6个特征图对应。每个神经元的感受野是2x2,每个神经元计算四个输入的平均,然后乘以一个系数,最后加上一个偏执,最后将值传递给一个sigmoid函数。相邻的神经元的感受野没有重叠。因此,下采样层的特征图的行和列是前一层特征图的一半。系数和偏置影响了sigmoid函数的效果。如果系数比较小,下采样层相当于对输入做了模糊操作。如果系数较大,根据偏置的值下采样层可以看成是“或”或者“与”操作。卷积层和下采样层是交替出现的,这种形式形成一个金字塔:每一层,特征图的分辨率逐渐减低,而特征图的数量逐渐增加。LeNet-5中第三个隐藏层(C3层)的每个神经元的输入可以来自前一层(S2)的多个特征图。卷积和下采样的结合的灵感来源于Hubel and Wiesel’s”简单”和”复杂”细胞的概念,虽然那个时候没有像反向传播的全局监督学习过程。下采样以及多个特征结合可以大大提高网络对几何变换的不变性。

由于所有的权值都是通过反向传播学习的,卷积网络可以看成是一个特征提取器。权值共享技术对降低参数的数量有重要的影响,同时权值共享技术减小了测试误差和训练误差之间的差距。LeNet-5包含了340908个连接,但是由于权值共享只包含了60000个可训练的参数。

卷积神经网络以及被应用在多个领域,包括手写体识别,打印字符识别,在线手写体提识别,以及人脸识别。在单个时间维度上权值共享的卷积神经网络被称为延时神经网络(TDNNs),TDNNs已经被用在场景识别(没有下采样)[40],语音识别(没有下采样),独立的手写体字符识别[44]以及手势验证[45]。

B LeNet-5

aliyun-obser

LeNet-5共有7层,不包含输入,每层都包含可训练参数(连接权重)。输入图像为32*32大小。这要比Mnist数据库(一个公认的手写数据库)中最大的字母还大(28*28)。这样做的原因是希望潜在的明显特征如笔画端点或角点能够出现在最高层特征监测器感受野的中心。在LeNet-5中,最后一层卷积层的感受野的中心在32x32的输入图像中形成了一个20x20的区域,输入像素值被归一化了,这样背景(白色)对应-0.1,前景(黑色)对应1.175.这使得输入的均值约等于0,方差约等于1,这样能够加速学习[46]。

下文中,卷积层标识为Cx,下采样层标识为Sx,全连接层标识为Fx,x标识层的索引。

C1层是一个卷积层,由6个特征图Feature Map构成。特征图中每个神经元与输入中5*5的邻域相连。特征图的大小为28*28,这样能防止输入的连接掉到边界之外。C1有156个可训练参数(每个滤波器5*5=25个unit参数和一个bias参数,一共6个滤波器,共(5*5+1)*6=156个参数),共122,304个连接(26*28*28*6,每个神经元对应26个连接,每个feature map有28*28个unit, 一共有6个feature map)。

S2层是一个下采样层,有6个14*14的特征图。特征图中的每个单元与C1中相对应特征图的2*2邻域相连接。S2层每个单元的4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果通过sigmoid函数计算。可训练系数和偏置控制着sigmoid函数的非线性程度。如果系数比较小,那么运算近似于线性运算,下采样相当于模糊图像。如果系数比较大,根据偏置的大小下采样可以被看成是有噪声的“或”运算或者有噪声的“与”运算。每个单元的2*2感受野并不重叠,因此S2中每个特征图的行列分别是C1中特征图的一半。S2层有12个可训练参数(每个feature map有一个系数和偏置)和5880个连接。

C3是一个有16个特征图的卷积层。C3层的卷积核大小为5*5,每个特征图中的每个单元与S2中的多个特征图相连,表1显示了C3中每个特征图与S2中哪些特征图相连。
那为什么不把S2中的每个特征图连接到每个C3的特征图呢?原因有2点。
第一,不完全的连接机制将连接的数量保持在合理的范围内。
第二,也是更加重要的,其破坏了网络的对称性。不完全连接能够保证C3中不同特征图提取不同的特征(希望是互补的),因为他们的输入不同。
表1中展示了一个合理的连接方式:C3的前6个特征图以S2中3个相邻的特征图为输入。接下来6个特征图以S2中4个相邻特征图为输入,下面的3个特征图以不相邻的4个特征图为输入。最后一个特征图以S2中所有特征图为输入。这样C3层有1516个可训练参数((25*3+1)*6+(25*4+1)*9+(25*6+1))和151600个连接。

aliyun-obser

(表1中第1列表示C3的第0个特征图,与S2中的第0,1,2个特征图连接)

S4层是一个下采样层,由16个5*5大小的特征图构成。特征图中的每个单元与C3中相应特征图的2*2邻域相连接,跟C1和S2之间的连接一样。S4层有32个可训练参数(每个特征图1个系数和一个偏置)和2000个连接(5*5*5*16,对于S4的每个unit,对应感受野4个参数,加上一个偏置)。

C5层是一个卷积层,有120个特征图。每个单元与S4层的全部16个特征图的5*5领域相连。由于S4层特征图的大小也为5*5(同滤波器一样),故C5特征图的大小为1*1:这构成了S4和C5之间的全连接。之所以仍将C5标示为卷积层而非全连接层,是因为如果LeNet-5的输入变大,而其他的保持不变,那么此时特征图的维数就会比1*1大。C5层有48120个可训练连接((5*5*16+1)*120)。

F6层有84个单元(之所以选这个数字的原因来自于输出层的设计,下面会有说明),与C5层全相连。有10164个可训练参数。

如同经典神经网络,F6层计算输入向量和权重向量之间的点积,再加上一个偏置。神经元i的加权和表示为aia_i,然后将其传递给sigmoid函数产生单元i的一个状态,表示为xix_i,

xi=f(ai)x_i = f(a_i)

Sigmoid函数是一个双曲线正切函数:

f(a)=Atanh(Sa)f(a)=Atanh(Sa)

A表示函数的振幅,S决定了斜率,这个函数是一个奇函数,水平渐近线为+A,-A。常量A通常取1.7159。选择该函数的原因见附录A。

最后,输出层(其实就是softmax loss)由欧式径向基函数(Euclidean Radial Basis Function,RBF)单元组成,每类一个单元,每个单元有84个输入,每个RBF单元yiy_i的输出按照如下方式计算:

yi=(xjwij)2y_i = \sum(x_j - w_ij)^2

换句话说,每个输出RBF单元计算输入向量和参数向量之间的欧式距离。输入离参数向量越远,RBF输出的越大。一个RBF输出可以被理解为衡量输入模式和与RBF相关联类的一个模型的匹配程度的惩罚项。用概率术语来说,RBF输出可以被理解为F6层配置空间的高斯分布的负的log似然(log-likelihood)。给定一个输入模式,损失函数应能使得F6的配置与RBF参数向量(即模式的期望分类)足够接近。这些单元的参数是人工选取并保持固定的(至少初始时候如此)。这些参数向量的成分被设为-1或1。虽然这些参数可以以-1和1等概率的方式任选,或者构成一个纠错码,但是被设计成一个相应字符类的7*12大小(即84)的格式化图片。这种表示对识别单独的数字不是很有用,但是对识别可打印ASCII集中的字符串很有用。基本原理就是字符是相似的,容易混淆,比如大小的O,小写的o和数字0或者小写的l与数字1,方括号和大写的I,会有相似的输出编码。如果一个系统与一个能够纠正此混淆的语言处理器相结合,这个就非常有用了。由于容易混淆的类别的编码是相似的,有歧义的字符的RBF输出是相似的,这个语言处理器就能够选择出合适的解释。图3给出了所有ASCII字符集的输出编码。

aliyun-obser

使用这种分布编码而非更常用的“1 of N”编码(又叫位置编码或者细胞编码)用于产生输出的另一个原因是,当类别比较大的时候,非分布编码的效果比较差。原因是大多数时间非分布编码的输出必须是关闭状态。这使得用sigmoid单元很难实现。另一个原因是分类器不仅用于识别字母,也用于拒绝非字母。使用分布编码的RBF更适合该目的,因为与sigmoid不同,他们在输入空间的较好得限制区域内兴奋,而非典型模式更容易落到外边。

RBF参数向量起着F6层目标向量的角色。需要指出这些向量的成分是+1或-1,这正好在F6 sigmoid的范围内,因此可以防止sigmoid函数饱和。实际上,+1和-1是sigmoid函数的最大曲率的点。这使得F6单元运行在最大非线性范围内。必须避免sigmoid函数的饱和,因为这将会导致损失函数较慢的收敛和病态问题。

LeNet论文解读

神经元模型

aliyun-obser

神经元模型是一个包含输入,输出的计算模型。

一个神经元对应一组权值(CNN中的卷积核+偏置),执行一次计算$$ y=f(\sumω_ix_i+b) $$(CNN中的卷积计算),产生一个输出(CNN中特征图的一个像素)。

从传统神经网络到CNN

第二章一开始,作者讨论了传统的全连接神经网络网络的缺点。

传统神经网络中,假设有如下全连接网络

aliyun-obser

如果我们有1000x1000像素的图像,有1百万个隐层神经元,那么他们全连接的话(每个隐层神经元都连接图像的每一个像素点),就有1000x1000x1000000个连接,也就是101210^12个权值参数,带来的缺点:

  1. 权值参数多,系统开销大。
  2. 全连接网络每个神经元感受到的都是整幅图像,对平移,形变不具有不变性。只要对同一幅图像加入一些扰动,输出就会不同。
  3. 全连接网络完全忽略了图像的局部结构,在不改变神经元的输出的前提下,输入数据可以是任意的顺序。

aliyun-obser

作者借鉴了视觉神经中局部感受神经元的思想,提出了局部感受野的概念,就是每个神经元不必感受整幅图像,只需要感受局部就可以了,从猫的视觉神经中发现了局部感受神经元也对该想法的正确性提供了支持(CNN的结构和理论基础启发自神经科学)。

aliyun-obser

aliyun-obser

如果是局部感受野,每个局部感受野10*10,1百万的隐藏神经元有 1000000*(10*10)
个连接,也就是只有10810^8 个参数,但是感觉参数还是很多。后来发现,局部感受野类似于图像的卷积操作,能够提取局部特征,而图像局部的特征检测器也可以用于整个图像,这样就可以提取整幅图像的特征,基于这个特性,我们可以将局部感受野位于不同位置的神经元设置为相同的权值,这些神经元的输出形成CNN中的一个特征图,这样直接将参数个数降到了100个,这就是权值共享的思想。

aliyun-obser

这样只提取了一种特征,为了能够提取多个特征,可以设置多个卷积核,得到多个特征图。

一旦计算出feature map,那么特征精确的位置就变得不重要了,特征的精确位置不仅对识别没有帮助,反而不利于识别,我们只需要知道特征的大概位置就可以了,这与生活中观察到的现象是一致的,两个相似的目标,从远处看,基本看不出区别,但是从近处看,细节方面还是有很多不同的,就很容易识别为两个不同的目标。下采样可以降低特征位置的精度,使得对平移和形变更加鲁棒,实现特征不变性。

上述就是CNN的三大核心思想:

局部感受野(local receptive fields):基于图像局部相关的原理,保留了图像局部结构,同时减少了网络的权值

权值共享(shared weights): 也是基于图像局部相关的原理,同时减少网络的权值参数

下采样(sub-sampling):对平移和形变更加鲁棒,实现特征的不变性,同时起到了一定的降维的作用

连接数和参数个数的计算

C1层一共122304个连接
(5*5+1)*28*28*6=122304
解释:由于每个特征图(feature map)对应一个卷积核和一个偏置,所以特征图中每个像素有26个权值,每个特征图共28*28个像素,一共有6个特征图。

注意:
计算连接数量的时候,从每个feature map 的每个像素出发计算。

特征图连接方式

LeNet中,特征图并不是与前一层所有特征图相连,比如C3层的每个特征图,只与S2层中部分特征图相连,如C3中的第0特征图与S2中的第0,1,2个特征图相连。

而现代CNN中,比如AlexNet,ResNet等, 特征图与前一层的所有特征图相连
此时,我们可以将CNN中的核看成是一个三维结构。

aliyun-obser

上图就是AlexNet网络结构示意图,我们可以将卷积核看成是一个三维结构。

各层参数详解

LeNet-5共有7层,不包含输入,每层都包含可训练参数;每个层有多个Feature Map,每个FeatureMap通过一种卷积滤波器提取输入的一种特征,然后每个FeatureMap有多个神经元

  1. C1层是一个卷积层

    输入图片:32*32

    卷积核大小:5*5

    卷积核种类:6

    输出featuremap大小:28*28 (32-5+1)

    神经元数量:28*28*6

    可训练参数:(5*5+1)*6(每个滤波器5*5=25个unit参数和一个bias参数,一共6个滤波器)

    连接数:(5*5+1)*6*28*28

  2. S2层是一个下采样层

    输入:28*28

    采样区域:2*2

    采样方式:4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果通过sigmoid

    采样种类:6

    输出featureMap大小:14*14(28/2)

    神经元数量:14*14*6

    可训练参数:2*6(和的权+偏置)

    连接数:(2*2+1)*6*14*14

    S2中每个特征图的大小是C1中特征图大小的1/4

  3. C3层也是一个卷积层

    输入:S2中所有6个或者几个特征map组合

    卷积核大小:5*5

    卷积核种类:16

    输出featureMap大小:10*10

    C3中的每个特征map是连接到S2中的所有6个或者几个特征map的,表示本层的特征map是上一层提取到的特征map的不同组合

    存在的一个方式是:C3的前6个特征图以S2中3个相邻的特征图子集为输入。接下来6个特征图以S2中4个相邻特征图子集为输入。然后的3个以不相邻的4个特征图子集为输入。最后一个将S2中所有特征图为输入。

    则:可训练参数:6*(3*25+1)+6*(4*25+1)+3*(4*25+1)+(25*6+1)=1516

    连接数:10*10*1516=151600

  4. S4层是一个下采样层

    输入:10*10

    采样区域:2*2

    采样方式:4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果通过sigmoid

    采样种类:16

    输出featureMap大小:5*5(10/2)

    神经元数量:5*5*16=400

    可训练参数:2*16=32(和的权+偏置)

    连接数:16*(2*2+1)*5*5=2000

    S4中每个特征图的大小是C3中特征图大小的1/4

  5. C5层是一个卷积层

    输入:S4层的全部16个单元特征map(与s4全相连)

    卷积核大小:5*5

    卷积核种类:120

    输出featureMap大小:1*1(5-5+1)

    可训练参数/连接:120*16*(5*5+1)=49920

  6. F6层全连接层

    输入:c5 120维向量

    计算方式:计算输入向量和权重向量之间的点积,再加上一个偏置,结果通过sigmoid函数

    可训练参数:84*(120+1)=10164

关于LeNet通俗易懂的讲解,我推荐卷积神经网络Lenet-5实现

aliyun-obser

Figure15 LeNet-5识别数字3的过程

LeNet-5的Tensorflow实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from tensorflow.contrib.layers import flatten
import numpy as np
import matplotlib.pyplot as plt
from sklearn.utils import shuffle

mnist = input_data.read_data_sets("MNIST_data/", reshape=False)
X_train, Y_train = mnist.train.images, mnist.train.labels
X_validation, Y_validation = mnist.validation.images, mnist.validation.labels
X_test, Y_test = mnist.test.images, mnist.test.labels

assert (len(X_train) == len(Y_train))
assert (len(X_validation) == len(Y_validation))
assert (len(X_test) == len(Y_test))

X_train = np.pad(X_train, ((0, 0), (2, 2), (2, 2), (0, 0)), 'constant')
X_validation = np.pad(X_validation, ((0, 0), (2, 2), (2, 2), (0, 0)), 'constant')
X_test = np.pad(X_test, ((0, 0), (2, 2), (2, 2), (0, 0)), 'constant')

X_train, Y_train = shuffle(X_train, Y_train)

num_epochs = 10
minibatch_size = 128
learning_rate = 0.001
print_cost = True


def create_placeholders(n_H0, n_W0, n_C0):
"""
Creates the placeholders for the tensorflow session.

Arguments:
n_H0 -- scalar, height of an input image
n_W0 -- scalar, width of an input image
n_C0 -- scalar, number of channels of the input

Returns:
X -- placeholder for the data input, of shape [None, n_H0, n_W0, n_C0] and dtype "float"
Y -- placeholder for the input labels, of shape [None] and dtype "int"
"""

X = tf.placeholder(shape=(None, n_H0, n_W0, n_C0), dtype=tf.float32)
Y = tf.placeholder(shape=(None), dtype=tf.int32)

return X, Y


def initialize_parameters():
"""
Initializes weight parameters to build a neural network with tensorflow.
Returns:
parameters -- a dictionary of tensors。
"""
mu = 0
sigma = 0.1

conv1_W = tf.Variable(tf.truncated_normal(shape=[5, 5, 1, 6], mean=mu, stddev=sigma))
conv1_b = tf.Variable(tf.zeros(6))

conv2_W = tf.Variable(tf.truncated_normal(shape=[5, 5, 6, 16], mean=mu, stddev=sigma))
conv2_b = tf.Variable(tf.zeros(16))

conv3_W = tf.Variable(tf.truncated_normal(shape=[5, 5, 16, 120], mean=mu, stddev=sigma))
conv3_b = tf.Variable(tf.zeros(120))

fc1_W = tf.Variable(tf.truncated_normal(shape=(120, 84), mean=mu, stddev=sigma))
fc1_b = tf.Variable(tf.zeros(84))

fc2_W = tf.Variable(tf.truncated_normal(shape=(84, 10), mean=mu, stddev=sigma))
fc2_b = tf.Variable(tf.zeros(10))

parameters = {
"conv1_W": conv1_W,
"conv1_b": conv1_b,
"conv2_W": conv2_W,
"conv2_b": conv2_b,
"conv3_W": conv3_W,
"conv3_b": conv3_b,
"fc1_W": fc1_W,
"fc1_b": fc1_b,
"fc2_W": fc2_W,
"fc2_b": fc2_b
}

return parameters


def forward_propagation(X, parameters):
"""
Implements the forward propagation for the model:
CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> FLATTEN -> FULLYCONNECTED -> FULLYCONNECTED

Arguments:
X -- input dataset placeholder, of shape (number of examples, input size)
parameters -- python dictionary

Returns:
logits -- the output of the last LINEAR unit
"""
conv1_W = parameters["conv1_W"]
conv1_b = parameters["conv1_b"]
conv2_W = parameters["conv2_W"]
conv2_b = parameters["conv2_b"]
conv3_W = parameters["conv3_W"]
conv3_b = parameters["conv3_b"]
fc1_W = parameters["fc1_W"]
fc1_b = parameters["fc1_b"]
fc2_W = parameters["fc2_W"]
fc2_b = parameters["fc2_b"]

print("X: ", X.shape)
print("conv1_W: ", conv1_W.shape)

# Layer 1: Convolutional. Input = 32x32x1, Output = 28x28x6
conv1 = tf.nn.conv2d(input=X, filter=conv1_W, strides=(1, 1, 1, 1), padding='VALID') + conv1_b
# Activation
conv1 = tf.nn.relu(conv1)
# Pooling. Input = 28x28x6, Output = 14x14x6
pool_1 = tf.nn.max_pool(value=conv1, ksize=(1, 2, 2, 1), strides=(1, 2, 2, 1), padding='VALID')

print("pool_1: ", pool_1.shape)

# Layer 2: Convolutional. Input = 14x14x6, Output = 10x10x16
conv2 = tf.nn.conv2d(input=pool_1, filter=conv2_W, strides=(1, 1, 1, 1), padding='VALID') + conv2_b
# Activation
conv2 = tf.nn.relu(conv2)
# Pooling. Input = 10x10x16, Output = 5x5x16
pool_2 = tf.nn.max_pool(value=conv2, ksize=(1, 2, 2, 1), strides=(1, 2, 2, 1), padding='VALID')

print("pool_2: ", pool_2.shape)

# Layer 3: Convolutional. Input = 5x5x16, Output = 1x1x120
conv3 = tf.nn.conv2d(input=pool_2, filter=conv3_W, strides=(1, 1, 1, 1), padding='VALID') + conv3_b
# Activation
conv3 = tf.nn.relu(conv3)
# FLATTEN
conv3 = flatten(conv3)
# conv3 = tf.nn.dropout(conv3, keep_prob=0.5)

print("conv3: ", conv3.shape)

# Layer 4: Fully Connected. Input = 120, Output = 84
fc1 = tf.matmul(conv3, fc1_W) + fc1_b
# Activation
fc1 = tf.nn.relu(fc1)
fc1 = tf.nn.dropout(fc1, keep_prob=0.5)
# Layer 5: Fully Conneceted. Input = 84, Output = 10
logits = tf.matmul(fc1, fc2_W) + fc2_b

return logits


def compute_cost(logits, Y):
"""
Computes the cost

Arguments:
logits -- output of forward propagation (output of the last LINEAR unit), of shape (number of examples, 10)
Y -- "true" labels vector placeholder, same shape as logits

Returns:
cost - Tensor of the cost function
"""
one_hot_Y = tf.one_hot(Y, 10)
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=one_hot_Y))
return cost


# tf.reset_default_graph()
# with tf.Session() as sess:
# X, Y = create_placeholders(32, 32)
# parameters = initialize_parameters()
# logits = forward_propagation(X, parameters)
# cost = compute_cost(logits, Y)
# init = tf.global_variables_initializer()
# sess.run(init)
# a = sess.run(cost, {X: X_train, Y: Y_train})
# print("cost=", str(a))

def model(X_train, Y_train, X_test, Y_test, learning_rate=learning_rate, num_epochs=num_epochs,
minibatch_size=minibatch_size, print_cost=print_cost):
"""
Implements the LeNet-5 in Tensorflow:
CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> FLATTEN -> FULLYCONNECTED -> FULLYCONNECTED

Arguments:
X_train -- training set, of shape (None, 32, 32, 1)
Y_train -- test set, of shape (None)
X_test -- training set, of shape (None, 32, 32, 1)
Y_test -- test set, of shape (None)
learning_rate -- learning rate of the optimization
num_epochs -- number of epochs of the optimization loop
minibatch_size -- size of a minibatch
print_cost -- True to print the cost every 100 epochs

Returns:
train_accuracy -- real number, accuracy on the train set (X_train)
test_accuracy -- real number, testing accuracy on the test set (X_test)
parameters -- parameters learnt by the model. They can then be used to predict.
"""
(m, n_H0, n_W0, n_C0) = X_train.shape
costs = []
X, Y = create_placeholders(n_H0, n_W0, n_C0)

parameters = initialize_parameters()

logits = forward_propagation(X, parameters)

cost = compute_cost(logits, Y)

optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)

init = tf.global_variables_initializer()

with tf.Session() as sess:
sess.run(init)

for epoch in range(num_epochs):
X_train, Y_train = shuffle(X_train, Y_train)
minibatch_cost = 0.
num_minibatches = int(m / minibatch_size)
for offset in range(0, m, minibatch_size):
end = offset + minibatch_size
minibatch_X, minibatch_Y = X_train[offset:end], Y_train[offset:end]
_, temp_cost = sess.run([optimizer, cost], feed_dict={X: minibatch_X, Y: minibatch_Y})
minibatch_cost += temp_cost / num_minibatches

if print_cost == True and epoch % 5 == 0:
print("Cost after epoch %i: %f" % (epoch, minibatch_cost))
if print_cost == True and epoch % 1 == 0:
costs.append(minibatch_cost)

# plot the cost
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per tens)')
plt.title('Learning rate =' + str(learning_rate))
# plt.show()
plt.savefig('cost.png')

# Calculate the correct predictions
predict_op = tf.argmax(logits, 1)
correct_prediction = tf.equal(predict_op, tf.argmax(tf.one_hot(Y, 10), 1))

# Calculate accuracy on the test set
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(accuracy)
train_accuracy = accuracy.eval({X: X_train, Y: Y_train})
test_accuracy = accuracy.eval({X: X_test, Y: Y_test})
print("Train Accuracy:", train_accuracy)
print("Test Accuracy:", test_accuracy)

return train_accuracy, test_accuracy, parameters


_, _, parameters = model(X_train, Y_train, X_test, Y_test)

Train Accuracy 0.99289125
Test Accuracy 0.9892002

aliyun-obser