省去介绍TensorFlow,在这个部分我们想要实现使用神经网络来确定一个移动目标的位置,然后实现控制四轴飞行器来跟随目标。简要的介绍神经网络,内容大部分不是来自于Udacity课程,而是来自于《Tensorflow实战Google深度学习框架》(ps.这本书个人强推,我的深度学习入门书籍。),其中内容有部分增改。关于神经网络的介绍,相信很多大佬已经解释的很清楚了。下面重点记录理论部分,前面大部分没有给出相关程序,若是未来有机会会补上。
1.线性模型局限性激活函数去线性化
在线性模型中,模型的输出为输入的加权和。假设一个模型的输出y和输入x?满足一下关系,那么这个模型就是一个线性模型。
其中wi,b∈Rw_i,b∈Rwi?,b∈R为模型参数。被称之被称之为线性模型是因为当模型的输入只有一个的时候,?和y形成了二维坐标系上的一条直线。类似地,当模型有n个输入时,?和y形成了n+1维空间中的一个平面。而一个线性模型中通过输入得到输出的函数被称之为一个线性变换。上面的公式就是一个线性变换。
线性模型的最大特点是任意线性模型的组合仍然还是线性模型。前向传播的计算公式为:
其中?为输入,?为参数。整理一下以上公式可以得到整个模型的输出为:
根据矩阵乘法的结合律有:
而W(1)W(2)W^{(1)}W^{(2)}W(1)W(2)其实可以被表示为一个新的参数?′:
这样输入和输出的关系就可以表示为:
其中?′是新的参数。这个前向传播的算法完全符合线性模型的定义。从这个例子可以看到,虽然这个神经网络有两层(不算输入层),但是它和单层的神经网络井没有区别。以此类推,只通过线性变换,任意层的全连接神经网络和单层神经网络模型的表达能力没有任何区别,而且它们都是线性模型。然而线性模型能够解决的问题是有限的,这就是线性模型最大的局限性,也是为什么深度学习要强调非线性。在下面的篇幅中,将通过TensorFlow Playground给出一个具体的例子来验证线性模型的局限性。
还是以判断零件是否合格为例,输入为?1和?2,其中?1代表一个零件质量和平均质量的差,?2代表一个零件长度和平均长度的差。假设一个零件的质量及长度离平均质量及长度越近,那么这个零件越有可能合格。于是训练数据很有可能服从图下所示的分布。
图中蓝色的点代表合格的零件,而黄色的点代表不合格的零件。可以看到虽然蓝色和黄色的点有一些重合,但是大部分代表合格零件的黑色点都在原点(0,0)的附近,而代表不合格零件的灰点都在离原点相对远的地方。这样的分布比较接近真实问题,因为大部分真实的问题都存在大致的趋势,但是很难甚至无法完全正确地区分不同的类别。
下图显示了使用TensorFlowPlayground训练线性模型解决这个问题的效果。
上图使用的模型有一个隐藏层,并且在顶部激活函数(Activation)那一栏中选择了线性(Linear)。通过TensorFlow游乐场对这个模型训练100轮之后,在最右边那一栏可以看到训练的结果。从上图上可以看出,这个模型并不能很好地区分灰色的点和黑色的点。虽然整个平面的颜色都比较浅,但是中间还是隐约有一条分界线,这说明这个模型只能通过直线来划分平面。如果一个问题可以通过一条直线来划分,那么线性模型就可以用来解决这个问题。下图显示了一个可以通过直线划分的数据。
从上图中可以看出,在线性可分问题中,线性模型就能很好区分不同颜色的点。因为线性模型就能解决线性可分问题,所以在深度学习的定义中特意强调它的目的为解决更加复杂的问题。所谓复杂问题,至少是无法通过直线(或者高维空间的平面)划分的。在现实世界中,绝大部分的问题都是无法线性分割的。
回到判断零件是否合格的问题,如果将激活函数换成非线性的,那么可以得到如下图所示的结果。在这个样例中使用了ReLU激活函数。使用其他非线性激活函数也可以得到类似的效果。从下图中可以看出,当加入非线性的元素之后,神经网络模型就可以很好地区分不同颜色的点了。
如果将每一个神经元(也就是神经网络中的节点)的输出通过一个非线性函数,那么整个神经网络的模型也就不再是线性的了。这个非线性函数就是激活函数。下图显示了加入激活函数和偏置项之后的神经元结构。以下公式给出了神经网络结构加上激活函s数和偏置项后的前向传播算法的数学定义:
以下公式给出了神经网络结构加上激活函s数和偏置项后的前向传播算法的数学定义:
相比定义,上面的定义主要有两个改变。第一个改变是新的公式中增加了偏置项(bias),偏置项是神经网络中非常常用的一种结构。第二个改变就是每个节点的取值不再是单纯的加权和。每个节点的输出在加权和的基础上还做了一个非线性变换。下图显示了几种常用的非线性激活函数的函数图像。
从上图中中可以看出,这些激活函数的函数图像都不是一条直线。所以通过这些激活函数,每一个节点不再是线性变换,于是整个神经网络模型也就不再是线性的了。
从(2-7)中可以看出,偏置项可以被表达为一个输出永远为1的节点。以下公式给出了这个新的神经网络模型前向传播算法的计算方法。
隐藏层推导公式:
输出层推导公式:
2. 损失函数
分类问题和回归是监督学习的两大种。这一节将分别介绍类问题和回归中 使用到的经典损失函数。分类问题希望解决是将不同样本事先定义好别中在这个问题中,需要将样本(也就是零件 )分到合格或是不两个类别中。
在解决判断零件是否合格的 二分类问题时,定义一个有单输出节点的神经网络。当这 个节点的输出越接近 0时,这个样本越有可能是不合格的:反之如果输出接近 1,样本越有可能是合格的。为了给出具体分类结果,以取 0.5 作为阔值。凡是输出大于 0.5 的样本都认为是合格,小于 0.5 的则是不合格。然而这样做法并容易直接推广 到多分类的问题。虽然设置个 阈值 在理论上是可能的,但解决实际问题过程中一般 不会这么处理。
通过神经网络解决多分类问题最常用的方法是设置 n个输出节点,其中 n为类别的个 数。对于每一个 样例,神经网络可以得到的n维数 组作为输出结果。数中的每一个 维度(也就是每一个输出节点)对应类别 。在理想情况下,如果样本属于k, 那么这个类别所对应的输出节点值该为 1,而其他节点的输出都为 0。以识别数字 l为例,神经网络模型的输出结果越接近[ 0,1,0,0,0,0,0,0,0,0 ]越好。那么如何判断一 个输出向 量和期望的量有多接近呢?
交叉熵 (cross entropy )是常用的评判方法之一。 交叉熵 刻画了两个概率分布之间的距离,它是类问题中使用比较广一种损失函数。
给定两个概率分布 p和 q,通过 q来表示 p的交叉熵 为:
H(p,q)=?∑xp(x)log?q(x)χH(p, q) = -\sum_{x}p(x)\log q(x) \chiH(p,q)=?x∑?p(x)logq(x)χ
注意交叉熵刻画的是两个概率分布之间距离 ,然而神经网络的输出却不一定是个 概 率分布 。概率分布刻画了不同事件发生的当总数 有限情况下,概率分布函数p(χ=x)p(\chi=x)p(χ=x)满足: ?xp(χ=x)∈[0,1]且∑p(χ=x)=1?x\; p(\chi=x)∈[0,1]且\sum p(\chi=x)=1?xp(χ=x)∈[0,1]且∑p(χ=x)=1
也就是说 ,任意事件发生的概率都在 0和 1之间,且总有某一个事件发生(概率的和为 1)。如果将分类问题中 )。如果将分类问题中 “一个样例属于某类别 ”看成一个概率事件,那么训练数据的 正确答案就符合一个概率分布。因为事件 “一个样例属于不正确的类别 ”的概率为 0,而 “一个样例属于正确的类别 ”的概率为 1。如何将神经网络前向传播得到的结果也变成概率 分布呢? Softmax 回归就是一个非常用的方法。
Softmax 回归本身可以作为一个学习算法来优化分类结果,但在 TensorFlow 中, Softmax 回归的参数被去掉了,它只是一层额外处理将神经网络 的输出变成一个概率 分布。
假设原始的神经网络输出为 ?1,?2,…,??,那么经过 Softmax回归处理之后的输出为:
softmax(y)i=yi′=eyi∑j=1neyjsoftmax(y)_i = y'_i = \frac{e^{yi}}{\sum_{j=1}^{n}e^{yj}}softmax(y)i?=yi′?=∑j=1n?eyjeyi?
从以上公式中可看出 ,原始神经网络的输 出被用作置信度来生成新而出满足概率分布的所有要求。这个新输可以理解为经过神网络推导,一样例不同类别的概率分是多大。这样就把神经网络输出也变成了一个布,从而可以通过 交叉熵 来计算预测的概率分布和真实答案之间距离了。
从交叉熵的公式中可以看到交叉熵函数不是对称的(H(p,q)≠H(q,p)),它刻画的是通过概率分布q来表达概率分布p的困难程度。因为正确答案是希望得到的结果,所以当交叉熵作为神经网络的损失函数时,p代表的是正确答案,q代表的是预测值。交叉熵刻画的是两个概率分布的距离,也就是说交叉熵值越小,两个概率分布越接近。下面将给出两个具体样例来直观地说明通过交叉熵可以判断预测答案和真实答案之间的距离。
假设有一个三分类问题,某个样例的正确答案是(1,0,0)。某模型经过Softmax回归之后的预测答案是(0.5,0.4,0.1),那么这个预测和正确答案之间的交叉熵为:
H((1,0,0),(0.5,0.4,0.1))=?(1×log?0.5+0×log?0.4+0×log?0.1)≈0.3H((1,0,0),(0.5,0.4,0.1))=-(1×log?0.5+0×log?0.4+0×log?0.1 )≈0.3H((1,0,0),(0.5,0.4,0.1))=?(1×log?0.5+0×log?0.4+0×log?0.1)≈0.3
如果另外一个模型的预测是(0.8,0.1,0.l),那么这个预测值和真实值之间的交叉熵是:
H((1,0,0),(0.8,0.1,0.1))=?(1×log?0.8?10×log0.1+0×log?0.1)≈0.1H((1,0,0),(0.8,0.1,0.1))=-(1×log?0.8-10×log0.1+0×log?0.1 )≈0.1H((1,0,0),(0.8,0.1,0.1))=?(1×log?0.8?10×log0.1+0×log?0.1)≈0.1
从直观上可以很容易地知道第二个预测答案要优于第一个。通过交叉熵计算得到的结果也是一致的(第二个交叉熵的值更小)。
3. 反向传播和梯度下降
通过**反向传播算法(backpropagation)和梯度下降算法(gradient decent)**可以调整神经网络中参数的取值。梯度下降算法主要用于优化单个参数的取值,而反向传播算法给出了一个高效的方式在所有参数上使用梯度下降算法,从而使神经网络模型在训练数据上的损失函数尽可能小。反向传播算法是训练神经网络的核心算法,它可以根据定义好的损失函数优化神经网络中参数的取值,从而使神经网络模型在训练数据集上的损失函数达到一个较小值。神经网络模型中参数的优化过程直接决定了模型的质量,是使用神经网络时非常重要的一步。
假设用θ表示神经网络中的参数,J(θ)J(\theta)J(θ)表示在给定的参数取值下,训练数据集上损失函数的大小,那么整个优化过程可以抽象为寻找一个参数θ,使得J(θ)J(\theta)J(θ)最小。因为目前没有一个通用的方法可以对任意损失函数直接求解最佳的参数取值,所以在实践中,梯度下降算法是最常用的神经网络优化方法。梯度下降算法会迭代式更新参数θ,不断沿着梯度的反方向让参数朝着总损失更小的方向更新。下图展示了梯度下降算法的原理。
上图中x 轴表示参数θ的取值,y轴表示损失函数J(θ)J(\theta)J(θ)的值。上图的曲线表示了在参数θ取不同值时,对应损失函数J(θ)J(\theta)J(θ)的大小。假设当前的参数和损失值对应上图中小圆点的位置,那么梯度下降算法会将参数向x轴左侧移动,从而使得小圆点朝着箭头的方向移动。参数的梯度可以通过求偏导的方式计算,对于参数θ,其梯度为??θJ(θ)\frac{?}{?θ} J(θ)?θ??J(θ)。有了梯度,还需要定义一个学习率η(learning rate) 来定义每次参数更新的幅度。从直观上理解,可以认为学习率定义的就是每次参数移动的幅度。通过参数的梯度和学习率,参数更新的公式为:
θn+1=θn?η??θnJ(θn)\theta_{n+1} = \theta_n - \eta\frac{\partial}{\partial\theta_n}J(\theta_n)θn+1?=θn??η?θn???J(θn?)
下面给出了一个具体的例子来说明梯度下降算法是如何工作的。假设要通过梯度下降算法来优化参数x,使得损失函数J(θ)=x2J(θ)=x^2J(θ)=x2的值尽量小。梯度下降算法的第一步需要随机产生一个参数x的初始值,然后再通过梯度和学习率来更新参数x的取值。在这个样例中,参数x的梯度为?=(?J(x))/?x?=(?J(x))/?x?=(?J(x))/?x,那么使用梯度下降算法每次对参数x的更新公式为xn+1=xn?η?x_{n+1}=x_n-η?xn+1?=xn??η?。假设参数的初始值为5,学习率为0.3,那么这个优化过程可以总结为下表。
轮数 | 当前轮参数值 | 梯度×学习率 | 更新后参数值 |
---|---|---|---|
1 | 5 | 2x5×0.3=3 | 5-3=2 |
2 | 2 | 2×2×0.3=1.2 | 2-1.2=0.8 |
3 | 0.8 | 2×0.8×0.3=0.48 | 0.8-0.48=0.32 |
4 | 0.32 | 2×0.32×0.3=0.192 | 0.32-0.192=0.128 |
5 | 0.128 | 2x0.128×0.3=0.0768 | 0.128-0.0768=0.0512 |
从表中可以看出,经过5次迭代之后,参数x的值变成了0.0512,这个和参数最优值0已经比较接近了。虽然这里给出的是一个非常简单的样例,但是神经网络的优化过程也是可以类推的。神经网络的优化过程可以分为两个阶段,第一个阶段先通过前向传播算法计算得到预测值,井将预测值和真实值做对比得出两者之间的差距。然后在第二个阶段通过反向传播算法计算损失函数对每一个参数的梯度,再根据梯度和学习率使用梯度下降算法更新每一个参数。
需要注意的是,梯度下降算法并不能保证被优化的函数达到全局最优解。如下图所示,图中给出的函数就有可能只能得到局部最优解而不是全局最优解。在小黑点处,损失函数的偏导为0,于是参数就不会再进一步更新。在这个样例中,如果参数x的初始值落在右侧深色的区间中,那么通过梯度下降得到的结果都会落到小黑点代表的局部最优解。只有当x的初始值落在左侧浅色的区间时梯度下降才能给出全局最优答案。由此可见在训练神经网络时,参数的初始值会很大程度影响最后得到的结果。只有当损失函数为凸函数时,梯度下降算法才能保证达到全局最优解。
除了不一定能达到全局最优,梯度下降算法的另外一个问题就是计算时间太长。因为要在全部训练数据上最小化损失,所以损失函数J(θ)J(\theta)J(θ)是在所有训练数据上的损失和。这样在每一轮迭代中都需要计算在全部训练数据上的损失函数。在海量训练数据下,要计算所有训练数据的损失函数是非常消耗时间的。为了加速训练过程,可以使用随机梯度下降的算法(stochastic gradient descent)。这个算法优化的不是在全部训练数据上的损失函数,而是在每一轮法代中,随机优化某一条训练数据上的损失函数。这样每一轮参数更新的速度就大大加快了。因为随机梯度下降算法每次优化的只是某一条数据上的损失函数,所以它的问题也非常明显:在某一条数据上损失函数更小并不代表在全部数据上损失函数更小,于是使用随机梯度下降优化得到的神经网络甚至可能无法达到局部最优。
为了综合梯度下降算法和随机梯度下降算法的优缺点,在实际应用中一般采用这两个算法的折中一一每次计算一小部分训练数据的损失函数。这一小部分数据被称之为一个batch。通过矩阵运算,每次在一个batch上优化神经网络的参数并不会比单个数据慢太多。另一方面,每次使用一个batch可以大大减小收敛所需要的法代次数,同时可以使收敛到的结果更加接近梯度下降的效果。
4. 过拟合与正则化
过拟合,指的是当一个模型过为复杂之后,它可以很好地“记忆”每一个训练数据中随机噪音的部分而忘记了要去“学习”训练数据中通用的趋势。举一个极端的例子,如果一个模型中的参数比训练数据的总数还多,那么只要训练数据不冲突,这个模型完全可以记住所有训练数据的结果从而使得损失函数为0。可以直观地想象一个包含n个变量和n个等式的方程组,当方程不冲突时,这个方程组是可以通过数学的方法来求解的。然而,过度拟合训练数据中的随机噪音虽然可以得到非常小的损失函数,但是对于未知数据可能无法做出可靠的判断。
下图显示了模型训练的三种不同情况。在第一种情况下,由于模型过于简单,无法刻画问题的趋势。第二个模型是比较合理的,它既不会过于关注训练数据中的噪音,又能够比较好地刻画问题的整体趋势。第三个模型就是过拟合了,虽然第三个模型完美地划分了不同形状的点,但是这样的划分并不能很好地对未知数据做出判断,因为它过度拟合了训练数据中的噪音而忽视了问题的整体规律。比如图中浅色方框“口”更有可能和“X”属于同一类,而不是根据图上的划分和“O”属于同一类。
(1)正则化
为了避免过拟合问题,一个非常常用的方法是正则化(regularization)。正则化的思想就是在损失函数中加入刻画模型复杂程度的指标。假设用于刻画模型在训练数据上表现的损失函数为J(θ)J(\theta)J(θ),那么在优化时不是直接优化J(θ)J(\theta)J(θ),而是优化J(θ)+λR(w)J(\theta)+λR(w)J(θ)+λR(w)。其中R(w)R(w)R(w)刻画的是模型的复杂程度,而λ表示模型复杂损失在总损失中的比例。注意这里θ表示的是一个神经网络中所有的参数,它包括边上的权重www和偏置项bbb。一般来说模型复杂度只由权重www决定。
常用的刻画模型复杂度的函数R(w)R(w)R(w)有两种,一种是L1L1L1正则化,计算公式是:
R(ω)=‖w‖1=∑i∣wi∣R(ω)=‖w‖_1=∑_i|w_i | R(ω)=‖w‖1?=i∑?∣wi?∣
另一种是L2正则化,计算公式是:
R(ω)=‖w‖22=∑i∣wi2∣R(ω)=‖w‖_2^2=∑_i|w_i^2 | R(ω)=‖w‖22?=i∑?∣wi2?∣
无论是哪一种正则化方式,基本的思想都是希望通过限制权重的大小,使得模型不能任意拟合训练数据中的随机噪音。但这两种正则化的方法也有很大的区别。首先,L1L1L1正则化会让参数变得更稀疏,而L2L2L2正则化不会。所谓参数变得更稀疏是指会有更多的参数变为0,这样可以达到类似特征选取的功能。之所以L2L2L2正则化不会让参数变得稀疏的原因是当参数很小时,比如0.001,这个参数的平方基本上就可以忽略了,于是模型不会进一步将这个参数调整为0。其次,L1L1L1正则化的计算公式不可导,而L2L2L2正则化公式可导。因为在优化时需要计算损失函数的偏导数,所以对含有L2L2L2正则化损失函数的优化要更加简洁。
(2)DROPOUT
具有大量参数的深度神经网络是非常强大的机器学习系统。但是,过度拟合是此类网络中的一个严重问题。大型网络的使用速度也很慢,因此难以通过在测试时结合许多不同大型神经网络的预测来处理过度拟合问题。
丢弃(dropout) 是一种解决此问题的技术。关键思想是在训练过程中从神经网络中随机删除单元(及其连接)。这样可以防止单元之间的多度匹配。在训练期间,从指数数量的不同“精简”网络中删除样本。在测试时,仅通过使用权重较小的单个未精简网络,就可以轻松近似平均所有这些精简网络的预测结果。与其他正则化方法相比,这显着减少了过度拟合并带来了重大改进。dropout提高了计算机视觉,语音识别,文档分类和计算生物学等有监督学习任务上神经网络的性能,并在许多基准数据集上获得了最新的结果。
Dropout是一种用于减少过度拟合的正则化技术。该技术会暂时从网络中删除单元(人工神经元),以及所有这些单元的传入和传出连接。图1说明了辍学的工作方式。
TensorFlow提供了tf.nn.dropout()
可用于实现 丢弃 的功能。
让我们看一下如何使用的示例tf.nn.dropout()
。
keep_prob = tf.placeholder(tf.float32) # 保持节点的概率hidden_layer = tf.add(tf.matmul(features, weights[0]), biases[0])
hidden_layer = tf.nn.relu(hidden_layer)
hidden_layer = tf.nn.dropout(hidden_layer, keep_prob)logits = tf.add(tf.matmul(hidden_layer, weights[1]), biases[1])
上面的代码说明了如何将dropout应用于神经网络。
该tf.nn.dropout()
函数接受两个参数:
hidden_layer
:要对其应用dropout的张量
keep_prob
:保留(即不丢弃)任何给定单位的概率
keep_prob
可以调整要下降的单位数。为了补偿掉落的单位,tf.nn.dropout()
请将保留(即未掉落)的所有单位乘以1/keep_prob
。
dropout相关程序
在训练期间,起始keep_prob
是0.5
是比较好的。
在测试过程中,使用keep_prob
值1.0可以保留所有单位并最大化模型的功效。
这个测试将从ReLU测试的代码开始,并应用一个dropout层。使用keep_prob
占位符传入概率为0.5的ReLU层和dropout层来构建一个模型。从模型中打印日志。
注意:每次运行代码时,输??出都会不同。这是由dropout随机将其丢弃的单位引起的。
hidden_layer_weights = [[0.1, 0.2, 0.4],[0.4, 0.6, 0.6],[0.5, 0.9, 0.1],[0.8, 0.2, 0.8]]
out_weights = [[0.1, 0.6],[0.2, 0.1],[0.7, 0.9]]# 权值和偏置
weights = [tf.Variable(hidden_layer_weights),tf.Variable(out_weights)]
biases = [tf.Variable(tf.zeros(3)),tf.Variable(tf.zeros(2))]# 输入
features = tf.Variable([[0.0, 2.0, 3.0, 4.0], [0.1, 0.2, 0.3, 0.4], [11.0, 12.0, 13.0, 14.0]])# 创建dropout层
keep_prob = tf.placeholder(tf.float32)
hidden_layer = tf.add(tf.matmul(features, weights[0]), biases[0])
hidden_layer = tf.nn.relu(hidden_layer)
hidden_layer = tf.nn.dropout(hidden_layer, keep_prob)logits = tf.add(tf.matmul(hidden_layer, weights[1]), biases[1])# 打印log
with tf.Session() as sess:sess.run(tf.global_variables_initializer())
5.MNIST手写数字识别
下面给出一个关于手写数字识别的完整TensorFlow程序来解决
MNIST手写数字识别问题。在神经网络结构上,深度学习一方面需要使用激活函数实现神经网络模型的去线性化,另一方面需要使用一个或多个隐藏层使得神经网络结构更深,以解决复杂问题.
以下代码给出了一个在MNIST数据集上实现这些功能的完整的TensorFlow程序:
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import timeINPUT_NODE = 784
OUTPUT_NODE = 10# 配置神经网络参数
LAYER1_NODE = 500
BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARZATION_RATE = 0.0001
TRAINING_STEPS = 30001
MOVING_AVERAGE_DECAY = 0.99# 定义前向传播过程
def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):# 当没有提供滑动平均类时,直接使用参数当前的取值。if avg_class is None:layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1)+biases1)return tf.matmul(layer1, weights2) + biases2else:# 首先使用avg_class.average函数来计算得出变量的滑动平均值。# 然后在计算相应的神经网络前向传播结果,只对参数w有调整layer1 = tf.nn.relu(tf.matmul(input_tensor, avg_class.average(weights1))+avg_class.average(biases1))return tf.matmul(layer1, avg_class.average(weights2)) + avg_class.average(biases2)# 训练模型的过程
def train(mnist):x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='x-input')y_ = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name='y-input')# 生成隐藏层函数# tf.truncated_normal(shape, mean, stddev)函数生成正态分布,shape表示生成张量的维度,mean是均值,stddev是标准差。weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE, LAYER1_NODE], stddev=0.1))biases1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE]))# 生成输出层函数weights2 = tf.Variable(tf.truncated_normal([LAYER1_NODE, OUTPUT_NODE], stddev=0.1))biases2 = tf.Variable(tf.constant(0.1, shape=[OUTPUT_NODE]))y = inference(x, None, weights1, biases1, weights2, biases2)# 定义储存训练轮数的变量global_step = tf.Variable(0, trainable=False)# 给定滑动平均衰减率和训练轮数的变量,初始化滑动平均类。variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)# 在所有代表神经网络参数的变量上使用滑动平均。variable_averages_op = variable_averages.apply(tf.trainable_variables())# 计算使用了滑动平均之后的前向传播结果.average_y = inference(x, variable_averages, weights1, biases1, weights2, biases2)# 计算交叉熵作为刻画预测值和真实值之间差距的损失函数。cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))# 计算在当前batch中所有样例的交叉熵平均值。cross_entropy_mean = tf.reduce_mean(cross_entropy)# 计算L2正则化损失函数regulatizer = tf.contrib.layers.l2_regularizer(REGULARZATION_RATE)# 计算模型正则化损失regularization = regulatizer(weights1) + regulatizer(weights2)# 总损失等于交叉熵损失和正则化损失的和。loss = cross_entropy_mean + regularization# 设置指数衰减学习率learning_rate = tf.train.exponential_decay(LEARNING_RATE_BASE,global_step,mnist.train.num_examples / BATCH_SIZE,LEARNING_RATE_DECAY)# 使用tf.train.GradientDescentOptimizer优化算法来优化损失函数train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)# 在训练神经网络模型时,每过一遍数据既需要通过反向传播来更新神经网络中的参数,又要更新每一个参数的滑动平均值。with tf.control_dependencies([train_step, variable_averages_op]):# tf.no_op()表示执行完 train_step, variable_averages_op 操作之后什么都不做train_op = tf.no_op(name='train')# 检测使用了滑动平均模型的神经网络前向传播结果是否正确。correct_prediction = tf.equal(tf.argmax(average_y, 1), tf.argmax(y_, 1))# tf.cast()函数的作用是执行 tensorflow 中张量数据类型转换。# 将一个布尔型的数值转换为实数型,然后计算平均值。这个平均值就是模型在这一组数据上的正确率。accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))# 初始化会话并开始训练过程。with tf.Session() as sess:tf.global_variables_initializer().run()# 准备验证数据validate_feed = {
x: mnist.validation.images, y_: mnist.validation.labels}# 准备测试数据test_feed = {
x: mnist.test.images, y_: mnist.test.labels}# 迭代地训练神经网络for i in range(TRAINING_STEPS):if i % 1000 == 0:validate_acc = sess.run(accuracy, feed_dict=validate_feed)print('After %d training step(s) , validation accuracy using average model is %g ”' % (i, validate_acc))# 产生这一轮使用的一个batch的训练数据,并运行训练结果。# feed_dict的作用是给使用placeholder创建出来的tensor赋值。xs, ys = mnist.train.next_batch(BATCH_SIZE)sess.run(train_op, feed_dict={
x: xs, y_: ys})test_acc = sess.run(accuracy, feed_dict=test_feed)print('Finish!After %d training step(s) , validation accuracy using average model is %g ”' % (TRAINING_STEPS, test_acc))def main(argv=None):mnist = input_data.read_data_sets("tmp/data", one_hot=True)train(mnist)if __name__ == '__main__':main()
t = time.process_time()
print(t)