web-dev-qa-db-ja.com

k-meansの分散測定の割合を計算していますか?

Wikipediaページ では、k-meansのクラスター数を決定するためのエルボー法が説明されています。 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メソッドを使用できるかどうか、または独自の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])
35
Legend

Kmeans に関する限り、歪みは停止基準として使用されます(2つの反復間の変化がしきい値よりも小さい場合、収束と見なされます)。

一連の点と重心から計算する場合、次のことができます(コードは pdist2 関数を使用してMATLABにありますが、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' (4機能、150インスタンス)に適用されたKMeansクラスタリングの例を示します。 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()

elbow_curvescatter_plot


編集#2:

コメントに応じて、 NIST手書き数字データセット を使用した別の完全な例を以下に示します。サイズは8〜8ピクセルの0〜9の数字の1797個の画像があります。上記の実験を少し変更して繰り返します。 主成分分析 を適用して、次元を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()

elbow_curvedigits_centroidsPCA_compare

一部のクラスターが実際に識別可能な数字に対応しているのに対し、他のクラスターは単一の数字と一致していないことがわかります。

注: K-means の実装は scikit-learn に含まれています(他の多くのクラスタリングアルゴリズムとさまざまな クラスタリングメトリック ) 。 ここ は別の同様の例です。

49
Amro

単純なクラスター測定:
1)各ポイントから最も近いクラスター中心に「サンバースト」光線を描画します。
2)すべての光線の長さ— distance(point、center、metric = ...)—を見てください。

_metric="sqeuclidean"_および1クラスターの場合、平均の長さの2乗は合計分散X.var();です。 2つのクラスターの場合、それは少ない... Nクラスターまで、長さはすべて0です。「分散の割合の説明」は100%です-この平均。

このコードは、 -to-it-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 、特に
クラスタリングアルゴリズムが意味のある結果を生成するのに十分なデータが「クラスター化」されているかどうかを確認する方法?

6
denis