数极客首页

面向程序员的数据挖掘指南8:聚类分析

前几章我们学习了如何构建分类系统,使用的是已经标记好类别的数据集进行训练:

面向程序员的数据挖掘指南8:聚类分析

可以看到,分类器在训练阶段就已经知道各个类别的名称了。那如果我们不知道呢?如何构建一个能够自动对数据进行分组的系统?比如有1000人,每人有20个特征,我想把这些人分为若干个组。

这个过程叫做聚类:通过物品特征来计算距离,并自动分类到不同的群集或组中。有两种聚类算法比较常用:

k-means聚类算法

我们会事先告诉这个算法要将数据分成几个组,比如“请把这1000个人分成5个组”,“将这些网页分成15个组”。这种方法就叫k-means,我们会在后面的章节讨论。

层次聚类法

对于层次聚类法,我们不需要预先指定分类的数量,这个算方法会将每条数据都当作是一个分类,每次迭代的时候合并距离最近的两个分类,直到剩下一个分类为止。因此聚类的结果是:顶层有一个大分类,这个分类下有两个子分类,每个子分类下又有两个子分类,依此类推,层次聚类也因此得命。

面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析

单链聚类

在单链聚类中,分类之间的距离由两个分类相距最近的两个元素决定。如上图中分类A和分类B的距离由A1和B1的距离决定,因为这个距离小于A1到B2、A2到B1的距离。这样一来我们会将A和B进行合并。

全链聚类

在全链聚类中,分类之间的距离由两个分类相距最远的两个元素决定。因此上图中分类A和B的距离是A2到B2的距离,最后会将分类B和C进行合并。

平均链接聚类

在这种聚类方法中,我们通过计算分类之间两两元素的平均距离来判断分类之间的距离,因此上图中会将分类B和C进行合并。

下面让我们用单链聚类法举个例子吧!

我们来用狗的高度和重量来进行聚类:

面向程序员的数据挖掘指南8:聚类分析

标准化!我们先将这些数据转换为修正的标准分。

面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析

计算过程

第一步:我们找到距离最近的两个元素,对他们进行聚类:

面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析

第四步:继续查找距离最近的元素,发现Border Collie已经属于一个分类的,因此进行如下图所示的合并:

面向程序员的数据挖掘指南8:聚类分析

动手实践

你能在下图的基础上继续完成聚类吗?

面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析
  • 我们可以使用优先队列来实现这个聚类算法。
  • 什么是优先队列呢?

普通的队列有“先进先出”的规则,比如向队列先后添加Moa、Suzuka、Yui,取出时得到的也是Moa、Suzuka、Yui:

面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析

在进行聚类时,我们将分类、离它最近的分类、以及距离插入到优先队列中,距离作为优先级。比如上面的犬种示例,Border Collie最近的分类是Portuguese WD,距离是0.232:

面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析

从文件中读取数据

数据文件是CSV格式的(以逗号分隔),第一行是列名,第一列是犬种,第二列之后是特征值:

面向程序员的数据挖掘指南8:聚类分析

初始化优先队列

以Border Collie为例,我们需要计算它和其它犬种的距离,保存在Python字典里:

面向程序员的数据挖掘指南8:聚类分析

此外,我们会记录Border Collie最近的分类及距离:这对犬种是(0, 8),即下标为0的Border Collie和下标为8的Portuguese WD,距离是0.232。

距离相等的问题以及为何要使用元组

你也许注意到了,Portuguese WD和Standard Poodle的距离是0.566,Boston Terrier和Briany Spaniel的距离也是0.566,如果我们通过最短距离来取,很可能会取出Standard Poodle和Boston Terrier进行组合,这显然是错误的,所以我们才会使用元组来存放这对犬种的下标,以作判断。比如说,Portuguese WD的记录是:

面向程序员的数据挖掘指南8:聚类分析

它的近邻Standard Poodle的记录是:

面向程序员的数据挖掘指南8:聚类分析

我们可以通过这个元组来判断这两条记录是否是一对。

距离相等的另一个问题

在介绍优先队列时,我用了歌手的年龄举例,如果他们的年龄相等,取出的顺序又是怎样的呢?

面向程序员的数据挖掘指南8:聚类分析

在犬种示例中,我们让距离成为第一优先级,下标成为第二优先级。因此,我们插入到优先队列的一条完整记录是这样的:

面向程序员的数据挖掘指南8:聚类分析

重复下述步骤,直到仅剩一个分类

我们从优先队列中取出两个元素,对它们进行合并。如合并Border Collie和Portuguese WD后,会形成一个新的分类:

面向程序员的数据挖掘指南8:聚类分析

然后我们需要计算新的分类和其它分类之间的距离,方法是对取出的两个分类的距离字典进行合并。如第一个分类的距离字段是distanceDict1,第二个分类的是distanceDict2,新的距离字段是newDistanceDict:

面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析

代码实践

你能将上面的算法用Python实现吗?你可以从hierarchicalClustererTemplate.py这个文件开始,完成以下步骤:

编写init方法,对于每条记录:

  • 计算该分类和其它分类之间的欧几里得距离;
  • 找出该分类的近邻;
  • 将这些信息放到优先队列的中。

编写cluster方法,重复以下步骤,直至剩下一个分类:

  • 从优先队列中获取两个元素;
  • 合并;
  • 将合并后的分类放回优先队列中。

解答

注意,我的实现并不一定是最好的,你可以写出更好的!

面向程序员的数据挖掘指南8:聚类分析

运行结果和我们手算的一致:

面向程序员的数据挖掘指南8:聚类分析

动手实践

这里提供了77种早餐麦片的营养信息,包括以下几项:

  1. 麦片名称
  2. 热量
  3. 蛋白质
  4. 脂肪
  5. 纤维
  6. 碳水化合物
  7. 维生素

请对这个数据集进行层次聚类:

  • 哪种麦片和Trix最相近?
  • 与Muesli Raisins & Almonds最相近的是?

数据集来自:http://lib.stat.cmu.edu/DASL/Datafiles/Cereals.html

结果

我们只需将代码中的文件名替换掉就可以了,结果如下:

面向程序员的数据挖掘指南8:聚类分析

好了,这就是层次聚类算法,很简单吧!

k-means聚类算法

使用k-means算法时需要指定分类的数量,这也是算法名称中“k”的由来。

k-means是Lloyd博士在1957年提出的,虽然这个算法已有50年的历史,但却是当前最流行的聚类算法!

下面让我们来了解一下k-means聚类过程:

面向程序员的数据挖掘指南8:聚类分析
  1. 我们想将图中的记录分成三个分类(即k=3),比如上文提到的犬种数据,坐标轴分别是身高和体重。
  2. 由于k=3,我们随机选取三个点来作为聚类的起始点(分类的中心点),并用红黄蓝三种颜色标识。
  3. 然后,我们根据其它点到中心点的距离来进行分配,这样就能将这些点分成三类了。
  4. 计算这些分类的中心点,以此作为下一次计算的起始点。重复这个过程,直到中心点不再变动,或迭代次数超过某个阈值为止。

所以k-means算法可概括为:

  1. 随机选取k个元素作为中心点;
  2. 根据距离将各个点分配给中心点;
  3. 计算新的中心点;
  4. 重复2、3,直至满足条件。

我们来看一个示例,将以下点分成两个分类:

面向程序员的数据挖掘指南8:聚类分析

我们选取(1, 4)作为分类1的中心点,(4, 2)作为分类2的中心点;

第二步 将各点分配给中心点

可以用各类距离计算公式,为简单起见,这里我们使用曼哈顿距离:

面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析

通过计算平均值来更新中心点,如x轴的均值是:

(1 + 1 + 2) / 3 = 4 / 3 = 1.33

y轴是:

(2 + 4 + 3) / 3 = 9 / 3 = 3

因此分类1的中心点是(1.33, 3)。计算得到分类2的中心点是(4, 2.4)。

第四步 重复前面两步

两个分类的中心点由(1, 4)、(4, 2)变为了(1.33, 3)、(4, 2.4),我们使用新的中心点重新计算。

重复第二步 将各点分配给中心点

同样是计算曼哈顿距离:

面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析
  • 分类1:(1.5, 2.75)
  • 分类2:(4.5, 2.5)

重复第二步 将各点分配给中心点

面向程序员的数据挖掘指南8:聚类分析
  • 分类1:(1.5, 2.75)
  • 分类2:(4.5, 2.5)

可以看到中心点并没有改变,所以计算也就结束了。

当中心点不再变化时,或者说不再有某个点从一个分类转移到另一个分类时,我们就会停止计算。这个时候我们称该算法已经收敛。算法运行过程中,中心点的大幅转移是在前几次迭代中产生的,后面的迭代中变动的幅度就会减小。也就是说,k-means算法的重点是在前期迭代,而后期的迭代只是细微的调整。

基于k-means的这种特点,我们可以将“没有点发生转移”弱化成“少于1%的点发生转移”来作为计算停止条件,这也是最普遍的做法。

k-means好简单呀!

扩展阅读

k-means是一种最大期望算法,这类算法会在“期望”和“最大化”两个阶段不断迭代。比如k-means的期望阶段是将各个点分配到它们所“期望”的分类中,然后在最大化阶段重新计算中心点的位置。如果你对此感兴趣,可以前去阅读维基百科上的词条。

登山式算法

再继续讨论k-means算法之前,我想先介绍一下登山式算法。

假设我们想要登上一座山的顶峰,可以通过以下步骤实现:

  1. 在山上随机选取一个点作为开始;
  2. 向高处爬一点;
  3. 重复第2步,直到没有更高的点。

这种做法看起来很合理,比如对于下图所示的山峰:

面向程序员的数据挖掘指南8:聚类分析

但对于下面这张图:

面向程序员的数据挖掘指南8:聚类分析

k-means就是这样一种算法,它不能保证最终结果是最优的,因为我们一开始选择的中心点是随机的,很有可能就会选到上面的A点,最终获得局部最优解B点。因此,最终的聚类结果和起始点的选择有很大关系。但尽管如此,k-means通常还是能够获得良好的结果的。

那我们如何比较不同的聚类结果呢?

误差平方和(SSE)

我们可以使用误差平方和(或称离散程度)来评判聚类结果的好坏,它的计算方法是:计算每个点到中心点的距离平方和。

面向程序员的数据挖掘指南8:聚类分析

假设我们对同一数据集使用了两次k-means聚类,每次选取的起始点不一样,想知道最后得到的聚类结果哪个更优,就可以计算和比较SSE,结果小的效果好。

下面让我们开始编程吧!

面向程序员的数据挖掘指南8:聚类分析

我们来分析一下这段代码。

犬种数据用表格来展示是这样的,身高和体重都做了标准化:

面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析

我们在层次聚类中用的也是此法,这样做的好处是能够方便地应用不同的数学函数。比如计算中位数和计算标准分的函数,都是以列表作为参数的:

面向程序员的数据挖掘指南8:聚类分析

__init__函数首先将文件读入进来,按列存储,并进行标准化。随后,它会选取k个起始点,并将记录中的点分配给这些中心点。kCluster函数则开始迭代计算中心点的新位置,直到少于1%的点发生变动为止。

程序的运行结果如下:面向程序员的数据挖掘指南8:聚类分析

聚类结果非常棒!

动手实践

用上面的聚类程序来对麦片数据集进行聚类,令k=4,并回答以下问题:

  1. 甜味麦片都被聚类到一起了吗,如Cap’’Crunch, Cocoa Puffs, Froot Loops, Lucky Charms?
  2. 麸类麦片聚到一起了吗,如100% Bran, All-Bran, All-Bran with Extra Fiber, Bran Chex?
  3. Cheerios被分到了哪个类别,是不是一直和Special K一起?

再来对加仑公里数的数据进行聚类,令k=8。运行结果大致令人满意,但有时候会出现记录数为空的分类。

我要求聚类成8个分类,但其中一个是空的,肯定代码有问题!

我们用示例来看这个问题,假设需要将以下8个点分成3个类别:

面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析

所以说,虽然我们指定了k的值,但不代表最终结果就会有k个分类。这通常是好事,比如上面的例子中,看起来就应该要分成两类。如果有1000条数据,我们指定k=10,但结果有两个为空,那很有可能这个数据集本来就该分成8个类别,因此可以尝试用k=8来重新计算。

另一方面,如果你要求分类都不为空,那就需要改变一下算法:当发现空的分类时,就重新指定这个分类的中心点。一种做法是选取离这个中心点最远的点,比如上面的例子中,发现粉色分类为空,就将中心点变为点1,因为它离粉色中心点最远。

面向程序员的数据挖掘指南8:聚类分析

k-means++

前面我们提到k-means是50年代发明的算法,它的实现并不复杂,但仍是现今最流行的聚类算法。不过它也有一个明显的缺点。在算法一开始需要随机选取k个起始点,正是这个随机会有问题。有时选取的点能产生最佳结果,而有时会让结果变得很差。k-means++则改进了起始点的选取过程,其余的和k-means一致。

以下是k-means++选取起始点的过程:

  1. 随机选取一个点;
  2. 重复以下步骤,直到选完k个点:
  3. 计算每个数据点(dp)到各个中心点的距离(D),选取最小的值,记为D(dp);
  4. 根据D(dp)的概率来随机选取一个点作为中心点。

我们来讲解一下何为“根据D(dp)的概率来随机选取”。假设选取过程进行到一半,已经选出了两个点,现在需要选第三个。假设还有五个点可供选择,它们离已有的两个中心点的距离是:

面向程序员的数据挖掘指南8:聚类分析

我们选取最小的距离:

面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析

比如我们有以下Python数据:

面向程序员的数据挖掘指南8:聚类分析

然后来编写一个函数来完成选取过程:

面向程序员的数据挖掘指南8:聚类分析

如果这个函数运行正确,我们选取100次的话,其中25次应该是dp1,40次是dp2,10次是dp3,15次是dp4,10次是dp5。让我们来测试一下:

面向程序员的数据挖掘指南8:聚类分析

结果是符合预期的!

k-means++选取起始点的方法总结下来就是:第一个点还是随机的,但后续的点就会尽量选择离现有中心点更远的点。

好了,下面让我们开始写代码吧!

代码实践

你能用Python实现k-means++算法吗?k-means++和k-means的唯一区别就是起始点的选取过程,你需要做的是将下面的代码:

面向程序员的数据挖掘指南8:聚类分析

替换为:

面向程序员的数据挖掘指南8:聚类分析

你的任务就是编写这个函数!

解答

面向程序员的数据挖掘指南8:聚类分析

安然事件

你应该还对这件事有些印象吧?安然公司曾是一家超大型企业,有着千亿元的收入和两万名员工(微软只有220亿收入)。由于管理体制的破败和受贿,包括人为制造能源危机致使加州大停电,安然公司最终面临破产,大批人员被判入狱。有一部名为“The Smartest Guys in the Room”的纪录片,读者可以到Netflix或亚马逊上观看。

安然事件的确挺有趣的,不过这和数据挖掘有什么关系呢?

在调查过程中,美国联邦能源管理委员会收获了60万封公司内部邮件。这些邮件可以从网络上下载:?>

https://www.cs.cmu.edu/~./enron/

我们来用其中的一小部分数据来举例,下表整理了一些公司人员互通邮件的次数:

面向程序员的数据挖掘指南8:聚类分析

动手实践

你能使用层次聚类算法将这些人分成若干类别吗?

解答

我们会通过两个人收发邮件的对象来计算相似度。比如我经常和Ann、Ben、Clara等人通信,你也一样,那么我俩就是相似的:

面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析

得到的层次聚类结果是:

面向程序员的数据挖掘指南8:聚类分析
面向程序员的数据挖掘指南8:聚类分析

安然数据中还能挖掘出很多有趣的模式,去下载完整的数据集进行尝试吧!

你也可以对其它数据集进行聚类,看看是否有新的发现。

最后,恭喜完成第八章的学习!

译者:jizhang
英文原文:http://guidetodatamining.com/chapter-8/
文章出处:https://github.com/jizhang/guidetodatamining/blob/master/chapter-8.md

本文采用「CC BY-SA 4.0 CN」协议转载学习交流,内容版权归原作者所有,如涉作品、版权和其他问题请联系「我们」处理。

发表评论

评论已关闭。

相关文章