转自 [译]与TensorFlow的第一次接触(三)之聚类

2016.08.09 16:58* 字数 4316 阅读 7916评论 5喜欢 18

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

本章中,会讲解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后,我们在下章中可以一步步建立一个单层神经网络。

[译]与TensorFlow的第一次接触(三)之聚类的更多相关文章

  1. 第一次接触FPGA至今,总结的宝贵经验

    从大学时代第一次接触FPGA至今已有10多年的时间,至今记得当初第一次在EDA实验平台上完成数字秒表.抢答器.密码锁等实验时那个兴奋劲.当时由于没有接触到HDL硬件描述语言,设计都是在MAX+plus ...

  2. Hybird App(一)----第一次接触

    App你知道多少 一 什么是Native App 长处 缺点 二 什么是Web App 长处 缺点 三 什么是Hybrid App 长处 缺点 四 Web AppHybrid AppNative Ap ...

  3. 第一次接触C++------感触

    2018/09/24 上大学第一次接触C++,感觉还挺有趣的. C语言是计算机的一门语言,顾名思义,语言嘛,有它自己独特的语法. 第一次用C++敲代码,觉得还挺不错的,可以从中找到乐趣.咏梅老师布置的 ...

  4. 孤荷凌寒自学python第五十天第一次接触NoSql数据库_Firebase

    孤荷凌寒自学python第五十天第一次接触NoSql数据库_Firebase (完整学习过程屏幕记录视频地址在文末) 之前对关系型数据库的学习告一段落,虽然能力所限没有能够完全完成理想中的所有数据库操 ...

  5. 百度地图API的第一次接触

    因为项目的需求,第一次接触了百度API. 第一步:引用百度地图API的脚本 如果在局域网环境中,要把地图文件和js文件都要下载下来 <script type="text/javascr ...

  6. 第一次接触终极事务处理——Hekaton

    在这篇文章里,我想给出如何与终极事务处理(Extreme Transaction Processing (XTP) )的第一次接触,即大家熟知的Hakaton.如果你想对XTP有个很好的概况认识,我推 ...

  7. 第一次接触数据库(SQLite)

    第一次接触,学了创建列表 + 行的删除 + 内容的更改 + 删除列表 第一次接触要知道一些基本知识 NULL(SQL) = Nnoe(python)  #空值 INTEGER = int  #整数 R ...

  8. R中K-Means、Clara、C-Means三种聚类的评估

    R中cluster中包含多种聚类算法,下面通过某个数据集,进行三种聚类算法的评估 # ============================ # 评估聚类 # # ================= ...

  9. 第一次接触Axure

    现在已经是凌晨4:21了,我的第一份Axure.RP文件终于接近尾声,我带着些许疲倦些许兴奋的状态写下这篇博客,记录我和Axure的初遇.       三天前,我加入了湖南大学金山俱乐部,参加了第一次 ...

随机推荐

  1. FCC JS基础算法题(3):Find the Longest Word in a String (找出最长单词)

    题目描述: 在句子中找出最长的单词,并返回它的长度.函数的返回值应该是一个数字. 基本思路,将字符串转换成数组,然后得出数组中单个元素的长度,对长度进行排序,返回最大的一个 代码: function ...

  2. brand new start

    做了约两年半的安全,留下了约五十多篇笔记,从电脑搬过来,免的丢了

  3. 浅入浅出JDBC————1分钟了解JDBC

    一.了解基本的几个jdbc需要的类 1.1DriverManager类 DriverManager类是一个jdbc的驱动服务类.通常使用该类获得一个Connection对象,得到一个数据库的链接. 1 ...

  4. 【转】spring boot mybatis 读取配置文件

    spring boot mybatis 配置整理 一.加载mybatis的配置 1.手写配置,写死在代码里 import java.io.IOException; import java.util.P ...

  5. /etc/hosts和/etc/hostname区别

    /etc/hosts主要是ip和域名的对应 /etc/hostname主要是本地主机域名(本地主机名修改过后需要重启服务器才能生效) 如果我想在另一台linux主机里面使用域名访问上面这台主机A,只需 ...

  6. vs2015 加载项目的时启动:无法启动 IIS Express Web 服务器

    使用Visual Studio 2015 运行ASP.NET项目时,提示“无法启动IIS Express Web服务器”,无法运行,如图: 一般出现在重装系统之后,或者项目是从别的电脑上复制过来的.解 ...

  7. [zz]LyX中文问题

    http://www.cnblogs.com/biaoyu/archive/2012/04/28/2475318.html LyX是一款极为优秀的所见即所得的文档处理软件,与MS Word相比,其排版 ...

  8. 域名到站点的负载均衡技术一览(主要是探讨一台Nginx抵御大并发的解决方案)(转)https://www.cnblogs.com/EasonJim/p/7823410.html

    一.问题域 Nginx.LVS.Keepalived.F5.DNS轮询,往往讨论的是接入层的这样几个问题: 1)可用性:任何一台机器挂了,服务受不受影响 2)扩展性:能否通过增加机器,扩充系统的性能 ...

  9. 3、PHP中常用的数据库操作函数解析

    mysql_connect  连接数据库 mysql_select_db 选择需要操作的数据库 mysql_query 执行数据库操作语句 mysql_fetch_array 以数组的形式返回每行查询 ...

  10. c++11 function_typetraits备忘

    function traits. 获取函数或成员函数的返回类型,参数类型,参数长度,类类型. 函数参数列表推断基于typelist:http://www.cnblogs.com/flytrace/p/ ...