Calcul du pourcentage de la mesure de la variance pour k-means?
sur la page de Wikipedia , une méthode du coude est décrite pour déterminer le nombre de grappes dans K-means. la méthode intégrée de scipy fournit une implémentation mais je ne suis pas sûr de comprendre comment la distorsion comme ils l'appellent, est calculée.
plus précisément, si vous graphiez le pourcentage de variation expliqué par les clusters par rapport au nombre de clusters, les premiers clusters seront ajouter beaucoup d'information (expliquer beaucoup de variance), mais à un moment donné le gain marginal chute, donnant un angle dans le graphique.
en supposant que j'ai les points suivants avec leurs centroïdes associés, Quelle est une bonne façon de calculer cette mesure?
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)
je cherche spécifiquement à calculer le 0.94.. mesure de simplement les points et les centroïdes. Je ne suis pas sûr si l'une des méthodes intégrées de scipy peut être utilisé ou je dois écrire le mien. Des suggestions sur la façon de le faire efficacement pour un grand nombre de points?
en bref, mes questions (toutes liées) sont les suivantes:
- compte tenu d'une matrice de distance et d'une cartographie du point auquel appartient cluster, Quelle est une bonne façon de calculer une mesure qui peut être utilisée pour dessiner le coup de coude?
- comment la méthodologie changerait-elle si une fonction de distance différente telle que similarité cosinus est utilisée?
EDIT 2: distorsion
from scipy.spatial.distance import cdist
D = cdist(points, centroids, 'euclidean')
sum(numpy.min(D, axis=1))
la sortie pour le premier ensemble de points est précise. Cependant, quand j'essaie un ensemble différent:
>>> 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
je suppose que la dernière valeur ne correspond pas parce que kmeans
semble plonger la valeur par le nombre total de points dans l'ensemble de données.
Modifier 1: Écart en pourcentage
mon code jusqu'à présent (devrait être ajouté à Denis k-means implementation):
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())
et suiv. est la sortie pour 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
sur mon ensemble de données réel (ne me semble pas juste!):
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 réponses
la distorsion, en ce qui concerne Kmeans , est utilisée comme critère d'arrêt (si le changement entre deux itérations est inférieur à un certain seuil, nous supposons la convergence)
si vous voulez le calculer à partir d'un ensemble de points et les centroïdes, vous pouvez faire ce qui suit (le code est dans MATLAB en utilisant pdist2
fonction, mais il devrait être simple à réécrire en 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);
le résultat:
% total distortion
>> sum(distortions)
ans =
9.4142135623731
EDIT#1:
j'ai eu le temps de jouer avec ça.. Voici un exemple de clustering KMeans appliqué sur le 'Fisher Iris Dataset' (4 Caractéristiques, 150 instances). Nous itérons sur k=1..10
, traçons la courbe du coude, choisissons K=3
comme nombre de grappes, et montrons un nuage de points du résultat.
noter que J'ai inclus un certain nombre de façons de calculer les variances à l'intérieur d'un groupe (distorsions), compte tenu des points et des centroïdes. La fonction scipy.cluster.vq.kmeans
renvoie cette mesure par défaut (calculée avec Euclide comme mesure de distance). Vous pouvez également utiliser la fonction scipy.spatial.distance.cdist
pour calculer les distances avec la fonction de votre choix (à condition que vous ayez obtenu les centroïdes de groupe en utilisant la même mesure de distance: @Denis avez une solution pour ça), puis calculer la distorsion.
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()
EDIT#2:
en réponse aux commentaires, je donne ci-dessous un autre exemple complet en utilisant le NIST main-written digits dataset : il a 1797 images de chiffres de 0 à 9, chacun de taille 8 par 8 pixels. Je répéter l'expérience ci-dessus légèrement modifié: L'analyse des composants principaux est appliquée pour réduire la dimensionnalité de 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()
vous pouvez voir comment certains groupes correspondent en fait à des chiffres identifiables, tandis que d'autres ne correspondent pas à un seul nombre.
Note: la mise en œuvre de k-means est inclus dans scikit-learn
(ainsi que de nombreux autres algorithmes de clustering et de divers clustering mesures ). ici est un autre exemple similaire.
Un simple cluster mesure:
1) tirer des rayons "sunburst" de chaque point à son centre d'amas le plus proche,
2) regardez les longueurs-distance (point, centre, métrique=... ) - de tous les rayons.
pour metric="sqeuclidean"
et 1 cluster,
la longueur moyenne au carré est la variance totale X.var()
; pour 2 grappes, c'est moins ... jusqu'à n grappes, longueurs toutes 0.
"Pourcentage de la variance expliquée" est de 100 % - cette moyenne.
Code pour cela, dans la section est-il-possible-de-indiquez-votre-propre-distance-fonction-aide-scikits-apprendre-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
comme n'importe quelle longue liste de nombres, ces distances peuvent être regardées de différentes manières: np.moyenne(), np.histogramme. )(.. Le traçage, la visualisation n'est pas facile.
Voir aussi stats.stackexchange.com/questions/tagged/clustering , en particulier
Comment dire si les données sont suffisamment "regroupées" pour que les algorithmes de regroupement produisent des résultats significatifs?