第3章

TensorFlow 
实现神经网络模型训练与测试

本章介绍在TensorFlow学习框架下,以MNIST手写字体的识别为例,实现
SoftmaxRegresion简易神经网络及卷积神经网络模型的训练,重点介绍基于FPGA实
现神经网络中用到的典型TensorFlow功能,如数据集格式转换等。

3.神经网络训练与测试的基本概念
1 

对于常规的监督机器学习或深度学习,一般分为训练和预测两个阶段。在训练阶段, 
需要准备原始数据和与之对应的分类标签数据,通过训练得到神经网络模型。在预测阶
段,需要使用训练阶段得到的神经网络模型对测试数据进行处理。训练阶段和测试阶段
使用的输入数据集合分别称为训练样本集和测试样本集。

在设计人工神经网络模型结构前,需要对输入和输出的数据进行量化。假设输入的
数据有
k 
个,输出为
n 
个分类,那么输入层的神经元节点数应设置为k,输出层神经元节
点数应设置为n。隐形层的神经元节点数可根据不同的应用需求进行合理设置,隐形层
的神经元节点数越多,神经网络的非线性越显著,能够处理的应用越复杂,但会增加计算
的复杂度。习惯上,一般第
l 
层神经网络的节点数是l-1层节点数的1~1.

5倍。
当人工神经网络模型结构定义好后,输入层、隐形层和输出层的节点数就会确定,剩
余没有确定的参数还有权值
W 
和偏置b。

3.1.1 神经网络的训练
训练神经网络模型的目的是确定权值
W 
和偏置b,训练的过程也称神经网络的学习

过程。神经网络的训练实际上是通过算法不断调整权值
W 
和偏置b,以尽可能地与真实

的模型逼近,从而使整个神经网络的预测效果达到最佳。

首先给所有的权值
W 
和偏置
b 
赋予随机值,使用这些随机生成的权值和偏置参数预

测训练样本集合。假设样本的预测值为y' 
,真实值为y。定义一个损失函数衡量预测值

和真实值的逼近程度,损失函数越小,神经网络模型的预测效果越好。因此,训练的过程


可以理解为调整权值
W 
和偏置
b 
以使损失函数最小。

损失函数的定义有多种形式,求损失函数最小值是一个优化问题。对神经网络的优
化就是使损失函数收敛。为解决该优化问题,目前的方法是使用反向传播算法求得网络
模型所有参数的梯度,通过梯度下降算法对网络参数进行更新,当损失函数收敛到一定程
度时结束训练,并保存训练结束时的神经网络参数。

3.1.2 神经网络的测试
在训练阶段,通过算法修改神经网络的权值
W 
和偏置b,以使损失函数最小,当损失

函数收敛到某个阈值或等于0时停止训练,就可以得到训练后的模型的权值和偏置。此

时,神经网络的所有参数:输入层、输出层、隐形层、权值和偏置都是已知的。

在测试阶段,将神经网络的测试样本集从输入层开始输入,沿数据流动的方向在网络

中进行计算,直到数据从输出层输出,输出层的输出即为预测结果。

3.基于Tnfrlw 
训练神经网络实现MNIST 
数据集识别
2 
esoFo

3.2.1 MNIST 
数据集
MNIST 数据集来自美国国家标准与技术研究所(NationalInstituteofStandards 
anehooNIST )。训练集(riigst)由250 个不同人手写的数字构成,

dTcnlgy,tanne其中
tessBaetst)

50% 是高中学生,50% 是人口普查局(heCnuureu)的工作人员。测试集(tse
也是同样比例的手写数字数据。训练集有55000 个样本,测试集有10000 个样本,同时
验证集有5000 个样本。每个样本都有与之相对应的标注信息,即label。MNIST 数据集
被分成三部分:55000 行的训练数据集(mnittan)、10000 行的测试数据集(mns.

s.riittet) mns.aiain)。这样的划分很重要,

s和5000 行的验证数据集(itvldto在机器学习模型设
计时,必须有一个单独的测试数据集不用于训练,而是用来评估这个模型的性能,从而可
以更容易地把设计的模型推广到其他数据集上,使其具有更好的泛化性能。

每个MNIST 数据样本都由两部分组成:一张包含手写数字的图片和一个对应的标

签。训练数据集的图片和标签分别是mnittan.mags和mns.rilbl

s.riieittan.aes。测试
数
据集的图片和标签分别是mnitts.maeitts.aes
。


s.etigs和mns.etlbl

所有数据集中的样本均为28×28 像素的灰度图片,即每张图片中有28×28=784 
个
像素点。可以用一个数字数组表示每张图片,如图3-1所示。空白部分全部为0,有笔
迹
的地方根据颜色深浅在0~1之间取值
。


把这个28×28 的矩阵展开成一维数组,数组长度是28×28=784 。如何展开这个数
组不重要,只要保持各张图片采用相同的方式展开即可。

注意:展平图片的数字数组会丢失图片的二维结构信息,这显然是不理想的,最优秀
的计算机视觉方法会挖掘并利用这些结构信息。此处不需要建立复杂的模型,所以对问
题进行了简化,丢弃了空间结构信息。

在MNIST 训练数据集中,mns.riigs是一个形状为[784]的张量

ittan.mae55000,


32 


图3-
1 
手写数字灰度信息示例

(Tensor),第一个维度是图片的编号,用来索引图片;第二个维度是图片中像素点的编
号,用来索引每张图片中的像素点。因此,mnittan.maes张量中的每个元素都表示

s.riig
某张图片中的某个像素的强度值,值介于0和1。mnis.riigs张量中的每张图片

ttan.mae
都可表示为一个784维的向量(如图3-2所示)。
相对应的MNIST数据集的标签mnitrailabl55000,的张量

stn.es是一个形状为[10]
(Tensor),第一个维度是标签的编号,用来索引(.) 图片(如图3-3所示)。第二个维度是标签
的取值,用来表示对应图片代表的数字,是0~9的数字。标签数据采用one-hot编码,即
一个one-hot数据除了某一位的数字是1以外,其余各维度的数字都是0。数字
n 
将表示
成一个只有在第
n 
维度(从0开始)数字为1的10维向量。例如,标签0将表示成([1,0, 
0,0,0,0,0,0,0,0,0])。


图3-
2 
MNIST训练数据的像素图3-
3 
MNIST训练数据的标签

3.2.2 Softmax 
Regression 
模型
MNIST的每张图片都表示一个0~9的数字。完成图片识别需要根据给定图片代
表的每个数字的概率。例如,模型可能推测一张图片代表数字9的概率是80%,代表数
字8的概率是5%,代表其他8种数字的概率比5%更小,则模型预测该图片代表数字9。
这是一个使用SoftmaxRegresion模型的经典案例。SoftmaxRegresion模型可以用来
给不同的对象分配概率。

为了得到一张给定图片属于某个特定数字类的特征,需要对图片像素进行加权求和。
如果这个像素具有很强的特征可以说明这张图片不属于该类,那么相应的权值为负数,相
反如果这个像素具有很强的特征支持这张图片属于这个类,那么权值是正数。

33

同时需要加入一个额外的偏置量(bias),因为输入往往会带有一些无关的干扰量。
因此对于给定的输入图片x,它代表数字
i 
的判定依据特征可以表示为
fetri=Σ jxj 
+b

aueWi,
i 

j 

xj 
表示图片
x 
中的第
j 
个像素,
j 
代表第
j 
个像素对应的权重,
i 
代表数字
i 
的偏置。

Wi,b

jxj 
表示对图片中所有像素与对应权值相乘后求和。使用softmax()函数可以将
j

ΣWi,

这些特征转换成概率: 
x(i)

yi=softmafeature

这里的softmax()可以看成是一个激活(activation)函数,把定义的线性函数的输出
转换成关于10个数字类的概率分布。因此,对于一张给定的图片,它与0~9的吻合度可
以用softmax()函数转换成一个概率值。

对于MNIST数据集分类,otmax()函数可以定义为

sf

softmax(xi)=normlize(exp(xi))
= 
exp(xi) Σ(9) exp(xk 
)

k=0 

图3-4是3×3网络模型结构,可以看出SoftmaxRegresion模型是一个没有隐形层
的最浅全链接神经网络。根据原理,可以很容易地将该模型扩展为适合0~9数字识别的
784×10网络结构。图3-5为图3-4网络模型的数学表达,图3-6将数学表达以矩阵形式
表示。


图3-
4 
3×3网络模型结构


图3-
5 
3×3网络模型的数学表达


图3-
6 
3×3网络模型的数学表达(矩阵形式) 


34 


35 
根据图3-6,可以分别将输入、输出、权值和偏置用向量的形式表示,则Softmax 
Regression模型的矩阵表达式为
y =softmax(Wx +b) 
3.2.3 MNIST 数据识别的Softmax Regression 神经网络模型
1.SoftmaxRegression神经网络模型训练与测试的TensorFlow 实现 
按照第2章的安装步骤完成TensorFlow 安装后,在路径/home/tensorflow/lib/ 
python2.7/site-packages/tensorflow/examples/tutorials/mnist/下有MNIST 模型的例
程。本教材不使用该例程,只使用例程中的个别文件。
(1)MNIST数据集下载。
下载地址为http://yann.lecun.com/exdb/mnist/(在网页地址栏中应严格按照下载
地址格式输入网址)。 
train-images-idx3-ubyte.gz:training set images (9912422 bytes) 
train-labels-idx1-ubyte.gz: training set labels (28881 bytes) 
t10k-images-idx3-ubyte.gz:test set images (1648877 bytes) 
t10k-labels-idx1-ubyte.gz: test set labels (4542 bytes) 
(2)在Ubuntu系统下,在/home/tensorflow/文件夹下新建design文件夹(若文件
夹已存在,则省略该操作),在design文件夹下新建文件夹my_mnist,在my_mnist文件
夹下新建文件夹MNIST_data。将下载的包含4 个压缩包的MNIST 数据集复制到
MNIST_data文件夹中。
(3) 将/home/tensorflow/lib/python2.7/site-packages/tensorflow/examples/tutorials/ 
mnist/中的input_data.py复制到/home/tensorflow/design/my_mnist/目录下。
(4)在/home/tensorflow/design/my_mnist/目录下新建空白文档,并命名为my_ 
mnist_simple.py,输入以下代码。 
#coding: utf-8 
import tensorflow as tf 
import input_data 
import pylab 
#载入数据集
mnist=input_data.read_data_sets("MNIST_data",one_hot=True) 
#每个批次的大小
batch_size=100 
#计算一共有多少个批次
n_batch=mnist.train.num_examples // batch_size 
#===============定义计算图======================= 
#定义两个placeholder 
x=tf.placeholder(tf.float32,[None,784])

36 
y=tf.placeholder(tf.float32,[None,10]) 
#创建一个简单的神经网络
W=tf.Variable(tf.zeros([784,10])) 
b=tf.Variable(tf.zeros([10])) 
prediction=tf.nn.softmax(tf.matmul(x,W)+b) 
#使用交叉熵,梯度下降更有效
loss=tf.reduce _mean(tf.nn.softmax _cross _entropy _with _logits(labels = y, 
logits=prediction)) 
#使用梯度下降法
train_step=tf.train.GradientDescentOptimizer(0.2).minimize(loss) 
#初始化变量
init=tf.global_variables_initializer() 
#结果存放在一个布尔型列表中
correct_prediction=tf.equal(tf.argmax(y,1),tf.argmax(prediction,1)) 
#argmax 返回一维张量中最大值所在的位置
#求准确率
accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32)) 
#====================定义计算图结束========================= 
#====================运行计算图============================ 
with tf.Session() as sess: 
sess.run(init) 
for epoch in range(101): 
for batch in range(n_batch): 
batch_xs,batch_ys= mnist.train.next_batch(batch_size) 
sess.run(train_step,feed_dict={x:batch_xs,y:batch_ys}) 
train_acc=sess.run(accuracy,feed_dict= 
{x:mnist.train.images,y:mnist.train.labels}) 
print("Iter "+str(epoch)+",Training Accuracy "+str(train_acc)) 
test_acc=sess.run(accuracy,feed_dict= 
{x:mnist.test.images,y:mnist.test.labels}) 
print("Testing Accuracy "+str(test_acc)) 
#===================计算图运行结束======================== 
(5)安装pylab和python-tk。 
python -m pip install matplotlib 
apt-get install python-tk 
(6)运行my_mnist_simple.py。 
(tensorflow)$ python my_mnist_simple.py 
运行结果如图3-7所示。

图3-
7 
SoftmaxRegresion神经网络模型的运行结果

2.代码解读
(1)#coding:utf-8。
当py文件中含有中文字符时,需要在py文件的开始执行#coding:utf-8命令。
(2)importtensorflowastf。
在Python中采用importtensorflowastf的形式载入TensofFlow,这样可以使用tf 
代替tensorflow作为模块名称,使整个程序更加简洁。
(3)importinput_data。
导入input_data模块,该模块中定义了读取数据的功能。
(4)importpylab。
导入pylab模块。
(5)mnitipt_aara_dt_st"dt",n_ht=Tue)。

s=nudt.edaaes(MNIST_aaoeor
载入MNIST数据集,MNIST_data为MNIST数据集所在的路径,one_hot=True 

表示使用onehot编码格式。
(6)batch(_) _size=100 。
定义每批次的训练样本数为100 。

(7)nbth=mns.rineampe//bthsz

_acittan.um_xlsac_ie。
根据训练样本集和每批次样本数量计算批次数量。mnittan.um_xls= 
55000,“//”代表除法运算符号。
s.rineampe(8)x=fpaeodr(ffotNoe,784 ])。

t.lchlet.la32,[n

为样本图片数据创建一个placeholder,即输入数据的地方,第一个参数代表数据类
型,第二个参数代表Tensor的shape,即数据的尺寸,None代表不限输入样本的数量, 
784表示每个样本是一个784维的向量。

(9)yt.aeodr(ffotNoe,

=fplchlet.la32,[n10 ])
。
为样本标签数据创建一个placeholder
。


37

fVaibe(fzrs([10 ]))。
为神经网络的权值创建一个Variable,尺寸为784×10,784对应输入图片的特征维
数,10对应10类数字。Variable在模型训练迭代中一直存在并在每轮迭代中进行更新。
(11)bt.ralt.eo10 ]))。

(10)W=t.ralt.eo784,

=fVaibe(fzrs(
[
为神经网络的偏置创建一个Variable,尺寸为10,10对应10类数字
。
(12)prdctint.nn.otmax(fmal(x,W)+b )
。


eio=fsft.tmutfnsfmaotmaegeosfx是t.n下面的一个函数。

.n.otx定义了SfxRrsin模型,otmafntfnn包含大量神经网络的组件,包括卷积操作conv、池化操作pooling、归一化、los
、分类操作(.) 等。tf.matmul是TensorFlow中的矩阵乘法函数。

(13)lst.euemen(fnsfx_rsetopy_ih_ogtlbl=y,

o=frdc_at.n.otmaco_nrwtlis(aeslogits=prediction))。
osfunctiolos 

为了训练模型,定义一个ln(损失函数)描述模型对问题的分类精度,
越小,代表模型的分类效果与真实值的偏差越小,即模型越准确。训练的目的是不断将这
个los 
减小,直至达到一个全局最优解或局部最优解。

对于多分类问题,通常使用cros-entropy作为losfunction,定义表达式如下: 

' 
(y)g(其中
y 
是预测的概率分布,即ll的

Hy=-Σy'iloyi), y' 
是真实的概率分布( abe

one-hot编码),(i) 通常可以用该losfunction判断模型对真实概率分布估计的准确程度。
注意:在本例程中,
y 
代表真实的概率分布,prediction代表预测的概率分布,因此los 

function的表达式应修改为Hy 
(prediction)=-Σyilog(predictioni)。

.n.otma_co_eto_wt_ots函数(i) 所做的操作是将每个样本真实标签的

tfnsfxrsnrpyihlgione-hot向量与其预测概率的对数相乘,将结果按元素取负号,然后按样本将元素逐行
相加。

tfrdc_men函数是求均值。

.euea
只要定义los,训练时就会自动求导并进行梯度下降,完成对SoftmaxRegresion模
型参数的自动学习。
(14)tri_se=ftan.rdetDsetOpiie0..iiie(os)。

antpt.riGainecntmzr(2)mnmzl

采用随机梯度下降SGD(StochasticGradientDescent)优化算法,定义优化算法后, 
TensorFlow就可以根据定义的整个计算图(代码中定义的公式可以自动构成计算图)自
动求导,并根据反向传播算法进行训练,在每一轮迭代时更新参数以减小los
。

tftan.rdetDsetOpiier是TnoFlow提供的一个封装好的优化器,自

.riGainecntmzesr
动添加许多运算操实现反向传播和梯度下降,用户只需要在每轮迭代时fed数据即可。
0.otant

在本例程中,2为学习速率,优化目标设置为ls,ri_sep为定义的训练操作。
(15)intt.lbl_aibe_iiilzr()。

i=fgoavralsntaie
定义初始化操作int, fgoa_

i运行该操作可以对程序中的所有参数进行初始化。t.lblvariablesinitializer()为TensorFlow的全局参数初始化函数。16)(_) creteito=feut.rt.rrdci

(oc_prdcint.qal(fagmax(y,1),fagmax(peiton,1))。
tfagmaesr中寻找最大值的序号,fagma1)

.rx是从一个tnot.rx(y,得到样本真实数字


38 


的类别,fagmardcin,t.qul判

t.rx(peito1)得到各个预测的数字中概率最大的那一个,fea
断tfagma1)和t.rx(peitocret_

.rx(y,fagmardcin,1)是否相等,当二者相等时,ocprediction为真,否则corect_prediction为假。
(17)
cuay=feueat.at(oc_peitot.la32 ))。

arctrdcmen(fcscretrdcin,ffot定义全部样本预测的(.) 准确率(_) 。t.at实现类型转换, oc_peitool类

fcs将cretrdcin由bo
型转换为float32类型
。


至此,我们使用TensorFlow实现了一个简单的机器学习算法模型Softmax 
Regresion,(1)~(17)完成了计算图的定义。接下来就是运行计算图,进行迭代训练和
测试。

(18)wtfSsin()ae

iht.eosss
。

创建一个名为ses 
的会话,并通过Python中的上下文管理这个会话。通过Python 
上下文管理机制,只要将所有的计算放在with内部即可,当上下文管理器退出时,会自动
释放所有的资源,这样既解决了异常退出时资源释放的问题,又解决了忘记调用Sesion. 
close函数而产生的资源泄露。

(19)ss.uiit)。

ern(
n
使用会话执行init操作,实现所有参数的初始化
。
(20)forepochinrange(101 )
。
for循环控制语句,epoch为循环变量,取值范围为0~100 
。
(21)forbatchinrange(n_batch)
。
for循环控制语句,batch为循环变量,取值范围为0~nbatch-1
。
(22)bac_xbths=mns.rinx_bth(ac_sze)
。


ths,ac_yittan.etacbth(_) i
每批次从训练集中一次性提取batch_size张图片和对应的标签。将图片赋值给
batchxs,将标签赋值给batchys。(2(_) 3)ss.utan_tp,
ed_itx:ac_xy:ac_ys})。

ern(risef(_) dc={bths,bth

使用会话执行trainstep操作,即trainstep是运行结果的输出张量。输入数据x为
batchxs,y为batch_ys。(_) 该代码的作用是实(_) 现神经网络训练,不断调整网络参数,以使
los 最(2(_) 4
小
)
。
tri_aern(arcy,
ed_it{x:mns.riigs,y:mns.

anc=ss.ucuafdc=ittan.maeittrilbl

an.aes})。
使用会话执行acuracy操作,即acuracy是运行结果的输出张量。输入数据x为
mnitriigs,y为mnittan.aes。该代码的作用是用训练样本集验证当前网络

stan.maes.rilbl
参数的识(.) 别准确率。
(25)prn"tr+sr(pc+,Tannrc"ttan_
c))。

it(Ie"teoh)"riigAcuay+sr(ria
打印每次epoch的训练样本的识别结果信息,需要将打印的参数用str转换为string
类型。

(26)tet_
css.uarcfdc={mns.etigs,mns.etlabels})。
sa=ern(
cuay,
ed_itx:itts.maey:itts. 
使用会话执行acuracy操作,即acuracy是运行结果的输出张量。输入数据x为


39 


mnitetmaey为mns.etlbl

stsigs,itts.aes。该代码的作用是用测试样本集验证当前网络参数的识(.) 别准(.) 确率。
27)prin"TestingAcuracy"+sttesta

(t(r(_
c))。

打印测试样本的识别结果信息,需要将打印的参数用str转换为string类型。

注意:若提示unexpectedindent错误信息,则说明代码中行之间的缩进不一致。

3.2.4 MNIST 
数据识别的卷积神经网络模型
卷积神经网络(convolutionalneuralnetworks,CNN)已成为众多科学领域的研究热
点,特别是在模式分类领域,由于卷积神经网络避免了对图像复杂的前期预处理,可以直
接输入原始图像,因此得到了更为广泛的应用。

1.卷积神经网络简介
局部感知、权值共享和池化是卷积神经网络的三个重要特征。

1)局部感知

当图像的尺寸很大时,全连接神经网络的参数将变得很多,从而导致计算量非常大。
为了降低计算量,卷积神经网络采用局部感知的方法,即每个神经元只感受局部的图像区
域,将这些不同局部的神经元综合起来就可以得到全局信息,如图3-8所示。


图3-
8 
全连接网络全面感知与CNN局部感知的对比

感受野(是CNN每一层输出的特征图(上每个像素点

receptivefield) featuremap)
(神经元)在该层输入图像上映射的区域大小。感受野也可以认为是特征图中的神经元
“看到的”输入区域,特征图上某个神经元的计算只受该神经元所看到的输入区域的影响, 
与区域之外的内容没有关系。图3-9展示了卷积神经网络中的输入图像、卷积核、感受野、
特征图等基本术语,以及输入图像与卷积和进行卷积操作得到特征图的基本原理。图3-9 
中感受野的尺寸为3×3,与卷积核尺寸相同,感受野与卷积核对应位置的元素相乘后累
加,将得到新的元素构成特征图。

特征图的大小(卷积特征)由3个参数控制:深度(depth)、步长(stride)、填充


40 


图3-
9 
CNN 
中的基本术语图示

(padding)。深度是指卷积操作所需的滤波器个数。步长是指每次卷积运算结束后至下
一次卷积运算开始前,卷积核在输入图像上滑动的像素数。当步长为n(n≥1 的整数)
时,卷积核每滑动一次,新的感受野与前一个感受野相比位置向右平移
n 
个像素单位,若
本行结束,则向下平移
n 
个像素单位,从最左侧重复滑动操作。填充可以解决图像边界
卷积造成图像信息丢失的问题,主要有3种方法:SamePadding、ValidPadding和自定义
Padding。SamePadding根据卷积核的大小对输入图像进行边界补充,一般填充0值,以
使卷积后得到的特征图与输入图像的尺寸相同;ValidPadding保持输入图像尺寸不变, 
即不进行填充;自定义Padding与生成特征图尺寸的计算表达式为

oupuh 
=(nptaigh 
-kreh 
)/tie+1 31)

ttiuh 
+2×pddnenlsrd(
=()/(2)

outputw 
inputw 
+2×paddingw 
-kernelw 
stride+1 3
式中,t、t分别为输入图像和特征图的尺寸,l为卷积核的尺寸,inpuoutpuh、
kernestride 

为步长,padding为边界填充尺寸,
w 
分别为输入图像的长和宽。

图3-10 为ValidPadding,stride=1、ValidPadding,stride=2以及SamePadding, 
stride=1的示例。读者可以根据式(3-1)和式(3-2), 自行推导SamePadding,stride=2 
时的情况。

2)权值共享

卷积核的局部感知机制将输入图像划分为不同区域(不同的感受野), 但不同的感受
野与具有相同数值的卷积核进行特征图计算,即图中每个感受野会被同样的卷积核处理。
卷积核中的数值称作权值(权重), 因此称为权值共享。如图3-11 所示,输入图像的不同
区域用同一个卷积核进行卷积运算可以得到特征图输出。


图3-10 
padding模式和步长应用示例

41

图3-10 
(续) 

42

图3-10 
(续) 


图3-11 
CNN 
中的权值共享

3)池化

池化也称下采样,是指将特征图中的若干元素根据池化规则合并为一个元素的过程。
池化可以看作是池化规则对特征图的滑动滤波,如图3-12 所示。基本池化规则主要有两

43

种:取最大值和求平均值。因此池化方式也有两种:最大池化和均值池化。图3-13 为最
大池化和平均池化的例子。


图3-12 
池化操作原理


图3-13 
池化示例

2. 
卷积神经网络的TensorFlow 
实现
1)卷积神经网络结构

两个卷积层+池化层,最后接上两个全连接层。第一层卷积使用32 个3×3×1 的卷
积核,步长为1,边界处理方式为samepadding,即卷积的输入和输出保持相同的尺寸。
激活函数为ReLU(RectifiedLinearUnit), 后接一个2×2 的池化层,方式为最大池化。
ReLU 的数学表达如式(3-3)所示,图3-14 给出了ReLU 函数的曲线表示。

f(x)=max(x,0) (3-3)

第二层卷积使用50 个3×3×32 的卷积核,步长为
1,边界处理方式为samepadding,激活函数为ReLU,后
接一个2×2 的池化层,方式为最大池化。

第一层全连接层使用1024 个神经元,激活函数依然
是ReLU 。
第二层全连接层使用10 个神经元,激活函数为

图3LU 
激活函数softmax,用于输出结果。-14 
Re

2)TensorFlow 代码

(1)在/home/tensorflow/design/my_mnist/目录下新建空白文档,命名为my_ 
mnitcn.输入以下代码:
s_npy, 


44 


45 
#coding=utf-8 
import input_data 
import tensorflow as tf 
import pylab 
#读取数据
mnist=input_data.read_data_sets('MNIST_data', one_hot=True) 
#每个批次的大小
batch_size=100 
#计算一共有多少个批次
n_batch=mnist.train.num_examples // batch_size 
#==============构建CNN 网络结构开始============= 
#自定义卷积函数
def conv2d(x,w): 
return tf.nn.conv2d(x,w,strides=[1,1,1,1],padding='SAME') 
#自定义池化函数
def max_pool_2_2(x): 
return tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME') 
#设置占位符,尺寸为样本输入和输出的尺寸
x=tf.placeholder(tf.float32,[None,784]) 
y_=tf.placeholder(tf.float32,[None,10]) 
x_img=tf.reshape(x,[-1,28,28,1]) 
#设置第一个卷积层和池化层
w_conv1=tf.Variable(tf.truncated_normal([3,3,1,32],stddev=0.1)) 
#32 个3×3×1 的卷积核
b_conv1=tf.Variable(tf.constant(0.1,shape=[32])) #32 个通道
h_conv1=tf.nn.relu(conv2d(x_img,w_conv1)+b_conv1) 
h_pool1=max_pool_2_2(h_conv1) 
#设置第二个卷积层和池化层
w_conv2=tf.Variable(tf.truncated_normal([3,3,32,50],stddev=0.1)) 
#50 个3×3×32 的卷积核
b_conv2=tf.Variable(tf.constant(0.1,shape=[50])) #50 个通道
h_conv2=tf.nn.relu(conv2d(h_pool1,w_conv2)+b_conv2) 
h_pool2=max_pool_2_2(h_conv2) 
#设置第一个全连接层
w_fc1=tf.Variable(tf.truncated_normal([7*7*50,1024],stddev=0.1)) 
b_fc1=tf.Variable(tf.constant(0.1,shape=[1024])) 
h_pool2_flat=tf.reshape(h_pool2,[-1,7*7*50]) 
h_fc1=tf.nn.relu(tf.matmul(h_pool2_flat,w_fc1)+b_fc1) 
h_fc1_drop=tf.nn.dropout(h_fc1,0.5) 
#设置第二个全连接层
w_fc2=tf.Variable(tf.truncated_normal([1024,10],stddev=0.1)) 
b_fc2=tf.Variable(tf.constant(0.1,shape=[10])) 
y_out=tf.nn.softmax(tf.matmul(h_fc1_drop,w_fc2)+b_fc2)

46 
#==============构建CNN 网络结构结束============= 
#使用交叉熵
loss= tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels= y_, 
logits=y_out)) 
#使用梯度下降法
train_step=tf.train.GradientDescentOptimizer(0.2).minimize(loss) 
#定义初始化操作
init=tf.global_variables_initializer() 
#建立正确率计算表达式
correct_prediction=tf.equal(tf.argmax(y_out,1),tf.argmax(y_,1)) 
accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32)) 
#开始训练
with tf.Session() as sess: 
sess.run(init) 
for epoch in range(101): 
for batch in range(n_batch): 
batch_xs,batch_ys=mnist.train.next_batch(batch_size) 
sess.run(train_step,feed_dict={x:batch_xs,y_:batch_ys}) 
train_acc=sess.run(accuracy,feed_dict={x:mnist.train.images, 
y_:mnist.train.labels}) 
print("Iter "+str(epoch)+",Training Accuracy "+str(train_acc)) 
test_acc= sess.run(accuracy,feed_dict= {x:mnist.test.images,y_:mnist. 
test.labels}) 
print("Testing Accuracy "+str(test_acc)) 
(2)运行my_mnist_cnn.py,在目录/home/tensorflow/design/my_mnist/下运行my 
_mnist_cnn.py。 
(tensorflow)$ python my_mnist_cnn.py 
图3-15为卷积神经网络模型的运行结果,显示测试结果为test_accuracy=0.9907。
图3-15 卷积神经网络模型的运行结果

47 
对比图3-15和图3-7可以发现,与SoftmaxRegression神经网络模型进行比较,采
用结构更复杂的卷积神经网络可以实现更高的识别正确率。
3.代码解读
本部分将对my_mnist_cnn.py代码中出现的新的API函数进行介绍。未解读的代
码请参考3.2.3节中的代码解读中对my_mnist_simple.py的分析。
(1)tf.nn.conv2d是TensorFlow专门用作卷积运算的API函数,该函数的原型为: 
tf.nn.conv2d(input, filter, strides, padding, use _cudnn _on _gpu = None, name = 
None) 
该API函数各参数的意义如下。
①input:输入图像或数据,是一个Tensor,shape=(4,),shape格式为1×4,4个元
素为[batch,in_height,in_width,in_channels],具体含义是[一个batch的图片数量,图片
高度,图片宽度,图像通道数],dtype为float32或float64。
②filter:CNN 的卷积核,也是一个Tensor,shape=(4,),shape格式为1×4,4个元
素为[filter_height,filter_width,in_channels,out_channels],具体含义是[卷积核高度,卷
积核宽度,图像通道数,卷积核个数],dtype与参数input相同。第3个元素in_channels 
与input的第4个元素in_channels相同。
③strides:卷积运算时在图像每一维的步长,是一个一维向量,长度为4,即含有4 
个元素,格式为[1,横向步长,纵向步长,1]。
④ padding:只能是SAME和VALID其中之一。
⑤ use_cudnn_on_gpu:bool类型,指是否使用cudnn加速,默认为true。
⑥ name:指定本次调用API函数操作的名字。
该API会返回一个Tensor,即卷积神经网络中的featuremap,shape格式为[batch, 
height,width,channels]。
(2)tf.nn.max_pool是TensorFlow专门用作池化运算的API函数,该函数的原型为: 
tf.nn.max_pool(value, ksize, strides, padding, name=None) 
该API函数各参数的意义如下。
① value:池化输入,一般池化层接在卷积层后面,所以输入通常是featuremap, 
shape格式依然是[batch,height,width,channels]。
② ksize:池化窗口尺寸,是一个含有4个元素的一维向量,格式为[1,height,width, 
1]。首尾两个元素为1,代表不在batch和channels上做池化。本例中ksize取值为[1, 
2,2,1],代表横向两个和纵向两个共4个特征图中数据为一组进行池化。
③strides:池化窗口在特征图的每一个维度上滑动的步长,格式为[1,stride,stride,1]。
④ padding:和卷积类似,可以取VALID或者SAME。
⑤ name:指定本次调用API函数操作的名字。
该API返回一个Tensor,类型与输入相同,shape格式为[batch,height,width,

48
channels]。
(3)tf.reshape是TensorFlow中用来改变张量shape的API函数,该函数的原型为: 
tf.reshape(tensor,shape,name=None) 
该API函数各参数的意义如下。
tensor:输入张量。
shape:变形后得到的输出张量的形状。
name:指定本次调用API函数操作的名字。
该API函数的作用是将输入tensor变换为参数shape形式,参数shape为一个列表
形式,如本例中的[-1,28,28,1]。-1代表不用指定这一维的大小,函数会自动进行计
算,但是列表中只能存在一个-1。如果batch为1,则每次输入一张图像,输入x的
shape为[1,784],则x_img的shape为[1,28,28,1]。如果batch为100,则每次输入100 
张图像,输入x的shape为[100,784],则x_img的shape为[100,28,28,1]。
(4)tf.truncated_normal是TensorFlow 中用来生成截断正态分布随机数据的API 
函数,该函数的原型为: 
tf.truncated_normal(shape, mean= 0.0, stddev= 1.0,dtype= tf.float32, seed= 
None,name=None) 
该API函数各参数的意义如下。
①shape:表示生成张量的维度。
② mean:均值,默认值为0.0。
③stddev:标准差,默认值为1.0。
④ dtype:数据类型,默认为tf.float32。
⑤seed:随机数种子。
⑥ name:指定本次调用API函数操作的名字。
该API函数根据设定的均值和标准差产生正态分布的随机数据,如果产生正态分布
的数值与均值的差值大于标准差的2倍,则重新生成,因此称为截断的正态分布生成函
数,该函数可保证产生的随机数与均值的差距不会超过标准差的2倍。
(5)tf.nn.relu实现ReLU 激活函数功能。
(6)tf.nn.dropout是TensorFlow 中用来随机屏蔽某些神经元输出的API函数,该
函数的原型为: 
tf.nn.dropout(x, keep_prob, noise_shape=None,seed=None,name=None) 
该API函数的各参数意义如下。
① x:输入,输入tensor。
② keep_prob:float类型,每个元素被保留下来的概率,设置神经元被选中的概率。
③ noise_shape:一个一维的int32张量,代表随机产生“保留/丢弃”标志的shape。
④seed:整型变量,随机数种子。

49 
⑤ name:指定本次调用API函数操作的名字。
该API函数可以在不同的训练过程中按设置的概率随机屏蔽一部分神经元的输出, 
即让某些神经元的激活值以一定的概率1-keep_prob让其停止工作。
各个列表中的取值,与输入图像尺寸、卷积核参数、池化参数等有关,读者可自行推算
列表参数中的取值依据。
3.3 MNIST数据集转换
之所以进行数据集转换,是因为神经网络需要处理的样本集可能是多种多样的,有的
是一维时间序列数据,有的是二维图像数据。为了能够实现不同格式样本集的处理,需要
将样本集转换为神经网络可以识别的数据格式。通过学习数据集转换,读者可以转换自
己采集的样本集的数据格式,然后用相同的方法采用神经网络进行训练和测试。
3.3.1 将数据集转换为以txt 文件保存的数据
本例将101张MNIST测试样本和对应的标签转换为txt文档。
(1)在/home/tensorflow/design/my_mnist/目录下新建空白文档,命名为txt_file_ 
gen.py,输入以下代码。 
#coding: utf-8 
import tensorflow as tf 
import input_data 
import numpy as np 
#载入数据集
mnist=input_data.read_data_sets("MNIST_data",one_hot=True) 
with tf.Session() as sess: 
for i in range(101): 
img_in_i=mnist.test.images[i] 
tag_i=np.argmax(mnist.test.labels[i]) 
np.savetxt('/home/tensorflow/design/my_mnist/mnist_txt/mnist_img_txt/ 
img_%d.txt'%i, img_in_i.reshape(-1), fmt="%.31f",delimiter=",") 
np.savetxt('/home/tensorflow/design/my_mnist/mnist_txt/mnist_lab_txt/ 
img_lab_%d.txt'%i, tag_i.reshape(-1), fmt="%.31f",delimiter=",") 
print("text write was sucessful") 
(2)在/home/tensorflow/design/my_mnist/目录下新建文件夹mnist_txt,并在该
文件夹下新建两个文件夹mnist_img_txt和mnist_lab_txt。
注意:必须提前建立文件夹,Python代码不能自动生成文件夹。
(3)运行txt_file_gen.py。 
(tensorflow)$ python txt_file_gen.py

50 
若成功运行,则/home/tensorflow/design/my_mnist/mnist_txt/mnist_img_txt和
/home/tensorflow/design/my_mnist/mnist_txt/mnist_lab_txt分别会有101个txt文件
生成。
3.3.2 将数据集转换为以bmp 文件保存的图片
1.训练样本集转换 
(1)在/home/tensorflow/design/目录下新建文件夹my_mnist_tfrecord。
(2)将/home/tensorflow/design/my_mnist目录下的MNIST_data和input_data.py 
复制到/home/tensorflow/design/my_mnist_tfrecord文件夹。
(3)在/home/tensorflow/design/my_mnist_tfrecord/目录下新建空白文档,命名为
mnist_bmp_train_gen.py,输入以下代码。 
#coding: utf-8 
import os 
import tensorflow as tf 
import input_data 
from PIL import Image 
#声明图片的宽和高
rows=28 
cols=28 
#当前路径下的保存目录
save_dir="./mnist_bmp_train" 
#读入MNIST 数据
mnist=input_data.read_data_sets("MNIST_data/", one_hot=False) 
#创建会话
sess=tf.Session() 
#获取图片总数
shape=sess.run(tf.shape(mnist.train.images)) 
images_count=shape[0] 
pixels_per_image=shape[1] 
#获取标签总数
shape=sess.run(tf.shape(mnist.train.labels)) 
labels_count=shape[0] 
#要提取的图片数量
images_to_extract=shape[0] 
#l
abels=mnist.train.labels 
#检查数据集是否符合预期格式
if (images_count==labels_count) and (shape.size==1): 
print ("数据集总共包含%s 张图片,和%s 个标签" %(images_count, labels_count)) 
print ("每张图片包含%s 个像素" %(pixels_per_image)) 
print ("数据类型: %s" %(mnist.train.images.dtype))