译文 | 与TensorFlow的第一次接触 第三章:聚类

前一章节中介绍的线性回归是一种监督学习算法,我们使用数据与输出值(标签)来建立模型拟合它们。但是我们并不总是有已经打标签的数据,却仍然想去分析它们。这种情况下,我们可以使用无监督的算法如聚类。因为聚类算法是一种很好的方法来对数据进行初步分析,所以它被广泛使用。

本章中,会讲解K-means聚类算法。该算法广泛用来自动将数据分类到相关子集合中,每个子集合中的元素都要比其它集合中的元素更相似。此算法中,我们没有任何目标或结果来预测评估。

本章中依然会介绍TensorFlow的使用,并介绍基础数据结构tensor的更多细节。本章开头介绍tensor的数据类型与分析可在该数据结构上执行的运算变换。接下来展示使用tensor来实现的K-means算法。

基础数据结构—tensor

TensorFlow使用基础数据结构---tensor来表示所有数据。一个tensor可以看成是一个拥用静态数据类型动态大小且多维的数组,它可以从布尔或string转换成数值类型。下表是一些主要类型及在Python中对对应的类型:

另外,每个tensor都有一个秩,也是tensor维度的数量。例如,下面的tensor的秩为2:

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

tensor可以拥有任意秩。秩为2的tensor经常被看作矩阵,秩为1的tensor会被看作vector,秩为0的被看作标量值。

TensorFlow文档中使用三种不同称谓来描述tensor的维度:Shape,Rank,Dimension。下面的表展示了它们三者之间的关系,

TensorFlow提供的一系列操作来计算这些tensor,接下来我们会讨论下表中的一些操作。

通过本章,我们会继续讨论更多的细节。在Tensorflow的官方网站上能找到更多的操作列表及每一个操作的细节。

举个例子,假如你想扩展一个2*2000(2D tensor)为立方体(3D tensor)。可以使用tf.expand_dims函数,它可以向tensor中插入一个维度。

tf.expand_dims会向tensor中插入一个维度,插入位置就是参数代表的位置(维度从0开始)。

以可视化来展示的话,上面的转换过程如下图所示:

正如你所看到的,我们得到一个3D tensor,但根据函数参数我们无法判断新维度D0的大小。

如果调用get_shape()来获得tensor的shape,可以看到D0没有大小:

print expanded_vectors.get_shape()

显示的结果如下:

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

本章的稍后,我们可以看到,由于TensorFlow的shape传递特性,很多tensor的数学运算函数(正如第一章中提到的)可以自已发现未确定大小维度的大小,并将该值赋给它。

TensorFlow中的数据存储

Tensorflow程序中主要有三种方式来获取数据:

1、从数据文件

2、以常数与变量预加载

3、Python代码提供的数据

下面简要描述这三种方式:

1、数据文件

通常,原始数据从数据文件中下载。这个过程并不复杂,建议读者去TensorFlow官方网站查看如何从不同类型文件中下载数据的细节。你也可以查看input_data.py代码(可以从github上下载),它会从文件中加载MNIST数据(下一章中使用该数据)。

2、变量与常数

当提到小的数据集时,数据可提前加载到内存中;正如之前例子中看到的,有两种基本方式来创建它们:

通过constant()来创建常数

通过Variable()来创建变量

TensorFlow提供了不同的操作来创建常数。下表中对最重要的几个操作进行了总结:

用TensorFlow训练模型的过程中,参数以变量的形式保存在内存中。当变量创建后,可以将其作为初始值(可能是一个常数或随机值)给一个tensor,该tensor可做为参数传给一个函数。Tensorflow提供了一系列操作来产生不同分布的随机tensor:

一个重要的细节是所有这些操作都需要一个确定shape的tensor作为参数,返回的变量拥有同样的shape。总而言之,变量拥有一个固定的shape,但如有需要,Tensorflow提供了reshape的机制。

当创建变量后,它们必须在图创建完之后且调用run()函数之前显示初始化。可以通过调用tf.initialize_all_variables()来进行初始化。在训练过程中与训练完成后,可通过tf.train.Saver()类来将变量保存到磁盘中,该类的相关细节超过了本书的讨论范围。

3、Python代码提供数据

最后,在程序执行过程中,我们可通过叫做“符号变量”或placeholder来操作数据。Placeholder()的调用,包含了元素类型与tensor的shape为参数,还有一个可选参数name。

在Python代码中调用Session.run()或Tensro.eval()的同时,这个tensor与feed_dict参数中指定的数据相关联。第一章中的代码如下:

代码的最后一行中调用sess.run()时,我们通过feed_dict参数给两个tensor赋值。

通过简短分析tensor,希望从现在开始读者阅读接下来章节的代码时,没有任何困难。

K-means算法

K-means是一种用来解决聚类问题的无监督算法。该算法依据一个简单容易的方式来对数据集分成一定数目(假设K个类别)的类别。一个类别中的数据点是相似的,不同类别中的数据点是各种各样的,也就是说同一子类别中的元素比其它子类别中的元素更相似。

算法的结果是生成K个点集合,叫做centroids,这是不同组的焦点,标签代表了集合中的点,k个聚类都有自己的tag。一个类中的所有点离centroid要比其它任意centroid要近。

如果我们想直接最小化error function,则生成聚类是非常耗计算的(也就是NP问题);一些算法通过启发式方法来达到局部快速收敛。更通用的算法使用迭代优化技术,仅覆盖几次迭代。

一般来说,这种技术主要有三步:

1.初始化(step 0):初始化K个centroid的集合

2.分配(step 1):将每一个对象赋给最近的组

3.更新(step 2):计算每个新组的新centroid

有多种方法来初始化K个centroid。其中之一就是在数据集中随机选取K个对像并将它们看作centroid;接下来的例子中我们会使用这种方法。

分配(step 1)与更新(step 2)在循环中是可选的,循环直到算法开始收敛,举例来说就是分配点到组后,就不再发生变化。

因为这是一个启发式算法,无法保证算法收敛到最佳目标,结果依赖于初始集合。因为算法通常运行很快,可用不同的初始centroid来多次执行该算法,然后评估结果。

开始在Tensorflow中编码实现K-means算法前,建议先生成一些数据用来进行实验。有一种简单的方式,在2D空间中随机生成2000个点,它们服从两个正态分布,我们可画出空间分布来更好的理解结果。示例代码如下:

如我们在之前章节中所做的,我们可使用Python图库来用图表画出这些点。建议使用matplotlib,这次我们使用基于matplotlib的可视化库Seaborn,操作数据用库pandas,该库能运算更复杂的数据结构。

如果你没有安装这些库,在继续下一步前可能过pip来安装它们。

建议使用如下代码来显示我们随机生成的点:

这段代码生成两维空间下的点图如下:

TensorFlow中实现的K-means算法来对上面生成的点进行分组,假如四个类,求例代码如下(基于Shawn Simister在他博客中发表的模型):

建议读者用如下代码来检查assignment_values tensor中的结果,它会生成一张分布图:

上面代码执行后生成的分布图如下:

新组

也许读者会对上面的K-means代码感到困惑。接下来详细分析每一行代码,我们会特别关注相关tensor的变化及它们在程序中如何运算。

首先需要做的是把我们的数据移到tensor中。我们将所有随机生成的点保存到常量tensor中:

vectors=tf.constant(conjunto_vectors)

根据之前讲解的算法,我们在开始就要决定初始centroids。一种方法就是从输入数据中随机选择K个对像。下面的代码就能达到这个目的,随机排列这些点并选择前K个点作为centroids:

这K个点保存在一个2D tensor中。可通过调用tf.Tensor.get_shape()获得这些tensor的shape:

我们可以看到,vectors是一个数组,D0包含了2000个positions,D1包含了每一个点的坐标x,y。centroids是包含四个元素的矩阵,D0代表每一个形心的位置,D1代表点的坐标x,y。

接下来,算法进入一个循环。第一步就是为每一个点,根据平方欧氏距离(只能被用来比较距离)计算最近的centroid。

为计算该值,需要使用tf.sub(vectors, centroides)。虽然这两个相减的tensor都是2维的,但在第1维度上有不同的大小(2000VS 4 D0中),实际上,这也代表了不同的意义。

为解决这个问题,我们需要使用之前提到的函数,如tf.expand_dims用来在两个tensor中插入一个维度。目的是把这两个tensor从2维转换成3维,使得大小匹配可以进行减法:

tf.expand_dims在每一个tensor中插入一个维度,在vector的tensor中第一维度(D0)插入,在centroides tensor中第二维度(D1)插入。从图片来看,扩展后的tensor中各维度有了同样的含义:

看上去这个问题解决了,实际上,如果仔细来看,两个tensor中都有维度不能确定大小。通过调用get_shape()可以看到:

输出如下:

1代表没有赋予大小。

之前就已经说明TensorFlow允许传递,所以tf.sub函数能够自己发现如何在两个tensor间进行减法。

直观地来看上面的图,两个tensor的形状是匹配的,而且在指定维度上也有相同的大小。这些数学运算就像发生在D2维度上那样。然而,D0中只有expanded_centroides有固定大小。

在这种情况下,TensorFlow假设expanded_vectors拥有同样的大小,如果我们想执行元素对元素的减法。

对于expended_centroides的D1同样如此,Tensorflow推断出expanded_vectors的D1大小。

在分配步骤(step 1),算法可实现为如下四行代码,用来计算平方欧氏距离:

如果我们察看tensor的形状,diff, sqr,distances and assignments的大小分别为:

tf.sub返回一个tensor,包含了两个tensor相减的值(vector表明D1的大小,centroid表明D0的大小。D2中表明了x,y)。sqr tensor包含了它们的平方。在distance tensor中,已经减少了一个维度,减少的维度在tf.reduce_sum函数中表明。

通过这个例子来表明TensorFlow提供了一些操作来进行运算,就像tf.reduce_sum来减少tensor的维度。下面的表中总结了一些很重要的操作:

最后,通过tf.argmin来赋值,它返回tensor某一维度中的最小值索引(此处为D0,代表centroid)。同样也有tf.argmax操作:

实际上,上面的四行代码可以总结成一行代码中:

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

不管如何,定义结点与执行内部图的那些内部tensor与操作都跟我们之前提到一样。

计算新形心

一旦在迭代中创建了新组,需要记住算法的新步骤中包含了计算组的新形心。正如我们之前看到的代码;

means=tf.concat(0,[tf.reduce_mean(tf.gather(vectors,tf.reshape(tf.where(tf.equal(assignments,c)),[1,-1])),reduction_indices=[1])forcinxrange(k)])

这段代码中,means tensor是连接k个tensor的结果,这k个tensor都是由那些平均值属于每一个k类的点组成的。

接下来,会详细分析计算每一个点属于哪个cluster的代码:

A.通过equal获得一个布尔tensor(Dimension(2000)),true代表了assignment tensor与K cluster相匹配的位置,同时我们也计算了点的平均值

B.where根据传进来的布尔tensor中元素值为true的位置来构造一个tensor(Dimension(1) x Dimension(2000))

C.reshape根据vectors tensor内部那些属于c cluster的点的索引来构建一个tensor(Dimension(2000) x Dimension(1))

D.gather从c cluster中收集所有点的坐标并创建tensor(Dimension(1) x Dimension(2000))

E.reduce_mean则是根据c cluster中所有点的平均值来创建tensor(Dimension(1) x Dimension(2))

如果读者想了解代码的更多细节,可访问TensorFlow api页面,通过解说例子来了解所有操作的细节。

图执行

最后,我们来描述循环相关的代码部分与用新计算的平均值tensor来更新centroide的部分。

当run()方法被调用时,我们要在更新的centroids值在下轮迭代使用前,先创建一个赋值操作符用means tensor值更新centroids值:

update_centroides=tf.assign(centroides,means)

我们同样需要在运行图之前创建一个操作来初始化所有变量:

init_op=tf.initialize_all_variables()

到现在为止,所有都准备就绪,可以开始运行图:

sess=tf.Session()

sess.run(init_op)

for step in xrange(num_steps):

_,centroid_values,assignment_values=sess.run([update_centroides,centroides,assignments])

在这段代码中,每次迭代时,centroids与为每个点新分配的cluster都会被更新。

代码中指定了三个操作,同时需要查看run()的执行状态,并按顺序来运行这三个操作。因为有三个值需要查找,sess.run()返回了三个numpy数组,每个数组分别包含了训练过程中相应的内容。

因为update_centroides这个操作的结果并不需要返回,在返回的turple中相应元素内容为空,用“_”表示不接收该参数。

对于另外两个值,centroids与将点赋给每一个cluster,一旦完成所有迭代计算后,我们可以将这两个变量打印在屏幕上。

使用简单的打印命令,输出如下:

希望读者的电脑上也有接近的值,这说明读者已经成功执行了本章中的相关代码。

建议读者在继续进行下一步之前,先尝试修改某些值。例如num_points,尤其聚类的数量,然后通过生成结果图来查看assignment_values如何变化。

为了测试本章中的代码,可通过github下载本代码。包含本章代码的文件为Kmeans.py,

本章中已经了解了一些TensorFlow的知识,尤其通过TensorFlow中实现一个聚类算法K-means来学习基础数据结构tensor。

了解了tensor后,我们在下章中可以一步步建立一个单层神经网络。

原文发布于微信公众号 - 人工智能LeadAI(atleadai)

原文发表时间:2017-12-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏企鹅号快讯

机器学习——SVM实战

机器学习(十八) ——SVM实战 (原创内容,转载请注明来源,谢谢) 一、概述 本篇主要用python来实现SVM算法,并用SVM算法进行预测分类结果。对于SM...

1988
来自专栏小鹏的专栏

tf API 研读2:math

TF API数学计算 tf...... :math (1)刚开始先给一个运行实例。         tf是基于图(Graph)的计算系统。而图的节点则是由操作(...

5035
来自专栏desperate633

LintCode 寻找缺失的数题目分析方法二 交换法

给出一个包含 0 .. N 中 N 个数的序列,找出0 .. N 中没有出现在序列中的那个数。

633
来自专栏Python中文社区

实现属于自己的TensorFlow(一) - 计算图与前向传播

前言 前段时间因为课题需要使用了一段时间TensorFlow,感觉这种框架很有意思,除了可以搭建复杂的神经网络,也可以优化其他自己需要的计算模型,所以一直想自...

3827
来自专栏深度学习入门与实践

【深度学习系列】PaddlePaddle之数据预处理

  上篇文章讲了卷积神经网络的基本知识,本来这篇文章准备继续深入讲CNN的相关知识和手写CNN,但是有很多同学跟我发邮件或私信问我关于PaddlePaddle如...

2188
来自专栏人工智能

Tensorflow下Char-RNN项目代码详解

前言 Char-RNN,字符级循环神经网络,出自于Andrej Karpathy写的The Unreasonable Effectiveness of Recu...

49210
来自专栏郭耀华‘s Blog

TensorFlow 常用函数汇总

1622
来自专栏专知

【干货】计算机视觉实战系列03——用Python做图像处理

【导读】专知成员Hui上一次为大家介绍Matplotlib的使用,包括绘图,绘制点和线,以及图像的轮廓和直方图,这一次为大家详细讲解Numpy工具包中的各种工具...

39210
来自专栏欧阳大哥的轮子

常见排列组合问题的计算公式

在进行排列组合计算以及概率计算时我们经常会遇到一些具有相同性质的问题。假设问题的样本空间Ω中一共有k种类型的元素α, β,γ... κ。每种类型的元素个数分别为...

832
来自专栏hadoop学习笔记

hanlp源码解析之中文分词算法详解

词图指的是句子中所有词可能构成的图。如果一个词A的下一个词可能是B的话,那么A和B之间具有一条路径E(A,B)。一个词可能有多个后续,同时也可能有多个前驱,它们...

803

扫码关注云+社区