聚类(clustering)算法初探

1. 聚类简介

0x1:聚类是什么?

聚类是一种运用广泛的探索性数据分析技术,人们对数据产生的第一直觉往往是通过对数据进行有意义的分组。很自然,首先要弄清楚聚类是什么?

直观上讲,聚类是将对象进行分组的一项任务,使相似的对象归为一类,不相似的对象归为不同类

但是,要达到这个目的存在几个很困难的问题

1. 上述提及的两个目标在很多情况下是互相冲突的。从数学上讲,虽然聚类共享具有等价关系甚至传递关系,但是相似性(或距离)不具有传递关系。具体而言,假定有一对象序列,X1,....,Xm,所有相邻元素(Xi-1、Xi+1)两两都非常相似,但是X1和Xm非常不相似。这种情况常常发生在cluster超过一定尺寸的时候,元素之间的传递性假设在这些场景下不一定100%符合真实规律。
2. 另一个基本问题是聚类缺乏实际情况,这是无监督学习的共同问题。聚类是一种无监督学习,即我们不能预测label,因此对于聚类,我们没有明确的成功评估过程。
另一方面,对于一个给定的对象集合,可以有多种有意义的划分方式,这可能是因为对象间的距离(或相似性)有多种隐式的定义,例如将演讲者的录音根据演讲者的口音聚类或根据内容聚类。所以,给定一个数据集,有多种不同的聚类解决方案

0x2:聚类的公理化描述

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

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

1. 尺度不变性是一种非常自然的要求,如果聚类函数输出的结果依赖于测量点之间的距离测度单位,那将显得十分奇怪。
2. 丰富性要求主要想说明聚类函数的输出是由函数d全权决定,也是一种非常直观的特性。
3. 一致性要求是和聚类基本定义相关的要求,即我们希望相似的点聚到一类,不相似的点分属不同类,因此共享同类的点更相似,已经分离的点不相似,聚类函数应当对之前的聚类决策有很强的“支撑”作用

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

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

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

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

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

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

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

1. 可伸缩性:

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

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

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

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

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

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

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

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

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

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

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

7. 高维性:

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

8. 基于约束的聚类:

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

9. 可解释性和可用性:

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

Relevant Link:

 

《深入理解机器学习 - 从原理到算法》
http://blog.csdn.net/itplus/article/details/10087581

 

 

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

0x1:K-means算法模型

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

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

0x2:K-means算法过程

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

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

1. 聚类中心个数K

聚类中心的个数K需要事先给定,但在实际中这个 K 值的选定是非常难以估计的,很多时候,事先并不知道给定的数据集应该分成多少个类别才最合适。这个过程会是一个漫长的调试过程,我们通过设置一个[k, k+n]范围的K类值,然后逐个观察聚类结果,最终决定该使用什么K值对当前数据集是最佳的

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

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 达到最小值。我们可以用一种迭代的方法完成这件事,其中每次迭代涉及到两个连续的步骤,分别对应

1. 的最优化(E期望):首先,我们为选择一些初始值。然后在第一阶段,我们保持固定,关于最小化 J

2. 的最优化(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

 

2. K-Means++算法

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

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

1. 从山上的某个随机位置出发开始
2. Repeast
3. 每次都都沿着“更高”的方向走一步
4. Until直到某一步发现无论往任何方向走都不会更高

这个算法看起来十分合理,但是我们考虑下面这个图上运行上述算法

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

k-means算法和上面的爬山法类似,不能保证最后能够找到最优的划分簇。这是因为算法一开始选择的是随机质心,基于随机质心,算法只能找到局部最优化分簇(例如图像的B点)

可以看到,对于kmeans这种EM过程来说,最终的聚类结果严重依赖于初始中心点的选择

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

1. K是事先给定的,这个K值的选定是非常难以估计的。很多时候,事先并不知道给定的数据集应该分成多少个类别才最合适 
2. K-Means算法需要用初始随机种子点来搞,这个随机种子点太重要,不同的随机种子点会有得到完全不同的结果 

但是k-means++算法从一定程度上解决了该问题,k-means++选择初始seeds的基本思想就是:初始的聚类中心之间的相互距离要尽可能的远

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

1. 从输入的数据点集合中随机选择一个点作为第一个种子点(聚类中心)
2. 对于数据集中的每一个点x,计算它与最近聚类中心(指已选择的聚类中心)的距离D(x)并保存在一个数组里,然后把这些距离加起来得到Sum(D(x))
3. 选择一个新的数据点作为新的聚类中心,选择的原则是: D(x)较大的点被选取作为聚类中心的概率较大(kMeans不同类别的距离越远越好)
    1) 先取一个能落在Sum(D(x))中的随机值Random
    2) 然后用Random -= D(x),直到其结果 <= 0
    3) 选取在这个"递减过程"中D(x)最大的点,此时的点就是下一个"种子点"
4. 重复2和3直到k个聚类中心被选出来
5. 利用这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

 

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

这种类型的聚类方法不需要指定最终聚成的簇的数目(例如K值)。取而代之的是,算法一开始将每个实例都看成一个簇,然后在算法的每次迭代中都将最相似的两个簇合并在一起,该过程不断重复直到只剩下一个簇为止。该方法称为层次聚类。算法最终终止于单个簇,该簇由两个子簇构成,而其中的每个子簇又由两个更小的子簇构成,如此递归循环下去

基于链接的聚类算法可能是最简单直观的聚类形式。这类算法需要一系列循环,从一些琐碎的聚类开始,将每个数据点作为一个单点聚类,然后,这类算法不断循环将前一阶段中“最近”的两个聚类合并。因此,聚类数目随着循环过程逐渐减少,如果一直进行下去,这类算法会将所有定义域数据点归为一个大类

为了将该类算法定义清楚,需要确定两个参数:

1. 我们需要决定怎样测量(或定义)类间距离
2. 我们需要确定什么时候终止合并

0x1:链接聚类模型

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

1. 域子集间距离度量公式

1.1 单链接聚类(single-linkage clustering):类间距离定义为两类元素间的最短距离(取两两组合最短距离的那个)。在单链接聚类中,如果簇A到簇B的距离要比簇C到簇B的距离更近,则会将A和B先合并成一个新簇。

1.2 最大链接聚类:类间距定义为两类元素间的最大距离(取两两组合最长距离的那个)。

1.3 平均链接聚类(average-linkage clustering):类间距离定义为两类元素之间距离的平均值。

2. 合并终止条件

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

常用的停止准则包括:

1. 固定类的数量:固定参数 k,当聚类数目为 k 时停止聚类
2. 设定距离上限:设定域子集间距最大上限,如果在某一轮迭代中,所有的组件距离都超过该阈值,则停止聚类

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()

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

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 

 

4. 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=(x1,x2,...,xm),邻域参数(ϵ,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

 

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

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

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

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

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

Relevant Link:

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

Copyright (c) 2018 LittleHann All rights reserved

posted @ 2018-01-20 10:23 骑着蜗牛逛世界 阅读(...) 评论(...) 编辑 收藏