【问题标题】:Calculating the percentage of variance measure for k-means?计算k-means的方差百分比?
【发布时间】:2011-10-02 12:27:52
【问题描述】:

Wikipedia page 上,描述了一种肘部方法,用于确定 k-means 中的聚类数。 The built-in method of scipy 提供了一个实现,但我不确定我是否理解他们所说的失真是如何计算的。

更准确地说,如果你用图表解释方差的百分比 集群对集群的数量,第一个集群将 添加很多信息(解释很多差异),但在某些时候 边际增益会下降,在图中给出一个角度。

假设我有以下点及其相关的质心,那么计算这个度量的好方法是什么?

points = numpy.array([[ 0,  0],
       [ 0,  1],
       [ 0, -1],
       [ 1,  0],
       [-1,  0],
       [ 9,  9],
       [ 9, 10],
       [ 9,  8],
       [10,  9],
       [10,  8]])

kmeans(pp,2)
(array([[9, 8],
   [0, 0]]), 0.9414213562373096)

我正在专门研究计算 0.94.. 仅给出点和质心的度量。我不确定是否可以使用任何 scipy 的内置方法,或者我必须自己编写。有关如何有效地为大量点执行此操作的任何建议?

简而言之,我的问题(所有相关的)如下:

  • 给定一个距离矩阵和哪个点属于哪个点的映射 集群,什么是计算可以使用的度量的好方法 绘制肘部图?
  • 如果使用不同的距离函数(例如余弦相似度),该方法将如何变化?

编辑 2:失真

from scipy.spatial.distance import cdist
D = cdist(points, centroids, 'euclidean')
sum(numpy.min(D, axis=1))

第一组点的输出是准确的。但是,当我尝试不同的设置时:

>>> pp = numpy.array([[1,2], [2,1], [2,2], [1,3], [6,7], [6,5], [7,8], [8,8]])
>>> kmeans(pp, 2)
(array([[6, 7],
       [1, 2]]), 1.1330618877807475)
>>> centroids = numpy.array([[6,7], [1,2]])
>>> D = cdist(points, centroids, 'euclidean')
>>> sum(numpy.min(D, axis=1))
9.0644951022459797

我猜最后一个值不匹配,因为kmeans 似乎将该值除以数据集中的点总数。

编辑 1:百分比方差

到目前为止我的代码(应该添加到 Denis 的 K-means 实现中):

centres, xtoc, dist = kmeanssample( points, 2, nsample=2,
        delta=kmdelta, maxiter=kmiter, metric=metric, verbose=0 )

print "Unique clusters: ", set(xtoc)
print ""
cluster_vars = []
for cluster in set(xtoc):
    print "Cluster: ", cluster

    truthcondition = ([x == cluster for x in xtoc])
    distances_inside_cluster = (truthcondition * dist)

    indices = [i for i,x in enumerate(truthcondition) if x == True]
    final_distances = [distances_inside_cluster[k] for k in indices]

    print final_distances
    print np.array(final_distances).var()
    cluster_vars.append(np.array(final_distances).var())
    print ""

print "Sum of variances: ", sum(cluster_vars)
print "Total Variance: ", points.var()
print "Percent: ", (100 * sum(cluster_vars) / points.var())

以下是 k=2 的输出:

Unique clusters:  set([0, 1])

Cluster:  0
[1.0, 2.0, 0.0, 1.4142135623730951, 1.0]
0.427451660041

Cluster:  1
[0.0, 1.0, 1.0, 1.0, 1.0]
0.16

Sum of variances:  0.587451660041
Total Variance:  21.1475
Percent:  2.77787757437

在我的真实数据集上(我觉得不合适!):

Sum of variances:  0.0188124746402
Total Variance:  0.00313754329764
Percent:  599.592510943
Unique clusters:  set([0, 1, 2, 3])

Sum of variances:  0.0255808508714
Total Variance:  0.00313754329764
Percent:  815.314672809
Unique clusters:  set([0, 1, 2, 3, 4])

Sum of variances:  0.0588210052519
Total Variance:  0.00313754329764
Percent:  1874.74720416
Unique clusters:  set([0, 1, 2, 3, 4, 5])

Sum of variances:  0.0672406353655
Total Variance:  0.00313754329764
Percent:  2143.09824556
Unique clusters:  set([0, 1, 2, 3, 4, 5, 6])

Sum of variances:  0.0646291452839
Total Variance:  0.00313754329764
Percent:  2059.86465055
Unique clusters:  set([0, 1, 2, 3, 4, 5, 6, 7])

Sum of variances:  0.0817517362176
Total Variance:  0.00313754329764
Percent:  2605.5970695
Unique clusters:  set([0, 1, 2, 3, 4, 5, 6, 7, 8])

Sum of variances:  0.0912820650486
Total Variance:  0.00313754329764
Percent:  2909.34837831
Unique clusters:  set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

Sum of variances:  0.102119601368
Total Variance:  0.00313754329764
Percent:  3254.76309585
Unique clusters:  set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

Sum of variances:  0.125549475536
Total Variance:  0.00313754329764
Percent:  4001.52168834
Unique clusters:  set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])

Sum of variances:  0.138469402779
Total Variance:  0.00313754329764
Percent:  4413.30651542
Unique clusters:  set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])

【问题讨论】:

    标签: python numpy statistics cluster-analysis k-means


    【解决方案1】:

    Kmeans而言,失真被用作停止标准(如果两次迭代之间的变化小于某个阈值,我们假设收敛)

    如果您想从一组点和质心计算它,您可以执行以下操作(代码在 MATLAB 中使用 pdist2 函数,但在 Python/Numpy/Scipy 中重写应该很简单):

    % data
    X = [0 1 ; 0 -1 ; 1 0 ; -1 0 ; 9 9 ; 9 10 ; 9 8 ; 10 9 ; 10 8];
    
    % centroids
    C = [9 8 ; 0 0];
    
    % euclidean distance from each point to each cluster centroid
    D = pdist2(X, C, 'euclidean');
    
    % find closest centroid to each point, and the corresponding distance
    [distortions,idx] = min(D,[],2);
    

    结果:

    % total distortion
    >> sum(distortions)
    ans =
               9.4142135623731
    

    编辑#1:

    我有一些时间来解决这个问题。这是一个在'Fisher Iris Dataset' 上应用的 KMeans 聚类示例(4 个特征,150 个实例)。我们遍历k=1..10,绘制弯头曲线,选择K=3 作为聚类数,并显示结果的散点图。

    请注意,在给定点和质心的情况下,我包含了多种计算聚类内方差(失真)的方法。 scipy.cluster.vq.kmeans 函数默认返回此度量(使用欧几里得计算作为距离度量)。您还可以使用scipy.spatial.distance.cdist 函数通过您选择的函数计算距离(前提是您使用相同的距离度量获得了集群质心:@Denis 对此有解决方案),然后从中计算失真。

    import numpy as np
    from scipy.cluster.vq import kmeans,vq
    from scipy.spatial.distance import cdist
    import matplotlib.pyplot as plt
    
    # load the iris dataset
    fName = 'C:\\Python27\\Lib\\site-packages\\scipy\\spatial\\tests\\data\\iris.txt'
    fp = open(fName)
    X = np.loadtxt(fp)
    fp.close()
    
    ##### cluster data into K=1..10 clusters #####
    K = range(1,10)
    
    # scipy.cluster.vq.kmeans
    KM = [kmeans(X,k) for k in K]
    centroids = [cent for (cent,var) in KM]   # cluster centroids
    #avgWithinSS = [var for (cent,var) in KM] # mean within-cluster sum of squares
    
    # alternative: scipy.cluster.vq.vq
    #Z = [vq(X,cent) for cent in centroids]
    #avgWithinSS = [sum(dist)/X.shape[0] for (cIdx,dist) in Z]
    
    # alternative: scipy.spatial.distance.cdist
    D_k = [cdist(X, cent, 'euclidean') for cent in centroids]
    cIdx = [np.argmin(D,axis=1) for D in D_k]
    dist = [np.min(D,axis=1) for D in D_k]
    avgWithinSS = [sum(d)/X.shape[0] for d in dist]
    
    ##### plot ###
    kIdx = 2
    
    # elbow curve
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot(K, avgWithinSS, 'b*-')
    ax.plot(K[kIdx], avgWithinSS[kIdx], marker='o', markersize=12, 
        markeredgewidth=2, markeredgecolor='r', markerfacecolor='None')
    plt.grid(True)
    plt.xlabel('Number of clusters')
    plt.ylabel('Average within-cluster sum of squares')
    plt.title('Elbow for KMeans clustering')
    
    # scatter plot
    fig = plt.figure()
    ax = fig.add_subplot(111)
    #ax.scatter(X[:,2],X[:,1], s=30, c=cIdx[k])
    clr = ['b','g','r','c','m','y','k']
    for i in range(K[kIdx]):
        ind = (cIdx[kIdx]==i)
        ax.scatter(X[ind,2],X[ind,1], s=30, c=clr[i], label='Cluster %d'%i)
    plt.xlabel('Petal Length')
    plt.ylabel('Sepal Width')
    plt.title('Iris Dataset, KMeans clustering with K=%d' % K[kIdx])
    plt.legend()
    
    plt.show()
    


    编辑#2:

    针对 cme​​ts,我在下面给出另一个使用 NIST hand-written digits dataset 的完整示例:它有 1797 个从 0 到 9 的数字图像,每个图像大小为 8×8 像素。我重复上面的实验稍作修改:Principal Components Analysis 用于将维数从 64 降到 2:

    import numpy as np
    from scipy.cluster.vq import kmeans
    from scipy.spatial.distance import cdist,pdist
    from sklearn import datasets
    from sklearn.decomposition import RandomizedPCA
    from matplotlib import pyplot as plt
    from matplotlib import cm
    
    ##### data #####
    # load digits dataset
    data = datasets.load_digits()
    t = data['target']
    
    # perform PCA dimensionality reduction
    pca = RandomizedPCA(n_components=2).fit(data['data'])
    X = pca.transform(data['data'])
    
    ##### cluster data into K=1..20 clusters #####
    K_MAX = 20
    KK = range(1,K_MAX+1)
    
    KM = [kmeans(X,k) for k in KK]
    centroids = [cent for (cent,var) in KM]
    D_k = [cdist(X, cent, 'euclidean') for cent in centroids]
    cIdx = [np.argmin(D,axis=1) for D in D_k]
    dist = [np.min(D,axis=1) for D in D_k]
    
    tot_withinss = [sum(d**2) for d in dist]  # Total within-cluster sum of squares
    totss = sum(pdist(X)**2)/X.shape[0]       # The total sum of squares
    betweenss = totss - tot_withinss          # The between-cluster sum of squares
    
    ##### plots #####
    kIdx = 9        # K=10
    clr = cm.spectral( np.linspace(0,1,10) ).tolist()
    mrk = 'os^p<dvh8>+x.'
    
    # elbow curve
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot(KK, betweenss/totss*100, 'b*-')
    ax.plot(KK[kIdx], betweenss[kIdx]/totss*100, marker='o', markersize=12, 
        markeredgewidth=2, markeredgecolor='r', markerfacecolor='None')
    ax.set_ylim((0,100))
    plt.grid(True)
    plt.xlabel('Number of clusters')
    plt.ylabel('Percentage of variance explained (%)')
    plt.title('Elbow for KMeans clustering')
    
    # show centroids for K=10 clusters
    plt.figure()
    for i in range(kIdx+1):
        img = pca.inverse_transform(centroids[kIdx][i]).reshape(8,8)
        ax = plt.subplot(3,4,i+1)
        ax.set_xticks([])
        ax.set_yticks([])
        plt.imshow(img, cmap=cm.gray)
        plt.title( 'Cluster %d' % i )
    
    # compare K=10 clustering vs. actual digits (PCA projections)
    fig = plt.figure()
    ax = fig.add_subplot(121)
    for i in range(10):
        ind = (t==i)
        ax.scatter(X[ind,0],X[ind,1], s=35, c=clr[i], marker=mrk[i], label='%d'%i)
    plt.legend()
    plt.title('Actual Digits')
    ax = fig.add_subplot(122)
    for i in range(kIdx+1):
        ind = (cIdx[kIdx]==i)
        ax.scatter(X[ind,0],X[ind,1], s=35, c=clr[i], marker=mrk[i], label='C%d'%i)
    plt.legend()
    plt.title('K=%d clusters'%KK[kIdx])
    
    plt.show()
    

    您可以看到一些集群实际上如何对应可区分的数字,而另一些则不匹配单个数字。

    注意:scikit-learn 中包含K-means 的实现(以及许多其他聚类算法和各种clustering metrics)。 Here 是另一个类似的例子。

    【讨论】:

    • +1 感谢您的解释。从您提到的内容来看,我现在要寻找的唯一确认点是此失真值是否用于评估 k 的值。在这里的帖子中:stats.stackexchange.com/questions/9850/…作者直接使用了失真,但我真的不明白他为什么这样做。您对此有什么想法吗?
    • 是的,在最小化簇内总平方和(此处称为 distortion)和最小化簇数之间需要权衡取舍。换句话说,我们想要一个模型能够很好地拟合数据(失真小),但同时,我们希望模型尽可能简单(不要因为集群太多而复杂)。肘部方法是一种简单的启发式方法来平衡两者。这个答案也很好地解释了它:stackoverflow.com/questions/1793532/…
    • Amro,很好。然而,Iris 很小,由此推断出它的不确定性。在来自scikits.learn 的 1797 x 64 位数据上运行 kmeans,它应该有 10 个分离良好的集群 :) 我得到 k = 7 .. 13:平均距离点 - 集群中心 27.7 26.2 25.3 26.2 24.6 24.5 24.1 。 10 点膝盖?
    • @Denis:我用手写数字数据集添加了另一个示例
    • @Denis:肘部方法是一种远非完美的启发式方法。还存在其他方法,例如 AIC/BIC...您还必须记住,Kmeans 是一种无监督学习技术,这意味着它不知道数据的实际类别是什么。相反,它试图从数据本身自然地发现集群。因此,如果两个数字在特征空间中看起来很相似,它们可能会像您在上面的示例中看到的那样组合在一起。此外,通过使用 PCA,我们丢失了一些信息以支持更少的维度......正如您现在可能已经发现的那样,聚类是一项艰巨的任务:)
    【解决方案2】:

    一个简单的集群度量:
    1) 从每个点到其最近的星团中心绘制“旭日形”射线,
    2) 查看所有光线的长度——距离(点、中心、度量=...)。

    对于metric="sqeuclidean" 和 1 个集群, 平均长度平方是总方差X.var();对于 2 个集群,它更少......下降到 N 个集群,长度均为 0。 “解释的方差百分比”是 100 % - 这个平均值。

    is-it-possible-to-specify-your-own-distance-function-using-scikits-learn-k-means 下的代码:

    def distancestocentres( X, centres, metric="euclidean", p=2 ):
        """ all distances X -> nearest centre, any metric
                euclidean2 (~ withinss) is more sensitive to outliers,
                cityblock (manhattan, L1) less sensitive
        """
        D = cdist( X, centres, metric=metric, p=p )  # |X| x |centres|
        return D.min(axis=1)  # all the distances
    

    就像任何一长串数字一样,可以通过多种方式查看这些距离:np.mean()、np.histogram() ... 绘图、可视化并不容易。
    另请参阅stats.stackexchange.com/questions/tagged/clustering,尤其是
    How to tell if data is “clustered” enough for clustering algorithms to produce meaningful results?

    【讨论】:

    • +1 感谢您的时间和解释!我尝试对您在帖子中解释的内容进行编码,并将其添加到问题的末尾。有空可以看看吗?
    • 当然,很好。真正的问题是,对于您的真实数据(请提供数字),这与 k 有何不同?如果 k = 说 5 和 6 很接近,继续前进。
    • 我猜我的函数有问题。我在 EDIT 1 下的函数下方发布了我的问题中的观察值。我得到的百分比超过 100% 并达到数千。我想现在我很确定我的实现是错误的。
    猜你喜欢
    • 2016-04-15
    • 2016-02-04
    • 2020-11-27
    • 2011-09-04
    • 2020-12-25
    • 1970-01-01
    • 2014-05-20
    • 2018-12-27
    相关资源
    最近更新 更多