Python 深度学习算法实用指南(一)
原文:
zh.annas-archive.org/md5/844a6ce45a119d3197c33a6b5db2d7b1译者:飞龙
前言
深度学习是人工智能领域最受欢迎的领域之一,允许你开发复杂程度各异的多层模型。本书介绍了从基础到高级的流行深度学习算法,并展示了如何使用 TensorFlow 从头开始实现它们。在整本书中,你将深入了解每个算法背后的数学原理及其最佳实现方式。
本书首先解释如何构建自己的神经网络,然后介绍了强大的 Python 机器学习和深度学习库 TensorFlow。接下来,你将快速掌握诸如 NAG、AMSGrad、AdaDelta、Adam、Nadam 等梯度下降变体的工作原理。本书还将为你揭示循环神经网络(RNNs)和长短期记忆网络(LSTM)的工作方式,并教你如何使用 RNN 生成歌词。接着,你将掌握卷积网络和胶囊网络的数学基础,这些网络广泛用于图像识别任务。最后几章将带你了解机器如何通过 CBOW、skip-gram 和 PV-DM 理解单词和文档的语义,以及探索各种 GAN(如 InfoGAN 和 LSGAN)和自编码器(如收缩自编码器、VAE 等)的应用。
本书结束时,你将具备在自己的项目中实现深度学习所需的技能。
本书适合谁
如果你是机器学习工程师、数据科学家、AI 开发者或者任何希望专注于神经网络和深度学习的人,这本书适合你。完全不熟悉深度学习,但具有一定机器学习和 Python 编程经验的人也会发现这本书很有帮助。
本书涵盖的内容
第一章,深度学习介绍,解释了深度学习的基础知识,帮助我们理解人工神经网络及其学习过程。我们还将学习如何从头开始构建我们的第一个人工神经网络。
第二章,TensorFlow 初探,帮助我们了解最强大和流行的深度学习库之一——TensorFlow。你将了解 TensorFlow 的几个重要功能,并学习如何使用 TensorFlow 构建神经网络以执行手写数字分类。
第三章,梯度下降及其变种,深入理解了梯度下降算法。我们将探索几种梯度下降算法的变种,如随机梯度下降(SGD)、Adagrad、ADAM、Adadelta、Nadam 等,并学习如何从头开始实现它们。
第四章,使用 RNN 生成歌词,描述了如何使用 RNN 建模顺序数据集以及它如何记住先前的输入。我们将首先对 RNN 有一个基本的理解,然后深入探讨其数学。接下来,我们将学习如何在 TensorFlow 中实现 RNN 来生成歌词。
第五章,改进 RNN,开始探索 LSTM 以及它如何克服 RNN 的缺点。稍后,我们将了解 GRU 单元以及双向 RNN 和深层 RNN 的工作原理。在本章末尾,我们将学习如何使用 seq2seq 模型进行语言翻译。
第六章,揭秘卷积网络,帮助我们掌握卷积神经网络的工作原理。我们将探索 CNN 前向和反向传播的数学工作方式。我们还将学习各种 CNN 和胶囊网络的架构,并在 TensorFlow 中实现它们。
第七章,学习文本表示,涵盖了称为 word2vec 的最新文本表示学习算法。我们将探索 CBOW 和 skip-gram 等不同类型的 word2vec 模型的数学工作方式。我们还将学习如何使用 TensorBoard 可视化单词嵌入。稍后,我们将了解用于学习句子表示的 doc2vec、skip-thoughts 和 quick-thoughts 模型。
第八章,使用 GAN 生成图像,帮助我们理解最流行的生成算法之一 GAN。我们将学习如何在 TensorFlow 中实现 GAN 来生成图像。我们还将探索不同类型的 GAN,如 LSGAN 和 WGAN。
第九章,深入了解 GAN,揭示了各种有趣的不同类型的 GAN。首先,我们将学习 CGAN,它条件生成器和鉴别器。然后我们看到如何在 TensorFlow 中实现 InfoGAN。接下来,我们将学习如何使用 CycleGAN 将照片转换为绘画作品,以及如何使用 StackGAN 将文本描述转换为照片。
第十章,使用自编码器重构输入,描述了自编码器如何学习重构输入。我们将探索并学习如何在 TensorFlow 中实现不同类型的自编码器,如卷积自编码器、稀疏自编码器、收缩自编码器、变分自编码器等。
第十一章,探索少样本学习算法,描述如何构建模型从少量数据点中学习。我们将了解什么是少样本学习,并探索流行的少样本学习算法,如孪生网络、原型网络、关系网络和匹配网络。
要从本书中获取最大收益
对于那些完全新手于深度学习,但在机器学习和 Python 编程方面有些经验的人,本书将会很有帮助。
下载示例代码文件
您可以从您的帐户在www.packt.com下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packt.com/support并注册,将文件直接发送到您的邮箱。
您可以按照以下步骤下载代码文件:
-
在www.packt.com登录或注册。
-
选择支持选项卡。
-
单击代码下载和勘误。
-
在搜索框中输入书名,然后按照屏幕上的说明操作。
下载文件后,请确保使用最新版本的以下软件解压或提取文件夹:
-
WinRAR/7-Zip for Windows
-
Zipeg/iZip/UnRarX for Mac
-
7-Zip/PeaZip for Linux
本书的代码包也托管在 GitHub 上:github.com/PacktPublishing/Hands-On-Deep-Learning-Algorithms-with-Python。如果代码有更新,将在现有的 GitHub 仓库上更新。
我们还提供来自丰富图书和视频目录的其他代码包,可以在**github.com/PacktPublishing/**上查看!
下载彩色图像
我们还提供一个 PDF 文件,其中包含本书中使用的截屏/图表的彩色图像。您可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781789344158_ColorImages.pdf。
使用的约定
本书使用了许多文本约定。
CodeInText:指示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。例如:"计算J_plus和J_minus。"
代码块设置如下:
J_plus = forward_prop(x, weights_plus)
J_minus = forward_prop(x, weights_minus)
任何命令行的输入或输出如下所示:
tensorboard --logdir=graphs --port=8000
粗体:指示一个新术语、重要词或屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如此。例如:"输入层和输出层之间的任何一层称为隐藏层。"
警告或重要说明如下。
提示和技巧以这种方式出现。
联系我们
我们非常欢迎读者的反馈。
常规反馈:如果您对本书的任何方面有疑问,请在邮件主题中提到书名,并发送邮件至customercare@packtpub.com。
勘误:尽管我们已经尽最大努力确保内容的准确性,但错误还是可能发生。如果您在本书中发现了错误,我们将不胜感激您向我们报告。请访问 www.packt.com/submit-erra…,选择您的书籍,点击“勘误提交表格”链接,并填写详细信息。
盗版:如果您在互联网上发现我们作品的任何非法副本,请提供给我们位置地址或网站名称。请通过 copyright@packt.com 发送包含该材料链接的邮件联系我们。
如果您有兴趣成为作者:如果您在某个专题上拥有专业知识,并且有意撰写或为书籍贡献内容,请访问 authors.packtpub.com。
评论
请留下您的评论。在您阅读并使用了本书之后,为什么不在您购买它的网站上留下一条评论呢?潜在的读者可以通过您的客观意见做出购买决策,我们在 Packt 可以了解您对我们产品的看法,而我们的作者也可以看到您对他们书籍的反馈。谢谢!
欲了解更多有关 Packt 的信息,请访问 packt.com。
第一部分:深度学习入门
在本节中,我们将熟悉深度学习,并理解基本的深度学习概念。我们还将学习一个强大的深度学习框架,称为 TensorFlow,并为我们未来的深度学习任务设置 TensorFlow。
本节包括以下章节:
-
第一章,深度学习简介
-
第二章,了解 TensorFlow
第一章:深度学习简介
深度学习是机器学习的一个子集,灵感来自人类大脑中的神经网络。它已经存在了十年,但现在之所以如此流行,是由于计算能力的提升和大量数据的可用性。有了大量的数据,深度学习算法表现优于经典的机器学习。它已经在计算机视觉、自然语言处理(NLP)、语音识别等多个跨学科科学领域得到广泛应用和变革。
在本章中,我们将学习以下主题:
-
深度学习的基本概念
-
生物和人工神经元
-
人工神经网络及其层次
-
激活函数
-
人工神经网络中的前向和后向传播
-
梯度检查算法
-
从头开始构建人工神经网络
什么是深度学习?
深度学习其实只是具有多层的人工神经网络的现代称谓。那么深度学习中的“深”指的是什么?基本上是由于人工神经网络(ANN)的结构。ANN 由若干层组成,用于执行任何计算。我们可以建立一个具有数百甚至数千层深度的网络。由于计算能力的提升,我们可以构建一个具有深层次的网络。由于 ANN 使用深层进行学习,我们称之为深度学习;当 ANN 使用深层进行学习时,我们称之为深度网络。我们已经了解到,深度学习是机器学习的一个子集。深度学习与机器学习有何不同?什么使深度学习如此特别和流行?
机器学习的成功取决于正确的特征集。特征工程在机器学习中起着至关重要的作用。如果我们手工制作了一组合适的特征来预测某一结果,那么机器学习算法可以表现良好,但是找到和设计合适的特征集并非易事。
在深度学习中,我们无需手工制作这些特征。由于深层次的人工神经网络采用了多层,它可以自动学习复杂的内在特征和多层次的抽象数据表示。让我们通过一个类比来探讨这个问题。
假设我们想执行一个图像分类任务。比如说,我们要学习识别一张图像是否包含狗。使用机器学习时,我们需要手工设计能帮助模型理解图像是否包含狗的特征。我们将这些手工设计的特征作为输入发送给机器学习算法,然后它们学习特征与标签(狗)之间的映射关系。但是从图像中提取特征是一项繁琐的任务。使用深度学习,我们只需将一堆图像输入到深度神经网络中,它将自动充当特征提取器,通过学习正确的特征集。正如我们所学到的,人工神经网络使用多个层次;在第一层中,它将学习表征狗的基本特征,例如狗的体态,而在后续层次中,它将学习复杂的特征。一旦学习到正确的特征集,它将查找图像中是否存在这些特征。如果这些特征存在,则说明给定的图像包含狗。因此,与机器学习不同的是,使用深度学习时,我们不必手动设计特征,而是网络自己学习任务所需的正确特征集。
由于深度学习的这一有趣特性,在提取特征困难的非结构化数据集中,例如语音识别、文本分类等领域中,它被广泛应用。当我们拥有大量的大型数据集时,深度学习算法擅长提取特征并将这些特征映射到它们的标签上。话虽如此,深度学习并不仅仅是将一堆数据点输入到深度网络中并获得结果。这也并非那么简单。我们将会探索的下一节内容中会讨论到,我们会有许多超参数作为调节旋钮,以获得更好的结果。
尽管深度学习比传统的机器学习模型表现更好,但不建议在较小的数据集上使用 DL。当数据点不足或数据非常简单时,深度学习算法很容易对训练数据集过拟合,并且在未见过的数据集上泛化能力不佳。因此,我们应仅在有大量数据点时应用深度学习。
深度学习的应用是多种多样且几乎无处不在的。一些有趣的应用包括自动生成图像标题,为无声电影添加声音,将黑白图像转换为彩色图像,生成文本等等。谷歌的语言翻译、Netflix、亚马逊和 Spotify 的推荐引擎以及自动驾驶汽车都是由深度学习驱动的应用程序。毫无疑问,深度学习是一项颠覆性技术,在过去几年取得了巨大的技术进步。
在本书中,我们将通过从头开始构建一些有趣的深度学习应用来学习基本的深度学习算法,这些应用包括图像识别、生成歌词、预测比特币价格、生成逼真的人工图像、将照片转换为绘画等等。已经兴奋了吗?让我们开始吧!
生物和人工神经元
在继续之前,首先我们将探讨什么是神经元以及我们的大脑中的神经元实际工作原理,然后我们将了解人工神经元。
神经元可以被定义为人类大脑的基本计算单位。神经元是我们大脑和神经系统的基本单元。我们的大脑大约有 1000 亿个神经元。每一个神经元通过一个称为突触的结构相互连接,突触负责从外部环境接收输入,从感觉器官接收运动指令到我们的肌肉,以及执行其他活动。
神经元还可以通过称为树突的分支结构从其他神经元接收输入。这些输入根据它们的重要性加强或减弱,然后在称为细胞体的细胞体内汇总在一起。从细胞体出发,这些汇总的输入被处理并通过轴突发送到其他神经元。
单个生物神经元的基本结构如下图所示:
现在,让我们看看人工神经元是如何工作的。假设我们有三个输入![], ![], 和 ![], 来预测输出![]。这些输入分别乘以权重![], ![], 和 ![],并按以下方式求和:
但是为什么我们要用权重乘以这些输入呢?因为在计算输出时,并不是所有的输入都同等重要!。假设
在计算输出时比其他两个输入更重要。那么,我们给![]赋予比其他两个权重更高的值。因此,在将权重与输入相乘后,![]将比其他两个输入具有更高的值。简单来说,权重用于加强输入。在用权重乘以输入之后,我们将它们加在一起,再加上一个叫做偏置的值,
:
如果你仔细观察前面的方程式,它看起来可能很熟悉?![] 看起来像线性回归的方程式吗?难道它不就是一条直线的方程式吗?我们知道直线的方程式如下所示:
这里,m 是权重(系数),x 是输入,b 是偏置(截距)。
好吧,是的。那么,神经元和线性回归之间有什么区别呢?在神经元中,我们通过应用称为激活或传递函数的函数 ,引入非线性到结果
。因此,我们的输出变为:
下图显示了单个人工神经元:
因此,一个神经元接收输入 x,将其乘以权重 w,加上偏置 b,形成 ,然后我们在
上应用激活函数,并得到输出
。
ANN 及其层
尽管神经元非常强大,但我们不能仅使用一个神经元执行复杂任务。这就是为什么我们的大脑有数十亿个神经元,排列成层,形成一个网络的原因。同样,人工神经元也被排列成层。每一层都将以信息从一层传递到另一层的方式连接起来。
典型的人工神经网络由以下层组成:
-
输入层
-
隐藏层
-
输出层
每一层都有一组神经元,而且一层中的神经元会与其他层中的所有神经元进行交互。然而,同一层中的神经元不会相互作用。这是因为相邻层的神经元之间有连接或边缘;然而,同一层中的神经元之间没有任何连接。我们使用术语节点或单元来表示人工神经网络中的神经元。
下图显示了典型的人工神经网络(ANN):
输入层
输入层是我们向网络提供输入的地方。输入层中的神经元数量等于我们向网络提供的输入数量。每个输入都会对预测输出产生一定影响。然而,输入层不进行任何计算;它只是用于将信息从外部世界传递到网络中。
隐藏层
输入层和输出层之间的任何层称为隐藏层。它处理从输入层接收到的输入。隐藏层负责推导输入和输出之间的复杂关系。也就是说,隐藏层识别数据集中的模式。它主要负责学习数据表示并提取特征。
隐藏层可以有任意数量;然而,我们根据使用情况选择隐藏层的数量。对于非常简单的问题,我们可以只使用一个隐藏层,但在执行像图像识别这样的复杂任务时,我们使用许多隐藏层,每个隐藏层负责提取重要特征。当我们有许多隐藏层时,网络被称为深度神经网络。
输出层
在处理输入后,隐藏层将其结果发送到输出层。顾名思义,输出层发出输出。输出层的神经元数量基于我们希望网络解决的问题类型。
如果是二元分类,则输出层的神经元数量为 1,告诉我们输入属于哪个类别。如果是多类分类,比如五类,并且我们想要得到每个类别的概率作为输出,则输出层的神经元数量为五个,每个神经元输出概率。如果是回归问题,则输出层有一个神经元。
探索激活函数
激活函数,也称为传递函数,在神经网络中起着至关重要的作用。它用于在神经网络中引入非线性。正如我们之前学到的,我们将激活函数应用于输入,该输入与权重相乘并加上偏置,即,,其中z = (输入 * 权重) + 偏置,
是激活函数。如果不应用激活函数,则神经元简单地类似于线性回归。激活函数的目的是引入非线性变换,以学习数据中的复杂潜在模式。
现在让我们看看一些有趣的常用激活函数。
Sigmoid 函数
Sigmoid 函数是最常用的激活函数之一。它将值缩放到 0 到 1 之间。Sigmoid 函数可以定义如下:
这是一个如下所示的 S 形曲线:
它是可微的,意味着我们可以找到任意两点处曲线的斜率。它是单调的,这意味着它要么完全非递减,要么非递增。Sigmoid 函数也称为logistic函数。由于我们知道概率介于 0 和 1 之间,由于 Sigmoid 函数将值压缩在 0 和 1 之间,因此用于预测输出的概率。
Python 中可以定义 sigmoid 函数如下:
def sigmoid(x):
return 1/ (1+np.exp(-x))
双曲正切函数
**双曲正切(tanh)**函数将值输出在 -1 到 +1 之间,表达如下:
它也类似于 S 形曲线。与 Sigmoid 函数以 0.5 为中心不同,tanh 函数是以 0 为中心的,如下图所示:
与 sigmoid 函数类似,它也是一个可微且单调的函数。tanh 函数的实现如下:
def tanh(x):
numerator = 1-np.exp(-2*x)
denominator = 1+np.exp(-2*x)
return numerator/denominator
矫正线性单元函数
矫正线性单元(ReLU)函数是最常用的激活函数之一。它输出从零到无穷大的值。它基本上是一个分段函数,可以表达如下:
即当 x 的值小于零时, 返回零,当 x 的值大于或等于零时,
返回 x。也可以表达如下:
ReLU 函数如下图所示:
如前图所示,当我们将任何负输入传入 ReLU 函数时,它将其转换为零。所有负值为零的这种情况被称为dying ReLU问题,如果神经元总是输出零,则称其为死神经元。ReLU 函数的实现如下:
def ReLU(x):
if x<0:
return 0
else:
return x
泄漏 ReLU 函数
泄漏 ReLU是 ReLU 函数的一个变种,可以解决 dying ReLU 问题。它不是将每个负输入转换为零,而是对负值具有小的斜率,如下所示:
泄漏 ReLU 可以表达如下:
的值通常设置为 0.01。泄漏 ReLU 函数的实现如下:
def leakyReLU(x,alpha=0.01):
if x<0:
return (alpha*x)
else:
return x
不要将一些默认值设置为 ,我们可以将它们作为神经网络的参数发送,并使网络学习
的最优值。这样的激活函数可以称为Parametric ReLU函数。我们也可以将
的值设置为某个随机值,这被称为Randomized ReLU函数。
指数线性单元函数
指数线性单元 (ELU),类似于 Leaky ReLU,在负值时具有小的斜率。但是它不是直线,而是呈现对数曲线,如下图所示:
可以表达如下:
ELU 函数在 Python 中的实现如下:
def ELU(x,alpha=0.01):
if x<0:
return ((alpha*(np.exp(x)-1))
else:
return x
Swish 函数
Swish 函数是 Google 最近引入的激活函数。与其他激活函数不同,Swish 是非单调函数,即它既不总是非递减也不总是非增加。它比 ReLU 提供更好的性能。它简单且可以表达如下:
这里, 是 sigmoid 函数。Swish 函数如下图所示:
我们还可以重新参数化 Swish 函数,并将其表达如下:
当 的值为 0 时,我们得到恒等函数
。
它变成了一个线性函数,当 的值趋于无穷大时,
变成了
,这基本上是 ReLU 函数乘以某个常数值。因此,
的值在线性和非线性函数之间起到了很好的插值作用。Swish 函数的实现如下所示:
def swish(x,beta):
return 2*x*sigmoid(beta*x)
Softmax 函数
Softmax 函数 基本上是 sigmoid 函数的一般化。它通常应用于网络的最后一层,并在执行多类别分类任务时使用。它给出每个类别的概率作为输出,因此 softmax 值的总和始终等于 1。
可以表示如下:
如下图所示,softmax 函数将它们的输入转换为概率:
可以在 Python 中如下实现 softmax 函数:
def softmax(x):
return np.exp(x) / np.exp(x).sum(axis=0)
人工神经网络中的前向传播
在本节中,我们将看到神经网络学习的过程,其中神经元堆叠在层中。网络中的层数等于隐藏层数加上输出层数。在计算网络层数时,我们不考虑输入层。考虑一个具有一个输入层 、一个隐藏层
和一个输出层
的两层神经网络,如下图所示:
假设我们有两个输入, 和
,我们需要预测输出,
。由于有两个输入,输入层中的神经元数为两个。我们设置隐藏层中的神经元数为四个,输出层中的神经元数为一个。现在,输入将与权重相乘,然后加上偏置,并将结果传播到隐藏层,在那里将应用激活函数。
在此之前,我们需要初始化权重矩阵。在现实世界中,我们不知道哪些输入比其他输入更重要,因此我们会对它们进行加权并计算输出。因此,我们将随机初始化权重和偏置值。输入层到隐藏层之间的权重和偏置值分别由 和
表示。那么权重矩阵的维度是多少呢?权重矩阵的维度必须是 当前层中的神经元数 x 下一层中的神经元数。为什么呢?
因为这是基本的矩阵乘法规则。要将任意两个矩阵 AB 相乘,矩阵 A 的列数必须等于矩阵 B 的行数。因此,权重矩阵 的维度应为 输入层中的神经元数 x 隐藏层中的神经元数,即 2 x 4:
上述方程表示,。现在,这将传递到隐藏层。在隐藏层中,我们对
应用激活函数。让我们使用 sigmoid
激活函数。然后,我们可以写成:
应用激活函数后,我们再次将结果 乘以一个新的权重矩阵,并添加一个在隐藏层和输出层之间流动的新偏置值。我们可以将这个权重矩阵和偏置表示为
和
,分别。权重矩阵
的维度将是 隐藏层神经元的数量 x 输出层神经元的数量。由于我们隐藏层有四个神经元,输出层有一个神经元,因此
矩阵的维度将是 4 x 1。所以,我们将
乘以权重矩阵,
,并添加偏置,
,然后将结果
传递到下一层,即输出层:
现在,在输出层,我们对 应用 sigmoid 函数,这将产生一个输出值:
从输入层到输出层的整个过程被称为 前向传播。因此,为了预测输出值,输入被从输入层传播到输出层。在这个传播过程中,它们在每一层上都乘以各自的权重,并在其上应用一个激活函数。完整的前向传播步骤如下所示:
前向传播的步骤可以在 Python 中实现如下:
def forward_prop(X):
z1 = np.dot(X,Wxh) + bh
a1 = sigmoid(z1)
z2 = np.dot(a1,Why) + by
y_hat = sigmoid(z2)
return y_hat
前向传播很酷,不是吗?但是我们如何知道神经网络生成的输出是否正确?我们定义一个称为 代价函数 () 或者 损失函数 (
) 的新函数,这个函数告诉我们神经网络的表现如何。有许多不同的代价函数。我们将使用均方误差作为一个代价函数,它可以定义为实际输出和预测输出之间平方差的均值:
这里, 是训练样本的数量,
是实际输出,
是预测输出。
好的,所以我们学到了成本函数用于评估我们的神经网络;也就是说,它告诉我们我们的神经网络在预测输出方面表现如何。但问题是我们的网络实际上是在哪里学习的?在前向传播中,网络只是尝试预测输出。但是它如何学习预测正确的输出呢?在接下来的部分,我们将探讨这个问题。
ANN 如何学习?
如果成本或损失非常高,那么意味着我们的网络没有预测出正确的输出。因此,我们的目标是最小化成本函数,这样我们神经网络的预测就会更好。我们如何最小化成本函数呢?也就是说,我们如何最小化损失?我们学到神经网络使用前向传播进行预测。因此,如果我们能在前向传播中改变一些值,我们就可以预测出正确的输出并最小化损失。但是在前向传播中可以改变哪些值呢?显然,我们不能改变输入和输出。我们现在只剩下权重和偏置值。请记住,我们只是随机初始化了权重矩阵。由于权重是随机的,它们不会是完美的。现在,我们将更新这些权重矩阵( 和
),使得我们的神经网络能够给出正确的输出。我们如何更新这些权重矩阵呢?这就是一个新技术,称为梯度下降。
通过梯度下降,神经网络学习随机初始化权重矩阵的最优值。有了最优的权重值,我们的网络可以预测正确的输出并最小化损失。
现在,我们将探讨如何使用梯度下降学习最优的权重值。梯度下降是最常用的优化算法之一。它用于最小化成本函数,使我们能够最小化误差并获得可能的最低误差值。但是梯度下降如何找到最优权重呢?让我们用一个类比来开始。
想象我们站在一个山顶上,如下图所示,我们想要到达山上的最低点。可能会有很多区域看起来像山上的最低点,但我们必须到达实际上是所有最低点中最低的点。
即,我们不应该固守在一个点上,认为它是最低点,当全局最低点存在时:
类似地,我们可以将成本函数表示如下。它是成本对权重的绘图。我们的目标是最小化成本函数。也就是说,我们必须到达成本最低的点。下图中的实心黑点显示了随机初始化的权重。如果我们将这一点向下移动,那么我们可以到达成本最低的点:
但是我们如何将这个点(初始权重)向下移动?我们如何下降并达到最低点?梯度用于从一个点移动到另一个点。因此,我们可以通过计算成本函数相对于该点(初始权重)的梯度来移动这个点(初始权重),即 。
梯度是实际上是切线斜率的导数,如下图所示。因此,通过计算梯度,我们向下移动并达到成本最低点。梯度下降是一种一阶优化算法,这意味着在执行更新时只考虑一阶导数:
因此,通过梯度下降,我们将权重移动到成本最小的位置。但是,我们如何更新权重呢?
由于前向传播的结果,我们处于输出层。现在我们将从输出层向输入层进行反向传播,并计算成本函数对输出层和输入层之间所有权重的梯度,以便最小化误差。在计算梯度之后,我们使用权重更新规则来更新旧权重:
这意味着 weights = weights -α * gradients。
什么是 ?它被称为学习率。如下图所示,如果学习率很小,那么我们将小步向下移动,我们的梯度下降可能会很慢。
如果学习率很大,那么我们会迈大步,梯度下降会很快,但我们可能无法达到全局最小值,并在局部最小值处卡住。因此,学习率应该被选择得最优:
从输出层到输入层反向传播网络并使用梯度下降更新网络权重以最小化损失的整个过程称为反向传播。现在我们对反向传播有了基本理解,我们将通过逐步学习详细了解,加深理解。我们将看一些有趣的数学内容,所以戴上你们的微积分帽子,跟随这些步骤。
因此,我们有两个权重,一个是,输入到隐藏层的权重,另一个是
,隐藏到输出层的权重。我们需要找到这两个权重的最优值,以减少误差。因此,我们需要计算损失函数
对这些权重的导数。因为我们正在反向传播,即从输出层到输入层,我们的第一个权重将是
。所以,现在我们需要计算
对
的导数。我们如何计算导数?首先,让我们回顾一下我们的损失函数,
:
从前述方程式中我们无法直接计算导数,因为没有
项。因此,我们不是直接计算导数,而是计算偏导数。让我们回顾一下我们的前向传播方程式:
首先,我们将计算对![]的偏导数,然后从![]中计算对![]的偏导数。从![]中,我们可以直接计算我们的导数![]。这基本上是链式法则。因此,对于![]对的导数如下所示:
现在,我们将计算前述方程式中的每一项:
这里,是我们的 sigmoid 激活函数的导数。我们知道 sigmoid 函数是![],因此 sigmoid 函数的导数为![]:
因此,将方程式*(1)*中的所有前述项代入,我们可以写成:
现在我们需要计算对于下一个权重![]的导数![]。
类似地,我们无法直接从 对
进行导数计算,因为我们在
中没有任何
项。所以,我们需要使用链式法则。让我们再次回顾前向传播的步骤:
现在,根据链式法则, 对
的导数如下:
我们已经看到如何计算前述方程式中的第一项;现在,让我们看看如何计算其余的项:
因此,将前述所有项代入方程 (3),我们可以写成:
在计算了 和
的梯度之后,我们将根据权重更新规则更新我们的初始权重:
就是这样!这就是我们如何更新网络的权重并最小化损失。如果你还不理解梯度下降,别担心!在第三章,梯度下降及其变体,我们将更详细地讨论基础知识并学习梯度下降及其多种变体。现在,让我们看看如何在 Python 中实现反向传播算法。
在方程 (2) 和 (4) 中,我们有项 ,所以我们不需要反复计算它们,只需称之为
delta2:
delta2 = np.multiply(-(y-yHat),sigmoidPrime(z2))
现在,我们计算相对于 的梯度。参考方程 (2):
dJ_dWhy = np.dot(a1.T,delta2)
我们计算相对于 的梯度。参考方程 (4):
delta1 = np.dot(delta2,Why.T)*sigmoidPrime(z1)
dJ_dWxh = np.dot(X.T,delta1)
我们将根据我们的权重更新规则方程 (5) 和 (6) 更新权重如下:
Wxh = Wxh - alpha * dJ_dWhy
Why = Why - alpha * dJ_dWxh
后向传播的完整代码如下:
def backword_prop(y_hat, z1, a1, z2):
delta2 = np.multiply(-(y-y_hat),sigmoid_derivative(z2))
dJ_dWhy = np.dot(a1.T, delta2)
delta1 = np.dot(delta2,Why.T)*sigmoid_derivative(z1)
dJ_dWxh = np.dot(X.T, delta1)
Wxh = Wxh - alpha * dJ_dWhy
Why = Why - alpha * dJ_dWxh
return Wxh,Why
在继续之前,让我们熟悉神经网络中一些经常使用的术语:
-
前向传播:前向传播意味着从输入层向输出层进行前向传播。
-
后向传播:后向传播意味着从输出层向输入层进行反向传播。
-
轮次:轮次指定了神经网络看到我们整个训练数据的次数。因此,我们可以说一轮次等于所有训练样本的一次前向传播和一次反向传播。
-
批量大小:批量大小指定了在一次前向传播和一次反向传播中使用的训练样本数。
-
迭代次数:迭代次数意味着传递的次数,其中一次传递 = 一次前向传播 + 一次反向传播。
假设我们有 12,000 个训练样本,并且我们的批量大小为 6,000。我们将需要两次迭代才能完成一个轮次。也就是说,在第一次迭代中,我们传递前 6,000 个样本,并执行一次前向传播和一次反向传播;在第二次迭代中,我们传递接下来的 6,000 个样本,并执行一次前向传播和一次反向传播。两次迭代后,我们的神经网络将看到全部 12,000 个训练样本,这构成了一个轮次。
使用梯度检查调试梯度下降
我们刚刚学习了梯度下降的工作原理,以及如何为简单的两层网络从头开始编写梯度下降算法。但是,实现复杂神经网络的梯度下降并不是一件简单的任务。除了实现之外,调试复杂神经网络架构的梯度下降又是一项繁琐的任务。令人惊讶的是,即使存在一些有缺陷的梯度下降实现,网络也会学到一些东西。然而,显然,与无缺陷实现的梯度下降相比,它的表现不会很好。
如果模型没有给出任何错误并且即使在梯度下降算法的有缺陷实现下也能学到东西,那么我们如何评估和确保我们的实现是正确的呢?这就是我们使用梯度检查算法的原因。它将通过数值检查导数来验证我们的梯度下降实现是否正确。
梯度检查主要用于调试梯度下降算法,并验证我们是否有正确的实现。
好了。那么,梯度检查是如何工作的呢?在梯度检查中,我们基本上比较数值梯度和解析梯度。等等!什么是数值梯度和解析梯度?
解析梯度意味着我们通过反向传播计算的梯度。数值梯度是梯度的数值近似。让我们通过一个例子来探讨这个问题。假设我们有一个简单的平方函数,。
上述函数的解析梯度使用幂次法则计算如下:
现在,让我们看看如何数值近似梯度。我们不使用幂次法则来计算梯度,而是使用梯度的定义来计算梯度。我们知道,函数的梯度或斜率基本上给出了函数的陡峭程度。
因此,函数的梯度或斜率定义如下:
函数的梯度可以表示如下:
我们使用上述方程并在数值上近似计算梯度。这意味着我们手动计算函数的斜率,而不是像以下图表中显示的幂次法则一样:
通过幂次法则 (7) 计算梯度,并在 Python 中近似计算梯度 (8) 本质上给出了相同的值。让我们看看如何在 Python 中 (7) 和 (8) 给出相同的值。
定义平方函数:
def f(x):
return x**2
定义 epsilon 和输入值:
epsilon = 1e-2
x=3
计算解析梯度:
analytical_gradient = 2*x
print analytical_gradient
6
计算数值梯度:
numerical_gradient = (f(x+epsilon) - f(x-epsilon)) / (2*epsilon)
print numerical_gradient
6.000000000012662
正如您可能已经注意到的那样,计算平方函数的数值和解析梯度本质上给出了相同的值,即当 x =3 时为 6。
在反向传播网络时,我们计算解析梯度以最小化成本函数。现在,我们需要确保我们计算的解析梯度是正确的。因此,让我们验证我们近似计算成本函数数值梯度。
相对于 ,
的梯度可以按如下数值近似:
它的表示如下:
我们检查解析梯度和近似数值梯度是否相同;如果不是,则我们的解析梯度计算存在错误。我们不想检查数值和解析梯度是否完全相同;因为我们只是近似计算数值梯度,我们检查解析梯度和数值梯度之间的差异作为错误。如果差异小于或等于一个非常小的数字,比如 1e-7,那么我们的实现是正确的。如果差异大于 1e-7,那么我们的实现是错误的。
而不是直接计算数值梯度和解析梯度之间的差异作为错误,我们计算相对误差。它可以定义为差异的比率与梯度绝对值的比率:
当相对误差的值小于或等于一个小的阈值值,比如 1e-7,那么我们的实现是正确的。如果相对误差大于 1e-7,那么我们的实现是错误的。现在让我们逐步看看如何在 Python 中实现梯度检查算法。
首先,我们计算权重。参考方程 (9):
weights_plus = weights + epsilon
weights_minus = weights - epsilon
计算 J_plus 和 J_minus。参考方程 (9):
J_plus = forward_prop(x, weights_plus)
J_minus = forward_prop(x, weights_minus)
现在,我们可以按 (9) 给出的方式计算数值梯度如下:
numerical_grad = (J_plus - J_minus) / (2 * epsilon)
可以通过反向传播获得解析梯度:
analytical_grad = backword_prop(x, weights)
计算相对误差,如方程 (10) 所示:
numerator = np.linalg.norm(analytical_grad - numerical_grad)
denominator = np.linalg.norm(analytical_grad) + np.linalg.norm(numerical_grad)
relative_error = numerator / denominator
如果相对误差小于一个小的阈值,比如1e-7,则我们的梯度下降实现是正确的;否则,是错误的:
if relative_error < 1e-7:
print ("The gradient is correct!")
else:
print ("The gradient is wrong!")
因此,借助梯度检查,我们确保我们的梯度下降算法没有错误。
将所有内容整合在一起
将我们迄今为止学到的所有概念综合起来,我们将看到如何从头开始构建一个神经网络。我们将了解神经网络如何学习执行 XOR 门操作。XOR 门只在其输入中恰好有一个为 1 时返回 1,否则返回 0,如下表所示:
从头开始构建神经网络
要执行 XOR 门操作,我们构建了一个简单的两层神经网络,如下图所示。您可以看到,我们有一个具有两个节点的输入层,一个具有五个节点的隐藏层和一个包含一个节点的输出层:
我们将逐步理解神经网络如何学习 XOR 逻辑:
- 首先,导入库:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
- 根据前面的 XOR 表准备数据:
X = np.array([ [0, 1], [1, 0], [1, 1],[0, 0] ])
y = p.array([ [1], [1], [0], [0]])
- 定义每层中的节点数:
num_input = 2
num_hidden = 5
num_output = 1
- 随机初始化权重和偏置。首先,我们初始化输入到隐藏层的权重:
Wxh = np.random.randn(num_input,num_hidden)
bh = np.zeros((1,num_hidden))
- 现在,我们将隐藏层到输出层的权重初始化:
Why = np.random.randn (num_hidden,num_output)
by = np.zeros((1,num_output))
- 定义 sigmoid 激活函数:
def sigmoid(z):
return 1 / (1+np.exp(-z))
- 定义 sigmoid 函数的导数:
def sigmoid_derivative(z):
return np.exp(-z)/((1+np.exp(-z))**2)
- 定义前向传播:
def forward_prop(X,Wxh,Why):
z1 = np.dot(X,Wxh) + bh
a1 = sigmoid(z1)
z2 = np.dot(a1,Why) + by
y_hat = sigmoid(z2)
return z1,a1,z2,y_hat
- 定义反向传播:
def backword_prop(y_hat, z1, a1, z2):
delta2 = np.multiply(-(y-y_hat),sigmoid_derivative(z2))
dJ_dWhy = np.dot(a1.T, delta2)
delta1 = np.dot(delta2,Why.T)*sigmoid_derivative(z1)
dJ_dWxh = np.dot(X.T, delta1)
return dJ_dWxh, dJ_dWhy
- 定义成本函数:
def cost_function(y, y_hat):
J = 0.5*sum((y-y_hat)**2)
return J
- 设置学习率和训练迭代次数:
alpha = 0.01
num_iterations = 5000
- 现在,让我们用以下代码开始训练网络:
cost =[]
for i in range(num_iterations):
z1,a1,z2,y_hat = forward_prop(X,Wxh,Why)
dJ_dWxh, dJ_dWhy = backword_prop(y_hat, z1, a1, z2)
#update weights
Wxh = Wxh -alpha * dJ_dWxh
Why = Why -alpha * dJ_dWhy
#compute cost
c = cost_function(y, y_hat)
cost.append(c)
- 绘制成本函数:
plt.grid()
plt.plot(range(num_iteratins),cost)
plt.title('Cost Function')
plt.xlabel('Training Iterations')
plt.ylabel('Cost')
正如您可以在下面的图中观察到的那样,损失随着训练迭代次数的增加而减少:
因此,在本章中,我们对人工神经网络及其学习方式有了全面的了解。
摘要
我们从理解深度学习是什么及其与机器学习的区别开始本章。后来,我们学习了生物和人工神经元的工作原理,然后探讨了 ANN 中的输入、隐藏和输出层,以及几种激活函数。
接下来,我们学习了前向传播是什么,以及 ANN 如何使用前向传播来预测输出。在此之后,我们学习了 ANN 如何使用反向传播来学习和优化。我们学习了一种称为梯度下降的优化算法,帮助神经网络最小化损失并进行正确预测。我们还学习了梯度检查,一种用于评估梯度下降的技术。在本章的结尾,我们实现了一个从头开始的神经网络来执行 XOR 门操作。
在下一章中,我们将学习一个名为TensorFlow的最强大和最广泛使用的深度学习库。
问题
让我们通过回答以下问题来评估我们新获得的知识:
-
深度学习与机器学习有何不同?
-
deep 在深度学习中是什么意思?
-
我们为什么使用激活函数?
-
解释 dying ReLU 问题。
-
定义前向传播。
-
什么是反向传播?
-
解释梯度检查。
进一步阅读
您还可以查看以下一些资源以获取更多信息:
-
从这个精彩的视频了解更多关于梯度下降的知识:
www.youtube.com/watch?v=IHZwWFHWa-w -
学习如何从头开始实现一个神经网络来识别手写数字:
github.com/sar-gupta/neural-network-from-scratch
第二章:认识 TensorFlow
在本章中,我们将学习 TensorFlow,这是最流行的深度学习库之一。在本书中,我们将使用 TensorFlow 从头开始构建深度学习模型。因此,在本章中,我们将了解 TensorFlow 及其功能。我们还将学习 TensorFlow 提供的用于模型可视化的工具 TensorBoard。接下来,我们将学习如何使用 TensorFlow 执行手写数字分类,以构建我们的第一个神经网络。随后,我们将了解 TensorFlow 2.0,这是 TensorFlow 的最新版本。我们将学习 TensorFlow 2.0 与其早期版本的区别,以及它如何使用 Keras 作为其高级 API。
在本章中,我们将涵盖以下主题:
-
TensorFlow
-
计算图和会话
-
变量、常量和占位符
-
TensorBoard
-
TensorFlow 中的手写数字分类
-
TensorFlow 中的数学运算
-
TensorFlow 2.0 和 Keras
TensorFlow 是什么?
TensorFlow 是来自 Google 的开源软件库,广泛用于数值计算。它是构建深度学习模型的最流行的库之一。它高度可扩展,可以运行在多个平台上,如 Windows、Linux、macOS 和 Android。最初由 Google Brain 团队的研究人员和工程师开发。
TensorFlow 支持在包括 CPU、GPU 和 TPU(张量处理单元)在内的所有设备上执行,还支持移动和嵌入式平台。由于其灵活的架构和易于部署,它已成为许多研究人员和科学家构建深度学习模型的流行选择。
在 TensorFlow 中,每个计算都由数据流图表示,也称为计算图,其中节点表示操作,如加法或乘法,边表示张量。数据流图也可以在许多不同的平台上共享和执行。TensorFlow 提供了一种称为 TensorBoard 的可视化工具,用于可视化数据流图。
张量只是一个多维数组。因此,当我们说 TensorFlow 时,它实际上是在计算图中流动的多维数组(张量)。
你可以通过在终端中输入以下命令轻松地通过pip安装 TensorFlow。我们将安装 TensorFlow 1.13.1:
pip install tensorflow==1.13.1
我们可以通过运行以下简单的Hello TensorFlow!程序来检查 TensorFlow 的成功安装:
import tensorflow as tf
hello = tf.constant("Hello TensorFlow!")
sess = tf.Session()
print(sess.run(hello))
前面的程序应该打印出Hello TensorFlow!。如果出现任何错误,那么您可能没有正确安装 TensorFlow。
理解计算图和会话
正如我们所学到的,TensorFlow 中的每个计算都由计算图表示。它们由多个节点和边缘组成,其中节点是数学操作,如加法和乘法,边缘是张量。计算图非常有效地优化资源并促进分布式计算。
计算图由几个 TensorFlow 操作组成,排列成节点图。
让我们考虑一个基本的加法操作:
import tensorflow as tf
x = 2
y = 3
z = tf.add(x, y, name='Add')
上述代码的计算图将如下所示:
当我们在构建一个非常复杂的神经网络时,计算图帮助我们理解网络架构。例如,让我们考虑一个简单的层,。其计算图将表示如下:
计算图中有两种依赖类型,称为直接和间接依赖。假设我们有b节点,其输入依赖于a节点的输出;这种依赖称为直接依赖,如下所示的代码:
a = tf.multiply(8,5)
b = tf.multiply(a,1)
当b节点的输入不依赖于a节点时,这被称为间接依赖,如下所示的代码:
a = tf.multiply(8,5)
b = tf.multiply(4,3)
因此,如果我们能理解这些依赖关系,我们就能在可用资源中分配独立的计算,并减少计算时间。每当我们导入 TensorFlow 时,会自动创建一个默认图,并且我们创建的所有节点都与默认图相关联。我们还可以创建自己的图而不是使用默认图,当在一个文件中构建多个不相互依赖的模型时,这非常有用。可以使用tf.Graph()创建 TensorFlow 图,如下所示:
graph = tf.Graph()
with graph.as_default():
z = tf.add(x, y, name='Add')
如果我们想要清除默认图(即清除先前定义的变量和操作),可以使用tf.reset_default_graph()。
会话
将创建一个包含节点和边缘张量的计算图,为了执行该图,我们使用 TensorFlow 会话。
可以使用tf.Session()创建 TensorFlow 会话,如下所示的代码,它将分配内存以存储变量的当前值:
sess = tf.Session()
创建会话后,我们可以使用sess.run()方法执行我们的图。
TensorFlow 中的每个计算都由计算图表示,因此我们需要为所有事物运行计算图。也就是说,为了在 TensorFlow 上计算任何内容,我们需要创建一个 TensorFlow 会话。
让我们执行以下代码来执行两个数字的乘法:
a = tf.multiply(3,3)
print(a)
而不是打印9,上述代码将打印一个 TensorFlow 对象,Tensor("Mul:0", shape=(), dtype=int32)。
正如我们之前讨论的,每当导入 TensorFlow 时,将自动创建一个默认计算图,并且所有节点都将附加到该图中。因此,当我们打印 a 时,它只返回 TensorFlow 对象,因为尚未计算 a 的值,因为尚未执行计算图。
为了执行图,我们需要初始化并运行 TensorFlow 会话,如下所示:
a = tf.multiply(3,3)
with tf.Session as sess:
print(sess.run(a))
上述代码将打印 9。
变量、常量和占位符
变量、常量和占位符是 TensorFlow 的基本元素。但是,总会有人对这三者之间感到困惑。让我们逐个看看每个元素,并学习它们之间的区别。
变量
变量是用来存储值的容器。变量作为计算图中几个其他操作的输入。可以使用 tf.Variable() 函数创建变量,如下面的代码所示:
x = tf.Variable(13)
让我们使用 tf.Variable() 创建一个名为 W 的变量,如下所示:
W = tf.Variable(tf.random_normal([500, 111], stddev=0.35), name="weights")
如前面的代码所示,我们通过从标准差为 0.35 的正态分布中随机抽取值来创建变量 W。
tf.Variable() 中的参数 name 被称为什么?
它用于在计算图中设置变量的名称。因此,在上述代码中,Python 将变量保存为 W,但在 TensorFlow 图中,它将保存为 weights。
我们还可以使用 initialized_value() 从另一个变量中初始化新变量的值。例如,如果我们想要创建一个名为 weights_2 的新变量,并使用先前定义的 weights 变量的值,可以按以下方式完成:
W2 = tf.Variable(weights.initialized_value(), name="weights_2")
然而,在定义变量之后,我们需要初始化计算图中的所有变量。可以使用 tf.global_variables_initializer() 完成此操作。
一旦我们创建了会话,首先运行初始化操作,这将初始化所有已定义的变量,然后才能运行其他操作,如下所示:
x = tf.Variable(1212)
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
print sess.run(x)
我们还可以使用 tf.get_variable() 创建 TensorFlow 变量。它需要三个重要参数,即 name、shape 和 initializer。
与 tf.Variable() 不同,我们不能直接将值传递给 tf.get_variable();相反,我们使用 initializer。有几种初始化器可用于初始化值。例如,tf.constant_initializer(value) 使用常量值初始化变量,tf.random_normal_initializer(mean, stddev) 使用指定均值和标准差的随机正态分布初始化变量。
使用 tf.Variable() 创建的变量不能共享,每次调用 tf.Variable() 时都会创建一个新变量。但是 tf.get_variable() 会检查计算图中是否存在指定参数的现有变量。如果变量已存在,则将重用它;否则将创建一个新变量:
W3 = tf.get_variable(name = 'weights', shape = [500, 111], initializer = random_normal_initializer()))
因此,上述代码检查是否存在与给定参数匹配的任何变量。如果是,则将重用它;否则,将创建一个新变量。
由于我们使用tf.get_variable()重用变量,为了避免名称冲突,我们使用tf.variable_scope,如下面的代码所示。变量作用域基本上是一种命名技术,在作用域内为变量添加前缀以避免命名冲突:
with tf.variable_scope("scope"):
a = tf.get_variable('x', [2])
with tf.variable_scope("scope", reuse = True):
b = tf.get_variable('x', [2])
如果您打印a.name和b.name,则它将返回相同的名称scope/x:0。正如您所见,我们在名为scope的变量作用域中指定了reuse=True参数,这意味着变量可以被共享。如果我们不设置reuse=True,则会出现错误,提示变量已经存在。
建议使用tf.get_variable()而不是tf.Variable(),因为tf.get_variable允许您共享变量,并且可以使代码重构更容易。
常量
常量与变量不同,不能改变其值。也就是说,常量是不可变的。一旦为它们分配了值,就不能在整个过程中更改它们。我们可以使用tf.constant()创建常量,如下面的代码所示:
x = tf.constant(13)
占位符和 feed 字典
我们可以将占位符视为变量,其中仅定义类型和维度,但不分配值。占位符的值将在运行时通过数据填充到计算图中。占位符是没有值的定义。
可以使用tf.placeholder()来定义占位符。它接受一个可选参数shape,表示数据的维度。如果shape设置为None,则可以在运行时提供任意大小的数据。占位符可以定义如下:
x = tf.placeholder("float", shape=None)
简单来说,我们使用tf.Variable存储数据,使用tf.placeholder来提供外部数据。
让我们通过一个简单的例子来更好地理解占位符:
x = tf.placeholder("float", None)
y = x +3
with tf.Session() as sess:
result = sess.run(y)
print(result)
如果我们运行上述代码,则会返回错误,因为我们试图计算y,其中y = x + 3,而x是一个占位符,其值尚未分配。正如我们所学的,占位符的值将在运行时分配。我们使用feed_dict参数来分配占位符的值。feed_dict参数基本上是一个字典,其中键表示占位符的名称,值表示占位符的值。
如您在下面的代码中所见,我们设置feed_dict = {x:5},这意味着x占位符的值为5:
with tf.Session() as sess:
result = sess.run(y, feed_dict={x: 5})
print(result)
前面的代码返回8.0。
如果我们想为x使用多个值会怎样?由于我们没有为占位符定义任何形状,它可以接受任意数量的值,如下面的代码所示:
with tf.Session() as sess:
result = sess.run(y, feed_dict={x: [3,6,9]})
print(result)
它将返回以下内容:
[ 6\. 9\. 12.]
假设我们将x的形状定义为[None,2],如下面的代码所示:
x = tf.placeholder("float", [None, 2])
这意味着 x 可以取任意行但列数为 2 的矩阵,如以下代码所示:
with tf.Session() as sess:
x_val = [[1, 2,],
[3,4],
[5,6],
[7,8],]
result = sess.run(y, feed_dict={x: x_val})
print(result)
上述代码返回以下内容:
[[ 4\. 5.]
[ 6\. 7.]
[ 8\. 9.]
[10\. 11.]]
介绍 TensorBoard
TensorBoard 是 TensorFlow 的可视化工具,可用于显示计算图。它还可以用来绘制各种定量指标和几个中间计算的结果。当我们训练一个非常深的神经网络时,如果需要调试网络,会变得很困惑。因此,如果我们能在 TensorBoard 中可视化计算图,就能轻松理解这些复杂模型,进行调试和优化。TensorBoard 还支持共享。
如以下截图所示,TensorBoard 面板由几个选项卡组成: SCALARS,IMAGES,AUDIO,GRAPHS,DISTRIBUTIONS,HISTOGRAMS 和 EMBEDDINGS:
选项卡的含义很明显。 SCALARS 选项卡显示有关程序中使用的标量变量的有用信息。例如,它显示标量变量名为损失的值如何随着多次迭代而变化。
GRAPHS 选项卡显示计算图。 DISTRIBUTIONS 和 HISTOGRAMS 选项卡显示变量的分布。例如,我们模型的权重分布和直方图可以在这些选项卡下看到。 EMBEDDINGS 选项卡用于可视化高维向量,如词嵌入(我们将在第七章,学习文本表示中详细学习)。
让我们构建一个基本的计算图,并在 TensorBoard 中进行可视化。假设我们有四个变量,如下所示:
x = tf.constant(1,name='x')
y = tf.constant(1,name='y')
a = tf.constant(3,name='a')
b = tf.constant(3,name='b')
让我们将 x 和 y 以及 a 和 b 相乘,并将它们保存为 prod1 和 prod2,如以下代码所示:
prod1 = tf.multiply(x,y,name='prod1')
prod2 = tf.multiply(a,b,name='prod2')
将 prod1 和 prod2 相加并存储在 sum 中:
sum = tf.add(prod1,prod2,name='sum')
现在,我们可以在 TensorBoard 中可视化所有这些操作。为了在 TensorBoard 中进行可视化,我们首先需要保存我们的事件文件。可以使用 tf.summary.FileWriter() 来完成。它需要两个重要参数,logdir 和 graph。
如其名称所示,logdir 指定我们希望存储图形的目录,graph 指定我们希望存储的图形:
with tf.Session() as sess:
writer = tf.summary.FileWriter(logdir='./graphs',graph=sess.graph)
print(sess.run(sum))
在上述代码中,graphs 是我们存储事件文件的目录,sess.graph 指定了我们 TensorFlow 会话中的当前图。因此,我们正在将 TensorFlow 会话中的当前图存储在 graphs 目录中。
要启动 TensorBoard,请转到您的终端,找到工作目录,并键入以下内容:
tensorboard --logdir=graphs --port=8000
logdir 参数指示事件文件存储的目录,port 是端口号。运行上述命令后,打开浏览器并输入 http://localhost:8000/。
在 TensorBoard 面板中,GRAPHS 选项卡下,您可以看到计算图:
正如您可能注意到的,我们定义的所有操作都清楚地显示在图中。
创建一个命名作用域
作用域用于降低复杂性,并帮助我们通过将相关节点分组在一起来更好地理解模型。使用命名作用域有助于在图中分组相似的操作。当我们构建复杂的架构时,它非常方便。可以使用 tf.name_scope() 创建作用域。在前面的示例中,我们执行了两个操作,Product 和 sum。我们可以简单地将它们分组为两个不同的命名作用域,如 Product 和 sum。
在前面的部分中,我们看到了 prod1 和 prod2 如何执行乘法并计算结果。我们将定义一个名为 Product 的命名作用域,并将 prod1 和 prod2 操作分组,如以下代码所示:
with tf.name_scope("Product"):
with tf.name_scope("prod1"):
prod1 = tf.multiply(x,y,name='prod1')
with tf.name_scope("prod2"):
prod2 = tf.multiply(a,b,name='prod2')
现在,为 sum 定义命名作用域:
with tf.name_scope("sum"):
sum = tf.add(prod1,prod2,name='sum')
将文件存储在 graphs 目录中:
with tf.Session() as sess:
writer = tf.summary.FileWriter('./graphs', sess.graph)
print(sess.run(sum))
在 TensorBoard 中可视化图形:
tensorboard --logdir=graphs --port=8000
正如你可能注意到的,现在我们只有两个节点,sum 和 Product:
一旦我们双击节点,我们就能看到计算是如何进行的。正如你所见,prod1 和 prod2 节点被分组在 Product 作用域下,它们的结果被发送到 sum 节点,在那里它们将被相加。你可以看到 prod1 和 prod2 节点如何计算它们的值:
前面的例子只是一个简单的示例。当我们在处理包含大量操作的复杂项目时,命名作用域帮助我们将相似的操作分组在一起,并且能够更好地理解计算图。
使用 TensorFlow 进行手写数字分类
将我们迄今学到的所有概念整合在一起,我们将看到如何使用 TensorFlow 构建一个神经网络来识别手写数字。如果你最近一直在玩深度学习,那么你一定听说过 MNIST 数据集。它被称为深度学习的hello world。它包含了 55000 个手写数字数据点(0 到 9)。
在这一节中,我们将看到如何使用我们的神经网络来识别这些手写数字,并且我们会掌握 TensorFlow 和 TensorBoard。
导入所需的库
作为第一步,让我们导入所有所需的库:
import warnings
warnings.filterwarnings('ignore')
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
tf.logging.set_verbosity(tf.logging.ERROR)
import matplotlib.pyplot as plt
%matplotlib inline
加载数据集
加载数据集,使用以下代码:
mnist = input_data.read_data_sets("data/mnist", one_hot=True)
在前面的代码中,data/mnist 表示我们存储 MNIST 数据集的位置,而 one_hot=True 表示我们正在对标签(0 到 9)进行 one-hot 编码。
通过执行以下代码,我们将看到我们的数据中包含什么:
print("No of images in training set {}".format(mnist.train.images.shape))
print("No of labels in training set {}".format(mnist.train.labels.shape))
print("No of images in test set {}".format(mnist.test.images.shape))
print("No of labels in test set {}".format(mnist.test.labels.shape))
No of images in training set (55000, 784)
No of labels in training set (55000, 10)
No of images in test set (10000, 784)
No of labels in test set (10000, 10)
我们在训练集中有 55000 张图像,每个图像的大小是 784,我们有 10 个标签,实际上是从 0 到 9。类似地,我们在测试集中有 10000 张图像。
现在,我们将绘制一个输入图像,看看它的样子:
img1 = mnist.train.images[0].reshape(28,28)
plt.imshow(img1, cmap='Greys')
因此,我们的输入图像看起来如下:
定义每个层中神经元的数量
我们将构建一个具有三个隐藏层和一个输出层的四层神经网络。由于输入图像的大小为 784,我们将 num_input 设置为 784,并且由于有 10 个手写数字(0 到 9),我们在输出层设置了 10 个神经元。我们如下定义每一层的神经元数量:
#number of neurons in input layer
num_input = 784
#num of neurons in hidden layer 1
num_hidden1 = 512
#num of neurons in hidden layer 2
num_hidden2 = 256
#num of neurons in hidden layer 3
num_hidden_3 = 128
#num of neurons in output layer
num_output = 10
定义占位符
正如我们所学,我们首先需要为 input 和 output 定义占位符。占位符的值将通过 feed_dict 在运行时传入:
with tf.name_scope('input'):
X = tf.placeholder("float", [None, num_input])
with tf.name_scope('output'):
Y = tf.placeholder("float", [None, num_output])
由于我们有一个四层网络,我们有四个权重和四个偏置。我们通过从标准偏差为 0.1 的截断正态分布中抽取值来初始化我们的权重。记住,权重矩阵的维度应该是前一层神经元的数量 x 当前层神经元的数量。例如,权重矩阵 w3 的维度应该是隐藏层 2 中的神经元数 x 隐藏层 3 中的神经元数。
我们通常将所有权重定义在一个字典中,如下所示:
with tf.name_scope('weights'):
weights = {
'w1': tf.Variable(tf.truncated_normal([num_input, num_hidden1], stddev=0.1),name='weight_1'),
'w2': tf.Variable(tf.truncated_normal([num_hidden1, num_hidden2], stddev=0.1),name='weight_2'),
'w3': tf.Variable(tf.truncated_normal([num_hidden2, num_hidden_3], stddev=0.1),name='weight_3'),
'out': tf.Variable(tf.truncated_normal([num_hidden_3, num_output], stddev=0.1),name='weight_4'),
}
偏置的形状应该是当前层中的神经元数。例如,b2 偏置的维度是隐藏层 2 中的神经元数。我们在所有层中将偏置值设置为常数 0.1:
with tf.name_scope('biases'):
biases = {
'b1': tf.Variable(tf.constant(0.1, shape=[num_hidden1]),name='bias_1'),
'b2': tf.Variable(tf.constant(0.1, shape=[num_hidden2]),name='bias_2'),
'b3': tf.Variable(tf.constant(0.1, shape=[num_hidden_3]),name='bias_3'),
'out': tf.Variable(tf.constant(0.1, shape=[num_output]),name='bias_4')
}
前向传播
现在我们将定义前向传播操作。我们在所有层中使用 ReLU 激活函数。在最后一层中,我们将应用 sigmoid 激活函数,如下所示的代码:
with tf.name_scope('Model'):
with tf.name_scope('layer1'):
layer_1 = tf.nn.relu(tf.add(tf.matmul(X, weights['w1']), biases['b1']) )
with tf.name_scope('layer2'):
layer_2 = tf.nn.relu(tf.add(tf.matmul(layer_1, weights['w2']), biases['b2']))
with tf.name_scope('layer3'):
layer_3 = tf.nn.relu(tf.add(tf.matmul(layer_2, weights['w3']), biases['b3']))
with tf.name_scope('output_layer'):
y_hat = tf.nn.sigmoid(tf.matmul(layer_3, weights['out']) + biases['out'])
计算损失和反向传播
接下来,我们将定义我们的损失函数。我们将使用 softmax 交叉熵作为我们的损失函数。TensorFlow 提供了 tf.nn.softmax_cross_entropy_with_logits() 函数来计算 softmax 交叉熵损失。它接受两个参数作为输入,logits 和 labels:
-
logits参数指定了网络预测的logits;例如,y_hat -
labels参数指定了实际的标签;例如,真实标签Y
我们使用 tf.reduce_mean() 取 loss 函数的均值:
with tf.name_scope('Loss'):
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=y_hat,labels=Y))
现在,我们需要使用反向传播来最小化损失。别担心!我们不必手动计算所有权重的导数。相反,我们可以使用 TensorFlow 的优化器。在本节中,我们使用 Adam 优化器。它是我们在《第一章》深度学习导论中学到的梯度下降优化技术的一个变体。在《第三章》梯度下降及其变体中,我们将深入探讨细节,看看 Adam 优化器和其他几种优化器的工作原理。现在,让我们说我们使用 Adam 优化器作为我们的反向传播算法:
learning_rate = 1e-4
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(loss)
计算精度
我们计算模型的精度如下:
-
参数
y_hat表示我们模型每个类别的预测概率。由于我们有10类别,我们将有10个概率。如果在位置7处的概率很高,则表示我们的网络以高概率预测输入图像为数字7。函数tf.argmax()返回最大值的索引。因此,tf.argmax(y_hat,1)给出概率高的索引位置。因此,如果索引7处的概率很高,则返回7。 -
参数
Y表示实际标签,它们是独热编码的值。也就是说,除了实际图像的位置处为1外,在所有位置处都是0。例如,如果输入图像是7,则Y在索引7处为1,其他位置为0。因此,tf.argmax(Y,1)返回7,因为这是我们有高值1的位置。
因此,tf.argmax(y_hat,1) 给出了预测的数字,而 tf.argmax(Y,1) 给出了实际的数字。
函数 tf.equal(x, y) 接受 x 和 y 作为输入,并返回 (x == y) 的逻辑值。因此,correct_pred = tf.equal(predicted_digit,actual_digit) 包含了当实际和预测的数字相同时为 True,不同时为 False。我们使用 TensorFlow 的 cast 操作将 correct_pred 中的布尔值转换为浮点值,即 tf.cast(correct_pred, tf.float32)。将它们转换为浮点值后,我们使用 tf.reduce_mean() 取平均值。
因此,tf.reduce_mean(tf.cast(correct_pred, tf.float32)) 给出了平均正确预测值:
with tf.name_scope('Accuracy'):
predicted_digit = tf.argmax(y_hat, 1)
actual_digit = tf.argmax(Y, 1)
correct_pred = tf.equal(predicted_digit,actual_digit)
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
创建摘要
我们还可以可视化模型在多次迭代过程中损失和准确率的变化。因此,我们使用 tf.summary() 来获取变量的摘要。由于损失和准确率是标量变量,我们使用 tf.summary.scalar(),如下所示:
tf.summary.scalar("Accuracy", accuracy)
tf.summary.scalar("Loss", loss)
接下来,我们合并图中使用的所有摘要,使用 tf.summary.merge_all()。我们这样做是因为当我们有许多摘要时,运行和存储它们会变得低效,所以我们在会话中一次性运行它们,而不是多次运行:
merge_summary = tf.summary.merge_all()
训练模型
现在,是时候训练我们的模型了。正如我们所学的,首先,我们需要初始化所有变量:
init = tf.global_variables_initializer()
定义批大小、迭代次数和学习率,如下所示:
learning_rate = 1e-4
num_iterations = 1000
batch_size = 128
启动 TensorFlow 会话:
with tf.Session() as sess:
初始化所有变量:
sess.run(init)
保存事件文件:
summary_writer = tf.summary.FileWriter('./graphs', graph=sess.graph)
训练模型一定数量的迭代次数:
for i in range(num_iterations):
根据批大小获取一批数据:
batch_x, batch_y = mnist.train.next_batch(batch_size)
训练网络:
sess.run(optimizer, feed_dict={ X: batch_x, Y: batch_y})
每 100 次迭代打印一次 loss 和 accuracy:
if i % 100 == 0:
batch_loss, batch_accuracy,summary = sess.run(
[loss, accuracy, merge_summary], feed_dict={X: batch_x, Y: batch_y}
)
#store all the summaries
summary_writer.add_summary(summary, i)
print('Iteration: {}, Loss: {}, Accuracy: {}'.format(i,batch_loss,batch_accuracy))
如您从以下输出中注意到的那样,损失减少,准确率在各种训练迭代中增加:
Iteration: 0, Loss: 2.30789709091, Accuracy: 0.1171875
Iteration: 100, Loss: 1.76062202454, Accuracy: 0.859375
Iteration: 200, Loss: 1.60075569153, Accuracy: 0.9375
Iteration: 300, Loss: 1.60388696194, Accuracy: 0.890625
Iteration: 400, Loss: 1.59523034096, Accuracy: 0.921875
Iteration: 500, Loss: 1.58489584923, Accuracy: 0.859375
Iteration: 600, Loss: 1.51407408714, Accuracy: 0.953125
Iteration: 700, Loss: 1.53311181068, Accuracy: 0.9296875
Iteration: 800, Loss: 1.57677125931, Accuracy: 0.875
Iteration: 900, Loss: 1.52060437202, Accuracy: 0.9453125
在 TensorBoard 中可视化图表
训练后,我们可以在 TensorBoard 中可视化我们的计算图,如下图所示。正如您所见,我们的模型接受输入、权重和偏差作为输入,并返回输出。我们根据模型的输出计算损失和准确性。通过计算梯度和更新权重来最小化损失。我们可以在下图中观察到所有这些:
如果我们双击并展开模型,我们可以看到我们有三个隐藏层和一个输出层:
同样地,我们可以双击并查看每个节点。例如,如果我们打开权重,我们可以看到四个权重如何使用截断正态分布进行初始化,并且如何使用 Adam 优化器进行更新:
正如我们所学到的,计算图帮助我们理解每个节点发生的情况。我们可以通过双击准确性节点来查看如何计算准确性:
记住,我们还存储了loss和accuracy变量的摘要。我们可以在 TensorBoard 的 SCALARS 选项卡下找到它们,如下面的截图所示。我们可以看到损失如何随迭代而减少,如下图所示:
下面的截图显示准确性随迭代次数增加的情况:
引入急切执行
TensorFlow 中的急切执行更符合 Python 风格,允许快速原型设计。与图模式不同,在图模式中,我们每次执行操作都需要构建一个图,而急切执行遵循命令式编程范式,可以立即执行任何操作,无需创建图,就像在 Python 中一样。因此,使用急切执行,我们可以告别会话和占位符。与图模式不同,它还通过立即运行时错误使得调试过程更加简单。
例如,在图模式中,要计算任何内容,我们需要运行会话。如下面的代码所示,要评估z的值,我们必须运行 TensorFlow 会话:
x = tf.constant(11)
y = tf.constant(11)
z = x*y
with tf.Session() as sess:
print sess.run(z)
使用急切执行,我们无需创建会话;我们可以像在 Python 中一样简单地计算z。为了启用急切执行,只需调用tf.enable_eager_execution()函数:
x = tf.constant(11)
y = tf.constant(11)
z = x*y
print z
它将返回以下内容:
<tf.Tensor: id=789, shape=(), dtype=int32, numpy=121>
为了获取输出值,我们可以打印以下内容:
z.numpy()
121
TensorFlow 中的数学操作
现在,我们将使用急切执行模式探索 TensorFlow 中的一些操作:
x = tf.constant([1., 2., 3.])
y = tf.constant([3., 2., 1.])
让我们从一些基本算术操作开始。
使用tf.add来添加两个数:
sum = tf.add(x,y)
sum.numpy()
array([4., 4., 4.], dtype=float32)
tf.subtract 函数用于找出两个数之间的差异:
difference = tf.subtract(x,y)
difference.numpy()
array([-2., 0., 2.], dtype=float32)
tf.multiply 函数用于两个数的乘法:
product = tf.multiply(x,y)
product.numpy()
array([3., 4., 3.], dtype=float32)
使用tf.divide除两个数:
division = tf.divide(x,y)
division.numpy()
array([0.33333334, 1\. , 3\. ], dtype=float32)
点积可以计算如下:
dot_product = tf.reduce_sum(tf.multiply(x, y))
dot_product.numpy()
10.0
下面,让我们找到最小和最大元素的索引:
x = tf.constant([10, 0, 13, 9])
最小值的索引是使用tf.argmin()计算的:
tf.argmin(x).numpy()
1
最大值的索引是使用tf.argmax()计算的:
tf.argmax(x).numpy()
2
运行以下代码以找到x和y之间的平方差:
x = tf.Variable([1,3,5,7,11])
y = tf.Variable([1])
tf.math.squared_difference(x,y).numpy()
[ 0, 4, 16, 36, 100]
让我们尝试类型转换;即,从一种数据类型转换为另一种。
打印x的类型:
print x.dtype
tf.int32
我们可以使用tf.cast将x的类型(tf.int32)转换为tf.float32,如下所示:
x = tf.cast(x, dtype=tf.float32)
现在,检查x的类型。它将是tf.float32,如下所示:
print x.dtype
tf.float32
按列连接两个矩阵:
x = [[3,6,9], [7,7,7]]
y = [[4,5,6], [5,5,5]]
按行连接矩阵:
tf.concat([x, y], 0).numpy()
array([[3, 6, 9],
[7, 7, 7],
[4, 5, 6],
[5, 5, 5]], dtype=int32)
使用以下代码按列连接矩阵:
tf.concat([x, y], 1).numpy()
array([[3, 6, 9, 4, 5, 6],
[7, 7, 7, 5, 5, 5]], dtype=int32)
使用stack函数堆叠x矩阵:
tf.stack(x, axis=1).numpy()
array([[3, 7],
[6, 7],
[9, 7]], dtype=int32)
现在,让我们看看如何执行reduce_mean操作:
x = tf.Variable([[1.0, 5.0], [2.0, 3.0]])
x.numpy()
array([[1., 5.],
[2., 3.]]
计算x的均值;即,(1.0 + 5.0 + 2.0 + 3.0) / 4:
tf.reduce_mean(input_tensor=x).numpy()
2.75
计算行的均值;即,(1.0+5.0)/2, (2.0+3.0)/2:
tf.reduce_mean(input_tensor=x, axis=0).numpy()
array([1.5, 4\. ], dtype=float32)
计算列的均值;即,(1.0+5.0)/2.0, (2.0+3.0)/2.0:
tf.reduce_mean(input_tensor=x, axis=1, keepdims=True).numpy()
array([[3\. ],
[2.5]], dtype=float32)
从概率分布中绘制随机值:
tf.random.normal(shape=(3,2), mean=10.0, stddev=2.0).numpy()
tf.random.uniform(shape = (3,2), minval=0, maxval=None, dtype=tf.float32,).numpy()
计算 softmax 概率:
x = tf.constant([7., 2., 5.])
tf.nn.softmax(x).numpy()
array([0.8756006 , 0.00589975, 0.11849965], dtype=float32)
现在,我们将看看如何计算梯度。
定义square函数:
def square(x):
return tf.multiply(x, x)
可以使用tf.GradientTape计算前述square函数的梯度,如下所示:
with tf.GradientTape(persistent=True) as tape:
print square(6.).numpy()
36.0
更多 TensorFlow 操作可在 GitHub 上的 Notebook 中查看,网址为bit.ly/2YSYbYu。
TensorFlow 远不止如此。随着我们在本书中的学习,我们将了解 TensorFlow 的各种重要功能。
TensorFlow 2.0 和 Keras
TensorFlow 2.0 具有一些非常酷的功能。它默认设置为即时执行模式。它提供了简化的工作流程,并使用 Keras 作为构建深度学习模型的主要 API。它还与 TensorFlow 1.x 版本向后兼容。
要安装 TensorFlow 2.0,请打开您的终端并输入以下命令:
pip install tensorflow==2.0.0-alpha0
由于 TensorFlow 2.0 使用 Keras 作为高级 API,我们将在下一节中看看 Keras 的工作原理。
Bonjour Keras
Keras 是另一个广泛使用的深度学习库。它由谷歌的 François Chollet 开发。它以快速原型设计而闻名,使模型构建简单。它是一个高级库,意味着它本身不执行任何低级操作,如卷积。它使用后端引擎来执行这些操作,比如 TensorFlow。Keras API 在tf.keras中可用,TensorFlow 2.0 将其作为主要 API。
在 Keras 中构建模型涉及四个重要步骤:
-
定义模型
-
编译模型
-
拟合模型
-
评估模型
定义模型
第一步是定义模型。Keras 提供了两种不同的 API 来定义模型:
-
顺序 API
-
函数式 API
定义一个序列模型
在序列模型中,我们将每个层堆叠在一起:
from keras.models import Sequential
from keras.layers import Dense
首先,让我们将我们的模型定义为Sequential()模型,如下所示:
model = Sequential()
现在,定义第一层,如下所示:
model.add(Dense(13, input_dim=7, activation='relu'))
在上述代码中,Dense 表示全连接层,input_dim 表示输入的维度,activation 指定我们使用的激活函数。我们可以堆叠任意多层,一层叠在另一层之上。
定义带有 relu 激活的下一层,如下所示:
model.add(Dense(7, activation='relu'))
定义带有 sigmoid 激活函数的输出层:
model.add(Dense(1, activation='sigmoid'))
顺序模型的最终代码块如下所示。正如您所见,Keras 代码比 TensorFlow 代码简单得多:
model = Sequential()
model.add(Dense(13, input_dim=7, activation='relu'))
model.add(Dense(7, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
定义函数式模型
函数式模型比顺序模型更灵活。例如,在函数式模型中,我们可以轻松连接任意一层到另一层,而在顺序模型中,每一层都是堆叠在另一层之上的。当创建复杂模型时,如有向无环图、具有多个输入值、多个输出值和共享层的模型时,函数式模型非常实用。现在,我们将看看如何在 Keras 中定义函数式模型。
第一步是定义输入维度:
input = Input(shape=(2,))
现在,我们将在测试集上评估我们的模型,首先定义第一个具有 10 个神经元和 relu 激活函数的全连接层,使用 Dense 类,如下所示:
layer1 = Dense(10, activation='relu')
我们已经定义了 layer1,但是 layer1 的输入从哪里来?我们需要在末尾的括号符号中指定 layer1 的输入,如下所示:
layer1 = Dense(10, activation='relu')(input)
我们使用 13 个神经元和 relu 激活函数定义下一层 layer2。layer2 的输入来自 layer1,因此在代码末尾加上括号,如下所示:
layer2 = Dense(10, activation='relu')(layer1)
现在,我们可以定义具有 sigmoid 激活函数的输出层。输出层的输入来自 layer2,因此在括号中添加了这一部分:
output = Dense(1, activation='sigmoid')(layer2)
在定义完所有层之后,我们使用 Model 类定义模型,需要指定 inputs 和 outputs,如下所示:
model = Model(inputs=input, outputs=output)
函数式模型的完整代码如下所示:
input = Input(shape=(2,))
layer1 = Dense(10, activation='relu')(input)
layer2 = Dense(10, activation='relu')(layer1)
output = Dense(1, activation='sigmoid')(layer2)
model = Model(inputs=input, outputs=output)
编译模型
现在我们已经定义了模型,下一步是编译它。在这个阶段,我们设置模型学习的方式。在编译模型时,我们定义了三个参数:
-
optimizer参数:这定义了我们想要使用的优化算法,例如在这种情况下的梯度下降。 -
loss参数:这是我们试图最小化的目标函数,例如均方误差或交叉熵损失。 -
metrics参数:这是我们想通过哪种度量来评估模型性能,例如accuracy。我们也可以指定多个度量。
运行以下代码来编译模型:
model.compile(loss='binary_crossentropy', optimizer='sgd', metrics=['accuracy'])
训练模型
我们已经定义并编译了模型。现在,我们将训练模型。使用 fit 函数可以完成模型的训练。我们指定特征 x、标签 y、训练的 epochs 数量和 batch_size,如下所示:
model.fit(x=data, y=labels, epochs=100, batch_size=10)
评估模型
训练完模型后,我们将在测试集上评估模型:
model.evaluate(x=data_test,y=labels_test)
我们还可以在相同的训练集上评估模型,这将帮助我们了解训练准确性:
model.evaluate(x=data,y=labels)
使用 TensorFlow 2.0 进行 MNIST 数字分类
现在,我们将看到如何使用 TensorFlow 2.0 执行 MNIST 手写数字分类。与 TensorFlow 1.x 相比,它只需要少量代码。正如我们所学的,TensorFlow 2.0 使用 Keras 作为其高级 API;我们只需在 Keras 代码中添加tf.keras。
让我们从加载数据集开始:
mnist = tf.keras.datasets.mnist
使用以下代码创建训练集和测试集:
(x_train,y_train), (x_test, y_test) = mnist.load_data()
将训练集和测试集标准化,通过将x的值除以最大值255.0来完成:
x_train, x_test = tf.cast(x_train/255.0, tf.float32), tf.cast(x_test/255.0, tf.float32)
y_train, y_test = tf.cast(y_train,tf.int64),tf.cast(y_test,tf.int64)
如下定义序列模型:
model = tf.keras.models.Sequential()
现在,让我们为模型添加层次。我们使用一个三层网络,其中最后一层采用relu函数和softmax:
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(256, activation="relu"))
model.add(tf.keras.layers.Dense(128, activation="relu"))
model.add(tf.keras.layers.Dense(10, activation="softmax"))
通过运行以下代码行来编译模型:
model.compile(optimizer='sgd', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
训练模型:
model.fit(x_train, y_train, batch_size=32, epochs=10)
评估模型:
model.evaluate(x_test, y_test)
就是这样!使用 Keras API 编写代码就是这么简单。
我们应该使用 Keras 还是 TensorFlow?
我们了解到 TensorFlow 2.0 使用 Keras 作为其高级 API。使用高级 API 可以进行快速原型设计。但是当我们想要在低级别上构建模型时,或者当高级 API 无法提供所需功能时,我们就不能使用高级 API。
除此之外,从头开始编写代码可以加深我们对算法的理解,并帮助我们更好地理解和学习概念,远胜于直接使用高级 API。这就是为什么在这本书中,我们将使用 TensorFlow 编写大部分算法,而不使用 Keras 等高级 API。我们将使用 TensorFlow 版本 1.13.1。
总结
我们从本章开始学习 TensorFlow 及其如何使用计算图。我们了解到,在 TensorFlow 中,每个计算都表示为一个计算图,该图由多个节点和边组成,其中节点是数学运算,如加法和乘法,边是张量。
我们学到了变量是用来存储值的容器,并且它们作为计算图中多个其他操作的输入。稍后,我们了解到占位符类似于变量,其中我们只定义类型和维度,但不会分配值,占位符的值将在运行时提供。
未来,我们学习了 TensorBoard,这是 TensorFlow 的可视化工具,可用于可视化计算图。它还可以用来绘制各种定量指标和多个中间计算结果的结果。
我们还学习了急切执行,它更符合 Python 风格,允许快速原型设计。我们了解到,与图模式不同,我们无需每次执行操作时都构建一个图来执行任何操作,急切执行遵循命令式编程范式,可以立即执行任何操作,就像我们在 Python 中所做的那样,而无需创建图形。
在下一章中,我们将学习梯度下降及其变种算法。
问题
通过回答以下问题来评估你对 TensorFlow 的了解:
-
定义一个计算图。
-
会话是什么?
-
我们如何在 TensorFlow 中创建一个会话?
-
变量和占位符之间有什么区别?
-
我们为什么需要 TensorBoard?
-
名称作用域是什么,它是如何创建的?
-
什么是即时执行?
进一步阅读
您可以通过查看官方文档www.tensorflow.org/tutorials来了解更多关于 TensorFlow 的信息。