当前位置:   article > 正文

与 TensorFlow 的初次相遇

与 TensorFlow 的初次相遇

与 TensorFlow 的初次相遇

原文:First Contact With TensorFlow

译者:飞龙

协议:CC BY-NC-SA 4.0

前言一

由于计算,海量数据存储和互联网技术等关键领域的共同发展,机器学习领域呈现了巨大的发展。许多人的日常生活中的许多技术和事件,直接或间接地受到自动学习的影响。语音识别,手机上的图像分类或垃圾邮件检测等技术的例子,使得一些应用成为可能,它们只出现在十年前科幻小说中。股票市场模型或医疗模型中的学习的使用,对我们的社会产生了巨大的影响。此外,具有巡航控制,无人机和各种机器人的汽车将在不久的将来影响社会。

深度学习是机器学习的一个子类型,自 2006 年重新发现以来,无疑是爆发性扩张的领域之一。事实上,硅谷的许多创业公司都专注于此,而谷歌,Facebook,微软或 IBM 等大型科技公司都有开发和研究团队。深度学习甚至引起了大学之外和研究领域的兴趣:许多专业杂志(如 Wired)甚至是通用杂志(如纽约时报,Bloomberg 或 BBC)为这个主题撰写了很多文章。

这种兴趣促使许多学生,企业家和投资者加入深度学习。由于产生的所有兴趣,几个软件包已被制作成“开源”的。作为库的主要推动者之一,我们在 2012 年作为博士生在伯克利(Caffe)开发了它。我可以说,TensorFlow 将成为研究人员和中小企业公司用于实现他们的深度学习和机器学习的想法的主要工具之一,它出现在本书中并由 Google(加州)设计,我自 2013 年以来一直在那里研究它。对此的保证是参与该项目的工程师和顶尖研究人员的数量,它最终得到了开源。

我希望这本入门书能够帮助有兴趣在这个非常有趣的领域开始冒险的读者。我要感谢作者,我很高兴了解到它传播这项技术的努力。在开源项目发布两个月后,他在创纪录的时间内写了这本书(首先是西班牙语版本)。这是巴塞罗那活力的另一个例子,它有兴趣成为这一技术场景中的参与者之一,无疑将影响我们的未来。

Oriol Vinyals,Google Brain 的研究科学家

前言二

教育是你用来改变世界的最有力的武器。

Nelson Mandela

本书的目的是有助于将这些知识转播给工程师,它们希望在激动人心的机器学习世界中扩展智慧。我相信任何具有工程背景的人都可能会发现,深度学习和机器学习的应用对他们的工作很有价值。

鉴于我的背景,读者可能会想知道为什么我提出了编写这种新的深度学习技术的挑战。我的研究重点是逐步从超级计算架构和运行时转向大数据工作负载的执行中间件,最近转向大规模数据的机器学习平台。

正是作为一名工程师,而不是数据科学家,我认为我可以为这一主题贡献这种介绍性的方法,并且它对早期阶段的许多工程师都有帮助;然后他们会选择深入了解他们的需求。

我希望这本书能为这个我非常喜爱的教育世界增添一些价值。我认为知识就是解放,应该让所有人都能获得。因此,本书的内容将在网站 www.JordiTorres.eu/TensorFlow 上完全免费提供。如果读者发现内容有用并认为适当补偿作者的写作,网站上有一个标签可以用于捐赠。另一方面,如果读者更喜欢选择纸质副本,你可以通过 Amazon.com 购买该书。

本书还提供西班牙语版本。事实上,这本书是西班牙语的翻译,该书于去年 1 月完成,并在 GEMLeB Meetup(Grup d’Estudi de Machine Learning de Barcelona)中展示,我是其中一个共同组织者。

感谢你阅读本书!它使我感到安慰,并证明了我写作的努力。那些了解我的人,知道技术传播是我的激情之一。它激励我继续学习。

Jordi Torres,2016 年 2 月

一种实用的方法

告诉我,我会忘记。教我,我会记得。让我参与,我会学习。

本杰明·富兰克林

深度学习的一个常见应用包括模式识别。因此,当你开始编程时,有个传统是打印“Hello World”,与它相同,在深度学习中,通常构造用于识别手写数字的模型 [1]。我将提供的第一个神经网络示例,也将允许我介绍这种名为 TensorFlow 的新技术。

但是,我不打算写一本关于机器学习或深度学习的研究书籍,我只想尽快为每个人提供这个新的机器学习软件包 TensorFlow。因此,我向我的数据科学家们道歉,为了与普通读者分享这些知识,我允许自己进行某些简化。

读者会在这里找到我在课堂上使用的常规结构;这会邀请你在学习的同时使用计算机的键盘。我们称之为“从实践中学习”,而我作为 UPC 教授的经历告诉我,这种方法对于尝试开始新主题的工程师来说非常有效。

出于这个原因,这本书具有实用性,因此我尽可能地减少了理论部分。然而,当学习过程需要时,文本中已包含某些数学细节。

我假设读者对机器学习有一些基本的理解,所以我将使用一些流行的算法逐步组织读者在 TensorFlow 中的训练。

在第一章中,除了介绍TensorFlow将扮演重要角色的场景之外,我还借此机会解释TensorFlow程序的基本结构,并简要解释它在内部维护的数据。

在第二章中,通过线性回归的一个例子,我将介绍一些代码基础知识,同时,如何调用学习过程中的各种重要组件,如损失函数或梯度下降优化算法。

在第三章中,我展示了一个聚类算法,我将详细介绍 TensorFlow 的基本数据结构,称为tensor(张量),以及 TensorFlow 包提供的用于创建和管理张量的不同类和函数。

第四章详细介绍了如何构建识别手写数字的单层神经网络。这将允许我们归纳上面提出的所有概念,以及查看创建和测试模型的整个过程。

下一章首先介绍基于前一章中所见的神经网络概念,并介绍如何构建多层神经网络来获得更好的手写数字识别结果。它将更详细地介绍所谓的卷积神经网络。

在第六章中,我们将讨论一个更具体的问题,利用 GPU 提供的计算能力,可能不是所有读者都感兴趣。如第 1 章所述,GPU 在神经网络的训练过程中发挥着重要作用。

本书以后记结束,其中我强调了一些结论。我想强调的是,本书中的代码示例可以从本书 [2] 的 github 仓库下载。

1. TensorFlow 基础知识

在本章中,我将简要介绍 TensorFlow 的代码及其编程模型。在本章的最后,读者可以在他们的个人计算机上安装 TensorFlow 软件包。

开源软件包

学术界已经对机器学习进行了数十年的调查,但直到近几年,它的渗透率在企业中也有所增加。这要归功于它已经拥有的大量数据以及现在可用的前所未有的计算能力。

在这种情况下,毫无疑问,在 Alphabet 的支持下,谷歌是机器学习技术在其所有虚拟计划和产品中发挥关键作用的最大公司之一。

去年10月,当 Alphabet 宣布那个季度谷歌的业绩,销售额和利润大幅增加时,首席执行官桑达皮采清楚地说:“机器学习是一种核心的,变革性的方式,我们正在重新思考我们正在做的一切”。

从技术上讲,我们正面临着谷歌不是唯一一个重要角色的时代变迁。其他技术公司,如微软,Facebook,亚马逊和苹果等众多公司也在增加对这些领域的投资。

在此背景下,几个月前谷歌在开源许可证(Apache 2.0)下发布了 TensorFlow 引擎。想要将机器学习纳入其项目和产品的开发人员和研究人员可以使用 TensorFlow,就像 Google 在内部使用 Gmail,Google 照片,搜索,语音识别等不同的商业产品一样。

TensorFlow 最初是由 Google Brain Team 开发的,目的是进行机器学习和深度神经网络研究,但该系统足以应用于各种其他机器学习问题。

由于我是一名工程师,而且我正在与工程师交谈,因此本书将深入了解数据流图如何表示算法。TensorFlow 可以看作是使用数据流图进行数值计算的库。图中的节点表示数学运算,而图的边表示多维数据数组(张量),它们将节点互连。

TensorFlow 围绕构建和操作计算图的基本思想构建,象征性地表示要执行的数值运算。这使得 TensorFlow 现在可以从 Linux 64 位平台(如 Mac OS X)以及 Android 或 iOS 等移动平台中利用 CPU 和 GPU。

这个新软件包的另一个优点是它的可视 TensorBoard 模块,它提供了大量有关如何监视和显示算法运行的信息。在创建更好的模型的过程中,能够测量和显示算法的行为是非常重要的。我感觉目前许多模型都是通过一个小型的盲目过程,通过试错来调优,明显浪费资源,以及最重要时间。

TensorFlow 服务

最近 Google 推出了 TensorFlow 服务 [3],这有助于开发人员将他们的 TensorFlow 机器学习模型(即使如此,也可以扩展来服务其他类型的模型)投入生产。TensorFlow 服务是一个开源服务系统(用 C++ 编写),现在可以在 Apache 2.0 许可下在 GitHub 上获得。

TensorFlow 和 TensorFlow 服务有什么区别? 在 TensorFlow 中,开发人员更容易构建机器学习算法,并针对某些类型的数据输入进行训练,TensorFlow 服务专门使这些模型可用于生产环境。我们的想法是开发人员使用 TensorFlow 训练他们的模型,然后他们使用 TensorFlow 服务的 API 来响应来自客户端的输入。

这允许开发人员根据实际数据大规模试验不同的模型,并随时间变化,保持稳定的架构和 API。

典型的流水线是将训练数据提供给学习器,学习器输出模型,模型在被验证之后准备好部署到 TensorFlow 服务系统。 随着时间的推移和新数据的出现,改进模型,启动和迭代我们的模型是很常见的。事实上,在 Google 的博文中 [4] 中,他们提到在谷歌,许多流水线都在持续运行,随着新数据的出现,产生了新的模型版本。

TensorFlowServing

开发人员用来与 TensorFlow 服务进行通信的前端实现,基于 gRPC ,这是一种来自 Google 的高性能开源RPC框架。

如果你有兴趣了解 TensorFlow 服务的更多信息,我建议你先阅读服务架构概述 [5] 部分,设置你的环境并开始阅读基础教程 [6]。

TensorFlow 的安装

是时候做一些事情了。从现在开始,我建议你交替阅读和在计算机上练习。

TensorFlow 有 Python API(以及 C/C++),需要安装 Python 2.7(我假设任何阅读本书的工程师都知道如何操作)。

通常,在使用 Python 时,应使用虚拟环境virtualenv。 virtualenv是一种工具,用于在同一台计算机的不同部分中保持不同项目所需的 Python 依赖关系。如果我们使用virtualenv来安装 TensorFlow,这将不会覆盖需要 TensorFlow 的其他项目的现有 Python 包版本。

首先,如果尚未安装pipvirtualenv,则应安装,如下面的脚本所示:

  1. # Ubuntu/Linux 64-bit
  2. $ sudo apt-get install python-pip python-dev python-virtualenv
  3. # Mac OS X
  4. $ sudo easy_install pip
  5. $ sudo pip install --upgrade virtualenv
  • 1
  • 2
  • 3
  • 4
  • 5

~/tensorflow目录中的环境virtualenv

$ virtualenv --system-site-packages ~/tensorflow

下一步是激活virtualenv。这可以按如下方式完成:

  1. $ source ~/tensorflow/bin/activate # with bash
  2. $ source ~/tensorflow/bin/activate.csh # with csh
  3. (tensorflow)$
  • 1
  • 2
  • 3

我们工作的虚拟环境的名称,将从现在开始显示在每个命令行的开头。激活virtualenv后,你可以使用pip在其中安装 TensorFlow:

  1. # Ubuntu/Linux 64-bit, CPU only:
  2. (tensorflow)$ sudo pip install --upgrade https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.7.1-cp27-none-linux_x86_64.whl
  3. # Mac OS X, CPU only:
  4. (tensorflow)$ sudo easy_install --upgrade six
  5. (tensorflow)$ sudo pip install --upgrade https://storage.googleapis.com/tensorflow/mac/tensorflow-0.7.1-cp27-none-any.whl
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我建议你访问此处提供的官方文档,来确保你安装的是最新版本。

如果运行代码的平台具有 GPU,要使用的包不同。我建议你访问官方文档,了解你的 GPU 是否符合支持 Tensorflow 所需的规范。运行 Tensorflow GPU 需要安装其他软件,所有信息都可以在下载和设置 TensorFlow [7] 网页上找到。对于使用 GPU 的更多信息,我建议阅读第 6 章。

最后,当你完成后,你应该按如下方式禁用虚拟环境:

(tensorflow)$ deactivate
  • 1

鉴于本书的介绍性质,我们建议读者访问上述官方文档页面,来查找安装 Tensorflow 的其他方法的更多信息。

我在 TensorFlow 中的第一个代码

正如我在开始时提到的那样,我们将通过很少的理论和大量练习来探索 TensorFlow 星球。开始吧!

从现在开始,最好使用任何文本编辑器编写 python 代码并使用扩展名.py保存(例如test.py)。要运行代码,使用命令python test.py就足够了。

为了获得 TensorFlow 程序的第一印象,我建议编写一个简单的乘法程序;代码看起来像这样:

  1. import tensorflow as tf
  2. a = tf.placeholder("float")
  3. b = tf.placeholder("float")
  4. y = tf.mul(a, b)
  5. sess = tf.Session()
  6. print sess.run(y, feed_dict={a: 3, b: 3})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在此代码中,在导入 Python 模块tensorflow之后,我们定义“符号”变量,称为占位符,以便在程序执行期间操作它们。然后,我们将这些变量作为参数,调用 TensorFlow 提供的乘法函数。tf.mul是 TensorFlow 为操纵张量而提供的众多数学运算之一。在这个时候,张量可以认为是动态大小的多维数据数组。

主要运算如下表所示:

运算描述
tf.add加法
tf.sub减法
tf.mul乘法
tf.div除法
tf.mod
tf.abs返回绝对值
tf.neg返回负值
tf.sign返回标志
tf.inv返回倒数
tf.square计算平方
tf.round返回最接近的整数
tf.sqrt计算平方根
tf.pow计算指数
tf.exp计算自然指数
tf.log计算自然对数
tf.maximum返回最大值
tf.minimum返回最小值
tf.cos计算余弦
tf.sin计算正弦

TensorFlow 还为程序员提供了许多函数,来对矩阵执行数学运算。一些列在下面:

运算描述
tf.diag返回具有给定对角线值的对角张量
tf.transpose返回参数的转置
tf.matmul返回由参数列出的两个张量的张量积
tf.matrix_determinant返回由参数指定的方阵的行列式
tf.matrix_inverse返回由参数指定的方阵的逆

下一步,最重要的一步是创建一个会话来求解指定的符号表达式。实际上,到目前为止,这个 TensorFlow 代码尚未执行任何操作。我要强调的是,TensorFlow 既是表达机器学习算法的接口,又是运行它们的实现,这是一个很好的例子。

程序通过使用Session()创建会话来与 Tensorflow 库交互;只有在我们调用run()方法时才会创建这个会话,这就是它真正开始运行指定代码的时候。在此特定示例中,使用feed_dict参数将变量的值传给run()方法。这里,相关代码求解表达式,并且从显示器返回 9 作为结果。

通过这个简单的例子,我试图介绍在 TensorFlow 中编程的常规方法,首先指定整个问题,并最终创建一个可以运行相关计算的会话。

然而,有时我们感兴趣的是构造代码的更多的灵活性,插入操作来构建某个图,这些操作运行它的一部分。例如,当我们使用 Python 的交互式环境时,例如 IPython [8],就会发生这种情况。为此,TesorFlow 提供了tf.InteractiveSession()类。

这种编程模型的动机超出了本书的范围。但是,为了继续下一章,我们只需要知道所有信息都在内部保存在图结构中,它包含所有操作和数据的信息。

该图描述了数学运算。节点通常实现数学运算,但它们也可以表示数据输入,输出结果或读/写持久变量。边描述节点与其输入和输出之间的关系,同时携带张量,即 TensorFlow 的基本数据结构。

将信息表示为图允许 TensorFlow 知道事务之间的依赖关系,并异步并行地将操作分配给设备,当这些操作已经具有可用的相关张量(在边缘输入中指示)时。

因此,并行性是使我们能够加速一些计算昂贵的算法的执行的因素之一,但也因为 TensorFlow 已经有效地实现了一组复杂的操作。此外,大多数这些操作都具有关联的内核,这些内核是为特定设备(如 GPU)设计的操作的实现。下表总结了最重要的操作/内核 [9]:

操作组操作
数学加,减,乘,除,指数,对数,大于,小于,等于
排列连接,切片,分割,常数,阶,形状,打乱
矩阵MatMul,MatrixInverse,MatrixDeterminant
神经网络SoftMax,Sigmoid,ReLU,Convolution2D,MaxPool
检查点保存,还原
队列和同步Enqueue,Dequeue,MutexAcquire,MutexRelease
流量控制合并,切换,进入,离开,NextIteration

显示面板 Tensorboard

为了使其更加全面,TensorFlow 包含了名为 TensorBoard 的可视化工具来调试和优化程序的功能。TensorBoard 可以以图形方式查看计算图任何部分的参数和细节的不同类型的统计信息。

TensorBoard 模块显示的数据在 TensorFlow 执行期间生成,并存储在跟踪文件中,其数据来自摘要操作。在 TensorFlow 的文档页面 [10] 中,你可以找到 Python API 的详细说明。

我们调用它的方式非常简单:从命令行中使用 Tensorflow 命令启动服务,它包含要跟踪的文件作为参数。

(tensorflow)$ tensorboard --logdir=
  • 1

你只需要使用http//localhost:6006 /从浏览器中 [11] 访问本地套接字 6006。

名为 TensorBoard 的可视化工具超出了本书的范围。对于 Tensorboard 如何工作的更多详细信息,读者可以访问 TensorFlow 教程页面中的 TensorBoard 图形可视化 [12] 部分。

2. TensorFlow 中的线性回归

在本章中,我将开始使用简单模型:线性回归来探索 TensorFlow 编程。基于这个例子,我将介绍一些代码基础知识,以及,如何调用学习过程中的各种重要组件,如函数函数或算法梯度下降。

变量之间的关系模型

线性回归是一种用于衡量变量之间关系的统计技术。它的有趣之处在于实现它的算法在概念上不复杂,并且还可以适应各种各样的情况。由于这些原因,我发现用线性回归的例子开始深入研究 TensorFlow 很有意思。

请记住,在两个变量(简单回归)和两个以上变量(多元回归)的情况下,线性回归拟合因变量和自变量之间的关系xi和随机项b

在本节中,我将创建一个简单的示例来解释 TensorFlow 如何工作,假设我们的数据模型对应简单的线性回归y = W * x + b。为此,我使用一个简单的 Python 程序在二维空间中创建数据,然后我会要求 TensorFlow 在这些点上寻找最适合的直线。

首先要做的是导入我们将用于生成点的 NumPy 包。我们创建的代码如下:

  1. import numpy as np
  2. num_points = 1000
  3. vectors_set = []
  4. for i in xrange(num_points):
  5. x1= np.random.normal(0.0, 0.55)
  6. y1= x1 * 0.1 + 0.3 + np.random.normal(0.0, 0.03)
  7. vectors_set.append([x1, y1])
  8. x_data = [v[0] for v in vectors_set]
  9. y_data = [v[1] for v in vectors_set]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

从代码中可以看出,我们根据关系y = 0.1 * x + 0.3生成了点,尽管有一些正态分布的变化,因此这些点并不完全对应一条线,让我们编写一个更有趣的例子。

在我们的例子中,所得到的点云是:

读者可以使用以下代码查看它们(这里,我们需要导入matplotlib包的一些函数,运行pip install matplotlib [13]):

  1. import matplotlib.pyplot as plt
  2. plt.plot(x_data, y_data, 'ro', label='Original data')
  3. plt.legend()
  4. plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5

这些点是我们将考虑的模型的训练数据集的数据。

损失函数和梯度下降算法

下一步是训练我们的学习算法,以便能够获得从输入数据x_data估计的输出值y。在这种情况下,正如我们事先所知,它是线性回归,我们只能用两个参数表示我们的模型:Wb

目标是生成 TensorFlow 代码,它能够找到最佳的参数Wb,它来自输入数据x_data,将其拟合到输出数据y_data,我们这里它是一条直线,由y_data = W * x_data + b定义。读者知道W应接近 0.1 且b为 0.3,但 TensorFlow 不知道它,必须自己实现。

解决此类问题的一种标准方法是,遍历数据集的每个值并修改参数Wb,以便每次都能获得更精确的答案。为了确定我们是否在这些迭代中有所改进,我们将定义一个损失函数(也称为“误差函数”)来衡量某条线有多“好”(实际上是有多“坏”)。

该函数接收参数Wb,并根据线与数据的拟合程度返回一个误差值。在我们的例子中,我们可以使用均方误差 [14] 作为损失函数。利用均方误差,我们得到“误差”的平均值,基于实际值与算法每次迭代估计值之间距离。

稍后,我将详细介绍损失函数及其替代方法,但对于这个介绍性示例,均方误差有助于我们一步一步向前推进。

现在是时候用 TensorFlow 编写我 解释过的所有内容了。为此,首先我们将使用以下语句创建三个变量:

  1. W = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
  2. b = tf.Variable(tf.zeros([1]))
  3. y = W * x_data + b
  • 1
  • 2
  • 3

现在,我们可以继续前进,只知道方法Variable的调用定义了一个变量,驻留在 TensorFlow 的内部图数据结构中,我在上面已经说过了。 稍后我们将回到方法参数的更多信息,但是现在我认为最好继续前进来推进第一种方法。

现在,通过定义这些变量,我们可以基于每个点与函数y = W * x + b计算的点之间的距离,来表示我们之前讨论的损失函数。之后,我们可以计算其平方和的平均值。 在 TensorFlow 中,此损失函数表示如下:

loss = tf.reduce_mean(tf.square(y - y_data))
  • 1

如我们所见,此表达式计算我们知道的y_data点与从输入x_data计算的点y之间的平方距离的平均值。

此时,读者可能已经怀疑最适合我们数据的直线是误差值较小的直线。 因此,如果我们使误差函数最小,我们将找到我们数据的最佳模型。

目前没有太多细节,这就是使函数最小的优化算法,称为梯度下降 [15]。 理论上,梯度下降是一种算法,它接受由一组参数定义的函数,它以一组初始参数值开始,并迭代地移向一组使函数最小的值。 在函数梯度 [16] 的负方向上移动来实现迭代式最小化。 通常计算距离平方来确保它是正的并且使误差函数可微分以便计算梯度。

算法从一组参数的初始值开始(在我们的例子中为Wb),然后算法以某种方式迭代地调整这些变量的值,在过程结束时,变量的值使成本函数最小。

要在 TensorFlow 中使用此算法,我们只需执行以下两个语句:

  1. optimizer = tf.train.GradientDescentOptimizer(0.5)
  2. train = optimizer.minimize(loss)
  • 1
  • 2

现在,这足以让 TensorFlow 在其内部数据结构中创建相关数据,并且在这个结构中也实现了一个可以由train调用的优化器,它是针对定义的成本函数的梯度下降算法。稍后,我们将讨论名为学习率的函数参数(在我们的示例中,值为 0.5)。

运行算法

正如我们之前所见,在代码的这个位置上,特定于 TensorFlow 库的调用,只向其内部图添加了信息,而 TensorFlow 的运行时尚未运行任何算法。因此,与前一章的示例一样,我们必须创建会话,调用run方法并传递train作为参数。另外,因为在代码中我们已经指定了变量,所以我们必须先使用以下调用对它们进行初始化:

  1. init = tf.initialize_all_variables()
  2. sess = tf.Session()
  3. sess.run(init)
  • 1
  • 2
  • 3
  • 4

现在我们可以开始迭代过程,这将允许我们找到Wb的值,它定义最适合输入点的模型直线。 训练过程一直持续到模型在训练数据上达到所需的准确度。 在我们的特定示例中,如果我们假设只有 8 次迭代就足够了,代码可能是:

  1. for step in xrange(8):
  2. sess.run(train)
  3. print step, sess.run(W), sess.run(b)
  • 1
  • 2
  • 3

运行此代码的结果表明,Wb的值接近我们事先知道的值。 在我的例子中,print的结果是:

(array([ 0.09150752], dtype=float32), array([ 0.30007562], dtype=float32))
  • 1

并且,如果我们使用以下代码以图形方式显示结果:

  1. plt.plot(x_data, y_data, 'ro')
  2. plt.plot(x_data, sess.run(W) * x_data + sess.run(b))
  3. plt.legend()
  4. plt.show()
  • 1
  • 2
  • 3
  • 4

我们可以用图形方式,看到参数W = 0.0854b = 0.299定义的直线,只需 8 次迭代:

请注意,我们只执行了八次迭代来简化说明,但如果我们运行更多,参数值会更接近预期值。 我们可以使用以下语句来打印Wb的值:

print(step, sess.run(W), sess.run(b))
  • 1

在我们的例子中,print输出是:

  1. (0, array([-0.04841119], dtype=float32), array([ 0.29720169], dtype=float32))
  2. (1, array([-0.00449257], dtype=float32), array([ 0.29804006], dtype=float32))
  3. (2, array([ 0.02618564], dtype=float32), array([ 0.29869056], dtype=float32))
  4. (3, array([ 0.04761609], dtype=float32), array([ 0.29914495], dtype=float32))
  5. (4, array([ 0.06258646], dtype=float32), array([ 0.29946238], dtype=float32))
  6. (5, array([ 0.07304412], dtype=float32), array([ 0.29968411], dtype=float32))
  7. (6, array([ 0.08034936], dtype=float32), array([ 0.29983902], dtype=float32))
  8. (7, array([ 0.08545248], dtype=float32), array([ 0.29994723], dtype=float32))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

你可以观察到算法以W = -0.0484b = 0.2972(在我们的例子中)的初始值开始,然后算法以一种方式迭代调整变量的值使损失函数最小。

你还可以检查损失函数是否随之减少

print(step, sess.run(loss))
  • 1

在这种情况下,print输出是:

  1. (0, 0.015878126)
  2. (1, 0.0079048825)
  3. (2, 0.0041520335)
  4. (3, 0.0023856456)
  5. (4, 0.0015542418)
  6. (5, 0.001162916)
  7. (6, 0.00097872759)
  8. (7, 0.00089203351)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

我建议读者在每次迭代时绘图,让我们可以直观地观察算法如何调整参数值。 在我们的例子中,8 个截图是:

正如读者可以看到的,在算法的每次迭代中,直线更适合数据。 梯度下降算法如何更接近最小化损失函数的参数值?

由于我们的误差函数由两个参数(Wb)组成,我们可以将它可视化为二维表面。 该二维空间中的每个点代表一条直线。 每个点的函数高度是该直线的误差值。 在该表面上,一些直线产生的误差值小于其他直线。 当 TensorFlow 运行梯度下降搜索时,它将从该表面上的某个位置开始(在我们的示例中,点W = -0.04841119b = 0.29720169)并向下移动来查找具有最小误差的直线。

要在此误差函数上运行梯度下降,TensorFlow 会计算其梯度。 梯度将像指南针一样,总是引导我们向下走。 为了计算它,TensorFlow 将对误差函数微分,在我们的情况下意味着它需要计算Wb的偏导数,它表明每次迭代中要移动的方向。

之前提到的学习率参数控制每次迭代期间 TensorFlow 的每一步的下降程度。 如果我们引入的参数太大,我们可能会越过最小值。 但是,如果我们让 TensorFlow 采取较小步骤,则需要多次迭代才能达到最小值。 因此,使用良好的学习率至关重要。 有不同的技术来调整学习率参数的值,但它超出了本入门书的范围。 确保梯度下降算法正常工作的一种好方法,是确保每次迭代中的误差减小。

请记住,为了便于读者测试本章所述的代码,你可以从本书的 Github [17] 下载regression.py。 在这里,你将发现所有东西都在一起以便跟踪:

  1. import numpy as np
  2. num_points = 1000
  3. vectors_set = []
  4. for i in xrange(num_points):
  5. x1= np.random.normal(0.0, 0.55)
  6. y1= x1 * 0.1 + 0.3 + np.random.normal(0.0, 0.03)
  7. vectors_set.append([x1, y1])
  8. x_data = [v[0] for v in vectors_set]
  9. y_data = [v[1] for v in vectors_set]
  10. import matplotlib.pyplot as plt
  11. #Graphic display
  12. plt.plot(x_data, y_data, 'ro')
  13. plt.legend()
  14. plt.show()
  15. import tensorflow as tf
  16. W = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
  17. b = tf.Variable(tf.zeros([1]))
  18. y = W * x_data + b
  19. loss = tf.reduce_mean(tf.square(y - y_data))
  20. optimizer = tf.train.GradientDescentOptimizer(0.5)
  21. train = optimizer.minimize(loss)
  22. init = tf.initialize_all_variables()
  23. sess = tf.Session()
  24. sess.run(init)
  25. for step in xrange(8):
  26. sess.run(train)
  27. print(step, sess.run(W), sess.run(b))
  28. print(step, sess.run(loss))
  29. #Graphic display
  30. plt.plot(x_data, y_data, 'ro')
  31. plt.plot(x_data, sess.run(W) * x_data + sess.run(b))
  32. plt.xlabel('x')
  33. plt.xlim(-2,2)
  34. plt.ylim(0.1,0.6)
  35. plt.ylabel('y')
  36. plt.legend()
  37. plt.show()
  • 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

在本章中,我们已经开始探索 TensorFlow 软件包的可能性,首先采用直观方法处理两个基本组件:损失函数和梯度下降算法,使用基本线性回归算法来介绍。 在下一章中,我们将详细介绍 TensorFlow 包使用的数据结构。

3. TensorFlow 中的聚类

前一章中介绍的线性回归是一种监督学习算法,我们使用数据和输出值(或标签)来构建适合它们的模型。但我们并不总是拥有标记数据,尽管如此,我们也希望以某种方式分析它们。在这种情况下,我们可以使用无监督学习算法,例如聚类。聚类方法被广泛使用,因为它通常是数据分析的初步筛选的好方法。

在本章中,我将介绍名为 K-means 的聚类算法。它肯定是最受欢迎的,广泛用于自动将数据分组到相关的子集中,以便子集中的所有元素彼此更相似。在此算法中,我们没有任何目标或结果变量来预测估计值。

我还将使用本章来介绍 TensorFlow 的知识,并在更详细地介绍名为tensor(张量)的基本数据结构。我将首先解释这种类型的数据是什么样的,并展示可以在其上执行的转换。然后,我将使用张量在案例研究中展示 K-means 算法的使用。

基本数据结构:张量

TensorFlow 程序使用称为张量的基本数据结构来表示其所有数据。张量可以被认为是动态大小的多维数据数组,其具有静态数据类型的属性,可以从布尔值或字符串到各种数字类型。下面是 Python 中的主要类型及其等价物的表格。

TensorFlow 中的类型Python 中的类型描述
DT_FLOATtf.float3232 位浮点
DT_INT16tf.int1616 位整数
DT_INT32tf.int3232 位整数
DT_INT64tf.int6464 位整数
DT_STRINGtf.string字符串
DT_BOOLtf.bool布尔值

另外,每个张量拥有阶(Rank),这是其维度的数量。例如,以下张量(在 Python 中定义为列表)的阶为 2:

t = [[1,2,3],[4,5,6],[7,8,9]]
  • 1

张量可以有任何阶。二阶张量通常被认为是矩阵,一阶张量将是向量。零阶被认为是标量值。

TensorFlow 文档使用三种类型的命名约定来描述张量的维度:形状(Shape),阶(Rank)和维数(Dimension Number)。下表显示了它们之间的关系,以便使跟踪 TensorFlow 文档更容易:

形状维数
[]00-D
[D0]11-D
[D0, D1]22-D
[D0, D1, D2]33-D
[D0, D1, ... Dn]nn-D

这些张量可以通过一系列 TensorFlow 软件包提供的转换进行操作。 下面,我们将在下表中讨论其中的一些内容。

在本章中,我们将详细介绍其中一些内容。 可以在 TensorFlow 的官方网站 [18] 上找到完整的转换列表和详细信息。

| 操作 | 描述 |
| tf.shape | 获取张量的形状 |
| tf.size | 获取张量的大小 |
| tf.rank | 获取张量的阶 |
| tf.reshape | 改变张量的形状,保持包含相同的元素 |
| tf.squeeze | 删除大小为 1 的张量维度 |
| tf.expand_dims | 将维度插入张量 |
| tf.slice | 删除部分张量 |
| tf.split | 将张量沿一个维度划分为多个张量 |
| tf.tile | 将一个张量多次复制,并创建新的张量 |
| tf.concat | 在一个维度上连接张量 |
| tf.reverse | 反转张量的特定维度 |
| tf.transpose | 转置张量中的维度 |
| tf.gather | 根据索引收集部分 |

例如,假设你要将2×2000(2D 张量)的数组扩展为立方体(3D 张量)。 我们可以使用tf.expand_ dims函数,它允许我们向张量插入一个维度:

  1. vectors = tf.constant(conjunto_puntos)
  2. extended_vectors = tf.expand_dims(vectors, 0)
  • 1
  • 2

在这种情况下,tf.expand_dims将一个维度插入到由参数给定的一个张量中(维度从零开始)。

从视觉上看,上述转变如下:

如你所见,我们现在有了 3D 张量,但我们无法根据函数参数确定新维度 D0 的大小。

如果我们使用get_shape()操作获得此tensor的形状,我们可以看到没有关联的大小:

print expanded_vectors.get_shape()
  • 1

它可能会显示:

TensorShape([Dimension(1), Dimension(2000), Dimension(2)])
  • 1

在本章的后面,我们将看到,由于 TensorFlow 形状广播, 张量的许多数学处理函数(如第一章所示),能够发现大小未指定的维度的大小,,并为其分配这个推导出的值。

TensorFlow 中的数据存储

在介绍 TensorFlow 的软件包之后,从广义上讲,有三种主要方法可以在 TensorFlow 程序上获取数据:

  1. 来自数据文件。
  2. 数据作为常量或变量预加载。
  3. 那些由 Python 代码提供的。

下面,我简要介绍其中的每一个。

1) 数据文件

通常,从数据文件加载初始数据。这个过程并不复杂,鉴于本书的介绍性质,我邀请读者访问TensorFlow 的网站 [19],了解如何从不同文件类型加载数据。你还可以查看 Python 代码input_data.py [20](可在 Github 上找到),它从文件中加载 MNIST 数据(我将在下面几章使用它)。

2) 变量和常量

当谈到小集合时,也可以预先将数据加载到内存中;创建它们有两种基本方法,正如我们在前面的例子中看到的那样:

  • constant(…)用于常量
  • Variable(…)用于变量

TensorFlow 包提供可用于生成常量的不同操作。在下表中,你可以找到最重要的操作的摘要:

操作描述
tf.zeros_like创建一个张量,所有元素都初始化为 0
tf.ones_like创建一个张量,所有元素都初始化为 1
tf.fill创建一个张量,其中所有元素都初始化为由参数给出的标量值
tf.constant使用参数列出的元素创建常量张量

在 TensorFlow 中,在模型的训练过程中,参数作为变量保存在存储器中。 创建变量时,可以使用由函数参数定义的张量作为初始值,该值可以是常量值或随机值。 TensorFlow 提供了一系列操作,可生成具有不同分布的随机张量:

操作描述
tf.random_normal具有正态分布的随机值
tf.truncated_normal具有正态分布的随机值,但消除那些幅度大于标准差 2 倍的值
tf.random_uniform具有均匀分布的随机值
tf.random_shuffle在第一维中随机打乱张量元素
tf.set_random_seed设置随机种子

一个重要的细节是,所有这些操作都需要特定形状的张量作为函数的参数,并且创建的变量具有相同的形状。 通常,变量具有固定的形状,但TensorFlow提供了在必要时对其进行重塑的机制。

使用变量时,必须在构造图之后,在使用run()函数执行任何操作之前显式初始化这些变量。 正如我们所看到的,为此可以使用tf.initialize_all_variables()。 通过 TensorFlow 的tf.train.Saver()类,可以在训练模型时和之后将变量保存到磁盘上,但是这个类超出了本书的范围。

3) 由Python代码提供

最后,我们可以使用我们所谓的“符号变量”或占位符来在程序执行期间操作数据。调用是placeholder(),参数为元素类型和张量形状,以及可选的名称。

从 Python 代码调用Session.run()Tensor.eval()的同时,张量由feed_dict参数中指定的数据填充。回想第 1 章中的第一个代码:

  1. import tensorflow as tf
  2. a = tf.placeholder("float")
  3. b = tf.placeholder("float")
  4. y = tf.mul(a, b)
  5. sess = tf.Session()
  6. print sess.run(y, feed_dict={a: 3, b: 3})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在最后一行代码中,调用sess.run()时,我们传递两个张量ab的值到feed_dict参数。

通过张量的简要介绍,我希望从现在起读者可以毫不费力地读懂下面几章的代码。

K-Means 算法

K-Means 是一种无监督算法,可以解决聚类问题。 它的过程遵循一种简单易行的方法,通过一定数量的簇(假设k簇)对给定数据集进行聚类。 簇内的数据点是同构的,不同簇的点是异构的,这意味着子集中的所有元素与其余元素相比更为相似。

算法的结果是一组K个点,称为质心,它们是所得的不同组的焦点,以及点集的标签,这些点分配给其中一个簇。 簇内的所有点与质心的距离都比任何其他质心更近。

如果我们想要直接最小化误差函数(所谓的 NP-hard 问题),那么簇的生成是一个计算上很昂贵的问题。因此,已经创建了一些算法,通过启发式在局部最优中快速收敛。 最常用的算法使用迭代优化技术,它在几次迭代中收敛。

一般来讲,这种技术有三个步骤:

  • 初始步骤(步骤 0):确定K个质心的初始集合。
  • 分配步骤(步骤 1):将每个观测值分配到最近的组。
  • 更新步骤(步骤 2):计算每个新组的新质心。

有几种方法可以确定初始K质心。 其中一个是在数据集中随机选择K个观测值并将它们视为质心;这是我们将在我们的示例中使用的那个。

分配(步骤 1)和更新(步骤 2)的步骤在循环中交替,直到认为算法已经收敛为止,这可以是,例如,当点到组的分配不再改变的时候。

由于这是一种启发式算法,因此无法保证它收敛于全局最优,结果取决于初始组。 因此,由于算法通常非常快,通常使用不同的初始质心值重复执行多次,然后权衡结果。

要在 TensorFlow 中开始编写 K-means 的示例,我建议首先生成一些数据作为测试平台。 我建议做一些简单的事情,比如在 2D 空间中随机生成 2,000 个点,遵循二维正态分布来绘制一个空间,使我们能够更好地理解结果。 例如,我建议使用以下代码:

  1. num_puntos = 2000
  2. conjunto_puntos = []
  3. for i in xrange(num_puntos):
  4. if np.random.random() > 0.5:
  5. conjunto_puntos.append([np.random.normal(0.0, 0.9), np.random.normal(0.0, 0.9)])
  6. else:
  7. conjunto_puntos.append([np.random.normal(3.0, 0.5), np.random.normal(1.0, 0.5)])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

正如我们在前一章中所做的那样,我们可以使用一些 Python 图形库来绘制数据。 我建议像以前一样使用 matplotlib,但这次我们还将使用基于 matplotlib 的可视化包 Seaborn 和数据操作包 pandas,它允许我们使用更复杂的数据结构。

如果未安装这些软件包,则必须先使用pip执行此操作,然后才能运行以下代码。

要显示随机生成的点,我建议使用以下代码:

  1. import matplotlib.pyplot as plt
  2. import pandas as pd
  3. import seaborn as sns
  4. df = pd.DataFrame({"x": [v[0] for v in conjunto_puntos],
  5. "y": [v[1] for v in conjunto_puntos]})
  6. sns.lmplot("x", "y", data=df, fit_reg=False, size=6)
  7. plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

此代码生成二维空间中的点图,如下面的截图所示:

在 TensorFlow 中实现的 k-means 算法将上述点分组,例如在四个簇中,可能像这样(基于 Shawn Simister 在他的博客中展示的模型 [21]):

  1. import numpy as np
  2. vectors = tf.constant(conjunto_puntos)
  3. k = 4
  4. centroides = tf.Variable(tf.slice(tf.random_shuffle(vectors),[0,0],[k,-1]))
  5. expanded_vectors = tf.expand_dims(vectors, 0)
  6. expanded_centroides = tf.expand_dims(centroides, 1)
  7. assignments = tf.argmin(tf.reduce_sum(tf.square(tf.sub(expanded_vectors, expanded_centroides)), 2), 0)
  8. means = tf.concat(0, [tf.reduce_mean(tf.gather(vectors, tf.reshape(tf.where( tf.equal(assignments, c)),[1,-1])), reduction_indices=[1]) for c in xrange(k)])
  9. update_centroides = tf.assign(centroides, means)
  10. init_op = tf.initialize_all_variables()
  11. sess = tf.Session()
  12. sess.run(init_op)
  13. for step in xrange(100):
  14. _, centroid_values, assignment_values = sess.run([update_centroides, centroides, assignments])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

我建议读者使用以下代码检查assignment_values张量中的结果,该代码生成像上面那样的图:

  1. data = {"x": [], "y": [], "cluster": []}
  2. for i in xrange(len(assignment_values)):
  3. data["x"].append(conjunto_puntos[i][0])
  4. data["y"].append(conjunto_puntos[i][1])
  5. data["cluster"].append(assignment_values[i])
  6. df = pd.DataFrame(data)
  7. sns.lmplot("x", "y", data=df, fit_reg=False, size=6, hue="cluster", legend=False)
  8. plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

截图以及我的代码执行结果如下图所示:

新的组

我假设读者可能会对上一节中介绍的 K-means 代码感到有些不知所措。 好吧,我建议我们一步一步详细分析它,特别是观察涉及的张量以及它们在程序中如何转换。

首先要做的是将所有数据移到张量。 在常数张量中,我们使初始点保持随机生成:

vectors = tf.constant(conjunto_vectors)
  • 1

按照上一节中介绍的算法,为了开始我们必须确定初始质心。 随着我前进,一个选项可能是,从输入数据中随机选择K个观测值。 一种方法是使用以下代码,它向 TensorFlow 表明,它必须随机地打乱初始点并选择前K个点作为质心:

  1. k = 4
  2. centroides = tf.Variable(tf.slice(tf.random_shuffle(vectors),[0,0],[k,-1]))
  • 1
  • 2

K个点存储在 2D 张量中。 要知道这些张量的形状,我们可以使用tf.Tensor.get_shape()

  1. print vectors.get_shape()
  2. print centroides.get_shape()
  3. TensorShape([Dimension(2000), Dimension(2)])
  4. TensorShape([Dimension(4), Dimension(2)])
  • 1
  • 2
  • 3
  • 4
  • 5

我们可以看到vectors是一个数组,D0 维包含 2000 个位置,每个位置一个向量,D1 的位置是每个点x, y。 相反,centroids是一个矩阵,维度 D0 有四个位置,每个质心一个位置,D1 和vectors相同。

接下来,算法进入循环。 第一步是为每个点计算其最接近的质心,根据平方欧几里德距离 [22](只能在我们想要比较距离时使用):

为了计算该值,使用tf.sub(vectors, centroides。 我们应该注意到,虽然减法的两个张量都有 2 个维度,但它们在一个维度上大小不同(维度 D0 为 2000 和 4),实际上它们也代表不同的东西。

为了解决这个问题,我们可以使用之前讨论过的一些函数,例如tf.expand_dims,以便在两个张量中插入一个维度。 目的是将两个张量从 2 维扩展到 3 维来使尺寸匹配,以便执行减法:

  1. expanded_vectors = tf.expand_dims(vectors, 0)
  2. expanded_centroides = tf.expand_dims(centroides, 1)
  • 1
  • 2

tf.expand_dims在每个张量中插入一个维度;在vectors张量的第一维(D0),以及centroides张量的第二维(D1)。 从图形上看,我们可以看到,在扩展后的张量中,每个维度具有相同的含义:

它似乎得到了解决,但实际上,如果你仔细观察(在插图中概述),在每种情况下都有大小无法确定的些维度。 请记住,使用get_shape()函数我们可以发现:

  1. print expanded_vectors.get_shape()
  2. print expanded_centroides.get_shape()
  • 1
  • 2

输出如下:

  1. TensorShape([Dimension(1), Dimension(2000), Dimension(2)])
  2. TensorShape([Dimension(4), Dimension(1), Dimension(2)])
  • 1
  • 2

使用 1 表示没有指定大小。

但我已经展示 TensorFlow 允许广播,因此tf.sub函数能够自己发现如何在两个张量之间将元素相减。

直观地,并且观察先前的附图,我们看到两个张量的形状匹配,并且在这些情况下,两个张量在一定维度上具有相同的尺寸。 这些数学,如 D2 维度所示。 相反,在维度 D0 中只有expanded_centroides的定义大小。

在这种情况下,如果我们想要在此维度内对元素执行减法,则 TensorFlow 假定expanded_vectors张量的维度 D0 必须是相同的大小。

对于expended_centroides张量的维度 D1 的大小也是如此,其中 TensorFlow 推导出expanded_vectors张量的尺寸 D1 的大小。

因此,在分配步骤(步骤 1)中,算法可以用 TensorFlow 代码的这四行表示,它计算平方欧几里德距离:

  1. diff=tf.sub(expanded_vectors, expanded_centroides)
  2. sqr= tf.square(diff)
  3. distances = tf.reduce_sum(sqr, 2)
  4. assignments = tf.argmin(distances, 0)
  • 1
  • 2
  • 3
  • 4

而且,如果我们看一下张量的形状,我们会看到它们分别对应diffsqrdistanceassign,如下所示:

  1. TensorShape([Dimension(4), Dimension(2000), Dimension(2)])
  2. TensorShape([Dimension(4), Dimension(2000), Dimension(2)])
  3. TensorShape([Dimension(4), Dimension(2000)])
  4. TensorShape([Dimension(2000)])
  • 1
  • 2
  • 3
  • 4

也就是说,tf.sub函数返回了张量dist,其中包含质心和向量的坐标的差(维度 D1 表示数据点,D0 表示质心,每个坐标x, y在维度 D2 中表示)。

sqr张量包含它们的平方。 在dist张量中,我们可以看到它已经减少了一个维度,它在tf.reduce_sum函数中表示为一个参数。

我用这个例子来解释 TensorFlow 提供的几个操作,它们可以用来执行减少张量维数的数学运算,如tf.reduce_sum。在下表中,你可以找到最重要的操作摘要。

操作描述
tf.reduce_sum沿一个维度计算元素总和
tf.reduce_prod沿一个维度计算元素的乘积
tf.reduce_min沿一个维度计算元素最小值
tf.reduce_max沿一个维度计算元素最大值
tf.reduce_mean沿一个维度计算元素平均值

最后,使用tf.argmin实现分配,它返回张量的某个维度的最小值的索引(在我们的例子中是 D0,记得它是质心)。 我们还有tf.argmax操作:

手术描述
tf.argmin沿某个维度返回最小值的索引
tf.argmax沿某个维度返回最大值的索引

事实上,上面提到的 4 条语句可以在一行代码中汇总,正如我们在上一节中看到的那样:

assignments = tf.argmin(tf.reduce_sum(tf.square(tf.sub(expanded_vectors, expanded_centroides)), 2), 0)
  • 1

但无论如何,内部的tensors,以及它们定义为节点和执行的内部图的操作,就像我们之前描述的那样。

计算新的质心

在那段代码中,我们可以看到means张量是k张量的连接结果,它们对应属于每个簇的每个点的平均值。

接下来,我将评论每个 TensorFlow 操作,这些操作涉及计算属于每个簇的每个点的平均值 [23]。

  • 使用equal,我们可以得到布尔张量(Dimension(2000)),它(使用true)表示assignments张量K个簇匹配的位置,当时我们正在计算点的平均值。
  • 使用where构造一个张量(Dimension(1) x Dimension(2000)),带有布尔张量中值为true的位置,布尔张量作为参数接收的布尔张量
  • reshape构造张量(Dimension(2000) x Dimension(1)),其中vectors张量内的点的索引属于簇c
  • gather构造张量(Dimension(1) x Dimension(2000)),它收集形成簇c的点的坐标。
  • 使用reduce_mean,构造张量Dimension(1) x Dimension(2),其中包含属于簇c的所有点的平均值。

无论如何,如果读者想要深入研究代码,正如我常说的那样,你可以在 TensorFlow API 页面上找到有关这些操作的更多信息,以及非常具有说明性的示例 [24]。

图表执行

最后,我们必须描述上述代码中,与循环相对应的部分,以及使用means张量的新值更新质心的部分。

为此,我们需要创建一个操作,它将means张量的值分配到质心中,而不是在执行操作run()时,更新的质心的值在循环的下一次迭代中使用:

update_centroides = tf.assign(centroides, means)
  • 1

在开始运行图之前,我们还必须创建一个操作来初始化所有变量:

init_op = tf.initialize_all_variables()
  • 1

此时一切准备就绪。 我们可以开始运行图了:

  1. sess = tf.Session()
  2. sess.run(init_op)
  3. for step in xrange(num_steps):
  4. _, centroid_values, assignment_values = sess.run([update_centroides, centroides, assignments])
  • 1
  • 2
  • 3
  • 4
  • 5

在此代码中,每次迭代中,更新每个初始点的质心和新的簇分配。

请注意,代码指定了三个操作,它必须查看run()调用的执行,并按此顺序运行。 由于要搜索三个值,sess.run()会在训练过程中返回元素为三个 numpy 数组的数据结构,内容为相应张量。

由于update_centroides是一个结果是不返回的参数的操作,因此返回元组中的相应项不包含任何内容,因此被排除,用_来表示 [25] 。

对于其他两个值,质心和每个簇的分配点,我们有兴趣在完成所有num_steps次迭代后在屏幕上显示它们。

我们可以使用简单的打印。 输出如下:

  1. print centroid_values
  2. [[ 2.99835277e+00 9.89548564e-01]
  3. [ -8.30736756e-01 4.07433510e-01]
  4. [ 7.49640584e-01 4.99431938e-01]
  5. [ 1.83571398e-03 -9.78474259e-01]]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我希望读者的屏幕上有类似的值,因为这表明他已成功执行了本书这一章中展示的代码。

我建议读者在继续之前尝试更改代码中的任何值。 例如num_points,特别是k的数量,并使用生成图的先前代码查看它如何更改assignment_values张量中的结果。

请记住,为了便于测试本章所述的代码,可以从 Github [26] 下载。 包含此代码的文件名是Kmeans.py

在本章中,我们展示了 TensorFlow 的一些知识,特别是基本数据结构张量,它来自实现 KMeans 聚类算法的 TensorFlow 代码示例。

有了这些知识,我们就可以在下一章中逐步使用 TensorFlow 构建单层神经网络。

4. TensorFlow 中的单层神经网络

在前言中,我评论说深度学习的一个常见用途包括模式识别。 考虑到这一点,就像初学者通过在屏幕上打印“Hello World”开始学习编程语言一样,在深度学习中,我们首先要识别手写数字。

在本章中,我将介绍如何在 TensorFlow 中逐步构建具有单个层的神经网络。 这个神经网络将识别手写数字,它基于 TensorFlow 的初学者教程 [27] 的不同示例之一。

鉴于本书的介绍风格,我选择引导读者,同时通过示例的某些步骤简化了一些概念和理论上的原因。

如果读者在阅读本章后有兴趣了解这个示例的理论概念,我建议阅读神经网络和深度学习 [28],可在线获取,它介绍了这个例子,但深入研究理论概念。

MNIST 数据集

MNIST 数据集由一组包含手写数字的黑白图像组成,包含60,000 多个用于训练模型的示例,以及 10,000 个用于测试它的示例。 MNIST 数据集可以在 MNIST 数据库 [29] 中找到。

这个数据集非常适合大多数开始在实例上进行模式识别的人,而不必花时间进行数据预处理或格式化,这是处理图像时的两个非常重要的步骤,但时间很长。

黑白图像(二值)已经标准化为20×20像的素图像,保留了宽高比。 对于这种情况,我们注意到图像包含灰色像素 [30],是归一化算法的结果(将所有图像的分辨率降低到最低级别之一)。 之后,通过计算质心并将其移动到帧的中心,图像以28×28像素帧为中心。 图像类似于此处显示的图像:

此外,本例所需的学习类型是监督学习;图像用它们代表的数字标记。 这是最常见的机器学习形式。

在这种情况下,我们首先收集数字图像的大数据集,每个数字都用其值标记。 在训练期间,模型接受图像并以得分向量的形式产生输出,每个类别一个得分。 我们希望所需类别在所有类别中得分最高,但这在训练之前不太可能发生。

我们计算一个目标函数来衡量输出分数和所需分数模式之间的误差(正如我们在前面章节中所做的那样)。 然后,模型修改其内部可调参数,称为权重,来减少此误差。 在典型的深度学习系统中,可能存在数亿个这样的可调节权重,以及用于训练机器的数亿个标记示例。 我们将考虑一个较小的例子,来帮助理解这种模型的工作原理。

要轻松下载数据,你可以使用从 Google 的网站 [32] 获取脚本input_data.py[31],但它为你上传到的这本书的 github 上。 只需将代码input_data.py下载到使用 TensorFlow 编写神经网络的同一工作目录中。 在你的应用程序中,你只需要按以下方式导入和使用:

  1. import input_data
  2. mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
  • 1
  • 2

执行这两条指令后,你将在mnist.train中获得完整的训练数据集,并在mnist.test中设置测试数据。 如前所述,每个元素由一个图像组成,标记为xs,并且其对应的标签ys,以便更容易表达处理代码。 请记住,所有数据集,训练和测试集都包含xsys;此外,训练图像通过mnist.train.images引用,训练标签通过mnist.train.labels引用。

如前所述,图像由28×28像素形成,并且可以表示为数字矩阵。 例如,数字 1 的图像之一可以表示为:

其中每个位置表示 0 到 1 之间每个像素的缺失度。 该矩阵可以表示为28×28 = 784个数的数组。 实际上,图像已经变换为 784 维度的向量空间中的一堆点中。 只是当我们将结构减少到 2 维时,我们可能会丢失部分信息,对于某些计算机视觉算法,这可能会影响他们的结果,但对于本教程中使用的最简单的方法,这不会是一个问题。

总而言之,我们在 2D 中拥有张量mnist.train.images,其中调用函数get_shape()表示其形状:

TensorShape([Dimension(60000), Dimension(784)])
  • 1

第一维索引每个图像和第二维是每个像素。 张量的每个元素是 0 到 1 之间的每个像素的强度。

此外,我们有 0 到 9 之间的数字形式的标签,表示每个图像代表哪个数字。 在这个例子中,我们将标签表示为 10 个位置的向量,其中所表示数字的对应位置是 1 而其余为 0。 所以mnist.train.labels es是形如TensorShape([Dimension(60000), Dimension10)])的张量。

人造神经元

虽然本书并未关注神经网络的理论概念,但简要而直观地介绍神经元如何学习训练数据,将有助于读者了解正在发生的事情。 那些已经了解该理论并且只是寻求如何使用 TensorFlow 的读者可以跳过本节。

让我们看一个神经元如何学习的简单但说明性的例子。 假设有一组标记为“方形”和“圆形”的点。 给定一个新的点X,我们想知道对应哪个标签:

通常的近似可能是绘制一条划分两组的直线并将其用作分类器:

在这种情况下,输入数据由形状为(x, y)的向量表示,表示此二维空间中的坐标,并且我们的函数返回“0”或“1”(线上方或下方)来了解如何将其归类为“方形”或“圆形”。 在数学上,正如我们在线性回归章节中所学到的,“直线”(分类器)可以表示为y = W * x + b

推广时,神经元必须学习权重W(与输入数据X维度相同)和偏移量b(在神经元中称为偏置),来学习如何分类这些值。 利用它们,神经元将计算权重输入XW的加权和,并添加偏移b;最后神经元将应用非线性“激活”函数来产生“0”或“1”的结果。

神经元的功能可以更正式地表示为:

在为我们的神经元定义了这个函数后,我们想知道神经元如何从带有“方块”和“圆圈”的标记数据中学习参数Wb,以便稍后标记新点X

第一种方法可以类似于我们对线性回归所做的,即用已知的标记数据喂养神经元,并将获得的结果与真实的结果进行比较。 然后,在迭代时,调整Wb来使误差最小化,如第 2 章中线性回归线所示。

一旦我们得到Wb参数,我们就可以计算加权和,现在我们需要函数将存储在z中的结果转换为01。 有几个可用的激活函数,对于这个例子,我们可以使用一个名为 sigmoid [33] 的流行函数,返回 0 到 1 之间的实数值。

看看公式,我们发现它将倾向于返回接近 0 或 1 的值。 如果输入z足够大且为正,则exp(-z)为零,然后y为 1。 如果输入z足够大且为负,则exp(-z)也会变为大正数,因此分母变大,最终y变为 0。 如果我们绘制函数,它将如下所示:

从这里我们已经介绍了如何定义神经元,但神经网络实际上是以不同方式互相连接,并使用不同激活函数的神经元组合。 鉴于本书的范围,我不会涉及神经网络的所有扩展,但我向你保证它真的令人兴奋。

只是提到神经网络的一个特定情况(其中第 5 章基于),神经元组织为层的形式,其中下层(输入层)接收输入,上层(输出层)生成响应值。 神经网络可以有几个中间层,称为隐藏层。 表示这种情况的直观方式是:

在这些网络中,每层的神经元与前一层的神经元通信来接收信息,然后将其结果传递给下一层的神经元。

如前所述,除了Sigmoid之外还有更多的激活函数,每个激活函数具有不同的属性。例如,当我们想要在输出层将数据分类为两个以上的类时,我们可以使用 Softmax [34] 激活函数,它是 sigmoid 函数的泛化。 Softmax 能够获得每个类的概率,因此它们的和为 1,最可能的结果是概率最高的结果。

一个简单的例子:Softmax

请记住,要解决的问题是,给定输入图像,我们得到它属于某个数字的概率。 例如,我们的模型可以预测,图像 80% 是“9”,但是有 5% 的机会为“8”(由于可疑性较低的痕迹),并且还给出,一定的低概率为任何其他数字。 识别手写数字存在一些不确定性,我们无法以 100% 的置信度识别数字。 在这种情况下,概率分布使我们更好地了解我们对预测的信心。

因此,我们有一个输出向量,其中包含不同输出标签的概率分布,这是多余的。 这是一个具有 10 个概率值的向量,每个概率值对应于 0 到 9 的每个数字,并且所有概率总和为 1。

如前所述,我们通过使用激活函数为 softmax 的输出层来实现此目的。 具有 softmax 函数的神经元的输出,取决于其层的其他神经元的输出,因为它们的所有输出必须总和为 1。

softmax 函数有两个主要步骤:首先,计算属于某个标签的图像的“证据”,然后将证据转换为每个可能标签的概率。

归属的证据

测量某个图像属于特定类别/标签的证据,通常的近似是计算像素强度的加权和。 当高强度的像素恰好不在给定类中时,该权重为负,如果该像素在该类中频繁出现,则该权重为正。

让我们看一个图形示例:假设一个数学“0”的学习模型(我们将看到以后如何学习)。 此时,我们将模型定义为“某事物”,其中包含了解数字是否属于特定类的信息。 在这种情况下,我们选择了如下所示的模型,其中红色(或 b/n 版本的亮灰色)代表负例(也就是,减少对“0”中存在的那些像素的支持),而蓝色(b/n 版的深灰色)代表了正例。看看它:

想象一下28×28像素的白纸,并画上“0”。 通常我们的零将绘制在蓝色区域中(请记住,我们在20×20绘图区域周围留下了一些空间,稍后将其居中)。

很明显,如果我们的绘图穿过红色区域,很可能我们没有绘制零。 因此,使用一种度量标准,奖励那些踩到蓝色区域的像素,并惩罚那些踩到红色区域的像素,似乎是合理的。

现在考虑“3”:很明显,我们的模型的“0”的红色区域将惩罚它为“0”的概率。 但是如果参考模型是下面的那个,通常形成“3”的像素将遵循蓝色区域; “0”的绘制也会进入红色区域。

我希望看到这两个例子的读者理解,所解释的近似如何让我们估计哪张图代表哪个数字。

下图显示了从 MNIST 数据集中学习的十个不同标签/类的示例(从 Tensorflow [35] 的示例中提取)。 请记住,红色(亮灰色)表示负权重,蓝色(深灰色)表示正值。

以更正式的方式,我们可以说给出输入x的类i的证据表示为:

其中i表示类(在我们的情况下,介于 0 和 9 之间),j是对输入图像求和的索引。 最后,Wi表示上述权重。

请记住,一般来说,模型还包括一个表示偏置的额外参数,增加了一些基本不确定性。 在我们的情况下,公式最终就像这样:

对于每个i(在 0 和 9 之间),我们得到 784 个元素(28×28)的矩阵Wi,其中每个元素j乘以输入图像的相应分量j,共有 784 个分量,然后加上bi。矩阵演算和索引的图形视图是这样的:

归属概率

我们评论说,第二步是计算概率。 具体来说,我们使用 softmax 函数将证据总和转换为预测概率,表示为y

请记住,输出向量必须是和为 1 的概率函数。 为了标准化每个成分,softmax 函数使用每个输入的指数值,然后按如下方式对它们进行标准化:

使用指数时获得的效果是权重的乘法效应。 此外,当一个类的证据很小时,这个类的支持由之前权重的一小部分减少。 此外,softmax 对权重进行归一化,使它们总和为 1,从而产生概率分布。

这种函数的一个有趣的事实是,好的预测将有一个接近 1 的输出值,而所有其他输出将接近零;在弱预测中,某些标签可能会显示类似的支持。

在 TensorFlow 中编程

在简要描述了算法做了什么来识别数字之后,我们可以在 TensorFlow 中实现它。 为此,我们可以快速了解张量应如何存储我们的数据和模型参数。 为此,下图描述了数据结构及其关系(来帮助读者轻松回忆我们的每个问题):

首先,我们创建两个变量来包含权重W和偏置b

  1. W = tf.Variable(tf.zeros([784,10]))
  2. b = tf.Variable(tf.zeros([10]))
  • 1
  • 2

这些变量是使用tf.Variable函数和变量的初始值创建的;在这种情况下,我们用包含零的常数张量初始化张量。

我们看到W的形状为[Dimension(784), Dimension(10)],由其参数定义,常数张量tf.zerosW一样为[784,10]。偏置b也是一样,由其参数将形状规定为[Dimension(10)]

矩阵W具有该大小,因为我们想要为 10 个可能的数字中的每一个乘以 784 个位置的图像向量,并在与b相加之后产生一定数量的证据。

在使用 MNIST 进行研究的情况下,我们还创建了二维张量来保存x点的信息,使用以下代码行​​:

x = tf.placeholder("float", [None, 784])
  • 1

张量x将用于存储 MNIST 图像,作为 784 个浮点向量(我们使用None指示维度可以是任何大小;在我们的例子中它将等于学习过程中包含的元素数量)。

现在我们定义了张量,我们可以实现我们的模型。 为此,TensorFlow 提供了几个操作,即tf.nn.softmax(logits, name=None)。它是其中一个可用的操作,实现了前面描述的 softmax 函数。 参数必须是张量,并且名称可选。 该函数返回类型和形状与传递的参数张量相同的张量。

在我们的例子中,我们为这个函数提供了图像向量x乘以权重矩阵W加上b的结果张量:

y = tf.nn.softmax(tf.matmul(x,W) + b)
  • 1

一旦指定了模型实现,我们就可以使用迭代训练算法,指定必要的代码来获得权重W和偏置b。 对于每次迭代,训练算法获得训练数据,应用神经网络并将获得的结果与预期结果进行比较。

要确定模型何时足够好,我们必须定义“足够好”的含义。 正如在前面的章节中所看到的,通常的方法是定义相反的东西:模型使用损失函数的“坏”的程度。 在这种情况下,目标是获得使函数最小的Wb的值,它指示模型“坏”的程度。

结果输出与训练数据的预期输出之间的误差有不同的度量标准。 一个常见的度量是均方误差或平方欧几里德距离,这是以前见过的。 尽管如此,一些研究在神经网络中为此目的提出了其他指标,例如在我们的例子中使用的交叉熵误差。 此度量标准的计算方式如下:

其中y是概率的预测分布,y'是从训练数据集的标签中获得的实际分布。 我们不会详细讨论交叉熵背后的数学及其在神经网络中的位置,因为它远比本书的预期范围复杂得多;只是表明当两个分布相同时有最小值。 同样,如果读者想要了解此函数的细节,我们建议阅读神经网络和深度学习 [36]。

要实现交叉熵度量,我们需要一个新的占位符来表示正确的标签:

y_ = tf.placeholder("float", [None,10])
  • 1

用这个占位符,我们可以使用以下代码行实现交叉熵,代表我们的损失函数:

cross_entropy = -tf.reduce_sum(y_*tf.log(y))
  • 1

首先,我们使用 TensorFlow 中的内置函数tf.log()计算每个元素y的对数,然后我们将它们乘以每个y_元素。 最后,使用tf.reduce_sum,我们对张量的所有元素求和(稍后我们将看到图像以批量的形式访问,在这种情况下,交叉熵的值对应于图像批量y而不是单个图像)。

在迭代中,一旦确定了样本的误差,我们必须更正模型(在我们的例子中是修改参数Wb)来减少下一次迭代中计算和预期输出之间的差异。

最后,它仍然只是指定了这个迭代式最小化过程。 在神经网络中有几种用于此目的的算法;我们将使用反向传播(误差向后传播)算法,并且如其名称所示,它向后传播在输出处获得的误差,来重新计算W的权重,尤其是对于多层神经网络很重要。

该方法与先前看到的梯度下降方法一起使用,该方法使用交叉熵损失函数,允许我们计算每次迭代时参数必须改变多少,以便在每个时刻使用可用的本地信息来减少误差。 在我们的例子中,直观地说,它包括在每次迭代时稍微改变权重W(这一点由学习率超参数表示,表示变化的速度)来减少错误。

由于在我们的例子中我们只有一层神经网络,我们不会进入反向传播方法。 只需记得 TensorFlow 知道整个计算图,允许它应用优化算法来找到训练模型的训练函数的正确梯度。

因此,在我们使用 MNIST 图像的示例中,以下代码行表明我们使用反向传播算法和梯度下降算法来最小化交叉熵,学习率为 0.01:

train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
  • 1

到这里之后,我们已经指定了所有问题,我们可以通过实例化tf.Session()来开始计算,它负责在系统,CPU 或 GPU 上的可用设备中执行 TensorFlow 操作:

sess = tf.Session()
  • 1

接下来,我们可以执行初始化所有变量的操作:

sess.run(tf.initialize_all_variables())
  • 1

从现在开始,我们可以开始训练我们的模型。 执行时,train_step的返回参数将梯度下降应用于所涉及的参数。 因此,可以通过重复执行train_step来实现模型的训练。 假设我们要迭代 1000 次train_step;我们必须指定以下代码行:

  1. for i in range(1000):
  2. batch_xs, batch_ys = mnist.train.next_batch(100)
  3. sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
  • 1
  • 2
  • 3

循环内的第一行指定,对于每次迭代,挑选从训练数据集中随机采样的 100 个数据输入的批量。 我们可以在每次迭代时使用所有训练数据,但为了使第一个示例更加灵活,我们每次都使用一个小样本。 第二行表示之前获得的输入必须提供给相应的占位符。

最后,基于梯度下降的机器学习算法可以利用 TensorFlow 自动微分的功能。 TensorFlow 用户只需定义预测模型的计算架构,将其与目标函数组合,然后只需添加数据即可。

TensorFlow 已经管理了学习过程背后的相关微分。 当执行minimize()方法时,TensorFlow 识别损失函数所依赖的变量集,并计算每个变量的梯度。 如果你想知道如何实现微分,可以查看ops/gradients.py文件 [37]。

模型评估

训练后必须评估模型,来查看有多“好”(或多“坏”)。 例如,我们可以计算预测中命中和未命中的百分比,看看哪些例子是正确预测的。 在前面的章节中,我们看到tf.argmax(y, 1)函数,根据张量的给定轴返回最高值的索引。 实际上,tf.argmax(y, 1)是对于每个输入的,概率最高的标签,而 tf.argmax(y_, 1)是正确标签。 使用tf.equal方法,我们可以比较我们的预测是否与正确的标签重合:

correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
  • 1

指令返回布尔列表。 要确定哪些预测部分是正确的,我们可以将值转换为数值变量(浮点)并执行以下操作:

accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
  • 1

例如,[True, False, True, True]将变为[1, 0, 1, 1],平均值将为 0.75,表示准确率。 现在我们可以使用mnist.test作为feed_dict参数来查询我们的测试数据集的准确率:

print sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})
  • 1

我的大约为 91%。 这些结果好吗? 我认为它们太棒了,因为这意味着读者已经能够使用 TensorFlow 编程并执行第一个神经网络。

另一个问题是其他模型可能提供更好的准确性,在下一章中介绍包含更多层的神经网络。

读者将在本书 github [38] 的文件RedNeuronalSimple.py中找到本章中使用的全部代码。 为了提供它的全局视图,我将把它放在一起:

  1. import input_data
  2. mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
  3. import tensorflow as tf
  4. x = tf.placeholder("float", [None, 784])
  5. W = tf.Variable(tf.zeros([784,10]))
  6. b = tf.Variable(tf.zeros([10]))
  7. matm=tf.matmul(x,W)
  8. y = tf.nn.softmax(tf.matmul(x,W) + b)
  9. y_ = tf.placeholder("float", [None,10])
  10. cross_entropy = -tf.reduce_sum(y_*tf.log(y))
  11. train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
  12. sess = tf.Session()
  13. sess.run(tf.initialize_all_variables())
  14. for i in range(1000):
  15. batch_xs, batch_ys = mnist.train.next_batch(100)
  16. sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
  17. correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
  18. accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
  19. print sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})
  • 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

5. TensorFlow 中的多层神经网络

在本章中,我将与读者一起编写一个简单的深度学习神经网络,该网络使用与前一章相同的 MNIST 数字识别问题。

随着我的前进,深度学习神经网络由叠在一起的多个层组成。 具体来说,在本章中我们将构建一个卷积网络,这是深度学习的典型例子。 卷扬神经网络由 Yann LeCunn 等人于 1998 年推出并推广。 这些卷积网络最近引领了图像识别领域的最新技术;例如:在我们的数字识别案例中,它们的准确度高于 99%。

在本章的其余部分,我将以示例代码为主,我将解释这些网络的两个最重要的概念:卷积和池化,而不输入参数的细节,鉴于本书的介绍性质。 但是,读者将能够运行所有代码,我希望它能让你了解卷积网络背后的通用思想。

卷积神经网络

卷积神经网络(也称为 CNN 或 CovNets)是深度学习的一个特例,并且在计算机视觉领域产生了重大影响。

CNN 的典型特征是它们几乎总是将图像作为输入,这产生了更有效的实现并且减少所需参数的数量。 让我们看看我们的 MNIST 数字识别示例:在读取 MNIST 数据并使用 TensorFlow 定义占位符之后,就像我们在上一个示例中所做的那样:

  1. import input_data
  2. mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
  3. import tensorflow as tf
  4. x = tf.placeholder("float", shape=[None, 784])
  5. y_ = tf.placeholder("float", shape=[None, 10])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我们可以重建输入数据图像的原始形状。 我们可以这样做:

x_image = tf.reshape(x, [-1,28,28,1])
  • 1

这里我们将输入形状更改为 4D 张量,第二维和第三维对应于图像的宽度和高度,而最后一维对应于颜色通道的数量,在这种情况下为 1。

通过这种方式,我们可以将神经网络的输入视为大小为28×28的二维空间,如图所示:

定义卷积神经网络有两个基本原则:滤波器和特征映射。 这些原则可以表示为特定的神经元分组,我们将很快看到。 但首先,鉴于它们在 CNN 中的重要性,我们将简要介绍这两个原则。

直觉上,我们可以说卷积层的主要目的是检测图像中的特征或视觉特征,考虑边缘,线条,颜色斑点等。 这是由我们刚刚讨论过的,连接输入层的隐藏层来处理的。 在我们感兴趣的 CNN 的案例中,输入数据没有完全连接到第一个隐藏层的神经元;这只发生在输入神经元中的一个小型局部空间中,输入神经元存储图像像素值。 这可以看作:

更确切地说,在给定的示例中,隐藏层的每个神经元与输入层的5×5小区域(因此是 25 个神经元)连接。

我们可以认为这是一个大小为5×5的窗口,它滑过包含输入图像的整个28×28大小的输入层。 窗口滑过整个神经元层。 对于窗口的每个位置,隐藏层中都有一个处理该信息的神经元。

我们可以通过假设窗口从图像的左上角开始来可视化;这将信息提供给隐藏层的第一个神经元。 然后窗口向右滑动一个像素;我们将这个5×5区域与隐藏层中的第二个神经元连接起来。 我们继续这样,直到整个空间从上到下,从左到右被窗口覆盖。

分析我们提出的具体案例,我们观察到,给定一个大小为28×28的输入图像和一个大小为5×5的窗口,在第一个隐藏层中产生了24×24的神经元,因为我们只能这样做,在触及输入图像的右下边缘之前,将窗口向下移动 23 次,向右移动 23 次。 这假设窗口每次只移动 1 个像素,因此新窗口与刚刚前进的旧窗口重叠。

但是,可以在卷积层中一次移动多于 1 个像素,该参数称为stride(步长)。 另一个扩展是用零(或其他值)填充边缘,以便窗口可以在图像的边缘上滑动,这可以产生更好的结果。 控制此功能的参数称为padding(填充)[39],你可以使用该参数确定填充的大小。 鉴于本书的介绍性质,我们不会进一步详细介绍这两个参数。

鉴于我们的研究案例,并遵循前一章的形式,我们将需要一个偏置值b和一个5×5的权重矩阵W来连接隐层和输入层的神经元。CNN的一个关键特性是,该权重矩阵W和偏置b在隐藏层中的所有神经元之间共享;我们对隐藏层中的神经元使用相同的Wb。 在我们的情况下,这是24×24(576)个神经元。 读者应该能够看到,与完全连接的神经网络相比,这大大减少了人们需要的权重参数。 具体而言,由于共享权重矩阵W,这从 14000(5x5x24x24)减少到仅 25(5x5)。

这个共享矩阵W和偏置b通常在 CNN 的上下文中称为核或过滤器。 这些过滤器类似于用于修饰图像的图像处理程序,在我们的例子中用于查找微分特征。 我建议查看 GIMP [40] 手册中的示例,以便了解卷积过程的工作原理。

矩阵和偏置定义了核。 核只检测图像中的某个相关特征,因此建议使用多个核,每个核对应我们想要检测的每个特征。 这意味着 CNN 中的完整卷积层由几个核组成。 表示几个核的常用方法如下:

第一个隐藏层由几个核组成。 在我们的例子中,我们使用 32 个核,每个核由5×5的权重矩阵W和偏置b定义,偏置b也在隐层的神经元之间共享。

为了简化代码,我定义了以下两个与权重矩阵W和偏置b相关的函数:

  1. def weight_variable(shape):
  2. initial = tf.truncated_normal(shape, stddev=0.1)
  3. return tf.Variable(initial)
  4. def bias_variable(shape):
  5. initial = tf.constant(0.1, shape=shape)
  6. return tf.Variable(initial)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在没有详细说明的情况下,习惯上用一些随机噪声初始化权重,偏置值略微为正。

除了我们刚才描述的卷积层之外,通常卷积层后面跟着一个所谓的池化层。 池化层简单地压缩来自卷积层的输出,并创建卷积层输出的信息的紧凑版本。 在我们的示例中,我们将使用卷积层的2×2区域,我们使用池化将它的数据汇总到单个点:

有几种方法可以执行池化来压缩信息;在我们的示例中,我们将使用名为最大池化的方法。 通过仅保留所考虑的2×2区域中的最大值来压缩信息。

如上所述,卷积层由许多核组成,因此,我们将分别对每个核应用最大池化。 通常,可以有多层池化和卷积:

这使24×24的卷积层结果,被对应12×12的最大池化层转换为12×12的空间,其中每个块来源于2×2的区域。 请注意,与卷积层不同,数据是平铺的,而不是由滑动窗口创建的。

直观上,我们可以解释最大池化,来确定特定特征是否存在于图像中的任何位置,特征的确切位置不如对于其他特征的相对位置重要。

模型的实现

在本节中,我将基于可在 TensorFlow [41] 网站上找到的高级示例(Deep MNIST for experts),提供编写 CNN 的示例代码。 正如我在开始时所说的那样,参数的许多细节需要处理和理论方法,比本书中给出的更详细。 因此,我将仅概述代码,而不涉及 TensorFlow 参数的许多细节。

正如我们已经看到的,我们必须为卷积和池化层定义几个参数。 我们将在每个维度中使用大小为 1 的步幅(这是滑动窗口的步长)和零填充模型。 我们将应用的池化是2×2的最大池化。 与上面类似,我建议使用以下两个通用函数来编写涉及卷积和最大池化的更清晰的代码。

  1. def conv2d(x, W):
  2. return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
  3. def max_pool_2x2(x):
  4. return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

现在是时候实现第一个卷积层,然后是池化层。 在我们的示例中,我们有 32 个过滤器,每个过滤器的窗口大小为5×5。 我们必须定义一个张量,来保持这个权重矩阵W的形状为[5,5,1,32]:前两个维度是窗口的大小,第三个是通道的数量,在我们的例子中为 1 。 最后一个定义了我们想要使用的过滤器数量。 此外,我们还需要为 32 个权重矩阵中的每一个定义偏置。 使用先前定义的函数,我们可以在 TensorFlow 中编写它,如下所示:

  1. W_conv1 = weight_variable([5, 5, 1, 32])
  2. b_conv1 = bias_variable([32])
  • 1
  • 2

ReLU(整流线性单元)激活函数最近成为深度神经网络隐藏层中使用的默认激活函数。 这个简单的函数返回max(0, x),因此它为负值返回 0,否则返回x。 在我们的示例中,我们将在卷积层之后的隐藏层中使用此激活函数。

我们编写的代码首先将卷积应用于输入图像x_image,它在 2D 张量W_conv1中,返回图像卷积的结果,然后加上偏置,最终应用 ReLU 激活函数。 最后一步,我们将最大池化应用于输出:

  1. h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
  2. h_pool1 = max_pool_2x2(h_conv1)
  • 1
  • 2

在构建深度神经网络时,我们可以将多个层叠在一起。 为了演示如何执行此操作,我将创建一个带有 64 个过滤器和5×5窗口的辅助卷积层。 在这种情况下,我们必须传递 32 作为我们需要的通道数,因为它是前一层的输出大小:

  1. W_conv2 = weight_variable([5, 5, 32, 64])
  2. b_conv2 = bias_variable([64])
  3. h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
  4. h_pool2 = max_pool_2x2(h_conv2)
  • 1
  • 2
  • 3
  • 4

由于我们将5×5窗口应用于步长为 1 的12×12空间,因此卷积的结果输出具有8×8的维数。 下一步是将一个全连接的层添加到8×8输出,然后将其输入到最后的 softmax 层,就像我们在前一章中所做的那样。

我们将使用 1024 个神经元的一层,允许我们处理整个图像。 权重和偏置的张量如下:

  1. W_fc1 = weight_variable([8 * 8 * 64, 1024])
  2. b_fc1 = bias_variable([1024])
  • 1
  • 2

请记住,张量的第一个维度表示来自第二个卷积层的大小为8x8的 64 个过滤器,而第二个参数是层中神经元的数量,我们可以自由选择(在我们的例子中是 1024)。

现在,我们想将张量展开为向量。 我们在前一章中看到,softmax 需要将向量形式的展开图像作为输入。 这通过将权重矩阵W_fc1与展开向量相乘,加上偏置b_fc1,再应用 ReLU 激活函数来实现:

  1. h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
  2. h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
  • 1
  • 2
  • 3

下一步将使用称为 dropout 的技术减少神经网络中的有效参数量。 这包括删除节点及其传入和传出连接。 丢弃和保留哪些神经元是随机决定的。 为了以一致的方式执行此操作,我们将在代码中为丢弃或保留的神经元分配概率。

在没有太多细节的情况下,dropout 降低了模型的过拟合风险。 当隐藏层具有大量神经元并因此可以产生非常富有表现力的模型时,这可能发生;在这种情况下,可能会对随机噪声(或误差)建模。 这被称为过拟合,如果与输入的维度相比,模型具有大量参数,则更有可能。 最好是避免这种情况,因为过拟合的模型具有较差的预测表现。

在我们的模型中,我们应用 dropout,它包括在最终的 softmax 层之前使用 dropout 函数 tf.nn.dropout。 为此,我们构造一个占位符来存储在 dropout 期间保留神经元的概率:

  1. keep_prob = tf.placeholder("float")
  2. h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
  • 1
  • 2

最后,我们将 softmax 层添加到我们的模型中,就像前一章中所做的那样。 请记住,sofmax 返回输入属于每个类的概率(在我们的例子中为数字),以便总概率加起来为 1。 softmax 层代码如下:

  1. W_fc2 = weight_variable([1024, 10])
  2. b_fc2 = bias_variable([10])
  3. y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
  • 1
  • 2
  • 3
  • 4

模型的训练和评估

我们现在通过调整卷积层和及全连接层中的所有权重,来准备训练我们刚刚定义的模型,并获得我们的带标签的图像的预测。 如果我们想知道模型的执行情况,我们必须遵循上一章中的示例。

以下代码与前一章中的代码非常相似,但有一个例外:我们用 ADAM 优化器替换梯度下降优化器,因为该算法实现了不同的优化器,根据文献 [42],它具有某些优点。

我们还需要在feed_dict参数中包含附加参数keep_prob,该参数控制我们之前讨论过的 dropout 层的概率。

  1. cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))
  2. train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
  3. correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
  4. accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
  5. sess = tf.Session()
  6. sess.run(tf.initialize_all_variables())
  7. for i in range(20000):
  8. batch = mnist.train.next_batch(50)
  9. if i%100 == 0:
  10. train_accuracy = sess.run( accuracy, feed_dict={x:batch[0], y_: batch[1], keep_prob: 1.0})
  11. print("step %d, training accuracy %g"%(i, train_accuracy))
  12. sess.run(train_step,feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
  13. print("test accuracy %g"% sess.run(accuracy, feed_dict={ x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

与之前的模型一样,整个代码可以在本书的 Github 页面上找到,可以验证该模型的准确率达到 99.2%。

以下是使用 TensorFlow 构建,训练和评估深度神经网络的简要介绍。 如果读者设法运行提供的代码,他或她已经注意到该网络的训练时间明显长于前几章的训练时间;你可以想象,拥有更多层的网络需要花费更长的时间来训练。 我建议你阅读下一章,其中解释了如何使用 GPU 进行训练,这将减少你的训练时间。

本章的代码可以在本书 github 页面 [43] 的CNN.py中找到,用于研究目的的代码在下面:

  1. import input_data
  2. mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
  3. import tensorflow as tf
  4. x = tf.placeholder("float", shape=[None, 784])
  5. y_ = tf.placeholder("float", shape=[None, 10])
  6. x_image = tf.reshape(x, [-1,28,28,1])
  7. print "x_image="
  8. print x_image
  9. def weight_variable(shape):
  10. initial = tf.truncated_normal(shape, stddev=0.1)
  11. return tf.Variable(initial)
  12. def bias_variable(shape):
  13. initial = tf.constant(0.1, shape=shape)
  14. return tf.Variable(initial)
  15. def conv2d(x, W):
  16. return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
  17. def max_pool_2x2(x):
  18. return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
  19. W_conv1 = weight_variable([5, 5, 1, 32])
  20. b_conv1 = bias_variable([32])
  21. h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
  22. h_pool1 = max_pool_2x2(h_conv1)
  23. W_conv2 = weight_variable([5, 5, 32, 64])
  24. b_conv2 = bias_variable([64])
  25. h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
  26. h_pool2 = max_pool_2x2(h_conv2)
  27. W_fc1 = weight_variable([7 * 7 * 64, 1024])
  28. b_fc1 = bias_variable([1024])
  29. h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
  30. h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
  31. keep_prob = tf.placeholder("float")
  32. h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
  33. W_fc2 = weight_variable([1024, 10])
  34. b_fc2 = bias_variable([10])
  35. y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
  36. cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))
  37. train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
  38. correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
  39. accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
  40. sess = tf.Session()
  41. sess.run(tf.initialize_all_variables())
  42. for i in range(200):
  43. batch = mnist.train.next_batch(50)
  44. if i%10 == 0:
  45. train_accuracy = sess.run( accuracy, feed_dict={ x:batch[0], y_: batch[1], keep_prob: 1.0})
  46. print("step %d, training accuracy %g"%(i, train_accuracy))
  47. sess.run(train_step,feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
  48. print("test accuracy %g"% sess.run(accuracy, feed_dict={
  49. x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
  • 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

6. 并行

2015 年 11 月发布的第一个 TensorFlow 软件包,已准备好在具有可用 GPU 的服务器上运行,并同时在其中执行训练操作。 2016 年 2 月,更新添加了分布式和并行化处理的功能。

在这个简短的章节中,我将介绍如何使用 GPU。 对于那些想要了解这些设备如何工作的读者,有些参考文献将在上一节中给出。但是,鉴于本书的介绍性,我不会详细介绍分布式版本,但对于那些感兴趣的读者,一些参考将在上一节中给出。

带有 GPU 的执行环境

支持 GPU 的 TensorFlow 软件包需要 CudaToolkit 7.0 和 CUDNN 6.5 V2。 对于安装环境,我们建议访问 cuda 安装 [44] 网站,为了不会深入细节,同时信息也是最新的。

在 TensorFlow 中引用这些设备的方法如下:

  • /cpu:0:引用服务器的 CPU。
  • /gpu:0:服务器的 GPU(如果只有一个可用)。
  • /gpu:1:服务器的第二个 GPU,依此类推。

要知道我们的操作和张量分配在哪些设备中,我们需要创建一个sesion,选项log_device_placementTrue。 我们在下面的例子中看到它:

  1. import tensorflow as tf
  2. a = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3], name='a')
  3. b = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[3, 2], name='b')
  4. c = tf.matmul(a, b)
  5. sess = tf.Session(config=tf.ConfigProto(log_device_placement=True))
  6. printsess.run(c)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

当读者在计算机中测试此代码时,应出现类似的输出:

  1. . . .
  2. Device mapping:
  3. /job:localhost/replica:0/task:0/gpu:0 -> device: 0, name: Tesla K40c, pci bus id: 0000:08:00.0
  4. . . .
  5. b: /job:localhost/replica:0/task:0/gpu:0
  6. a: /job:localhost/replica:0/task:0/gpu:0
  7. MatMul: /job:localhost/replica:0/task:0/gpu:0
  8. [[ 22.28.]
  9. [ 49.64.]]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

此外,使用操作的结果,它通知我们每个部分的执行位置。

如果我们想要在特定设备中执行特定操作,而不是让系统自动选择设备,我们可以使用变量tf.device来创建设备上下文,因此所有操作都在上下文将分配相同的设备。

如果我们在系统中拥有更多 GPU,则默认情况下将选择具有较低标识符的 GPU。 如果我们想要在不同的 GPU 中执行操作,我们必须明确指定它。 例如,如果我们希望先前的代码在 GPU#2 中执行,我们可以使用tf.device('/gpu:2'),如下所示:

  1. import tensorflow as tf
  2. with tf.device('/gpu:2'):
  3. a = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3], name='a')
  4. b = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[3, 2], name='b')
  5. c = tf.matmul(a, b)
  6. sess = tf.Session(config=tf.ConfigProto(log_device_placement=True))
  7. printsess.run(c)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

多个 GPU 的并行

如果我们有更多的 GPU,通常我们希望一起使用它们来并行地解决同样的问题。 为此,我们可以构建我们的模型,来在多个 GPU 之间分配工作。 我们在下一个例子中看到它:

  1. import tensorflow as tf
  2. c = []
  3. for d in ['/gpu:2', '/gpu:3']:
  4. with tf.device(d):
  5. a = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3])
  6. b = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[3, 2])
  7. c.append(tf.matmul(a, b))
  8. with tf.device('/cpu:0'):
  9. sum = tf.add_n(c)
  10. # Creates a session with log_device_placement set to True.
  11. sess = tf.Session(config=tf.ConfigProto(log_device_placement=True))
  12. print sess.run(sum)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

正如我们所看到的,代码与前一代码相同,但现在我们有 2 个 GPU,由tf.device指定,它们执行乘法(两个 GPU 在这里都做同样的操作,以便简化示例代码),稍后 CPU 执行加法。 假设我们将log_device_placement设置为true,我们可以在输出中看到,操作如何分配给我们的设备 [45]。

  1. . . .
  2. Device mapping:
  3. /job:localhost/replica:0/task:0/gpu:0 -> device: 0, name: Tesla K40c
  4. /job:localhost/replica:0/task:0/gpu:1 -> device: 1, name: Tesla K40c
  5. /job:localhost/replica:0/task:0/gpu:2 -> device: 2, name: Tesla K40c
  6. /job:localhost/replica:0/task:0/gpu:3 -> device: 3, name: Tesla K40c
  7. . . .
  8. . . .
  9. Const_3: /job:localhost/replica:0/task:0/gpu:3
  10. I tensorflow/core/common_runtime/simple_placer.cc:289] Const_3: /job:localhost/replica:0/task:0/gpu:3
  11. Const_2: /job:localhost/replica:0/task:0/gpu:3
  12. I tensorflow/core/common_runtime/simple_placer.cc:289] Const_2: /job:localhost/replica:0/task:0/gpu:3
  13. MatMul_1: /job:localhost/replica:0/task:0/gpu:3
  14. I tensorflow/core/common_runtime/simple_placer.cc:289] MatMul_1: /job:localhost/replica:0/task:0/gpu:3
  15. Const_1: /job:localhost/replica:0/task:0/gpu:2
  16. I tensorflow/core/common_runtime/simple_placer.cc:289] Const_1: /job:localhost/replica:0/task:0/gpu:2
  17. Const: /job:localhost/replica:0/task:0/gpu:2
  18. I tensorflow/core/common_runtime/simple_placer.cc:289] Const: /job:localhost/replica:0/task:0/gpu:2
  19. MatMul: /job:localhost/replica:0/task:0/gpu:2
  20. I tensorflow/core/common_runtime/simple_placer.cc:289] MatMul: /job:localhost/replica:0/task:0/gpu:2
  21. AddN: /job:localhost/replica:0/task:0/cpu:0
  22. I tensorflow/core/common_runtime/simple_placer.cc:289] AddN: /job:localhost/replica:0/task:0/cpu:0
  23. [[44.56.]
  24. [98.128.]]
  25. . . .
  • 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

GPU 的代码示例

为了总结这一简短的章节,我们提供了一段代码,其灵感来自 DamienAymeric 在 Github [46] 中共享的代码,计算An + Bnn=10,使用 Python datetime包,将 1 GPU 的执行时间与 2 个 GPU 进行比较。

首先,我们导入所需的库:

  1. import numpy as np
  2. import tensorflow as tf
  3. import datetime
  • 1
  • 2
  • 3

我们使用numpy包创建两个带随机值的矩阵:

  1. A = np.random.rand(1e4, 1e4).astype('float32')
  2. B = np.random.rand(1e4, 1e4).astype('float32')
  3. n = 10
  • 1
  • 2
  • 3
  • 4

然后,我们创建两个结构来存储结果:

  1. c1 = []
  2. c2 = []
  • 1
  • 2

接下来,我们定义matpow()函数,如下所示:

  1. defmatpow(M, n):
  2. if n < 1: #Abstract cases where n < 1
  3. return M
  4. else:
  5. return tf.matmul(M, matpow(M, n-1))
  • 1
  • 2
  • 3
  • 4
  • 5

正如我们所见,要在单个 GPU 中执行代码,我们必须按如下方式指定:

  1. with tf.device('/gpu:0'):
  2. a = tf.constant(A)
  3. b = tf.constant(B)
  4. c1.append(matpow(a, n))
  5. c1.append(matpow(b, n))
  6. with tf.device('/cpu:0'):
  7. sum = tf.add_n(c1)
  8. t1_1 = datetime.datetime.now()
  9. with tf.Session(config=tf.ConfigProto(log_device_placement=True)) as sess:
  10. sess.run(sum)
  11. t2_1 = datetime.datetime.now()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

对于 2 个 GPU 的情况,代码如下:

  1. with tf.device('/gpu:0'):
  2. #compute A^n and store result in c2
  3. a = tf.constant(A)
  4. c2.append(matpow(a, n))
  5. with tf.device('/gpu:1'):
  6. #compute B^n and store result in c2
  7. b = tf.constant(B)
  8. c2.append(matpow(b, n))
  9. with tf.device('/cpu:0'):
  10. sum = tf.add_n(c2) #Addition of all elements in c2, i.e. A^n + B^n
  11. t1_2 = datetime.datetime.now()
  12. with tf.Session(config=tf.ConfigProto(log_device_placement=True)) as sess:
  13. # Runs the op.
  14. sess.run(sum)
  15. t2_2 = datetime.datetime.now()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

最后,我们打印计算时间的结果:

  1. print "Single GPU computation time: " + str(t2_1-t1_1)
  2. print "Multi GPU computation time: " + str(t2_2-t1_2)
  • 1
  • 2

TensorFlow 的分布式版本

正如我之前在本章开头所说,2016 年 2 月,Google 发布了 TensorFlow 的分布式版本,该版本由 gRPC 支持,这是一个用于进程间通信的高性能开源 RPC 框架(TensorFlow 服务使用的相同协议)。

对于它的用法,必须构建二进制文件,因为此时包只提供源代码。 鉴于本书的介绍范围,我不会在分布式版本中解释它,但如果读者想要了解它,我建议从 TensorFlow 的分布式版本的官网开始 [47]。

与前面的章节一样,本书中使用的代码可以在本书的 Github [48] 中找到。 我希望本章足以说明如何使用 GPU 加速代码。

后记

探索是促进创新的引擎。创新促进经济增长。让我们一起去探索吧。

Edith Widder

在这里,我提供了一个介绍性指南,解释了如何使用 TensorFlow,为这种技术提供热身,这无疑将在迫在眉睫的技术场景中发挥主导作用。 事实上,还有 TensorFlow 的其他替代方案,每个方案最适合特定问题;我想邀请读者探索 TensorFlow 包之外的内容。

这些包有很多不同之处。 有些更专业,有些更不专业。 有些比其他更难安装。 其中一些有很好的文档,而另一些尽管运作良好,但更难找到如何使用它们的详细信息。

重要的是:之后的日子里,TensorFlow 由谷歌发布,我在推文 [49] 中读到了 2010-2014 期间,新的深度学习包每 47 天发布一次,2015 年每 22 天发布一次。 这很惊人,不是吗? 正如我在本书的第一章中提出的那样,作为读者的起点,可以在 Awesome Deep Learning [50] 找到一个广泛的列表。

毫无疑问,2015 年 11 月,随着 Google TensorFlow 的发布,深度学习的格局受到影响,现在它是 Github 上最受欢迎的开源机器学习库 [51]。

请记住,Github 的第二个最着名的机器学习项目是 Scikit-learn [52],事实上的 Python 官方的通用机器学习框架。 这些用户可以通过 Scikit Flow(skflow)[53] 使用 TensorFlow,这是来自 Google 的 TensorFlow 的简化接口。

实际上,Scikit Flow 是 TensorFlow 库的高级包装,它允许使用熟悉的 Scikit-Learn 方法训练和拟合神经网络。 该库涵盖了从线性模型到深度学习应用的各种需求。

在我看来,在 TensorFlow 分布式,TensorFlow 服务和 Scikit Flow 发布后,TensorFlow 将成为事实上的主流深度学习库。

深度学习大大提高了语音识别,视觉对象识别,对象检测和许多其他领域的最新技术水平。 它的未来会是什么? 根据 Yann LeCun,Yoshua Bengio 和 Geoffrey Hilton 在 Nature 杂志上的精彩评论,答案是无监督学习 [54]。 他们期望从长远来看,无监督学习比监督学习更重要。 正如他们所提到的,人类和动物的学习基本上没有受到监督:我们通过观察世界来发现它的结构,而不是通过被告知每个物体的名称。

他们对系统的未来进展有很多期望,系统将 CNN 与递归神经网络(RNN)相结合,并使用强化学习。 RNN 处理一个输入,该输入一次编码一个元素,在其隐藏单元中维护序列的所有过去元素的历史的信息。 对于 TensorFlow 中 RNN 实现的介绍,读者可以查看 TensorFlow 教程中的循环神经网络 [55] 部分。

此外,深度学习还面临许多挑战;训练它们的时间推动了新型超级计算机系统的需求。 为了将最佳的知识分析与新的大数据技术和新兴计算系统的强大功能相结合,以前所未有的速度解释大量异构数据,仍然需要进行大量研究。

科学进步通常是大型社区的跨学科,长期和持续努力的结果,而不是突破,深度学习和机器学习一般也不例外。 我们正在进入一个非常激动人心的跨学科研究时期,其中像巴塞罗那那样的生态系统,如 UPC 和 BSC-CNS,在高性能计算和大数据技术方面具有丰富的知识,将在这个新场景中发挥重要作用。

参考

[1]   The MNIST database of handwritten digits. [Online]. Available at:http://yann.lecun.com/exdb/mnist [Accessed: 16/12/2015].

[2]_  Github_, (2016) Fist Contact with TensorFlow. Source code [Online]. Available at:https://github.com/jorditorresBCN/TutorialTensorFlow[Accessed: 16/12/2015].

[3]_  TensorFlow Serving_[Online]. Available at: http://tensorflow.github.io/serving/[Accessed: 24/02/2016].

[4]  Google Research Blog [Online]. Available at: http://googleresearch.blogspot.com.es/2016/02/running-your-models-in-production-with.html?m=1[Accessed: 24/02/2016].

[5]_  TensorFlow Serving_-Architecture Overview[Online]. Available at: http://tensorflow.github.io/serving/[Accessed: 24/02/2016].

[6]  TensorFlow Serving– Serving a TensorFlow Model [Online]. Available at:http://tensorflow.github.io/serving/serving_basic [Accessed: 24/02/2016].

[7] TensorFlow, (2016) Download & Setup [Online]. Available at: https://www.tensorflow.org/versions/master/get_started/os_setup.html#download-and-setup[Accessed: 16/12/2015].

[8]  Wikipedia, (2016). IPython. [Online]. Available at: https://en.wikipedia.org/wiki/IPython [Accessed: 19/03/2016].

[9]  TensorFlow: Large-scale machine learning on heterogeneous systems, (2015). [Online]. Available at:http://download.tensorflow.org/paper/whitepaper2015.pdf[Accessed: 20/12/2015].

[10] TensorFlow, (2016)Python API – Summary Operations. [Online]. Available at:https://www.tensorflow.org/versions/master/api_docs/python/train.html#summary-operations [Accessed: 03/01/2016].

[11]  I recommend using Google Chrome to ensure proper display.

[12]TensorFlow, (2016) TensorBoard: Graph Visualization.[Online]. Available at:https://www.tensorflow.org/versions/master/how_tos/graph_viz/index.html[Accessed: 02/01/2016].

[13] One reviewer of this book has indicated that he also had to install the package_python-gi-cairo_.

[14] Wikipedia, (2016). Mean Square Error. [Online]. Available at: https://en.wikipedia.org/wiki/Mean_squared_error [Accessed: 9/01/2016].

[15]  Wikipedia, (2016). Gradient descent. [Online]. Available at: https://en.wikipedia.org/wiki/Gradient_descent [Accessed: 9/01/2016].

[16]  Wikipedia, (2016). Gradient. [Online]. Available at: https://en.wikipedia.org/wiki/Gradient[Accessed: 9/01/2016].

[17]  Github, (2016) Book source code [Online]. Available at:https://github.com/jorditorresBCN/TutorialTensorFlow. [Accessed: 16/12/2015].

[18]   TensorFlow, (2016) API de Python – Tensor Transformations [Online]. Available at:https://www.tensorflow.org/versions/master/api_docs/python/array_ops.html [Accessed: 16/12/2015].

[19]  TensorFlow, (2016) Tutorial – Reading Data [Online]. Available at:https://www.tensorflow.org/versions/master/how_tos/reading_data[Accessed: 16/12/2015].

[20] Github, (2016) TensorFlow Book – Jordi Torres. [Online]. Available at:https://github.com/jorditorresBCN/LibroTensorFlow/blob/master/input_data.py[Accessed: 19/02/2016].

[21]  Github, (2016) Shawn Simister. [Online]. Available at: https://gist.github.com/narphorium/d06b7ed234287e319f18 [Accessed: 9/01/2016].

[22]   Wikipedia, (2016). Squared Euclidean distance. [Online]. Available at:https://en.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance[Accessed: 9/01/2016].

[23]  In my opinion, the level of explanation of each operation it’s enough for the purpose of this book.

[24]  TensorFlow, (2016) Python API. [online]. Available in: https://www.tensorflow.org/versions/master/api_docs/index.html [Accessed: 19/02/2016].

[25]Actually “_” is like any other variable, but many Python users, by convention, we use it to discard results.

[26]  Github, (2016) TensorFlow Book – Jordi Torres. [online]. Available at: https://github.com/jorditorresBCN/LibroTensorFlow [Accessed: 19/02/2016].

[27]   TensorFlow, (2016) Tutorial MNIST beginners. [online]. Available at:https://www.tensorflow.org/versions/master/ tutorials/mnist/beginners[Accessed: 16/12/2015].

[28]   Neural Networks and Deep Learning.Michael Nielsen. [online]. Available at: http://neuralnetworksanddeeplearning.com/index.html [Accessed: 6/12/2015].

[29]    The MNIST database of handwritten digits.[online]. Available at:http://yann.lecun.com/exdb/mnist [Accessed: 16/12/2015].

[30]    Wikipedia, (2016). Antialiasing [online]. Available at: https://en.wikipedia.org/wiki/Antialiasing[Accessed: 9/01/2016].

[31]_    Github_, (2016) Book TensorFlow – Jordi Torres. [online]. Available at:https://github.com/jorditorresBCN/LibroTensorFlow/blob/master/input_data.py [Accessed: 9/01/2016].

[32]   Google (2016) TensorFlow. [online]. Available at: https://tensorflow.googlesource.com[Accessed: 9/01/2016].

[33]  Wikipedia, (2016). Sigmoid function [online]. Avaliable at: https://en.wikipedia.org/wiki/Sigmoid_function [Accessed: 12/01/2016].

[34]  Wikipedia, (2016). Softmax function [online]. Available at: https://en.wikipedia.org/wiki/Softmax_function [Accessed: 2/01/2016].

[35]   TensorFlow, (2016) Tutorial MNIST beginners. [online]. Available at:https://www.tensorflow.org/versions/master/tutorials/mnist/beginners[Accessed: 16/12/2015].

[36]  Neural Networks & Deep Learning.Michael Nielsen. [online]. Available at:http://neuralnetworksanddeeplearning.com/index.html[Accessed: 6/12/2015].

[37]  TensorFlow Github: tensorflow/tensorflow/python/ops/gradients.py [Online].  Available at:https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/ops/gradients.py[Accessed: 16/03/2016].

[38]_  Github_, (2016) Libro TensorFlow – Jordi Torres. [online]. Available at:https://github.com/jorditorresBCN/LibroTensorFlow[Accessed: 9/01/2016].

[39]  The reader can read more about the details of these parameters on the course website of CS231 –Convolutional Neural Networks for Visual Recognition(2015) [online]. Available at:http://cs231n.github.io/convolutional-networks[Accessed: 30/12/2015].

[40]  GIMP –_Image processing software by GNU,_Convlution matrix documentation available at:https://docs.gimp.org/es/plug-in-convmatrix.html[Accessed: 5/1/2016].

[41]  TensorFlow, (2016_) Tutorials: Deep MNIST for experts_. [on line]. Availbile at:https://www.tensorflow.org/versions/master/tutorials/mnist/pros/index.html [Consulted on: 2/1/2016]

[42]  TensorFlow, (2016)Python API. ADAM Optimizer[on líne]. Available at:https://www.tensorflow.org/versions/master/ api_docs/python/train.html#AdamOptimizer[Accessed: 2/1/2016].

[43] Github, (2016) Source code of this book [on líne]. Availible at: https://github.com/jorditorresBCN/TutorialTensorFlow [Consulted on: 29/12/2015].

[44]   TensorFlow, (2016) GPU-related issues. [online]. Available at: https://www.tensorflow.org/versions/master/get_started/os_setup.html#gpu-related-issues[Accessed: 16/12/2015].

[45]  This output is result of using a server with 4 Tesla K40 GPUs from theBarcelona Supercomputing Center (BSC-CNS).

[46]_  Github_(2016) AymericDamien. [online]. Available at: https://github.com/aymericdamien/TensorFlow-Examples [Accessed: 9/1/2015].

[47]  Distributed TensorFlow, (2016) [online]. Available at: https://github.com/tensorflow/tensorflow/tree/master/tensorflow/core/distributed_runtime[Accessed: 16/12/2015].

[48] Github, (2016) Source code of this book [on líne]. Availible at: https://github.com/jorditorresBCN/TutorialTensorFlow [Consulted on: 29/12/2015].

[49]  Twitter (11/11/2015). Kyle McDonald:2010-2014: new deep learning toolkit is released every 47 days.__2015: every 22 days.[Online]. Available at:https://twitter.com/kcimc/status/664217437840257024[Accessed: 9/01/2016].

[50]  GitHub,(2016)Awesome Deep Learning. [Online]. Available at: https://github.com/ChristosChristofidis/awesome-deep-learning[Accessed: 9/01/2016].

[51]  Explore GitHub, Machine learning: [Online]. Available at:https://github.com/showcases/machine-learning [Accessed on: 2/01/2016]

[52]  Scikit-Learn GitHub: [Online]. Available at:https://github.com/scikit-learn/scikit-learn[Accessed: 2/3/2016]

[53]  Tensorflow/skflow GitHub: [Online]. Available at:https://github.com/tensorflow/skflow[Accessed: 2/1/2016]

[54]  Yann LeCun, Yoshua Bengio and Geoffrey Hinton (2015). “Deep Learning”. Nature 521: 436–444 doi:10.1038/nature14539.  Available at:http://www.nature.com/nature/journal/v521/n7553/full/nature14539.html  [Accessed: 16/03/2016].

[55]  TensorFlow, (2016) Tutorial – Recurrent Neural Networks [Online]. Available at:https://www.tensorflow.org/versions/r0.7/tutorials/recurrent/index.html[Accessed: 16/03/2016].

[56]  Hello World en TensorFlow. Spanish version of this book [Online]. Available at:https://jorditorres.org/libro-hello-world-en-tensorflow/[Accessed: 16/03/2016].

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/934193
推荐阅读
相关标签
  

闽ICP备14008679号