书名:TensorFlow机器学习项目实战
ISBN:978-7-115-46362-3
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
• 著 [阿根廷] Rodolfo Bonnin
译 姚鹏鹏
责任编辑 陈冀康
• 人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
• 读者服务热线:(010)81055410
反盗版热线:(010)81055315
Copyright ©2016 Packt Publishing. First published in the English language under the title Building Machine Learning Projects with TensorFlow.
All rights reserved.
本书由英国Packt Publishing公司授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。
版权所有,侵权必究。
TensorFlow是Google所主导的机器学习框架,也是机器学习领域研究和应用的热门对象。
本书主要介绍如何使用TensorFlow库实现各种各样的模型,旨在降低学习门槛,并为读者解决问题提供详细的方法和指导。全书共10章,分别介绍了TensorFlow基础知识、聚类、线性回归、逻辑回归、不同的神经网络、规模化运行模型以及库的应用技巧。
本书适合想要学习和了解 TensorFlow 和机器学习的读者阅读参考。如果读者具备一定的C++和Python的经验,将能够更加轻松地阅读和学习本书。
Rodolfo Bonnin是一名系统工程师,同时也是阿根廷国立理工大学的博士生。他还在德国斯图加特大学进修过并行编程和图像理解的研究生课程。
他从2005年开始研究高性能计算,并在2008年开始研究和实现卷积神经网络,编写过一个同时支持CPU和GPU的神经网络前馈部分。最近,他一直在进行使用神经网络进行欺诈模式检测的工作,目前正在使用ML技术进行信号分类。
感谢我的妻子和孩子们,尤其感谢他们在我写这本书时表现出的耐心。感谢本书的审稿人,他们让这项工作更专业化。感谢Marcos Boaglio,他安装调试了设备,以使我能完成这本书。
Niko Gamulin是CloudMondo的高级软件工程师,CloudMondo是美国的一家创业公司,在那里他开发并实现了系统的预测行为模型。他曾开发过深度学习模型,用于满足各种应用。2015年他从卢布尔雅那大学获得电气工程博士学位。他的研究集中在创建流失预测的机器学习模型。
我要感谢我最棒的女儿Agata,她激励我更多地了解、学习这一过程。同时也要感谢Ana,她是世界上最好的妻子。
近年来,机器学习已经从科学和理论专家的技术资产转变为IT领域大多数大型企业日常运营中的常见主题。
这种现象开始于可用数据量的爆炸:从2005年到2011年,出现了多种廉价的数据捕获设备(具有集成GPS、数百万像素相机和重力传感器的手机)以及普及的新型高维数据捕获装置(3D LIDAR和光学系统,IOT设备的爆炸等),它们使得访问前所未有的大量信息成为了可能。
此外,在硬件领域,摩尔定律的尽头已经近在咫尺,于是促使大量并行设备的开发,这让用于训练同一模型的数据能够成倍增长。
硬件和数据可用性方面的进步使研究人员能够重新审视先驱者基于视觉的神经网络架构(卷积神经网络等)的工作,将它们用于许多新的问题。这都归功于具备普遍可用性的数据以及强悍的计算能力。
为了解决这些新的问题,机器学习的从业者,创建了许多优秀的机器学习包,如Keras、Scikyt-learn、Theano、Caffe和Torch。它们每个都拥有一个特定的愿景来定义、训练和执行机器学习模型。
2015 年 11 月 9 日,Google公司进入了机器学习领域,决定开源自己的机器学习框架TensorFlow,Google内部许多项目都以此为基础。首次发布的是0.5版本,这与其他版本相比有一些缺点,这些在后面讨论。不能运行分布式模型就是其中很突出的一个缺点。
于是,这个小故事带我们来到了今天,TensorFlow成为了该领域开发人员的主要竞争对手之一。因为使用它的项目数量增加,对于任何数据科学的从业者来说,它作为一个工具箱的重要性正在逐步提高。
在本书中,我们将使用TensorFlow库实现各种各样的模型,旨在降低读者的学习门槛,并为解决问题提供详细的方法。
第1章,探索和转换数据,帮助读者理解TensorFlow应用程序的主要组件和其包含的主要的数据探索方法。
第2章,聚类,告诉你怎样定义相似性标准,并将数据元素分组为不同的类。
第3章,线性回归,帮助读者定义第一个数学模型来解释不同的现象。
第4章,逻辑回归,是用非常强大而简单的数学函数建模非线性现象的第一步。
第5章,简单的前向神经网络,帮助你理解主要组件和神经网络的机制。
第6章,卷积神经网络,解释了最近重新发现的一组特殊网络的功能和实际应用。
第7章,循环神经网络和LSTM,详细地解释了这个对时序数据非常有用的框架。
第8章,深度神经网络,提供混合类型神经网络的最新发展的概述。
第9章,规模化运行模型——GPU和服务,解释怎样通过将工作划分为协调单元来解决
更复杂的问题。
第10章,库的安装和其他技巧,涵盖在Linux、Windows和Mac架构上安装TensorFlow的流程,并向你介绍一些有用的代码技巧,可以简化日常任务。
软件需求(包括版本) |
硬件规格 |
操作系统需求 |
---|---|---|
TensorFlow 1.0,Jupyter Notebook |
任何x86电脑 |
Ubuntu Linux 16.04 |
本书面向希望机器学习任务的结果更快、更高效的数据分析师、数据科学家和研究人员。对于那些想要寻找一个用TensorFlow进行复杂数值计算的清晰指南的人来说,他们会发现本书非常有用。本书也适用于想要在各种场景中应用TensorFlow的开发人员。本书期望读者有一些C++和Python的经验。
在本书中,你会发现一些不同的文本样式,用以区别不同种类的信息。下面举例说明。正文中的代码段、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟URL、用户输入和Twitter句柄如下所示:“我们可以通过使用include
指令包括其他上下文。”
代码段的格式如下:
>>> import tensorflow as tf
>>> tens1 = tf.constant([[[1,2],[2,3]],[[3,4],[5,6]]])
>>> print sess.run(tens1)[1,1,0]
5
当我们想提醒你注意代码块的特定部分时,相关的行或部分会加粗显示:
>>> import tensorflow as tf
>>> tens1 = tf.constant([[[1,2],[2,3]],[[3,4],[5,6]]])
>>> print sess.run(tens1)[1,1,0]
5
命令行输入写成如下的形式:
# cp /usr/src/asterisk-addons/configs/cdr_mysql.conf.sample
/etc/asterisk/cdr_mysql.conf
新术语和重要词语以粗体显示。例如,你在屏幕上看到的字,在菜单栏或对话框中,出现在文本中,如下所示:“单击下一步按钮可以转到下一个界面。”
![]()
这个图标表示警告或需要特别注意的内容。
![]()
这个图标表示提示或者技巧。
欢迎提出反馈。如果你对本书有任何想法,喜欢它什么,不喜欢它什么,请让我们知道。要写出真正对大家有帮助的书,了解读者的反馈很重要。
一般的反馈,请发送电子邮件至feedback@packtpub.com,并在邮件主题中包含书名。
如果你有某个主题的专业知识,并且有兴趣写成或帮助促成一本书,请参考我们的作者指南http://www.packtpub.com/authors。
现在,你是一位自豪的Packt图书的读者,我们会尽全力帮你充分利用你手中的书。
你可以从异步社区(www.epubit.com.cn)下载本书最新版的示例代码。
当你下载完之后,请确保你使用如下最新版的软件来解压该文件:
本书的代码同时维护于GitHub上https://github.com/PacktPublishing/Building-Machine- Learning-Projects-with-TensorFlow。我们在https://github.com/PacktPublishing/上维护了很多数据的代码和视频。欢迎查看!
虽然我们已尽力确保本书内容正确,但错误仍旧在所难免。如果你在我们的书中发现错误,不管是文本还是代码,希望能告知我们,我们不胜感激。这样做可以减少其他读者的困扰,帮助我们改进本书的后续版本。如果你发现任何错误,请访问http://www.packtpub.com/ submit-errata提交,选择你的书,单击勘误表提交表单的链接,并输入详细说明。勘误一经核实,你的提交将被接受,此勘误将被上传到本公司网站或添加到现有勘误表。从http://www.packtpub.com/support选择书名就可以查看现有的勘误表。
如果想要查看已经提交的勘误表,请登录https://www.packtpub.com/books/content/support 并在搜索框中输入书名,你所查找的信息就会显示在Errata部分了。
互联网上的盗版是所有媒体都要面对的问题。Packt非常重视保护版权和许可证。如果你发现我们的作品在互联网上被非法复制,不管以什么形式,都请立即为我们提供位置地址或网站名称,以便我们可以寻求补救。
请把可疑盗版材料的链接发到copyright@packtpub.com。非常感谢你帮助我们保护作者,以及保护我们给你带来有价值内容的能力。
如果你对本书内容存有疑问,不管是哪个方面,都可以通过questions@packtpub.com联系我们,我们将尽最大努力来解决。
TensorFlow是一个开源软件库,用于使用数据流图进行数值计算。图中的节点表示数学运算,而图边表示在它们之间传递的多维数据数组(张量,tensor)。
该库包括各种功能,使你能够实现和探索用于图像和文本处理的前沿卷积神经网络(CNN)和循环神经网络(RNN)架构。由于复杂计算以图形的形式表示,TensorFlow可以用作一个框架,使你能够轻松开发自己的模型,并在机器学习领域中使用它们。
它还能够在最不同的环境中运行,从CPU到移动处理器,包括高度并行的GPU计算,并且新的服务架构能够运行所有命名选项的非常复杂的混合,见表1-1。
表1-1 TensorFlow
向量(Tensor) |
操作(Operation) |
---|---|
图(Graph) |
|
运行时(CPU、GPU、移动设备等) |
TensorFlow基于张量数据管理。张量是数学领域的概念,并且被开发为向量和矩阵的线性代数项的泛化。
具体到TensorFlow中,一个张量就是一个张量类的实例,是绑定了相关运算的一个特定类型的多维数组。
之前已经介绍过,TensorFlow使用张量数据结构来表征所有的数据。所有的张量都有一个静态的类型和动态的维数。所以你能够实时地改变一个张量的内部结构。
张量的另一个属性就是只有张量类型的对象才能在计算图的节点中传递。
我们开始来讨论张量的其他属性(从此处开始,我们所有说的张量都是TensorFlow中的张量对象)。
张量的阶(rank)表征了张量的维度,但是跟矩阵的秩(rank)不一样。它表示张量的维度的质量。
阶为1的张量等价于向量,阶为2的向量等价于矩阵。对于一个阶为2的张量,通过t[i, j]就能获取它的每个元素。对于一个阶为3的张量,需要通过t[i, j, k]进行寻址,以此类推,见表1-2。
表1-2 张量的阶
阶 |
数学实体 |
代码示例 |
---|---|---|
0 |
Scalar |
scalar = 1000 |
1 |
Vector |
vector = [2, 8, 3] |
2 |
Matrix |
matrix = [[4, 2, 1], [5, 3, 2], [5, 5, 6]] |
3 |
3-tensor |
tensor = [[[4], [3], [2]], [[6], [100], [4]], [[5], [1], [4]]] |
n |
n-tensor |
… |
在下面这个例子中,我们创建了一个张量,并获取其元素:
>>> import tensorflow as tf
>>> tens1 = tf.constant([[[1,2],[2,3]],[[3,4],[5,6]]])
>>> print sess.run(tens1)[1,1,0]
5
这个张量的阶是3,因为该张量包含的矩阵中的每个元素,都是一个向量。
TensorFlow文档使用三个术语来描述张量的维度:阶(rank),形状(shape)和维数(dimension number)。表1-3展示了它们彼此之间的关系。
表1-3 三者之间的关系
阶 |
形状 |
维数 |
例子 |
---|---|---|---|
0 |
[] |
0-D |
4 |
1 |
[D0] |
1-D |
[2] |
2 |
[D0,D1] |
2-D |
[6,2] |
3 |
[D0,D1,D2] |
3-D |
[7,3,2] |
n |
[D0,D1, …, Dn-1] |
n-D |
形为[D0, D1, … Dn-1]的张量 |
图1-1的例子中,我们创建了一个三阶张量,并打印出它的形状。
图1-1 三阶张量
除了维度,张量还有一个确定的数据类型。你可以把表1-4中的任意一个类型指派给向量。
表1-4 张量数据类型
数据类型 |
Python 类型 |
描述 |
---|---|---|
DT_FLOAT |
tf.float32 |
32位浮点型 |
DT_DOUBLE |
tf.float64 |
64位浮点型 |
DT_INT8 |
tf.int8 |
8位有符号整型 |
DT_INT16 |
tf.int16 |
16位有符号整型 |
DT_INT32 |
tf.int32 |
32位有符号整型 |
DT_INT64 |
tf.int64 |
64位有符号整型 |
DT_UINT8 |
tf.uint8 |
8位无符号整型 |
DT_STRING |
tf.string |
可变长度的字节数组,每一个张量元素都是一个字节数组 |
DT_BOOL |
tf.bool |
布尔型 |
我们既可以创建我们自己的张量,也可以从著名的Python库numpy中继承。下面的例子中,我们创建了一些numpy数组,并对它们进行了简单的数学操作:
import tensorflow as tf
import numpy as np
x = tf.constant(np.random.rand(32).astype(np.float32))
y= tf.constant ([1,2,3])
TensorFlow与numpy是可互操作的,通常调用eval()函数会返回numpy对象。该函数可以用作标准数值工具。
![]()
我们一定要注意张量对象只是一个操作结果的符号化句柄,所以它并不持有该操作的结果。因此,我们必须使用eval()方法来获得实际的值。该方法等价于Session.run(tesnsor_ to_eval)。
本例中,我们会创建两个numpy数组,并将它们转化成张量:
import tensorflow as tf #we import tensorflow
import numpy as np #we import numpy
sess = tf.Session() #start a new Session Object
x_data = np.array([[1.,2.,3.],
[3.,2.,6.]]) # 2x3 matrix
x = tf.convert_to_tensor(x_data, dtype=tf.float32) #Finally, we create the
#tensor, starting from the fload 3x matrix
tf.convert_to_tensor``:
该方法将Python对象转化为tensor对象。它的输入可以是tensor对象、numpy数组、Python列表和Python标量。
与大多数Python的模块一样,TensorFlow允许使用Python的交互式控制台。
在图1-2中,我们调用Python解释器(在终端对话框输入Python调用),并创建一个常量类型的张量。然后再次调用它,Python解释器显示张量的形状和类型。
图1-2 在Python解释器中运行TensorFlow
我们还可以使用IPython解释器,这将允许我们采用一种类似于笔记本式工具的格式,例如Jupyter,如图1-3所示。
图1-3 IPython对话框
以交互方式运行TensorFlow会话时,我们最好使用InteractiveSession对象。
与正常的tf.Session类不同,tf.InteractiveSession类将其自身设置为构建时的默认会话。因此,当你尝试评估张量或运行一个操作时,不必传递一个Session对象来指示它所引用的会话。
TensorFlow的数据流图(data flow graph)符号化地表示了模型的计算是如何工作的。
简单地说,数据流图是完整的TensorFlow计算,如图1-4所示。图中的节点(node)表示操作(operation),而边(edge)表示各操作之间流通的数据。
图1-4 在TensorBoard中一个简单的数据流图示例
通常,节点实现数学运算,同时也表示数据或变量的供给(feed),或输出结果。
边描述节点之间的输入/输出关系。这些数据边缘专门传输张量。节点被分配给计算设备,并且一旦其输入边缘上的所有张量都到位,则开始异步地并行执行。
所有的操作(operation)都拥有一个名字,可以表示一个抽象的计算(例如,矩阵求逆或者相乘)。
计算图(computation graph)通常并不需要直接构建Graph类对象,而是由用户在创建张量(tensor)和操作(operation)的时候自动创建的。TensorFlow张量构造函数(如tf.constant())将向默认的计算图添加必要的元素。其他TensorFlow操作也同样如此。
例如,语句“c = tf.matmul(a,b)”创建一个MatMul类型的操作,它接受张量a和b作为输入,并产生c作为输出。
有用的操作对象方法如下:
TensorFlow还提供了一种将张量直接注入到图内任何操作中的数据供给(feed)机制。
feed用张量值临时替换操作的输出。将feed的数据作为参数传入run函数。feed只在调用它的方法内有效。最常见的用例是,通过使用tf.placeholder()创建特定的feed操作的方法。
在大多数计算中,会多次执行计算图。大多数张量的生存周期不会超过单次执行周期。然而,变量是一种特殊的操作,它返回一个持久的、可变的张量的句柄,存活于多次计算图执行之中。对于TensorFlow的机器学习应用,模型的参数通常存储在变量中,并且在运行模型的训练阶段被更新。
初始化变量时,只需以张量作为参数,传入Variable对象构造函数中。
在下面例子中,我们用长度为1000的零数组初始化一个变量:
b = tf.Variable(tf.zeros([1000]))
数据流图是使用Google的协议缓存(protocol buffers)编写的,因此可以用各种语言读取。
协议缓存(protocol buffers)是一种语言中立、平台中立、可扩展的结构化数据序列化机制。首先定义数据结构,然后使用特定的代码(各种编程语言都可以)读写它。
(1)有用的方法
tf.Graph.as_graph_def(from_version=None, add_shapes=False):返回一个序列化的计算图表示GraphDef 。
(2)参数
本例中,我们将构建一个非常简单的数据流图,并观察生成的protobuffer文件:
import tensorflow as tf
g = tf.Graph()
with g.as_default():
import tensorflow as tf
sess = tf.Session()
W_m = tf.Variable(tf.zeros([10, 5]))
x_v = tf.placeholder(tf.float32, [None, 10])
result = tf.matmul(x_v, W_m)
print g.as_graph_def()
生成的protobuffer(简略后)如下:
node {
name: "zeros"
op: "Const"
attr {
key: "dtype"
value {
type: DT_FLOAT
}
}
attr {
key: "value"
value {
tensor {
dtype: DT_FLOAT
tensor_shape {
dim {
size: 10
}
dim {
size: 5
}
}
float_val: 0.0
}
}
}
}
...
node {
name: "MatMul"
op: "MatMul"
input: "Placeholder"
input: "Variable/read"
attr {
key: "T"
value {
type: DT_FLOAT
}
}
...
}
versions {
producer: 8
}
客户端程序通过创建会话(Session)与TensorFlow系统交互。Session对象是运行环境的表示。Session对象开始为空,当程序员创建不同的操作和张量时,它们将被自动添加到Session,直到Run方法被调用,才开始运算。
Run方法输入是需要计算的操作,以及一组可选的张量,用来代替图中某些节点的输出。
如果我们调用这个方法,并且有命名操作所依赖的操作,Session对象将执行所有这些操作,然后继续执行命名操作。
用以下简单的代码可以创建一个会话:
s = tf.Session()
本节中,我们将探讨TensorFlow支持的一些基本方法。它们将有利于初步数据探索和为并行计算做准备。
TensorFlow支持许多常见的矩阵运算,如转置、乘法、获取行列式和逆。
下面简单演示一下怎样使用这些函数。
In [1]: import tensorflow as tf
In [2]: sess = tf.InteractiveSession()
In [3]: x = tf.constant([[2, 5, 3, -5],
...: [0, 3,-2, 5],
...: [4, 3, 5, 3],
...: [6, 1, 4, 0]])
In [4]: y = tf.constant([[4, -7, 4, -3, 4],
...: [6, 4,-7, 4, 7],
...: [2, 3, 2, 1, 4],
...: [1, 5, 5, 5, 2]])
In [5]: floatx = tf.constant([[2., 5., 3., -5.],
...: [0., 3.,-2., 5.],
...: [4., 3., 5., 3.],
...: [6., 1., 4., 0.]])
In [6]: tf.transpose(x).eval() # Transpose matrix
Out[6]:
array([[ 2, 0, 4, 6],
[ 5, 3, 3, 1],
[ 3, -2, 5, 4],
[-5, 5, 3, 0]], dtype=int32)
In [7]: tf.matmul(x, y).eval() # Matrix multiplication
Out[7]:
array([[ 39, -10, -46, -8, 45],
[ 19, 31, 0, 35, 23],
[ 47, 14, 20, 20, 63],
[ 38, -26, 25, -10, 47]], dtype=int32)
In [8]: tf.matrix_determinant(floatx).eval() # Matrix determinant
Out[8]: 818.0
In [9]: tf.matrix_inverse(floatx).eval() # Matrix inverse
Out[9]:
array([[-0.00855745, 0.10513446, -0.18948655, 0.29584351],
[ 0.12958434, 0.12224938, 0.01222495, -0.05134474],
[-0.01955992, -0.18826403, 0.28117359, -0.18092911],
[-0.08557458, 0.05134474, 0.10513448, -0.0415648 ]], dtype=float32)
In [10]: tf.matrix_solve(floatx, [[1],[1],[1],[1]]).eval() # Solve Matrix
system
Out[10]:
array([[ 0.20293398],
[ 0.21271393],
[-0.10757945],
[ 0.02933985]], dtype=float32)
约简(reduction)是一种跨维度张量操作,计算结果比原张量缩减一个维度。
支持的操作包括(具有相同的参数)product,minimum,maximum,mean,all,any和accumulate_n。
In [1]: import tensorflow as tf
In [2]: sess = tf.InteractiveSession()
In [3]: x = tf.constant([[1, 2, 3],
...: [3, 2, 1],
...: [-1,-2,-3]])
In [4]:
In [4]: boolean_tensor = tf.constant([[True, False, True],
...: [False, False, True],
...: [True, False, False]])
In [5]: tf.reduce_prod(x, reduction_indices=1).eval() # reduce prod
Out[5]: array([ 6, 6, -6], dtype=int32)
In [6]: tf.reduce_min(x, reduction_indices=1).eval() # reduce min
Out[6]: array([ 1, 1, -3], dtype=int32)
In [7]: tf.reduce_max(x, reduction_indices=1).eval() # reduce max
Out[7]: array([ 3, 3, -1], dtype=int32)
In [8]: tf.reduce_mean(x, reduction_indices=1).eval() # reduce mean
Out[8]: array([ 2, 2, -2], dtype=int32)
In [9]: tf.reduce_all(boolean_tensor, reduction_indices=1).eval() # reduce
all
Out[9]: array([False, False, False], dtype=bool)
In [10]: tf.reduce_any(boolean_tensor, reduction_indices=1).eval() # reduce
any
Out[10]: array([ True, True, True], dtype=bool)
张量分割是张量一个维度减小的过程,并且所得到的元素由索引行确定,如图1-5所示。如果索引行中的某些元素重复,则对拥有重复索引的索引进行操作。
图1-5 张量分割的解释
索引数组大小应该与索引数组的维度0的大小相同,并且它们必须增加1。
In [1]: import tensorflow as tf
In [2]: sess = tf.InteractiveSession()
In [3]: seg_ids = tf.constant([0,1,1,2,2]); # Group indexes : 0|1,2|3,4
In [4]: tens1 = tf.constant([[2, 5, 3, -5],
...: [0, 3,-2, 5],
...: [4, 3, 5, 3],
...: [6, 1, 4, 0],
...: [6, 1, 4, 0]]) # A sample constant matrix
In [5]: tf.segment_sum(tens1, seg_ids).eval() # Sum segmentation
Out[5]:
array([[ 2, 5, 3, -5],
[ 4, 6, 3, 8],
[12, 2, 8, 0]], dtype=int32)
In [6]: tf.segment_prod(tens1, seg_ids).eval() # Product segmentation
Out[6]:
array([[ 2, 5, 3, -5],
[ 0, 9, -10, 15],
[ 36, 1, 16, 0]], dtype=int32)
In [7]: tf.segment_min(tens1, seg_ids).eval() # minimun value goes to group
Out[7]:
array([[ 2, 5, 3, -5],
[ 0, 3, -2, 3],
[ 6, 1, 4, 0]], dtype=int32)
In [8]: tf.segment_max(tens1, seg_ids).eval() # maximum value goes to group
Out[8]:
array([[ 2, 5, 3, -5],
[ 4, 3, 5, 5],
[ 6, 1, 4, 0]], dtype=int32)
In [9]: tf.segment_mean(tens1, seg_ids).eval() # mean value goes to group
Out[9]:
array([[ 2, 5, 3, -5],
[ 2, 3, 1, 4],
[ 6, 1, 4, 0]], dtype=int32)
序列实用程序包括诸如argmin和argmax(显示维度的最小和最大值),listdiff(显示列表之间的交集的补码),where(显示张量上的真实值的索引)和unique(在列表上去除重复的元素)。
In [1]: import tensorflow as tf
In [2]: sess = tf.InteractiveSession()
In [3]: x = tf.constant([[2, 5, 3, -5],
...: [0, 3,-2, 5],
...: [4, 3, 5, 3],
...: [6, 1, 4, 0]])
In [4]: listx = tf.constant([1,2,3,4,5,6,7,8])
In [5]: listy = tf.constant([4,5,8,9])
In [6]:
In [6]: boolx = tf.constant([[True,False], [False,True]])
In [7]: tf.argmin(x, 1).eval() # Position of the maximum value of columns
Out[7]: array([3, 2, 1, 3])
In [8]: tf.argmax(x, 1).eval() # Position of the minimum value of rows
Out[8]: array([1, 3, 2, 0])
In [9]: tf.listdiff(listx, listy)[0].eval() # List differences
Out[9]: array([1, 2, 3, 6, 7], dtype=int32)
In [10]: tf.where(boolx).eval() # Show true values
Out[10]:
array([[0, 0],
[1, 1]])
In [11]: tf.unique(listx)[0].eval() # Unique values in list
Out[11]: array([1, 2, 3, 4, 5, 6, 7, 8], dtype=int32)
这些类型的函数与矩阵形状相关。它们用于调整不匹配的数据结构,并快速获取数据张量的信息。这对于运行时决定处理策略时很有用。
在下面的例子中,我们将从二阶张量开始,并打印一些关于它的信息。然后我们将探讨修改矩阵的操作(添加或删除维度),例如squeeze和expand_dims。
In [1]: import tensorflow as tf
In [2]: sess = tf.InteractiveSession()
In [3]: x = tf.constant([[2, 5, 3, -5],
...: [0, 3,-2, 5],
...: [4, 3, 5, 3],
...: [6, 1, 4, 0]])
In [4]: tf.shape(x).eval() # Shape of the tensor
Out[4]: array([4, 4], dtype=int32)
In [5]: tf.size(x).eval() # size of the tensor
Out[5]: 16
In [6]: tf.rank(x).eval() # rank of the tensor
Out[6]: 2
In [7]: tf.reshape(x, [8, 2]).eval() # converting to a 10x2 matrix
Out[7]:
array([[ 2, 5],
[ 3, -5],
[ 0, 3],
[-2, 5],
[ 4, 3],
[ 5, 3],
[ 6, 1],
[ 4, 0]], dtype=int32)
In [8]: tf.squeeze(x).eval() # squeezing
Out[8]:
array([[ 2, 5, 3, -5],
[ 0, 3, -2, 5],
[ 4, 3, 5, 3],
[ 6, 1, 4, 0]], dtype=int32)
In [9]: tf.expand_dims(x,1).eval() #Expanding dims
Out[9]:
array([[[ 2, 5, 3, -5]],
[[ 0, 3, -2, 5]],
[[ 4, 3, 5, 3]],
[[ 6, 1, 4, 0]]], dtype=int32)
对于一个很大的数据集,我们可能并不需要全部的信息。这时候我们可以使用张量的切片(slicing)和连接(joining)。这样我们能够节省下很多不必要的内存开销。
在以下示例中,我们将提取矩阵切片、拆分、添加填充(add padding),以及打包(pack)和解包(unpack)行。
In [1]: import tensorflow as tf
In [2]: sess = tf.InteractiveSession()
In [3]: t_matrix = tf.constant([[1,2,3],
...: [4,5,6],
...: [7,8,9]])
In [4]: t_array = tf.constant([1,2,3,4,9,8,6,5])
In [5]: t_array2= tf.constant([2,3,4,5,6,7,8,9])
In [6]: tf.slice(t_matrix, [1, 1], [2,2]).eval() # cutting an slice
Out[6]:
array([[5, 6],
[8, 9]], dtype=int32)
In [7]: tf.split(0, 2, t_array) # splitting the array in two
Out[7]:
[<tf.Tensor 'split:0' shape=(4,) dtype=int32>,
<tf.Tensor 'split:1' shape=(4,) dtype=int32>]
In [8]: tf.tile([1,2],[3]).eval() # tiling this little tensor 3 times
Out[8]: array([1, 2, 1, 2, 1, 2], dtype=int32)
In [9]: tf.pad(t_matrix, [[0,1],[2,1]]).eval() # padding
Out[9]:
array([[0, 0, 1, 2, 3, 0],
[0, 0, 4, 5, 6, 0],
[0, 0, 7, 8, 9, 0],
[0, 0, 0, 0, 0, 0]], dtype=int32)
In [10]: tf.concat(0, [t_array, t_array2]).eval() #concatenating list
Out[10]: array([1, 2, 3, 4, 9, 8, 6, 5, 2, 3, 4, 5, 6, 7, 8, 9],
dtype=int32)
In [11]: tf.pack([t_array, t_array2]).eval() # packing
Out[11]:
array([[1, 2, 3, 4, 9, 8, 6, 5],
[2, 3, 4, 5, 6, 7, 8, 9]], dtype=int32)
In [12]: sess.run(tf.unpack(t_matrix)) # Unpacking, we need the run method
to view the tensors
Out[12]:
[array([1, 2, 3], dtype=int32),
array([4, 5, 6], dtype=int32),
array([7, 8, 9], dtype=int32)]
In [13]: tf.reverse(t_matrix, [False,True]).eval() # Reverse matrix
Out[13]:
array([[3, 2, 1],
[6, 5, 4],
[9, 8, 7]], dtype=int32)
可视化汇总信息是任何一个数据科学家工具箱的重要组成部分。
TensorBoard是一个软件实用程序,它支持数据流图的图形表示,还可以在仪表板(dashboard)上解释结果。它的数据通常来自日志数据,如图1-6所示。
可以将图的所有张量和操作信息写入日志。TensorBoard会从日志信息中分析这些数据,并将其以图形的方式呈现给用户。TensorBoard可以在会话运行的过程中查看。
想要启动TensorBoard,可使用如图1-7所示的命令行。
我们构建的每个计算图,TensorFlow都有一个实时记录机制,以便保存模型拥有的几乎所有信息。
图1-6 TensorBoard GUI
图1-7 启动TensorBoard
然而,模型构建器必须考虑到有些信息的维度甚至高达上百维,以便稍后用作分析工具。
为了保存所有必需的信息,TensorFlow API使用数据输出对象,称为汇总(summaries)。
这些汇总将结果写入TensorFlow事件文件(event file),该文件收集会话运行期间生成的所有必需数据。在图1-8中,我们将直接在生成的事件日志目录上运行TensorBoard。
图1-8 运行TensorBoard
(1)添加汇总节点
TensorFlow会话中的汇总,都是由SummaryWriter对象写入。此方法的函数名为:
tf.train.SummaryWriter. init (logdir, graph_def=None)
该方法将在参数的路径中创建一个SummaryWriter和一个事件文件。
SummaryWriter的构造函数将在logdir目录中创建一个新的事件文件。当你调用以下方法(add_summary()、add_session_log()、add_event()或add_graph())的时候,该事件文件将会添加一个Event类型的协议缓存。
如果将graph_def协议缓存传递给构造函数,它将会被添加到事件文件(这等效于稍后调用add_graph())。
当运行TensorBoard时,它将从文件中读取图形定义,并以图形方式显示,以便与其交互。
首先,创建想要汇总数据的TensorFlow图,并决定要在哪些节点进行汇总操作。
TensorFlow中的操作只有在你运行它们的时候,或者另一个操作依赖于它的输出的时候才运行。我们刚刚创建的汇总节点是图形的外设:当前运行的任何操作都不取决于它们。因此,要生成汇总,我们需要运行所有这些汇总节点。手动管理它们将是乏味的,因此使用tf.merge_all_summaries将它们组合到单个操作中,生成所有汇总数据。
然后,你可以运行合并的汇总操作,这将在特定的步骤生成一个序列化的汇总协议缓存(protobuf)对象与所有的汇总数据。最后,要将此汇总数据写入磁盘,将Summary protobuf传递给tf.train.SummaryWriter。
SummaryWriter在其构造函数中使用logdir,这个logdir是非常重要的,所有的事件都会被写入这个目录。此外,SummaryWriter可以选择在其构造函数中使用GraphDef。如果SummaryWriter接收到一个GraphDef,那么TensorBoard也将可视化你的数据流图。
现在你已经改动了图表并拥有一个SummaryWriter,那就可以开始运行网络了!如果需要,你可以每一步运行合并汇总,并记录大量训练数据。但是这样容易造成数据过多,所以比较合适的方法是每n步执行合并汇总操作。
(2)常见汇总操作
这是不同的汇总类型的列表,和它们构造函数的参数:
(3)特殊汇总功能
这些是特殊汇总函数,用于合并不同操作的值:
最后,作为可视化的一个辅助,TensorBoard中使用各种图标表示不同的操作或者变量。这里有一个节点符号表,见表1-5。
表1-5 节点符号表
图标 |
含义 |
---|---|
|
高级节点表示名称域。双击展开高级节点 |
|
彼此未连接的编号节点的顺序 |
|
彼此连接的编号节点的顺序 |
|
单个操作节点 |
|
常数节点 |
|
汇总节点 |
|
表示操作之间的数据流 |
|
表示操作之间的控制依赖 |
|
表示输出操作节点可以突变输入张量 |
通过平移和缩放使用计算图。单击并拖动即可平移,使用滚动手势可以进行缩放。双击一个节点,或单击其“+”按钮,可展开代表一组操作的名称域,如图1-9所示。为了在缩放和平移时轻松跟踪当前视点,右下角有一个缩略图。
图1-9 TensorBoard的节点展开
要关闭打开的节点,可再次双击它,或单击它的“−”按钮。你也可以单击一次以选择节点,节点将变成更暗的颜色,详细信息和连接的节点将出现在TensorBoard右上角的信息卡中。
选择对于 high-degree 节点的理解也很有帮助,选择任意节点,则与它的其余连接的相应节点也会被选中,这使得在进行例如查看哪一个节点是否已保存等操作时非常容易。
单击详情卡片中的一个节点名称时会选中该节点,必要的话,视角会自动平移以使该节点可见。
最后,使用图例上方的颜色菜单,你可以给你的图表选择两个颜色配色方案。默认的结构视图下,当两个high-level节点颜色一样时,其会以相同的彩虹色彩出现,而结构唯一的节点颜色是灰色。还有一个视图则展示了不同的操作运行于什么设备之上。名称域被恰当地根据其中的操作节点的设备来按比例着色。
TensorFlow可以读取许多常用的标准格式,包括大家耳熟能详的CSV、图像文件(JPG和PNG格式)和标准TensorFlow格式。
为了读CSV格式,TensorFlow构建了自己的方法。与其他库(如pandas)相比,读取一个简单的CSV文件的过程有点复杂。
读取CSV文件需要几个准备步骤。首先,我们必须创建一个文件名队列对象与我们将使用的文件列表,然后创建一个TextLineReader。使用此行读取器,剩余的操作将是解码CSV列,并将其保存于张量。如果我们想将同质数据混合在一起,可以使用pack方法。
鸢尾花(Iris)数据集或Fisher's Iris数据集是分类问题的一个常用的基准。它是由Ronald Fisher 1936年在论文《The use of multiple measurements in taxonomic problems》中引入的多变量数据集。Ronald Fisher用其做线性判别分析的示例。
数据集包括3种鸢尾(分别是山鸢尾、变色鸢尾和维吉尼亚鸢尾),各50个样本。在每个样品中测量4个特征:萼片和花瓣的长度和宽度,以厘米计。基于这4个特征的组合,Fisher开发了线性判别模型来区分物种(你可以在本书的代码包中获取此数据集的.csv文件)。
为了读取CSV文件,首先下载下来,并将其放在Python可执行文件的相同目录中。
在下面的代码示例中,我们将从知名的Iris数据库中读取并打印前5个记录。
import tensorflow as tf
sess = tf.Session()
filename_queue = tf.train.string_input_producer(
tf.train.match_filenames_once("./*.csv"),
shuffle=True)
reader = tf.TextLineReader(skip_header_lines=1)
key, value = reader.read(filename_queue)
record_defaults = [[0.], [0.], [0.], [0.], [""]]
col1, col2, col3, col4, col5 = tf.decode_csv(value,
record_defaults=record_defaults) # Convert CSV records to tensors. Each
#column maps to one tensor.
features = tf.pack([col1, col2, col3, col4])
tf.initialize_all_variables().run(session=sess)
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(coord=coord, sess=sess)
for iteration in range(0, 5):
example = sess.run([features])
print(example)
coord.request_stop()
coord.join(threads)
输出如图1-10所示。
图1-10 输出
TensorFlow能够以图像格式导入数据,这对于面向图像的模型非常有用,因为这些模型的输入往往是图像。TensorFlow支持的图像格式是JPG和PNG,程序内部以uint8张量表示,每个图像通道一个二维张量,如图1-11所示。
图1-11 样例图形
本例中,我们会加载一个样例图像,并对其进行一些处理,最后将其保存。
import tensorflow as tf
sess = tf.Session()
filename_queue =
tf.train.string_input_producer(tf.train.match_filenames_once("./blue_jay.jp
g"))
reader = tf.WholeFileReader()
key, value = reader.read(filename_queue)
image=tf.image.decode_jpeg(value)
flipImageUpDown=tf.image.encode_jpeg(tf.image.flip_up_down(image))
flipImageLeftRight=tf.image.encode_jpeg(tf.image.flip_left_right(image))
tf.initialize_all_variables().run(session=sess)
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(coord=coord, sess=sess)
example = sess.run(flipImageLeftRight)
print example
file=open ("flippedUpDown.jpg", "wb+")
file.write (flipImageUpDown.eval(session=sess))
file.close()
file=open ("flippedLeftRight.jpg", "wb+")
file.write (flipImageLeftRight.eval(session=sess))
file.close()
打印示例行将逐行汇总显示图像中RGB值,如图1-12所示。
图1-12 图像中的RGB值
最终的图片如图1-13所示。
图1-13 原始图像和转变后的图像对比(向上翻转和向左翻转)
另一种方法是将任意数据转换为 TensorFlow 官方格式。这种方法将简化混合或者匹配数据集与网络结构。
你可以编写一个小程序,获取你的数据,将它填充到一个示例协议缓存,序列化协议缓存成一个字符串,然后使用tf.python_io.TFRecordWriter类将该字符串写入一个TFRecords文件。
要读取 TFRecords 的文件,可以使用 tf.TFRecordReader 的 tf.parse_single_example 解析器。parse_single_example操作将示例协议缓存解析为张量。
在本章中,我们学习了TensorFlow的主要数据结构和简单操作,并简要介绍了计算图的各个部分。
这些操作将是后续章节的基础。数据科学家可以根据当前数据的整体特性,决定使用简单的模型,或者是使用更复杂的工具。
在下一章中,我们将开始构建和运行计算图,并将使用本章中介绍的一些方法解决问题。
在本章中,我们将会使用上一章节学习的数据转化操作,并且使用聚类(clustering)技术,从给定的数据中发掘有趣的模式,将数据分组。
在处理过程中,我们将会用到两个新的工具,scikit-learn和matplotlib库。其中scikit-learn库能够生成特定结构的数据集,而matplotlib库可以对数据和模型作图。
本章包含如下主题:
本章中,我们会学习两个无监督学习(unsupervised learning)的例子。
无监督学习可以从给定的数据集中找到感兴趣的模式(pattern)。无监督学习,一般不给出模式的相关信息。所以,无监督学习算法需要自动探索信息是怎样组成的,并识别数据中的不同结构。
对于没有标签(unlabeled)的数据,我们首先能做的,就是寻找具有相同特征的数据,将它们分配到相同的组。
为此,数据集可以分成任意数量的段(segment),其中每个段都可以用它的成员的质量中心(质心,centroid)来替代表示。
为了将不同的成员分配到相同的组中,我们需要定义一下,怎样表示不同元素之间的距离(distance)。在定义距离之后,我们可以说,相对于其他的质心,每个类成员都更靠近自己所在类的质心。
在图2-1中,我们可以看到典型的聚类算法的结果和聚类中心的表示。
图2-1 简单聚类算法的输出
k均值(k-means)是一种常见的聚类算法,并且比较容易实现。它非常直接,一般是用于分析数据的第一步。经过该处理,我们能够得到一些关于数据集的先验知识。
k均值算法试图将给定的数据分割为k个不相交的组(group)或者簇(cluster),每个簇的指标就是该组所有成员的均值。这个点通常称为质心,指具有相同名称的算术实体,并且可以被表示为任意维度中的向量。
k均值是一个朴素的方法,因为它在不知道簇的数量的前提下,寻找合适的质心。
要想知道多少个簇能够比较好地表示给定数据,一个常用的方法是Elbow方法。
此方法的判据和目标是最小化簇成员到包含该成员的簇的实际质心的平方距离的总和。这也称为惯性最小化,k均值的损失函数如下:
k均值算法的机制可以由图2-2所示的流程图展示。
图2-2 k均值流程简单流程图
算法流程可以简化如下。
① 对于未分类的样本,首先随机以k个元素作为起始质心。为了简洁,也可以简化该算法,取元素列表中的前k个元素作为质心。
② 计算每个样本跟质心的距离,并将该样本分配给距离它最近的质心所属的簇,重新计算分配好后的质心。从图中,你能看到质心在像真正的质心移动。
③ 在质心改变之后,它们的位移将引起各个距离改变,因此需要重新分配各个样本。
④ 在停止条件满足之前,不断重复第二步和第三步。
可以使用不同类型的停止条件。
k均值示意图如图2-3所示。
图2-3 k均值示意图
该方法的优点:
但是简单是有成本的(没有银弹规则):
k最近邻(k-nearest neighbors,简写为k-nn)是一种简单而经典的聚类方法。该方法只需查看周围点的类别信息,并且假设所有的样本都属于已知的类别。其流程图如图2-4所示,示意图如图2-5所示。
图2-4 k最近邻流程图
图2-5 k-nn示意图
k-nn有多种实现方式,本章中我们会使用半监督(Semi Supervised)方式。我们有一个训练集,它已经有了类别信息,随后我们猜测给定样本所该具有的类别信息。
在图2-4中,我们可以看到算法的步骤分解。可以通过以下步骤概括:
① 设定训练集的数据类别信息。
② 然后读取下一个要分类的样本,并计算从新样本到训练集的每个样本的欧几里得距离。
③ 同欧几里得距离上最近的样本来确定新样本的类别信息。确定的方式就是最近的k个样本的投票。
④ 重复以上步骤,直到所有测试样本都确定了类别。
该方法的优点如下。
缺点是:计算成本高(必须计算训练集点和每个新样本之间的所有距离)。
我们将会在本部分讨论有用的库。
数据绘图是数据科学学科的一个组成部分。为此,我们需要一个非常强大的框架,以能够绘制我们的结果。对于这个任务,matplotlib中没有通用框架来解决,我们使用matplotlib库。
在matplotlib官方网站(http://matplotlib.org/),matplotlib的定义是:
“matplotlib是一个Python下的2D绘图库,能够在跨平台的交互环境中产生各种硬拷贝格式的印刷级的图形。”
下面进行合成数据绘图示例。本例中,我们将会产生一个包含100个随机数的列表,用matplotlib绘制这100个数据,并生成图像文件。
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
with tf.Session() as sess:
fig, ax = plt.subplots()
ax.plot(tf.random_normal([100]).eval(), tf.random_normal([100]
).eval(),'o')
ax.set_title('Sample random plot for TensorFlow')
plt.savefig("result.png")
绘图结果如图2-6所示。
图2-6 TensorFlow生成数据的matplotlib绘图
![]()
查看matplotlib绘图模块的更多的用法,请参考:http://matplotlib.org/。
TensorFlow当前还没有集成易于生成人工数据集的方法。因此,我们会使用sklearn库来帮忙。
在scikit-learn的官方网站(https://scikit-learn.org/stable/),scikit-learn的介绍是:
“scikit-learn(原名 scikits.learn)是一个基于Python编程语言的开源机器学习库。它具有各种分类,回归和聚类算法,并可以与Python数值和科学计算库NumPy以及SciPy互操作。”
本例中,我们会使用scikit-learn的数据集模块,来生成和加载各种人工数据集。
![]()
查看更多的scikit-learn数据集模块的说明和解释,请参考:http://scikit-learn.org/stable/datasets/。
我们将会使用如下的人工数据集,如图2-7所示。
图2-7 块状、环状和月牙状数据集
这个数据集用于测试聚类算法。该数据集是特意设计,专门用于测试聚类算法的准确程度的。
以下是生成块状数据集的方法:
sklearn.datasets.make_blobs(n_samples=100, n_features=2, centers=3,
cluster_std=1.0, center_box=(-10.0, 10.0), shuffle=True,
random_state=None)
n_samples是数据的数目,n_features是特征数据的列的数目(维度),centers是类的中心,cluster_std是标准差,center_box是随机生成数据中心时中心的边界,shuffle指是否打乱样品,random_state是随机种子。
这是一个圆环套圆环的数据集。这是一个非线性可分的问题,所以需要使用非线性模型。对于这种数据集,简单的算法如k-means就不能处理。
以下是生成环形数据集的方法:
sklearn.datasets.make_circles(n_samples = 100,shuffle = True,noise = None,
random_state = None,factor = 0.8)
n_samples是数据的数目,shuffle指数据是否发乱,noise是添加到圆形数据集上的随机噪声数据,random_state是随机种子,factor是环形数据间的比例因子。
本章中,我们使用的是人工数据集,它的生成方式如下:
centers = [(-2, -2), (-2, 1.5), (1.5, -2), (2, 1.5)]
data, features = make_blobs (n_samples=200, centers=centers, n_features =
2, cluster_std=0.8, shuffle=False, random_state=42)
通过matplotlib绘制该数据集:
ax.scatter(np.asarray(data).transpose()[0],
np.asarray(data).transpose()[1], marker = 'o', s = 250)
plt.plot()
最终结果如图2-8所示。
图2-8 块状数据集散点图
Points变量用来存放数据集的点的坐标,centroids变量用于存放每个组质心的坐标,clustering_assignments变量用来存放为每个数据元素分配的类的索引。
比如说,clustering_assignments[2]=1表示数据data[2]的数据点被分配到1类,而1类的质心坐标通过访问centroids[1]得到。
points=tf.Variable(data)
cluster_assignments = tf.Variable(tf.zeros([N], dtype=tf.int64))
centroids = tf.Variable(tf.slice(points.initialized_value(), [0,0], [K,2]))
然后,我们可以通过matplotlib库绘制出质心的位置:
fig, ax = plt.subplots()
ax.scatter(np.asarray(centers).transpose()[0],
np.asarray(centers).transpose()[1], marker = 'o', s = 250)
plt.show()
图2-9是绘制结果。
图2-9 中心点的位置
然后我们对所有的质心做N次复制,对每个样本点做K次复制,这样样本点和质心的形状都是N×K×2,我们就可以计算每一个样本到每一个质心点之间在所有维度上的距离。
rep_centroids = tf.reshape(tf.tile(centroids, [N, 1]), [N, K, 2])
rep_points = tf.reshape(tf.tile(points, [1, K]), [N, K, 2])
sum_squares = tf.reduce_sum(tf.square(rep_points - rep_centroids),
reduction_indices=2)
然后我们对所有维度求和,得到和最小的那个索引(这个索引就是每个点所属的新的类):
best_centroids = tf.argmin(sum_squares, 1)
centroids也会在每个迭代之后由bucket_mean函数更新,具体请查看完整的源码。
本例的停止条件是所有的质心不再变化:
did_assignments_change = tf.reduce_any(tf.not_equal(best_centroids,
cluster_assignments))
此处,我们使用control_dependencies来控制是否更新质心:
with tf.control_dependencies([did_assignments_change]):
do_updates = tf.group(
centroids.assign(means),
cluster_assignments.assign(best_centroids))
当程序结束的时候,我们得到如图2-10所示的输出。
图2-10 k均值运行结果
图2-10是一次迭代之中的质心变化,而图2-11是不同迭代中质心的变化。
图2-11 不同迭代中的质心变化
完整源代码如下:
import tensorflow as tf
import numpy as np
import time
import matplotlib
import matplotlib.pyplot as plt
from sklearn.datasets.samples_generator import make_blobs
from sklearn.datasets.samples_generator import make_circles
DATA_TYPE = 'blobs'
# Number of clusters, if we choose circles, only 2 will be enough
if (DATA_TYPE == 'circle'):
K=2
else:
K=4
# Maximum number of iterations, if the conditions are not met
MAX_ITERS = 1000
start = time.time()
centers = [(-2, -2), (-2, 1.5), (1.5, -2), (2, 1.5)]
if (DATA_TYPE == 'circle'):
data, features = make_circles(n_samples=200, shuffle=True, noise= 0.01, factor=0.4)
else:
data, features = make_blobs (n_samples=200, centers=centers, n_features = 2, cluster_std=0.8, shuffle=False, random_state=42)
fig, ax = plt.subplots()
ax.scatter(np.asarray(centers).transpose()[0], np.asarray(centers).transpose()[1], marker = 'o', s = 250)
plt.show()
fig, ax = plt.subplots()
if (DATA_TYPE == 'blobs'):
ax.scatter(np.asarray(centers).transpose()[0], np.asarray(centers).transpose()[1], marker = 'o', s = 250)
ax.scatter(data.transpose()[0], data.transpose()[1], marker = 'o', s = 100, c = features, cmap=plt.cm.coolwarm )
plt.plot()
points=tf.Variable(data)
cluster_assignments = tf.Variable(tf.zeros([N], dtype=tf.int64))
centroids = tf.Variable(tf.slice(points.initialized_value(), [0,0], [K,2]))
sess = tf.Session()
sess.run(tf.initialize_all_variables())
sess.run(centroids)
rep_centroids = tf.reshape(tf.tile(centroids, [N, 1]), [N, K, 2])
rep_points = tf.reshape(tf.tile(points, [1, K]), [N, K, 2])
sum_squares = tf.reduce_sum(tf.square(rep_points - rep_centroids),
reduction_indices=2)
best_centroids = tf.argmin(sum_squares, 1)
did_assignments_change = tf.reduce_any(tf.not_equal(best_centroids, cluster_assignments))
def bucket_mean(data, bucket_ids, num_buckets):
total = tf.unsorted_segment_sum(data, bucket_ids, num_buckets)
count = tf.unsorted_segment_sum(tf.ones_like(data), bucket_ids, num_buckets)
return total / count
means = bucket_mean(points, best_centroids, K)
with tf.control_dependencies([did_assignments_change]):
do_updates = tf.group(
centroids.assign(means),
cluster_assignments.assign(best_centroids))
changed = True
iters = 0
fig, ax = plt.subplots()
if (DATA_TYPE == 'blobs'):
colourindexes=[2,1,4,3]
else:
colourindexes=[2,1]
while changed and iters < MAX_ITERS:
fig, ax = plt.subplots()
iters += 1
[changed, _] = sess.run([did_assignments_change, do_updates])
[centers, assignments] = sess.run([centroids, cluster_assignments])
ax.scatter(sess.run(points).transpose()[0], sess.run(points).transpose()[1],
marker = 'o', s = 200, c = assignments, cmap=plt.cm.coolwarm )
ax.scatter(centers[:,0],centers[:,1], marker = '^', s = 550, c = colourindexes, cmap=plt.cm.plasma)
ax.set_title('Iteration ' + str(iters))
plt.savefig("kmeans" + str(iters) +".png")
ax.scatter(sess.run(points).transpose()[0], sess.run(points).transpose()[1], marker = 'o', s = 200, c = assignments, cmap=plt.cm.coolwarm )
plt.show()
end = time.time()
print ("Found in %.2f seconds" % (end-start)), iters, "iterations"
print "Centroids:"
print centers
print "Cluster assignments:", assignments
对于环状数据,我们知道,每个类不能由简单的均值替代。如图2-12所示,两个圆共享同一个质心,或者两个质心非常接近,这样我们就不能预测出一个清晰的输出。
对于本数据集,我们只用两个类,以确保该算法的缺点能够被读者理解,如图2-13所示。
图2-12 环状数据集
图2-13 k均值算法应用于环状数据集
正如我们所见,初始质心向样本最集中的地方漂移,能够将数据线性分割。这是这个算法最大的限制之一。要处理非线性可分的数据集,我们可以尝试其他的分类方法,如基于密度的抗噪聚类方法(density based spatial clustering of applications with noise,DBSCAN),但这已经超过本书的范围。
本例中,我们使用的数据集是上面的算法(k均值)不能正确分类的问题。
本例中的数据集跟上例一样,还是两类,但是这次我们会加大数据的噪声(从0.01到0.12):
data, features = make_circles(n_samples=N, shuffle=True, noise=0.12, factor=0.4)
训练集的数据绘制如图2-14所示。
图2-14 生成环状数据集
这里面的变量除了存放原始数据,还有一个列表,用来存放为每个测试数据预测的测试结果。
在聚类问题中,我们使用的距离描述跟前面一样,都是欧几里得距离。在每一个聚类的循环中,计算测试点与每个存在的训练点之间的距离,找到最接近那个训练点的索引,使用该索引寻找最近邻的点的类:
distances = tf.reduce_sum(tf.square(tf.sub(i , tr_data)),reduction_indices=1)
neighbor = tf.arg_min(distances,0)
本例中,当处理完测试集中所有的样本后,整个过程结束。
图2-15是k-nn算法的结果。我们可以看到,至少在有限数据集的范围内,该算法比无重叠、块状优化、k均值方法的效果好。
图2-15 k-nn对环状数据集的结果
完整源代码如下:
import tensorflow as tf
import numpy as np
import time
import matplotlib
import matplotlib.pyplot as plt
from sklearn.datasets.samples_generator import make_circles
N=210
K=2
# Maximum number of iterations, if the conditions are not met
MAX_ITERS = 1000
cut=int(N*0.7)
start = time.time()
data, features = make_circles(n_samples=N, shuffle=True, noise= 0.12, factor=0.4)
tr_data, tr_features= data[:cut], features[:cut]
te_data,te_features=data[cut:], features[cut:]
fig, ax = plt.subplots()
ax.scatter(tr_data.transpose()[0], tr_data.transpose()[1], marker = 'o', s = 100, c = tr_features, cmap=plt.cm.coolwarm )
plt.plot()
points=tf.Variable(data)
cluster_assignments = tf.Variable(tf.zeros([N], dtype=tf.int64))
sess = tf.Session()
sess.run(tf.initialize_all_variables())
test=[]
for i, j in zip(te_data, te_features):
distances = tf.reduce_sum(tf.square(tf.sub(i , tr_data)),reduction_indices=1)
neighbor = tf.arg_min(distances,0)
test.append(tr_features[sess.run(neighbor)])
print test
fig, ax = plt.subplots()
ax.scatter(te_data.transpose()[0], te_data.transpose()[1], marker = 'o', s = 100,
c = test, cmap=plt.cm.coolwarm )
plt.plot()
end = time.time()
print ("Found in %.2f seconds" % (end-start))
print "Cluster assignments:", test
在本章中,我们学习了一些目前可以实现的简单模型,并在细节部分做到了尽可能的详细。
从现在起,我们掌握了如何生成人工数据集,这使我们能够快速测试一个模型对于不同数据集的有效性,从而评估它们的优点和缺点。
此外,我们已经实现了第一个迭代方法并进行了收敛性测试。下面的章节中,我们会用类似的方式实现其他模型,但使用更精细和更精确的方法。
在下一章中,我们将使用线性函数解决分类问题,并且第一次使用来自训练集的信息来学习数据特征。这是有监督学习(supervised learning)的目标,而且这对于解决许多现实生活中的问题更有用。