我们已经学习了TensorFlow的一些基础知识,
深度学习三人行(第1期)---- TensorFlow爱之初体验
深度学习三人行(第2期)---- TensorFlow爱之再体验
该期我们将从DNN入手开始学习TensorFlow方面的相关知识。学习的路上,我们多多交流,共同进步。本期主要内容如下:
文末附本期代码关键字,回复关键字即可下载。
一. 从生物学到人工神经网络
鸟类启发我们飞翔,牛蒡植物启发魔术贴,而大自然激发了许多其他发明。 那么,大脑的体系结构,是激发人工神经网络(ANN)的关键思想。人工神经网络是深度学习的核心。
1.1 从生物到人工神经元
在讨论人造神经元之前,让我们快速看一下生物神经元,如下图所示。它是一种看起来很奇特的细胞,主要存在于动物大脑皮层(例如,你的大脑)中,由含有细胞核和大部分细胞复杂成分的细胞体以及许多称为树突的分支延伸部分组成,还有一个非常长的延伸部分,称为轴突。轴突的长度可能比细胞体长几倍,或长达几万倍。靠近其末端的轴突分裂出成许多分支称为终树突,并且在这些分支的尖端是微小的结构,称为突触末端(或简称突触),其被连接到树突(或直接到细胞体)的其他神经元。生物神经元通过这些突触接收来自其他神经元的信号的短电脉冲。当一个神经元在几毫秒内接收到来自其他神经元的足够数量的信号时,它会触发自己的信号。
tf.assign()函数的作用是创建一个将新值赋给变量的一个节点,这里相当于执行如下迭代:
因此,单个生物神经似乎表现得相当简单,但它们组织在一个数十亿个神经元的庞大网络中,每个神经元通常与数千个其他神经元相连时情况就不同了。生物神经网络(BNN)的体系结构仍然是积极研究的主题,大脑的某些部分结构已经被打印出来,似乎神经元通常以连续的层次组织,如下图所示。
二. 训练多层感知机
Warren McCulloch和Walter Pitts提出了一个非常简单的生物神经元模型,该模型后来被称为人造神经元:它具有一个或多个二进制(开/关)输入和一个二进制输出。 当超过一定数量的输入有效时,人造神经元会简单地激活其输出。 McCulloch和Pitts表明,有了这样一个简化的模型,我们可以建立一个人造神经元网络,计算你想要的任何逻辑命题。 例如,假设神经元至少有两个输入有效时激活神经元,就可以构建一些执行各种逻辑运算的ANN,如下图所示。
感知机是Frank Rosenblatt于1957年发明的最简单的ANN架构之一。 它基于一个稍微不同的人造神经元(见下图),称为线性阈值单元(LTU):输入和输出现在是数字(而不是二进制开/关值),每个输入连接都与一个重量。 LTU计算其输入的加权和(z = w1 x1 + w2 x2 + ⋯ + wn xn = wT · x),然后对该和应用阶跃函数并输出结果:hw(x)= step(z) = step(wT·x)。
在感知机中使用的最常用的是Heaviside阶跃函数(见如下公式)。 有时使用符号函数代替。
单层感知器仅由单层LTU组成,每个神经元连接到所有输入,通常会添加一个额外的偏差特征(x0 = 1)。具有两个输入和三个输出的感知器如下图所示。 这个感知器可以将实例同时分为三个不同的二进制类,这使得它成为一个多输出分类器。
一个MLP由一个(直通)输入层,一个或多个LTU层组成,称为隐藏层,最后一层LTU称为输出层(如下图所示)。 除输出层以外的每一层都包含一个偏置神经元,并完全连接到下一层。 当ANN具有两个或更多个隐藏层时,它被称为深度神经网络(DNN)。
多年来,研究人员一直在努力寻找一种培训MLP的方法,但没有成功。但在1986年,D. E. Rumelhart 等人。发表了一篇突破性文章,介绍反向传播训练算法。对于每个训练实例,算法将其输入到网络并计算每个神经元的输出(这是正向传递,就像进行预测时一样)。 然后它计算网络的输出误差(即期望的输出和网络的实际输出之间的差异),并且它计算最后一个隐藏层中的每个神经元对每个输出神经元的误差有多大贡献。然后继续测量这些误差贡献中多少来自前一个隐藏层中的每个神经元 - 等等直到算法到达输入层。
为了使这个算法正常工作,作者对MLP的架构做了一个关键的改变:他们用logistic函数σ(z)= 1 /(1 + exp(-z))代替了阶跃函数。 这是很重要的,因为阶跃函数只包含平坦段,所以没有梯度可用(梯度下降不能在平坦表面上移动),而logistic函数在每个地方都有一个定义明确的非零导数,允许渐变下降 每一步都有进步。 反向传播算法可以与其他激活函数一起使用,而不是逻辑函数。 其他两种流行的激活功能是:
1.双曲正切函数tanh(z)=2σ(2z) - 1
它是S形的,连续的,可微分的,但是它的输出值范围从-1到1(而不是在逻辑函数中为0到1),这往往会使每一层的输出更大或训练开始时标准化程度较低(即以0为中心)。
2.ReLU功能
ReLU(z)= max(0,z)。 它是连续的,但不幸的是它在z = 0时不可微分(斜率突然变化,这可能导致梯度下降反弹)。实际上它运行得非常好,并且具有快速计算的优点。
这些流行的激活函数及其衍生物如图下图所示。
MLP通常用于分类,每个输出对应于不同的二进制类(例如,垃圾邮件/火腿,紧急/不紧急等等)。当这些类是排他性的(例如,数字图像分类的类0到9)时,输出层通常通过用共享的softmax函数代替单独的激活函数(见下图)。 softmax函数在机器学习系列中介绍过。每个神经元的输出对应于相应类的估计概率。 请注意,信号仅在一个方向上(从输入到输出)流动,所以此架构是前馈神经网络(FNN)的一个示例。
使用TensorFlow训练MLP的最简单方法是使用高级API TF.Learn,它与Scikit-Learn的API非常相似。 DNNClassifier类使得使用任意数量的隐藏层训练深层神经网络和softmax输出层来输出估计类别概率变得十分简单。 例如,下面的代码训练一个DNN用于分类两个隐藏层(一个具有300个神经元,另一个具有100个神经元)以及一个具有10个神经元的softmax输出层:
如果您在MNIST数据集上运行此代码(在对其进行缩放后(例如,通过使用ScikitLearn的StandardScaler),您可能会得到一个在测试集上的准确率超过98.2%的模型! 这比我们在机器学习系列文章的模型中训练的最佳模型要好:
代码过长,详细代码请移步公众号“智能算法”回复文末关键字下载。
三. 训练DNN
这里我们将实现Minibatch渐变下降以在MNIST数据集上进行训练。 第一步是构建阶段,构建TensorFlow图。 第二步是执行阶段,您可以在其中实际运行图来训练模型。
3.1 构建阶段
首先,我们需要导入tensorflow库。 然后,我们必须指定输入和输出的数量,并设置每层中隐藏的神经元的数量:
接下来,我们可以使用占位符节点来表示训练数据和目标。 X的形状只是部分定义的。 我们知道它将是一个二维张量(即矩阵),沿第一维的实例和沿第二维的特征,并且我们知道特征的数量将是28 x 28(每像素一个特征) ,但我们还不知道每个培训批次将包含多少个实例。 所以X的形状是(None,n_inputs)。 同样,我们知道y将是每个实例有一个入口的一维张量,但是在这一点上我们也不知道训练批量的大小,因此形状是(无)。
现在我们来创建实际的神经网络。 占位符X将充当输入层; 在执行阶段,它将一次替换为一个训练批次(请注意,训练批次中的所有实例都将由神经网络同时处理)。 现在你需要创建两个隐藏层和输出层。 这两个隐藏层几乎是相同的:它们的区别仅在于它们所连接的输入以及它们包含的神经元的数量。 输出层也非常相似,但它使用softmax激活功能而不是ReLU激活功能。 因此,让我们创建一个我们将用来一次创建一个图层的neuron_layer()函数。 它将需要参数来指定输入,神经元的数量,激活函数和图层的名称:
让我们一行一行地看看这段代码:
1.首先我们使用图层的名称创建一个名称范围:它将包含该神经元图层的所有计算节点。 这是可选的,但如果TensorBoard中的节点组织良好,该图形在TensorBoard中看起来会更好。
2.接下来,我们通过查询输入矩阵的形状并获得第二维的大小(第一维是实例)来获得输入的数量。
3.接下来的三行创建一个W变量,它将保存权重矩阵。 它将是一个二维张量,其中包含每个输入和每个神经元之间的所有连接权重; 因此,它的形状将是(n_inputs,n_neurons)。 它将被随机初始化,使用标准偏差为2 / ninputs的截断法线(高斯)分布。 使用这个特定的标准偏差有助于算法更快地收敛。 为所有隐藏层随机初始化连接权重非常重要,以避免梯度下降算法无法打破的任何对称性。
4.下一行为偏差创建一个b变量,初始化为0(在这种情况下不存在对称性问题),每个神经元具有一个偏置参数。
5.然后我们创建一个子图来计算z = X·W + b。 这种向量化的实现将有效地计算输入的加权和加上层中每个神经元的偏置项,对于批处理中的所有实例,只需一次。
6.最后,如果激活参数设置为“relu”,则代码返回relu(z)(即,max(0,z)),否则它只返回z。
好的,现在你有一个很好的功能来创建一个神经元层。 让我们用它来创建深度神经网络! 第一个隐藏层将X作为输入。 第二个将第一个隐藏层的输出作为输入。 最后,输出层将第二个隐藏层的输出作为输入。
请注意,为了清晰起见,我们再次使用名称范围。 还要注意,在通过softmax激活函数之前,logits是神经网络的输出:出于优化原因,我们稍后将处理softmax计算。
正如你所期望的那样,TensorFlow具有许多方便的功能来创建标准的神经网络图层,所以通常不需要像我们刚才那样定义自己的神经元层()函数。 例如,TensorFlow的dense()函数创建一个完全连接的层,其中所有输入连接到该层中的所有神经元。 只需导入该函数并用以下代码替换dnn构造部分:
现在我们已经准备好了神经网络模型,我们需要定义我们将用来训练它的代价函数。我们将使用交叉熵,交叉熵会惩罚估计目标类别的概率较低的模型。 TensorFlow提供了几个函数来计算交叉熵。 我们将使用sparse_softmax_cross_entropy_with_logits()。 这将给我们一个包含每个实例的交叉熵的一维张量。然后,我们可以使用TensorFlow的reduce_mean()函数来计算所有实例的平均交叉熵。
我们有神经网络模型,我们有代价函数,现在我们需要定义一个GradientDescentOptimizer来调整模型参数以最小化代价函数:
构造阶段的最后一个重要步骤是指定如何评估模型。我们可以使用in_top_k()函数。 这将返回一个布尔值为1D的张量,所以我们需要将这些布尔值转换为浮点数,然后计算平均值。 这会给我们网络的整体准确性。
我们需要创建一个节点来初始化所有变量,并且我们还将创建一个Saver以将我们训练好的模型参数保存到磁盘:
3.2 构建阶段
这部分更短,更简单。 首先,我们加载MNIST。 我们可以使用ScikitLearn,但TensorFlow提供了自己的帮助程序,它可以提取数据,对数据进行缩放(0到1之间),对其进行混洗,并提供一个简单的函数来一次加载一个小批量。 所以让我们用它来执行:
此代码打开TensorFlow会话,并运行初始化所有变量的init节点。 然后它运行主要的训练循环:在每个时代,代码迭代对应于训练集大小的许多小批量。 每个小批量都通过next_batch()方法获取,然后代码简单地运行训练操作,为其提供当前的最小批量输入数据和目标。 接下来,在每个迭代结束时,代码将在最后一个小批量和完整训练集上评估模型,并打印出结果。 最后,模型参数保存到磁盘。
3.3 使用神经网络
现在神经网络已经过训练,您可以使用它来进行预测。 要做到这一点,你可以重复使用相同的构造阶段,但像这样改变执行阶段:
首先代码从磁盘加载模型参数。 然后它加载一些你想分类的新图像。然后代码评估logits节点。 如果你想知道所有估计的类概率,你需要将softmax()函数应用于logits,但是如果你只是想预测一个类,你可以简单地选择具有最高logit值的类(使用 argmax()函数执行这个技巧)。
四. 本期小结
至此,我们了解了神经网络的由来,并且训练了多层感知机以及DNN神经网络。
(如需更好的了解相关知识,欢迎加入智能算法社区,在“智能算法”公众号发送“社区”,即可加入算法微信群和QQ群)
本文代码回复关键字:tfdnn