Han Zheng, Practitioners and Theoretical Researcher, Now working in Alibaba Cloud Corp, China

Welcome to contact me. Wechat:LittleHann,My email, 306211321@qq.com,Job mail:zhenghan.zh@alibaba-inc.com,Job Description:aHR0cHM6Ly9qb2IuYWxpYmFiYS5jb20vemhhb3Bpbi9wb3NpdGlvbl9kZXRhaWwuaHRtP3RyYWNlPXFyY29kZV9zaGFyZSZwb3NpdGlvbkNvZGU9R1A2MDAxMjk=

1. 聚类简介

0x1:聚类是什么?

聚类是一种运用广泛的探索性数据分析技术,人们对数据产生的第一直觉往往是通过 对数据进行有意义的分组,通过对对象进行分组,使相似的对象归为一类,不相似的对象归为不同类。

0x2:聚类的悖论

在研究聚类算法原理以及应用聚类算法的时候,我们自己首先要明白,聚类算法并不总是有效,甚至是完全不合理的。我们称其为” 聚类的悖论 “,之所以会出现悖论,主要的原因是:

  • ”相似对象归为一类,不相似对象归为不同类“这两个目标在很多情况下是互相冲突的。从数学上讲,虽然聚类共享具有等价关系甚至传递关系,但是 相似性(或距离)不具有传递关系 。具体而言,假定有一对象序列,X1,....,Xm,所有相邻元素(Xi-1、Xi+1)两两都非常相似,但是X1和Xm非常不相似。这种情况常常发生在cluster超过一定尺寸的时候,元素之间的传递性假设在这些场景下不一定100%成立。
  • 聚类是一种无监督学习,即我们不能预测label,因此 对于聚类,我们没有明确的成功评估过程 。当然也不是完全没有任何的聚类效果评价指标,分类效用(category utility)就是一个理论上的评价指标。
  • 对于 一个给定的对象集合,可以有多种有意义的划分方式 ,这可能是因为 对象间的距离(或相似性)有多种隐式的定义 ,例如将演讲者的录音根据演讲者的口音聚类或根据内容聚类。所以,给定一个数据集,有多种不同的聚类解决方案
  • 0x3:聚类的公理化描述

    对于各种各样的聚类算法,有没有一些基本性质是独立于具体算法或任务的呢?很多人尝试对聚类提出一个公理化的定义,让我们讨论Kleninberg(2003)提出的尝试方法:

    考虑一个聚类函数F,将任意有限域X及不相似函数d作为输入,返回X的一个划分

    1. 尺度不变性(SI)

    对任意的域集 X,不相似函数 d,以及任意的 a > 0,下式成立:

    尺度不变性是一种非常自然的要求,因为如果聚类函数输出的结果依赖于测量点之间的距离测度单元,那将显得十分奇怪和不合理。

    2. 丰富性(Ri)

    对任意的有限集 X 和划分 C = (C1,C2,....,Ck)(划分到非空子集),存在多种不相似函数 d 使得下式成立:

    丰富性要求主要想说明聚类函数的输出是由函数 d 全权决定,也是一种十分直观的特征。

    3. 一致性(Co)

    如果 d 和 d' 都是 X 上的不相似函数,对任一 ,根据 F(X,d):

  • 如果 x,y 属于同一类,则
  • 如果 x,y 属于不同类,则
  • 一致性要求是和聚类基本定义相关的要求,我们希望相似的点聚到一类,不相似的点分属不同类,因此共享同类的点更相似,已经分离的点不相似,聚类函数应当对之前的聚类决策有很强的“支撑”作用。

    值得注意的是,Kleinberg在2003已经给出了下述“不可能”结论!

    不存在一个函数F同时满足上述三种属性:尺度不变性,丰富性,一致性

    但是,Kleninberg的“不可能”结论可以通过改变属性的限制范围来规避。即不用100%满足上述三条件

    例如,如果讨论含固定数量参数的聚类函数,很自然地将丰富性改为 k-丰富性(即,将域划分到k个子集,这个限制比丰富性RI原则更松散一些) 。k-均值聚类满足k-丰富性、尺度不变性和一致性,因此能够达到一致。或者可以放松一致性属性。

    笔者思考 :

    给定一项任务,聚类函数的选取必须考虑该任务的特定属性(是否可以放松要求,又是否有一些地方是需要强要求)。没有统一的聚类解决方案,就像没有一种分类算法能够对每一项可学习任务都能学习(no free lunch定理)。和其他分类预测一样,聚类必须考虑特定任务的先验知识 。

    0x4:数据挖掘对聚类的典型泛化要求

    不同的算法有着不同的应用背景,有的适合大数据集,可以发现任意形状的聚类;有的算法思想简单直观,适用于小数据集。总的来说,算法都试图从不同途径实现对数据集进行高效、可靠的聚类。数据挖掘对聚类的典型要求包括:

    1. 可伸缩性:

    当聚类对象由几百上升到几百万,我们希望最后的聚类结果的准确度能保持一致

    2. 处理不同类型属性的能力:

    有些聚类算法,其处理对象的属性的数据类型只能是数值类型,但是在实际应用场景中,我们往往会遇到其他类型的数据(例如二元数据),分类数据等等,虽然我们也可以在预处理数据时将这些其他类型的数据转换成数值型数据,但是在聚类效率上或者聚类准确度上往往会有折损

    3. 发现任意形状的类簇:

    因为许多聚类算法是基于距离(例如欧式距离或曼哈顿距离)来量化实例对象之间的相似度的,基于这种方式,我们往往只能发现相似尺寸和密度的球状类簇或者凸形类簇。但是在很多场景下,类簇的形状可能是任意的

    4. 对聚类算法初始化参数的知识需求的最小化:

    很多算法在分析过程中需要开发者提供一定的参数(例如期望的类簇K个数、类簇初始质心),这导致了聚类结果对这些参数是十分敏感的,这不仅加重了开发者的负担,也非常影响聚类结果的准确性

    5. 处理噪声数据的能力:

    所谓的噪声数据,可以理解为影响聚类结果的干扰数据,这些噪声数据的存在会造成聚类结果的“畸变”,最终导致低质量的聚类

    6. 增量聚类和对输入次序的不敏感:

    一些聚类算法不能将新加入的数据插入到已有的聚类结果,输入次序的敏感是指,对于给定的数据对象集合,以不同的次序提供输入对象时,最终产生的聚类结果的差异会比较大

    7. 高维性:

    有些算法只适合处理2维或者3维的数据,而对高维数据的处理能力很弱,因为在高维空间中数据的分布可能十分稀疏,而且高度倾斜。

    8. 基于约束的聚类:

    在实际应用中可能需要在各种条件下进行聚类,因为同一个聚类算法,在不同的应用场景中所带来的聚类结果也是各异的,因此找到满足“特定约束”的具有良好聚类特性的数据分组是十分有挑战的。这里最困难的问题就在于如何是识别我们要解决的问题中隐含的“特定约束”具体是什么,以及该使用什么算法来最好的“适配”这种约束

    9. 可解释性和可用性:

    我么希望得到的聚类结果都能用特定的语义、知识进行解释,和实际的应用场景相联系

    0x5:聚类的大致分类

    聚类是将数据对象的集合分成相似的对象类的过程,使得同一个簇(或类)中的对象之间具有较高的相似性,而不同簇中的对象具有较高的相异性。

    按照聚类的尺度,聚类方法可被分为以下三种:

  • 基于距离的聚类算法:基于距离的聚类算法是用各式各样的距离来衡量数据对象之间的相似度
  • 基于密度的聚类方法:基于密度的聚类算法主要是依据合适的密度函数进行类间聚类
  • 基于互连性的聚类算法:基于互连性的聚类算法通常基于图或超图模型,将高度连通的对象聚为一类
  • 0x6:信息瓶颈 - 信息论视角下的聚类算法理解

    信息瓶颈是由 Tishby,Pereira和 Bialek 提出的聚类技术,其概念来源于信息论。

    用一个文本聚类的问题来解释这个概念,假设我们将每个文本表示为一个词袋,即,每个文档都是一个向量

    其中 n 是字典的长度, 当且仅当第 i 个词在文档中出现。给定一个有 m 个文档的集合,我们可以将 m 个文档的词袋表示理解为随机变量 x 的联合概率分布,指示文档的身份,以及一个随机变量 y,指示单词在词典中的身份。

    根据这种解释,信息瓶颈是指: 将聚类属性表示为另一个随机变量C(归纳到具体的类别 k 中)(其中 k 同样由方法确定) 。一旦将 x,y,C 表述为随机变量,我们可以使用信息论中的方法来表示聚类目标。

    信息瓶颈的目标是:

    其中 是两个随机变量的互信息,在每个点分属聚类的所有可能概率分布中求取极小值。

    直观上讲,我们希望达到两个矛盾的目标。

  • 一方面,我们希望文档属性和聚类属性的互信息尽可能小,这反映了我们希望对原始数据进行强压缩。
  • 另一方面,我们希望聚类变量和词属性的互信息尽可能大,这反映了保留文档信息(用词在文档中出现来表示)的目标。将参数统计中的最小充分统计量推广到了任意分布。
  • 解信息瓶颈准则下的最优化问题通常是非常困难的,解决方案思路上类似EM准则。

    0x7:分类效用(category utility) - 聚类效果评价指标

    分类效用度量的是聚类算法将实例集分隔成聚类的总体质量。需要注意的是,分类效用不是以MDL为基础的,而是一个类似于定义在条件概率上的二次损失函数,它借助了信息论中互信息的一些思想理念。

  • C 1 ,C 2 ,...,C k 是 k 个聚类,外层的求和是针对每一个聚类的
  • 内层两个求和分别是针对每个属性和每个实例的
  • a i 代表第 i 个属性,它的值有 v i1 ,v i2 ,....,v ij ,一共要处理 j 个
  • 聚类的意义不仅仅在于分类就完了,而更多是聚类后的结果对数据集的结构的发掘,对后续分类的帮助,即有利于更好地预测聚类中实例的属性值。
  • 对于聚类结果 C l 中的某个实例,属性 a i 的值为 v ij 的概率是 Pr[a i =v ij | C l ],相对于概率 Pr[a i =v ij ]来说,是一个更好的估计,因为实例所在的聚类起到降低不确定性的作用,换句话说,聚类结果提供了有价值的信息,提高了分类结果的精确度。公式用两个概率的平方差来度量。
  • 外部的 Pr[C l ] 以及除以 k 是考量整个聚类结果的复杂性,防止过度拟合。否与,由于概率是累计所有适当的实例获得的,每个实例单独作为一个聚类是数学上最好的结果。
  • 《深入理解机器学习 - 从原理到算法》
    http://blog.csdn.net/itplus/article/details/10087581
    http://www.sohu.com/a/193777221_473283

    2. K-Means算法(K-means clustering K均值聚类算法) - 基于硬划分的聚类

    0x1:K-means算法模型

    一种流行的聚类算法是首先 对可能的聚类定义一个代价函数 ,聚类算法的目标是寻找一种使代价最小的划分。

    在这类范例中,聚类任务转化为一个优化问题,目标函数是一个从输入(X,d)和聚类方案 C = (C1,C2,....,Ck)映射到正实数(即损失值)的函数。

    给定这样一个目标函数,我们将其表示为 G,对于给定的一个输入(X,d),聚类算法的目标被定义为寻找一种聚类 C 使 G((X,d),C) 最小。

    其中,Ci 中心点被定义为:

    所以上式也可以写成如下形式:

    上述定义的损失函数,将聚类问题转换为了一个优化问题, 但是要注意的是,理论和实际的工程化是存在一定的差距的。

    k均值目标函数在实际的聚类应用中很常见。然而,事实证明 寻找k均值(k-means)算法的最优解通常是计算不可行的(NP问题) 。所以通常会用下面这种简单的 迭代算法 作为替代算法

    因此,多数情况下, k均值聚类值得是这种算法的结果而不是最小化 k 均值目标函数的结果。

    在学习具体的 K-means 算法细节之前,我们需要了解 K-means 原生存在的一些问题:

  • k均值算法的目标函数优化过程是单调非增的(也就是每次的迭代至少不会让结果更糟),但是 k均值算法本身 对达到收敛的迭代次数并没有给出理论保证 。
  • 算法给出的 k均值目标函数输出值和目标函数的最小可能值之差,并没有平凡下界,实际上,k均值可能会 收敛到局部最小值 。为了提高 k均值的结果,通常使用不同的随机初始化中心点,将该程序运行多次,并选取最优的结果。除此之外,有一些无监督的算法可以作为 k均值算法的前置算法,用来选取初始化中心。
  • 在训练集上根据距离平方总和标准得到的”最佳“聚类,将 不可避免地选择与数据点一样多的聚类 !因为此时损失为0,为了抑制这种倾向,需要应用MDL准则进行模型结构复杂性惩罚,在模型复杂度和损失目标最优化之间寻求一个平衡。
  • 0x2:K-means算法过程

    kMeans聚类算法是数据挖掘十大算法之一,算法需要接受参数 k(k 个初始聚类中心)(也可由算法随机产生指定),即将数据集进行聚类的数目和k个簇的初始聚类“中心”,结果是同一类簇中的对象相似度最高,不同类簇中的数据相似度最低,其聚类过程可以用下图表示

    如图所示,数据样本用圆点表示,每个簇的中心点用叉叉表示

    1. 聚类中心个数K

    聚类中心的个数K需要事先给定,但在实际中这个 K 值的选定是非常难以估计的,很多时候,事先并不知道给定的数据集应该分成多少个类别才最合适。有几种可能的策略可以缓解这种问题。

    1)遍历尝试所有k值

    这个过程会是一个漫长的调试过程,我们通过设置一个[k, k+n]范围的K类值,然后逐个观察聚类结果,最终决定该使用什么K值对当前数据集是最佳的

    2)小数据集抽样试验

    在实际情况中,往往是 对特定的数据集有对应一个最佳的K值,而换一个数据集,可能原来的K值效果就会下降 。但是同一个项目中的一类数据,总体上来说,通过一个抽样小数据集确定一个最佳K值后,对之后的所有K值都能获得较好的效果。

    3)启发式分裂探索策略

    另一种可能的方法是开始先找出很少几个聚类,然后决定是否值得将它们分裂。

    我们可以选择k=2,执行k均值聚类直到它终止,然后考虑分裂每个聚类。

    分裂聚类的一种方法是在变化最大的方向、距离聚类中心一个标准差的位置产生一个新的种子,随后在反方向、等距建立第二个种子。然后基于这两个新的种子,对聚类中的点进行k均值聚类。

    聚类分裂暂时完成时,是否值得保留分裂,或者原来的聚类也是合理的?查看所有点离聚类中心距离的平方和是不合理的,因为量子子聚类的和一定是较小的。这个时候需要引入MDL原则,为每新建一个聚类引入惩罚。在分裂与聚类效果之间寻求最佳平衡。

    2. 初始聚类中心(质心)的选择

    刚开始时是原始数据,杂乱无章,没有label,看起来都一样,都是绿色的

    Kmeans需要人为地确定初始聚类中心,不同的初始聚类中心可能导致完全不同的聚类结果。在实际使用中我们往往不知道我们的待聚类样本中哪些是我们关注的label,人工事先指定质心基本上是不现实的,在大多数情况下我们采取 随机产生聚类质心 这种策略

    假设数据集可以分为两类,令 K = 2 ,随机在坐标上选两个点,作为两个类的中心点(聚类质心)

    3. 确定了本轮迭代的质心后,将余下的样本点根据距离度量标准进行归类

    这一步也非常直观,计算样本点和所有质心的“距离”,选取“距离”最小(argmin)的那个质心作为该样本所属的类别。

    这里要注意的是,特征空间中两个实例点的距离是两个实例点相似程度的反映,高维向量空间点的距离求解,可以泛化为Lp距离公式,它在不同的阶次数中分为不同的形式

    p = 1:Manhattan Distance距离(曼哈顿距离):

    p = 2:Euclidean Distance距离(欧拉距离) :

    p = λ( 3 <= λ <= 正无穷 ): Lp距离

    p = 正无穷: 距离等于各个坐标点的最大值

    为了帮助理解,下图展示了二维空间中p取不同值时,与原点的Lp距离为1(Lp = 1)的点的图形

    4. 算法收敛(终止/停机)条件是什么?

    我们前面说过,计算K-means问题的目标函数最优解是一个NP问题,我们大多数时候都是针对聚类结果施加一些约束,得到一个终止/停止条件,例如一种比较常用的停止条件:

  • 这样不断进行"划分—更新—划分—更新",直到每个簇的中心不在移动为止
  • 0x3:Kmeans算法步骤图示描述

    1. 步骤一 - 选取质心

    在输入数据集里面随机选择三个向量作为初始中心点(下图中的红绿蓝三个圆点),这里的K值为3, 也就是一开始从数据集里面选择了三个向量,这算作第一次迭代

    2. 步骤二 - 距离聚类

    将每个向量分配到离各自最近的中心点,从而将数据集分成了K个类

    3. 步骤三 - 重选新质心

    计算得到上步得到聚类中每一聚类观测值的均值作为新的质心。这里体现的思想是这样的:因为我们是无监督学习,对于待分类的样本集群我们没有任何的先验知识,完全不知道该怎么分类,那么我们就暴力地、勇敢地、随机地踏出第一步,然后不断地去修正我们的分类器,不得不说,这和人生的很多的做人做事的道理是类似的

    4. 步骤四(人生就是一个勇敢踏出第一步,然后不断自我否定和修正成长的过程)

    重复步骤三,直至结果收敛,这里的收敛是指所有点到各自中心点的距离的和收敛

    用sklearn python 对2维数据点进行kMeans聚类

    # -*- coding: utf-8 -*-
    from sklearn.cluster import KMeans
    import numpy as np
    if __name__ == '__main__':
        X = np.array([
            [1, 2], [1, 4], [1, 0],
            [4, 2], [4, 4], [4, 0]
        kmeans = KMeans(n_clusters=2, random_state=0).fit(X)
        print kmeans.labels_
        kmeans.predict([[0, 0], [4, 4]])
        print kmeans.cluster_centers_

    kMeans这种无监督聚类进行一次无监督聚类后,也可以得到一个分类器(classifier),它是对当前train set的一个特征空间划分,基于这个分类器可以用于之后对新样本点的分类预测,这种就是有监督聚类

    0x4:不同K值、不同的初始化中心点方式对Kmeans分类效果的影响

    我们知道,K-mean的3大核心要素是:K值、距离度量公式、初始质心的选择。我们这个小节用一小代码来讨论下这些值是如何影响算法的分类效果

    # -*- coding: utf-8 -*-
    import numpy as np
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    from sklearn.cluster import KMeans
    from sklearn import datasets
    if __name__ == '__main__':
        np.random.seed(5)
        centers = [[1, 1], [-1, -1], [1, -1]]
        iris = datasets.load_iris()
        X = iris.data
        y = iris.target
        estimators = {'k_means_iris_3': KMeans(n_clusters=3),
                      'k_means_iris_8': KMeans(n_clusters=8
    
    
    
    
        
    ),
                      'k_means_iris_bad_init': KMeans(n_clusters=3, n_init=1, init='random')}
        fignum = 1
        for name, est in estimators.items():
            fig = plt.figure(fignum, figsize=(4, 3))
            plt.clf()
            ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=48, azim=134)
            plt.cla()
            est.fit(X)
            labels = est.labels_
            ax.scatter(X[:, 3], X[:, 0], X[:, 2], c=labels.astype(np.float))
            ax.w_xaxis.set_ticklabels([])
            ax.w_yaxis.set_ticklabels([])
            ax.w_zaxis.set_ticklabels([])
            ax.set_xlabel('Petal width')
            ax.set_ylabel('Sepal length')
            ax.set_zlabel('Petal length')
            fignum = fignum + 1
        # Plot the ground truth
        fig = plt.figure(fignum, figsize=(4, 3))
        plt.clf()
        ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=48, azim=134)
        plt.cla()
        for name, label in [('Setosa', 0),
                            ('Versicolour', 1),
                            ('Virginica', 2)]:
            ax.text3D(X[y == label, 3].mean(),
                      X[y == label, 0].mean() + 1.5,
                      X[y == label, 2].mean(), name,
                      horizontalalignment='center',
                      bbox=dict(alpha=.5, edgecolor='w', facecolor='w'))
        # Reorder the labels to have colors matching the cluster results
        y = np.choose(y, [1, 2, 0]).astype(np.float)
        ax.scatter(X[:, 3], X[:, 0], X[:, 2], c=y)
        ax.w_xaxis.set_ticklabels([])
        ax.w_yaxis.set_ticklabels([])
        ax.w_zaxis.set_ticklabels([])
        ax.set_xlabel('Petal width')
        ax.set_ylabel('Sepal length')
        ax.set_zlabel('Petal length')
        plt.show()

    可以看到,Kmeans这种无监督学习的算法并不能保证聚类出来的"族群"是有"实际意义"的,即Kmeans得到的分类族群可能只是在欧式空间上相近的点集,但是实际上它们并不一定真的就属于同一个类别。另外一方面,Kmeans的分类结果和K值有强关联,如果我们传入了一个"不合理"的K值,有可能导致Kmeans的过拟合,最后得到一个"错误"的分类结果

    在思考的深层次一些,这其实和Kleinberg对聚类算法的公理化定义有关,标准的聚类公理化定义是需要满足 丰富性(Ri)原则 的,但是这个条件太强了,所以k-means是一个满足 k-丰富性原则 的算法,k-丰富性原则是一个弱约束,不同的k值下,产生的分类结果也自然是不同的

    0x5:K-means在图像处理上的应用

    1. Color Quantization(真彩色降维低彩色) using K-Means

    这个例子要求做的是从96615真彩色"降维"到64种色彩,这里我个人理解聚类和降维的本质有共通的地方,聚类的目标是把相似的数据集归纳到同一个类别里,如果归纳完之后直接用这个新的类别代表该类别所有的样本集,那这个聚类就是一次降维过程。同样,回到这个例子,如果我们对一副真彩色的高维度像素的图像进行相似像素色聚类,本质上就是把原始图像降维成了一个低纬度低彩色图

    # -*- coding: utf-8 -*-
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.cluster import KMeans
    from sklearn.metrics import pairwise_distances_argmin
    from sklearn.datasets import load_sample_image
    from sklearn.utils import shuffle
    from time import time
    if __name__ == '__main__':
        n_colors = 64
        # Load the Summer Palace photo
        china = load_sample_image("china.jpg")
        # Convert to floats instead of the default 8 bits integer coding. Dividing by
        # 255 is important so that plt.imshow behaves works well on float data (need to
        # be in the range [0-1])
        china = np.array(china, dtype=np.float64) / 255
        # Load Image and transform to a 2D numpy array.
        w, h, d = original_shape = tuple(china.shape)
        assert d == 3
        image_array = np.reshape(china, (w * h, d))
        # 用1000张图像来训练出一个Kmeans分类器
        print("Fitting model on a small sub-sample of the data")
        t0 = time()
        image_array_sample = shuffle(image_array, random_state=0)[:1000]
        kmeans = KMeans(n_clusters=n_colors, random_state=0).fit(image_array_sample)
        print("done in %0.3fs." % (time() - t0))
        # 用训练好的Kmeans分类器对本例中的"夏宫"图像进行聚类处理,实际上就是在降维
        # Get labels for all points
        print("Predicting color indices on the full image (k-means)")
        t0 = time()
        labels = kmeans.predict(image_array)
        print("done in %0.3fs." % (time() - t0))
        codebook_random = shuffle(image_array, random_state=0)[:n_colors + 1]
        print("Predicting color indices on the full image (random)")
        t0 = time()
        labels_random = pairwise_distances_argmin(codebook_random,
                                                  image_array,
                                                  axis=0)
        print("done in %0.3fs." % (time() - t0))
        def recreate_image(codebook, labels, w, h):
            """Recreate the (compressed) image from the code book & labels"""
            d = codebook.shape[1]
            image = np.zeros((w, h, d))
            label_idx = 0
            for i in range(w):
                for j in range(h):
                    image[i][j] = codebook[labels[label_idx]]
                    label_idx += 1
            return image
        # Display all results, alongside original image
        plt.figure(1)
        plt.clf()
        ax = plt.axes([0, 0, 1, 1])
        plt.axis('off')
        plt.title('Original image (96,615 colors)')
        plt.imshow(china)
        plt.figure(2)
        plt.clf()
        ax = plt.axes([0, 0, 1, 1])
        plt.axis('off')
        plt.title('Quantized image (64 colors, K-Means)')
        plt.imshow(recreate_image(kmeans.cluster_centers_, labels, w, h))
        plt.figure(3)
        plt.clf()
        ax = plt.axes([0, 0, 1, 1])
        plt.axis('off')
        plt.title('Quantized image (64 colors, Random)')
        plt.imshow(recreate_image(codebook_random, labels_random, w, h))
        plt.show()

    把原始图像的96615像素点聚类为64像素点,中间的图像就是Kmeans聚类后的结果,可以看到,从肉眼视觉角度看,图像并没有明显地失真

    0x6:从混合模型和EM算法的角度来谈kmeans聚类

    实际上,混合模型和EM算法又是另一块非常庞大的主题,我们这里不深入讨论,只讨论kmeans更高层次的抽象概括,混合模型除了提供一个构建更复杂的概率分布的框架之外,混合模型也可以用于数据聚类。本质上来说,K均值算法对应于用于高斯混合模型的EM算法的一个特定的非概率极限

    1. 用形式化方式描述Kmeasn

    引入一组 D 维向量 ,其中, ,且 是与第 k 个聚类关联的一个代表。我们可以认为 表示了聚类的中心,我们的目标是找到所有数据点分别属于的聚类,以及一组向量 ,使得每个数据点和它最近的向量 之间的距离的平方和最小。对于每个数据点 ,我们引入一组对应的二值指示变量 ,其中, 表示数据点 属于 K 个聚类中的哪一个(指示函数)

    有了以上定义,之后我们可以定义一个目标函数,也被称为 失真度量(distortion measure)

    (公式9.1)

    它表示每个数据点与它被分配的向量 之间的距离的平方和。我们的目标是找到 和 的值,使得 J 达到最小值。我们可以用一种迭代的方法完成这件事,其中每次迭代涉及到两个连续的步骤,分别对应

  • 的最优化(E期望):首先,我们为 选择一些初始值。然后在第一阶段,我们保持 固定,关于 最小化 J
  • 的最优化(M最大化):在第二阶段,我们保持 固定,关于 最小化 J。不断重复这2个阶段优化直到收敛
  • 我们看到,在EM步骤中,每次都是固定其中一个变量,求另一个变量的极值,避免直接对二元变量直接求极值的计算困难问题。为了说明这一点,我们在K均值算法中使用 E步骤和 M步骤的说法

    首先考虑 最优化,公式(9.1)给出的 J 是 的一个线性函数,因此最优化过程可以很容易地进行,得到一个解析解。与不同的 n 相关的项是独立的,因此我们可以对每个 n 分别进行最优化,只要 k 的值使 最小,我们就令 等于1。换句话说,我们可以简单地将数据点的聚类设置为最近的聚类中心,更形式化地,这可以表达为:

    (1-of-k)

    现在考虑 固定时,关于 的最优化。目标函数 J 是 的一个二次函数,令它关于 的导数等于零,即可达到最小值,即

    求导结果为:

    这个表达式的分母等于单个聚类 k 中数据点的数量,因此这个结果有一个非常简单直观的含义,即令 等于单个类别 k 的所有数据点的均值

    上述两个步骤合起来称为K均值(K-means)算法

    重新为数据点分配聚类的步骤以及重新计算聚类均值的步骤重复进行,直到聚类的分配不改变(或者直到迭代次数超过了某个最大值)。由于每个阶段都减小了目标函数 J 的值,因此算法的收敛性得到了保证,但是要注意的是,算法可能收敛到 J 的一个局部最小值而不是全局最小值

    Relevant Link:

    http://blog.itpub.net/12199764/viewspace-1479320/
    https://blog.yueyu.io/p/1614
    http://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html
    http://scikit-learn.org/stable/auto_examples/cluster/plot_cluster_iris.html#sphx-glr-auto-examples-cluster-plot-cluster-iris-py
    http://scikit-learn.org/stable/auto_examples/cluster/plot_color_quantization.html#sphx-glr-auto-examples-cluster-plot-color-quantization-py
    http://scikit-learn.org/stable/auto_examples/cluster/plot_face_compress.html#sphx-glr-auto-examples-cluster-plot-face-compress-py 
    http://blog.csdn.net/gamer_gyt/article/details/51244850
    http://scikit-learn.org/stable/auto_examples/cluster/plot_kmeans_assumptions.html#sphx-glr-auto-examples-cluster-plot-kmeans-assumptions-py
    http://scikit-learn.org/stable/auto_examples/cluster/plot_kmeans_digits.html#sphx-glr-auto-examples-cluster-plot-kmeans-digits-py
    http://chunqiu.blog.ustc.edu.cn/?p=435#comment-3556
    http://f.dataguru.cn/thread-639729-1-1.html
    http://www.cnblogs.com/bourneli/p/3645049.html 
    http://www.cnblogs.com/bourneli/p/3645049.html
    https://en.wikipedia.org/wiki/Silhouette_(clustering)
    https://kapilddatascience.wordpress.com/2015/11/10/using-silhouette-analysis-for-selecting-the-number-of-cluster-for-k-means-clustering/
    http://scikit-learn.org/stable/auto_examples/cluster/plot_kmeans_silhouette_analysis.html#sphx-glr-auto-examples-cluster-plot-kmeans-silhouette-analysis-py
    http://scikit-learn.org/stable/auto_examples/cluster/plot_kmeans_stability_low_dim_dense.html#sphx-glr-auto-examples-cluster-plot-kmeans-stability-low-dim-dense-py
    http://scikit-learn.org/stable/modules/clustering.html#mini-batch-kmeans
    http://scikit-learn.org/stable/auto_examples/cluster/plot_mini_batch_kmeans.html#sphx-glr-auto-examples-cluster-plot-mini-batch-kmeans-py
    http://scikit-learn.org/stable/auto_examples/text/document_clustering.html#sphx-glr-auto-examples-text-document-clustering-py
    http://coolshell.cn/articles/7779.htm

    3. K-Means++算法

    0x1:从爬山法问题中切入谈kmeans的缺陷以及kmeans++缓解了什么问题

    假设我们的目标是到达山顶,我们采用的算法如下

  • 从山上的某个随机位置出发开始
  • Repeast
  • 每次都都沿着“更高”的方向走一步
  • Until直到某一步发现无论往任何方向走都不会更高
  • 这个算法看起来十分合理,但是我们考虑下面这个图上运行上述算法

    我们如何从A点出发,只会到达B点而不会到达最高顶点D点,即我们到达的是局部最优点而不是全局最优点。

    k-means算法和上面的爬山法类似,不能保证最后能够找到最优的划分簇。这是因为算法一开始选择的是 随机质心 ,基于随机质心,算法只能找到局部最优化分簇(例如图像的B点)。对于kmeans这种EM过程来说,最终的聚类结果 严重依赖于初始中心点的选择 。

    K-Means主要有两个最重大的缺陷 - 都和初始值有关

  • K需要事先给定,这个K值的选定是非常难以估计的。很多时候,事先并不知道给定的数据集应该分成多少个类别才最合适
  • K-Means算法需要用初始随机种子点来启动优化,这个随机种子点太重要,不同的随机种子点会有得到完全不同的结果
  • 但是k-means++算法从一定程度上解决了该问题,k-means++选择初始seeds的基本思想就是: 初始的聚类中心之间的相互距离要尽可能的远。

    0x2:k-Means++算法步骤

  • 从输入的数据点集合中随机选择一个点作为第一个种子点(聚类中心)
  • 对于数据集中的每一个点x,计算它与最近聚类中心(指已选择的聚类中心)的距离D(x)并保存在一个数组里,然后把这些距离加起来得到Sum(D(x))
  • 选择一个新的数据点作为新的聚类中心,选择的原则是: D(x)较大的点被选取作为聚类中心的概率较大(kMeans不同类别的距离越远越好)
  • 先取一个能落在Sum(D(x))中的随机值Random
  • 然后用Random -= D(x),直到其结果 <= 0
  • 选取在这个"递减过程"中D(x)最大的点,此时的点就是下一个"种子点"
  • 重复2和3直到k个聚类中心被选出来
  • 利用这k个初始的聚类中心来运行标准的k-means算法
  • 可以看到算法的第三步选取新中心的方法,这样就能保证距离D(x)较大的点,会被选出来作为聚类中心了。

    同时k-measn这里并不直接用“hard方式”通过遍历每次选取D(x)最大的值作为下一个中心点,而是引入了 随机过程 ,通过随机过程,柔和地体现了” 以正比于D(x)的概率(概率由距离决定)随机选择一个数据点作为新的中心点 “这个思想。

    以一段python代码模拟该过程。假设由包含数据点及其权重的元祖构成列表。roulette函数会基于随机过程以正比于某个数据点的权重的概率来选择点。

    #-*- coding:utf-8 -*-
    import collections
    import random
    random.seed()
    def roulette(datalist):
        i = 0
        soFar = datalist[0][1]
        ball = random.random()
        while soFar < ball:
            i += 1
            soFar += datalist[i][1]
        return datalist[i][0]
    if __name__ == "__main__":
        data = [
            ("dp1", 0.25),
            ("dp2", 0.4),
            ("dp3", 0.1),
            ("dp4", 0.15),
            ("dp5", 0.1)
        results = collections.defaultdict(int)
        for i in range(1000):
            results[roulette(data)] += 1
        print results

    我们可以看到,在1000次选择中,函数按照正比于权重的方式柔和地进行了点的选择

    0x3:Kmeans++算法策略

    k-means++聚类的基本思想就是: 虽然第一个中心点仍然随机选择,但其他的点则优先选择那些彼此相距很远的点

    Relevant Link:

    http://www.csdn.net/article/2012-07-03/2807073-k-means

    4. 基于链接的层次聚类算法 - 基于分层的聚类(自底向上)

    层次聚类方法不需要指定最终聚成的簇的数目(例如K值)。取而代之的是,算法一开始将每个实例都看成一个簇,在一个确定的” 聚类间度量函数 “的驱动下,算法的每次迭代中都将最相似的两个簇合并在一起,该过程不断重复直到只剩下一个簇为止。该方法称为 层次聚类 ,也叫 凝聚聚类(agglomerative clustering) 。

    合并过程的记录会形成一个层次聚类结构,即一个二叉树状图,如下图

    层次聚类的思想并不复杂,引起层次聚类算法区别的核心因素有两个:

  • 聚类间距离度量 :决定怎样测量(或定义)类间距离
  • 提前终止条件 :我们需要确定什么时候终止合并,否则最终只会得到一个单聚类
  • 接下来我们按照这两条脉络进行梳理和讨论。

    0x1:链接聚类模型

    我们前面说了,对这类链接聚类(自下而上聚类)有两个非常关键的因素就是距离度量和终止条件,它们共同决定了链接聚类算法的算法模型,我们这节来讨论它们。

    1. 聚类间距离度量公式

    1)单链接(single-linkage clustering)

    单链接最小距离聚类(single-linkage clustering):类间距离定义为 两类元素间的最短距离(取两两组合最短距离的两个) 。由于该度量值考虑了两个聚类中最近的两个成员间的距离,所以该过程对离群点很敏感,增加单个实例就可能完全改变整个聚类结构。

  • 单链接最大链接聚类:类间距定义为 两类元素间的最大距离(取两两组合最长距离的那个) 。在这种度量标准下,单链接聚类会产生直径非常大的聚类。
  • 2)完全链接(complete-linkage)

  • 完全链接聚类: 只有两个聚类的所有成员都相似时,才认为两个聚类的距离很近 、这种度量标准也对离群点很敏感,并且倾向于寻找紧凑的、直径较小的聚类。同时,也可能会出现一些实例与其他聚类的距离比与它自己所在聚类内其他点的距离还近很多的情况。
  • 3)中心链接(centroid-linkage)

  • 中心链接聚类:中心链接度量代表了聚类成员间最小和最大距离的一种折中,用聚类成员的中心来代表聚类。
  • 4)平均链接(average-linkage clustering)

  • 平均链接聚类(average-linkage clustering): 类间距离定义为两类元素之间距离的平均值 。当实例位于欧式空间时,中心的定义很明确,该方法效果很好。但是当实例之间只有成对的相似性度量时,由于中心不能用实例表达,它们之间的相似性不能定义,所以该方法不可行。
  • 5)组平均(group-average)

  • 组平均聚类:它使用合并后的聚类中的所有成员间的平均距离。这与平均聚类方法不同,它包含了相同原始聚类的平均值对。
  • 值得注意的是,如果原始数据集中所有的聚类都是紧凑并且分割良好的,那么所有这些度量都会得到同样的层次聚类结果,否则,它们会得到相当不同的结构。

    同时,上述讨论的这些距离度量都有一个技术上的缺陷,即它们的结果依赖于距离度量的数值规模。最小和最大距离度量得到的结果强依赖于距离的大小顺序(ordering)。如果对所有的距离进行单调变换,即使保持它们的相对顺序不变,基于中心和基于平均距离的聚类结果也会改变,其他的也会不同程度受到影响。

    笔者思考 :

    我们在工程项目中相似度匹配方法,就是单链接最小距离聚类的一种特殊应用。在实际情况中,我们会有一个所谓的黑规则库,这代表了一个聚类集。对于新的实例我们会将其和这个聚类集进行比较,如果找到了一个”匹配“,即新实例和聚类集中任意某个实例非常接近,则将这个新实例归入到黑规则聚类集中。这样,经过一段时间后,黑规则聚类集就会不断扩大。就像完成了一个自我生长过程一样 。

    2. 合并终止条件

    基于链接的聚类算法是 凝聚式 的,一开始,数据完全是碎片化的,然后逐步构建越来越大的聚类,如果我们没有加入停止规则,链接算法的结果可以用聚类系统树图来描述,即,一个域子集构成的树,其叶子节点是单元素集,根节点为全域。例如下图所示:

    常用的停止准则包括:

  • 固定类的数量:固定参数 k,当聚类数目为 k 时停止聚类。使用这种停止准则需要我们对我们的场景要有较强的领域知识,即预先知道需要聚类的数量
  • 设定距离上限:设定域子集间距最大上限,如果在某一轮迭代中,所有的组件距离都超过该阈值,则停止聚类
  • 使用这种停止准则,实际上隐含了一种假设,即相似的样本在特征空间上距离很近,且聚集在一个较小的区域内,彼此之间距离较近。而不同类别的样本在族群上彼此会拉开距离。所以通过设定一定的距离上限,可以在对一个族群聚集完毕后及时收敛停止,开启新族群的聚集

    0x2:单链接/平均链接/最大链接聚类效果对比

    # -*- coding:utf-8 -*-
    from time import time
    import numpy as
    
    
    
    
        
     np
    from scipy import ndimage
    from matplotlib import pyplot as plt
    from sklearn import manifold, datasets
    digits = datasets.load_digits(n_class=10)
    X = digits.data
    y = digits.target
    n_samples, n_features = X.shape
    np.random.seed(0)
    def nudge_images(X, y):
        # Having a larger dataset shows more clearly the behavior of the
        # methods, but we multiply the size of the dataset only by 2, as the
        # cost of the hierarchical clustering methods are strongly
        # super-linear in n_samples
        shift = lambda x: ndimage.shift(x.reshape((8, 8)),
                                      .3 * np.random.normal(size=2),
                                      mode='constant',
                                      ).ravel()
        X = np.concatenate([X, np.apply_along_axis(shift, 1, X)])
        Y = np.concatenate([y, y], axis=0)
        return X, Y
    X, y = nudge_images(X, y)
    #----------------------------------------------------------------------
    # Visualize the clustering
    def plot_clustering(X_red, X, labels, title=None):
        x_min, x_max = np.min(X_red, axis=0), np.max(X_red, axis=0)
        X_red = (X_red - x_min) / (x_max - x_min)
        plt.figure(figsize=(6, 4))
        for i in range(X_red.shape[0]):
            plt.text(X_red[i, 0], X_red[i, 1], str(y[i]),
                     color=plt.cm.spectral(labels[i] / 10.),
                     fontdict={'weight': 'bold', 'size': 9})
        plt.xticks([])
        plt.yticks([])
        if title is not None:
            plt.title(title, size=17)
        plt.axis('off')
        plt.tight_layout()
    #----------------------------------------------------------------------
    # 2D embedding of the digits dataset
    print("Computing embedding")
    X_red = manifold.SpectralEmbedding(n_components=2).fit_transform(X)
    print("Done.")
    from sklearn.cluster import AgglomerativeClustering
    for linkage in ('ward', 'average', 'complete'):
        clustering = AgglomerativeClustering(linkage=linkage, n_clusters=10)
        t0 = time()
        clustering.fit(X_red)
        print("%s : %.2fs" % (linkage, time() - t0))
        plot_clustering(X_red, X, clustering.labels_, "%s linkage" % linkage)
    plt.show()

    单纯就这个数字聚类的场景来看,平均链接聚类的效果最好

    0x3:链接算法模型

    决定链接算法具体形式的最重要的因素就是“域间相似性度量函数”,因此可分为单链接、平均链接、最大链接 这3种。

    1. 单链接聚类算法

    单链接算法和 Kruskal 算法很相似,目的是在加权图上找到一个最小生成树。

    从图的视角来看单链接算法的算法运行过程,图的顶点是 X 中的元素,边(x,y)的权重是距离 d(x,y)。每次单链接算法将两个聚类进行合并,相当于在一颗树中添加一条边。

    单链接算法得到的边集合和最小生成树是一致的。

    2. 平均链接聚类算法

    3. 最大链接聚类算法

    Relevant Link:

    http://scikit-learn.org/stable/auto_examples/cluster/plot_digits_linkage.html#sphx-glr-auto-examples-cluster-plot-digits-linkage-py
    http://scikit-learn.org/stable/modules/clustering.html#hierarchical-clustering 

    5. DBSCAN(Density-Based Spatial Clustering of Application with Noise) - 基于密度的聚类算法

    基于密度的聚类方法与其他方法的一个根本区别是:它不是基于各种各样的距离度量的,而是基于密度的。因此它能克服基于距离的算法只能发现“类圆形”的聚类的缺点。DBSCAN的指导思想是:

    用一个点的∈邻域内的邻居点数衡量该点所在空间的密度,只要一个区域中的点的密度大过某个阈值,就把它加到与之相近的聚类中去

    它可以找出形状不规则(oddly-shaped)的cluster,且聚类时不需要事先知道cluster的个数

    0x1:DBSCAN模型

    考虑数据集合 ,首先引入以下概念与数学记号:

    1. ∈邻域(∈ neighborhood)

    设 ,称 为 x 的∈邻域。显然,

    2. 密度(density)

    设 ,称 为 x 的密度。注意,这里的密度是一个整数值,且依赖于半径 ∈

    3. 核心点(core point)

    设 ,若 (核心点阈值 minimum numberof points required to form a cluster),则称 x 为 X 的核心点。记由 X 中所有核心点构成的集合为 ,并记 表示由 X 中的所有非核心点构成的集合

    4. 边界点(border point)

    若 满足 。即 x 的 ∈ 邻域中存在核心点,则称 x 为 X 的边界点。记由 X 中所有边界点构成的集合为

    此外,边界点也可以这么定义,若 ,且 x 落在某个核心点的 ∈ 邻域内,则称 x 为 X 的一个边界点。 一个边界点可能同时落入一个或多个核心点的∈邻域

    5. 噪音点(noise point)

    记 ,则称 x 为噪音点

    直观上来说,核心点对应稠密区域内部的点,边界点对应稠密区域边缘的点,而噪音点对应稀疏区域中的点。如下图所示:

    需要注意的是,核心点位于簇的内部,它确定无误地属于某个特定的簇;噪音点是数据集中的干扰数据,它不属于任何一个簇;而边界点是一类特殊的点,它位于一个或几个簇的边缘地带,它可能属于一个簇,也可能属于另外一个簇,其簇归属并不明确

    6. 直接密度可达(directly density-reachable)

    设 ,则称 y 是从 x 直接密度可达的

    7. 密度可达(density-reachable)

    设 ,若它们满足 直接密度可达的, ,则称 是从 密度可达的

    值得注意的是,当 m = 2时,密度可达即为直接密度可达。密度可达是直接密度可达的一种推广。事实上,密度可达是直接密度可达的 传递闭包 。

    8. 密度相连(density-connected)

    设 ,若 y 和 z 均是从 x 密度可达的,则称 y 和 z 是密度相连的。显然, 密度相连具有对称性 。

    9. 类(cluster)

    称非空集合 是 X 的一个类(cluster),如果它满足:对于

    (1)Maximality:若 ,且 y 是从 x 密度可达的,则

    (2)Connectivity:若 ,则 x,y 是密度相连的

    0x2:DBSCAN算法过程

    DBSCAN算法的核心思想如下: 从某个选定的核心点出发,不断向密度可达的区域扩张,从而得到一个包含核心点和边界点的最大化区域,区域中任意亮点密度相连

    考虑数据集合 。DBSCAN算法的目标是将数据集合 X 分成 K 个cluster(k由算法自动推断得到,无需事先指定)及噪音点组成,为此,引入cluster标记数组

    由此,DBSCAN算法的目标就是生成标记数组 ,而 K 即为 中互异的非负数的个数

    输入:样本集D= ( x 1 , x 2 , . . . , x m ) ,邻域参数 (ϵ,MinPts), 样本距离度量方式

    输出: 簇划分C.

    可以看到,DBSCAN在不断发现新的核心点的同时,还通过直接密度可达,发现核心点邻域内的核心点,并把这些邻域内的核心点都归纳到第 k 个聚类中。而噪音点没每轮 k 聚类中会被全局过滤不会参与下一轮启发式发现中,只有边界点在下一次跌打中会被再次尝试检验是否能够成为新聚类的核心点

    0x3:hello world!DBSCAN

    # -*- coding:utf-8 -*-
    import numpy 
    
    
    
    
        
    as np
    from sklearn.cluster import DBSCAN
    from sklearn import metrics
    from sklearn.datasets.samples_generator import make_blobs
    from sklearn.preprocessing import StandardScaler
    # #############################################################################
    # Generate sample data
    centers = [[1, 1], [-1, -1], [1, -1]]
    X, labels_true = make_blobs(n_samples=750, centers=centers, cluster_std=0.4,
                                random_state=0)
    X = StandardScaler().fit_transform(X)
    # #############################################################################
    # Compute DBSCAN
    db = DBSCAN(eps=0.3, min_samples=10).fit(X)
    print db.labels_
    core_samples_mask = np.zeros_like(db.labels_, dtype=bool)
    print core_samples_mask
    core_samples_mask[db.core_sample_indices_] = True
    labels = db.labels_
    # Number of clusters in labels, ignoring noise if present.
    n_clusters_ = len(set(labels)) - (1 if -1 in labels else 0)
    print('Estimated number of clusters: %d' % n_clusters_)
    print("Homogeneity: %0.3f" % metrics.homogeneity_score(labels_true, labels))
    print("Completeness: %0.3f" % metrics.completeness_score(labels_true, labels))
    print("V-measure: %0.3f" % metrics.v_measure_score(labels_true, labels))
    print("Adjusted Rand Index: %0.3f"
          % metrics.adjusted_rand_score(labels_true, labels))
    print("Adjusted Mutual Information: %0.3f"
          % metrics.adjusted_mutual_info_score(labels_true, labels))
    print("Silhouette Coefficient: %0.3f"
          % metrics.silhouette_score(X, labels))
    # #############################################################################
    # Plot result
    import matplotlib.pyplot as plt
    # Black removed and is used for noise instead.
    unique_labels = set(labels)
    colors = [plt.cm.Spectral(each)
              for each in np.linspace(0, 1, len(unique_labels))]
    for k, col in zip(unique_labels, colors):
        if k == -1:
            # Black used for noise.
            col = [0, 0, 0, 1]
        class_member_mask = (labels == k)
        xy = X[class_member_mask & core_samples_mask]
        plt.plot(xy[:, 0], xy[:, 1], 'o', markerfacecolor=tuple(col),
                 markeredgecolor='k', markersize=14)
        xy = X[class_member_mask & ~core_samples_mask]
        plt.plot(xy[:, 0], xy[:, 1], 'o', markerfacecolor=tuple(col),
                 markeredgecolor='k', markersize=6)
    plt.title('Estimated number of clusters: %d' % n_clusters_)
    plt.show()

    Relevant Link:

    http://shiyanjun.cn/archives/1288.html
    https://en.wikipedia.org/wiki/DBSCAN 
    https://www.cnblogs.com/hdu-2010/p/4621258.html
    http://blog.csdn.net/itplus/article/details/10088625
    https://www.cnblogs.com/pinard/p/6208966.html
    http://blog.csdn.net/xieruopeng/article/details/53675906
    http://www.cnblogs.com/aijianiula/p/4339960.html
    http://scikit-learn.org/stable/modules/generated/sklearn.cluster.DBSCAN.html

    6. Clustering by fast search and find of density peaks(CFSFDP聚类)- 基于密度(Density)的聚类

    该算法是Alex Rodriguez和Alessandro Laio在Science上发表的《 Clustering by fast search and find of density peaks 》所提出的一种新型的基于密度的聚类算法。

    0x1:算法思想

    该聚类算法的核心思想在于对聚类中心(cluster centers)的刻画上,算法认为聚类中心同时具有以下两个特点:

    1. 聚类中心本身密度大,即它被密度均不超过它的邻居包围,或者说聚类中心是整个簇中密度最大的点;
    2. 聚类中心与其他密度更大的数据点之间的“距离”相对更大;

    0x2:算法模型

    该算法的假设类簇(cluster centers)的中心由一些局部密度比较低的点围绕,并且这些点距离其他有高局部密度的点的距离都比较大。

    对于样本集中的任何数据点s,可以定义两个值: 局部密度ρi 以及 到高局部密度点的距离δi ,这两个值仅仅取决于两点之间的距离dij。

    1. 局部密度ρi

    包括 Cut-off kernel 和 Gaussian kernel 两种计算方式 :

    1) Cut-off kernel:阶跃统计函数,只关注数据点是否在dc阈值范围内

    参数 称为 截断距离(cutoff distance) ,需要在算法启动时显式指定。
    从该模型公式中可以看出,局部密度 ρi 表示的是样本集 S 中与数据点 xi 之间的的距离小于 dc 的数据点。

    这里 dc 可以理解为一个边界阈值,dc 设置的越小,表示希望算法对聚类的敏感度越高,即在尽可能小的区域内发现聚类社区。

    2)Gaussian kerne:柔性距离统计函数

    和 cut-off kernel一样,与 x 的距离小于 dc 的数据点越多,pi 的值越大。

    对比上面两式可以看出,cut-off kernel为离散值,Gaussian kernel为连续值,因此,相对来说,Gaussian产生冲突(即不同的数据点具有相同的局部密度值)的概率更小

    2. 到高局部密度点的距离δi

    设 表示数据点局部密度 的一个降序排列下标序,即它满足: 。

    则可以定义:

    这个公式非常优美,它定义了:

    1)当 Xi 具有最大局部密度时,δi 表示 S 中与 Xi 距离最大的数据点与 Xi 之间的距离;
    2)否则,δi 表示在所有局部密度大于 Xi 的数据点中(即其他cluster centers),与 Xi 距离最小的那个(或那些)数据点与 Xi 之间的距离。

    0x3:聚类过程

    至此,对于 S 中的每一个数据点 Xi,可以通过 进行抽象表征。考虑下图的例子,其中包含28个二维数据点,将二元对 在平面上画出来。

    我们看到,1号和10号数据点由于 同时具有较大的局部密度和类间距离 ,于是从数据集中“脱颖而出(pop up)”了,而这两个数据点恰好是左图中数据集的两个聚类中心。

    此外,26,27,28这三个数据点在原始数据集中是“离群点”,它们在右图中也很有特点: 其局部密度很小,类间距离很大 。

    从直观上来理解,上面右图对 确定聚类中心(cluster centers) 有决定性作用,因此也将这种由 对应的图称为 决策图(decision graph) 。

    聚类中心确定之后,剩余点被分配给与其具有较高密度的最近邻居相同的类簇。与其他迭代优化的聚类算法不同,类簇分配在单个步骤中执行。

    在聚类分析中, 通常需要确定每个点划分给某个类簇的可靠性。

    在该算法中, 可以首先为每个类簇定义一个 边界区域(border region), 亦即划分给该类簇但是距离其他类簇的点的距离小于dc的点,这个区域中的点集可以认为是圈出了聚类中心的整体。然后为每个类簇找到其边界区域的局部密度最大的点,该类簇中所有局部密度大于该点的局部密度的点被认为是类簇核心的一部分(亦即将该点划分给该类簇的可靠性很大),其余的点被认为是该类簇的光晕, 亦即可以认为是噪音(outlier)。

    图A表示点分布,其中包含非球形点集和双峰点集。B和C分别表示4000和1000个点按照A中模式的分布,其中点根据其被分配的不同类簇着色,黑色的点属于类簇光晕。D和E是对应的决策图,而F表示的是不同点量下不正确聚类点的比率,误差线代表平均值的标准差。

    下图是分别利用点集和Olivetti脸部图片集的聚类结果

    0x4:算法形式化描述

    设待聚类数据集 ,其包含 个cluster

    值得注意的是,该聚类算法得到的聚类中心可以作为其他聚类算法(例如k-means)的初始聚类中心。

    Relevant Link:

    http://science.sciencemag.org/content/344/6191/1492
    https://segmentfault.com/a/1190000011337432
    https://blog.csdn.net/itplus/article/details/38926837

    7. SOM(Self-organizing Maps)- 基于模型的聚类(model-based methods)

    基于模型的方法给每个聚类假定一个模型(预先设定),然后寻找能够很好地满足这个模型的数据集。这样一个模型可能是数据点在空间中的密度分布函数或者其他。它的一个潜在的假定就是:

    目标数据集是由一系列的概率分布所决定的

    通常有两种尝试方向:统计方案;和神经网络方案

    关于神经网络方案方面的讨论可以参阅我之前的一篇blog

    Relevant Link:

    http://www.cnblogs.com/LittleHann/p/7101992.html

    8. GMM(Gaussian Mixture Model, 高斯混合模型)- 基于概率的聚类(probability-based methods)

    0x1:传统硬分类(hard decision)聚类算法存在的挑战

  • 以分类效用公式为驱动力的聚类算法,为了避免过度拟合必须选择除数k,即需要提供一个人为的聚类标准差最小值,以及为避免每个实例成为一个聚类的特定的截止值
  • 增量启发式算法本身存在不确定性:
  • 结果在多大程度上依赖于实例的输入顺序
  • 合并、分裂等局部重建操作是否足以扭转由于不好的实例次序所带来的糟糕的初始决定
  • 最终结构是否代表分类效用的局部最大值
  • 无法知道最终的结果离全局最大值到底有多远
  • 结果的层次性也不能回避哪个是最好的聚类这个问题
  • 互斥的聚类划分引入了脆弱性,位于灰色地带的样本很可能会被错误地分到错误的聚类中。同时不小心引入的离群点可能会极大地破坏原本有效的聚类划分
  • 为了缓解上述问题,一个更为理论性的统计学方法可以克服聚类问题的部分上述缺点。

    从概率的角度看,聚类的目标是寻找给定数据的最有可能的集合 。由于任何有限数量的证据都不足以对某件事做完全肯定的结论,所以实例甚至是训练实例也不能绝对地被分在这个聚类或那个聚类。而应当说 实例都以一定的可能性分属于每个聚类 。这有助于消除那些硬性而快速的判断方案引发的脆弱性。

    0x2:概率聚类基本概念

    统计聚类的基础是建立在一个称为 有限混合(finite mixture) 的统计模型上。

    混合是指用k个概率分布代表k个聚类,控制聚类成员的属性值。换句话说,对某个具体实例,每个分布给出假设已知实例属于这个聚类,并且它有某组属性值集合的概率(即 P(v | C))。

    每个聚类都有不同的分布,任何具体实例”实际上“属于且只属于一个聚类,但不知道是哪个。最后,各个聚类并不是等可能的,存在某种反映它们相对总体数量的概率分布。

    以一维情况为例,最简单的有限混合情况是在一个一维坐标轴上的数值属性,每个聚类都是一个高斯分布,但有不同的均值和方差。我们的目标是计算每个聚类的平均值和方差,以及聚类之间的总体分布。混合模型将几个正态分布组合起来,它的概率密度函数看起来就像一组连绵的山脉,每座山峰代表一个高斯分布。

    一个二类混合模型

    上图中有两个聚类A和B,每个都呈正态分布,

  • 聚类A的均值和标准差分别是μ A 和σ A
  • 聚类B的均值和标准差分别是μ B 和σ B
  • 从这些分布中抽样,聚类A的抽样概率为p A ,聚类B的抽样概率为p B ,p A +p B =1
  • 现在,想象所给的数据集没有类值,只有数据,要求确定模型的5个参数:(μ A, σ A ,μ B ,σ B ,p A ),这就是 有限混合问题 。

    0x3:概率聚类训练

    关于使用EM算法训练GMM模型的相关讨论,可以参阅另一篇 文章 。

    0x4:混合模型预测

    如果5个参数一致,要找出某个给定实例来自每种分布的概率是很简单的。给定实例x,它属于聚类A的概率是:

    这里, 是聚类A的正态分布函数,即:

    在做比较时,分母Pr[x]会被消除,只要比较分子大小即可。这和朴素贝叶斯分类是一样的,都是简单的贝叶斯定理的应用。

    Relevant Link:

    https://baike.baidu.com/item/GMM/4258031?fr=aladdin