Расчет процента дисперсии мера для k-средних? - PullRequest
35 голосов
/ 11 июля 2011

На странице Википедия описан коленчатый метод определения количества кластеров в k-средних. Встроенный метод 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: Процентное отклонение

Пока мой код (должен быть добавлен к реализации Дениса 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])

Ответы [ 2 ]

48 голосов
/ 12 июля 2011

Искажение, что касается 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

EDIT # 1:

У меня было время поиграть с этим .. Вот пример кластеризации KMeans, примененной к 'Fisher Iris Dataset' (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()

elbow_curve scatter_plot


EDIT # 2:

В ответ на комментарии ниже приведу еще один полный пример использования набора данных рукописных цифр NIST : в нем содержится 1797 изображений цифр от 0 до 9, каждое размером 8 на 8 пикселей. , Я повторяю эксперимент, слегка измененный выше: Анализ основных компонентов применяется для уменьшения размерности с 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_curve digits_centroids PCA_compare

Вы можете видеть, как некоторые кластеры действительно соответствуют различимым цифрам, в то время как другие не совпадают ни с одним числом.

Примечание. Реализация K-средних включена в scikit-learn (а также во многие другие алгоритмы кластеризации и различные метрики кластеризации ). Здесь - другой подобный пример.

6 голосов
/ 11 июля 2011

Простая кластерная мера:
1) нарисуйте лучи "солнечных лучей" от каждой точки до ближайшего к ней центра кластера,
2) посмотрите на длину - расстояние (точка, центр, метрика = ...) -всех лучей.

Для metric="sqeuclidean" и 1 кластера средняя длина в квадрате равна общей дисперсии X.var();для 2 кластеров это меньше ... до N кластеров, длины все 0. "Процент объяснения дисперсии" равен 100% - это среднее значение.

Код для этого, под is-it-возможно-to-указать-вашу-собственную-расстояние-функцию-используя-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 , в частности
Как определить, достаточно ли «кластеризованы» данные для того, чтобы алгоритмы кластеризации давали значимые результаты?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...