Je recherche l'algorithme le plus rapide pour regrouper des points sur une carte en groupes de taille égale, par distance. algorithme de clustering k-means semble simple et prometteur, mais ne produit pas de groupes de taille égale.
Existe-t-il une variante de cet algorithme ou un autre qui permet un nombre égal de membres pour tous les clusters?
Voir aussi: Grouper n points en k grappes de taille égale
Cela pourrait faire l'affaire: appliquer algorithme de Lloyd pour obtenir k centroïdes. Triez les centroïdes par taille décroissante de leurs clusters associés dans un tableau. Pour i = 1 à k - 1, poussez les points de données dans cluster i avec une distance minimale à tout autre centroïde j ( i < j ≤ k ) off pour j et recalculer le centroïde i (mais ne recalculez pas le cluster) jusqu'à ce que la taille du cluster soit n / k .
La complexité de cette étape de post-traitement est O ( k ² n lg n ).
Le ELKI cadre d'exploration de données a un tutoriel sur les k-moyennes de taille égale .
Ce n'est pas un particulièrement bon bon algorithme, mais c'est une variation k-means assez simple pour écrire un tutoriel et enseigner aux gens comment implémenter leur propre algorithme de clustering variation; et apparemment, certaines personnes ont vraiment besoin que leurs grappes aient la même taille, bien que la qualité SSQ soit pire qu'avec des k-means ordinaires.
Dans ELKI 0.7.5, vous pouvez sélectionner cet algorithme comme tutorial.clustering.SameSizeKMeansAlgorithm
.
Vous pouvez voir les distances comme définissant un graphique pondéré. Un certain nombre d'algorithmes de partitionnement de graphe sont explicitement basés sur la tentative de partitionner les sommets de graphe en deux ensembles de taille égale. Cela apparaît, par exemple, dans algorithme de Kernighan-Lin et dans partitionnement du graphe spectral en utilisant le laplacien. Pour obtenir plusieurs clusters, vous pouvez appliquer récursivement l'algorithme de partitionnement; il y a une belle discussion à ce sujet au lien sur le partitionnement des graphes spectraux.
Essayez cette variation k-means:
Initialisation:
k
centres de l'ensemble de données au hasard, ou mieux en utilisant la stratégie kmeans ++En fin de compte, vous devriez avoir un partitionnement qui répond à vos exigences du + -1 même nombre d'objets par cluster (assurez-vous que les derniers clusters ont également le bon nombre. Les premiers clusters m
devraient avoir ceil
objets, le reste exactement floor
objets.)
étape d'itération:
Conditions requises: une liste pour chaque cluster avec des "propositions d'échange" (objets qui préféreraient être dans un cluster différent).
E étape: calculer les centres de cluster mis à jour comme dans les k-means ordinaires
M étape: itération sur tous les points (soit un seul, soit tous en un seul lot)
Calculez le centre de cluster le plus proche de l'objet/tous les centres de cluster qui sont plus proches que les clusters actuels. S'il s'agit d'un cluster différent:
Les tailles de cluster restent invariantes (+ - la différence plafond/sol), un objet n'est déplacé d'un cluster à un autre que tant qu'il en résulte une amélioration de l'estimation. Il devrait donc converger à un moment comme k-means. Cela pourrait être un peu plus lent (c'est-à-dire plus d'itérations).
Je ne sais pas si cela a été publié ou mis en œuvre auparavant. C'est juste ce que j'essaierais (si j'essayais k-means. Il y a de bien meilleurs algorithmes de clustering.)
Après avoir lu cette question et plusieurs autres similaires, j'ai créé une implémentation python des k-moyennes de même taille en utilisant le tutoriel Elki sur https: //elki-project.github. io/tutorial/same-size_k_means qui utilise l'implémentation K-Means de scikit-learn pour la plupart des méthodes courantes et des API familières.
Mon implémentation se trouve ici: https://github.com/ndanielsen/Same-Size-K-Means
La logique de clustering se trouve dans cette fonction: _labels_inertia_precompute_dense()
Considérez une forme de fusion récursive gourmande - chaque point commence comme un cluster singleton et fusionne à plusieurs reprises les deux plus proches de sorte qu'une telle fusion ne dépasse pas max. Taille. Si vous n'avez pas d'autre choix que de dépasser la taille maximale, recalculez localement. Il s'agit d'une forme de clustering hiérarchique de retour en arrière: http://en.wikipedia.org/wiki/Hierarchical_clustering
Il existe un post-traitement plus propre, étant donné les centroïdes de cluster. Soit N
le nombre d'éléments, K
le nombre de clusters et S = ceil(N/K)
la taille maximale du cluster.
(item_id, cluster_id, distance)
(item_id, cluster_id, distance)
dans la liste triée de tuples: cluster_id
dépasse S
ne fait rienitem_id
regrouper cluster_id
.Cela fonctionne en O (NK lg (N)), devrait donner des résultats comparables à la solution @larsmans et est plus facile à implémenter. En pseudo-python
dists = []
clusts = [None] * N
counts = [0] * K
for i, v in enumerate(items):
dist = map( lambda x: dist(x, v), centroids )
dd = map( lambda (k, v): (i, k, v), enumerate(dist) )
dists.extend(dd)
dists = sorted(dists, key = lambda (x,y,z): z)
for (item_id, cluster_id, d) in dists:
if counts[cluster_id] >= S:
continue
if clusts[item_id] == None:
clusts[item_id] = cluster_id
counts[cluster_id] = counts[cluster_id] + 1
En général, grouper des points sur une carte en groupes de taille égale, par distance, est une mission impossible en théorie. Parce que le regroupement des points en groupes de taille égale est en conflit avec regroupement des points en grappes par distance.
voir cette intrigue:
Il y a quatre points:
A.[1,1]
B.[1,2]
C.[2,2]
D.[5,5]
Si nous regroupons ces points en deux groupes. Évidemment, (A, B, C) sera le cluster 1, D sera le cluster 2. Mais si nous avons besoin de groupes de taille égale, (A, B) sera un cluster, (C, D) sera l'autre. Cela viole les règles de cluster car C est plus proche du centre de (A, B) mais il appartient au cluster (C, D). Ainsi, l'exigence de cluster et de groupes de taille égale ne peut pas être satisfaite en même temps.
Ajouté en janvier 2012: mieux que le post-traitement serait de garder les tailles de cluster à peu près les mêmes au fur et à mesure de leur croissance.
Par exemple, trouvez pour chaque X les 3 centres les plus proches, puis ajoutez X à celui qui a la meilleure distance - λ clustersize.
Un simple post-traitement gourmand après k-means peut être suffisant, si vos clusters de k-means sont à peu près de taille égale.
(Cela rapproche un algorithme d'affectation sur la matrice de distance Npt x C des k-moyennes.)
On pourrait répéter
diffsizecentres = kmeans( X, centres, ... )
X_centre_distances = scipy.spatial.distance.cdist( X, diffsizecentres )
# or just the nearest few centres
xtoc = samesizeclusters( X_centre_distances )
samesizecentres = [X[xtoc[c]].mean(axis=0) for c in range(k)]
...
Je serais surpris si les itérations changeaient beaucoup les centres, mais cela dépendra ™.
Quelle est la taille de votre Npoint Ncluster et Ndim?
#!/usr/bin/env python
from __future__ import division
from operator import itemgetter
import numpy as np
__date__ = "2011-03-28 Mar denis"
def samesizecluster( D ):
""" in: point-to-cluster-centre distances D, Npt x C
e.g. from scipy.spatial.distance.cdist
out: xtoc, X -> C, equal-size clusters
method: sort all D, greedy
"""
# could take only the nearest few x-to-C distances
# add constraints to real assignment algorithm ?
Npt, C = D.shape
clustersize = (Npt + C - 1) // C
xcd = list( np.ndenumerate(D) ) # ((0,0), d00), ((0,1), d01) ...
xcd.sort( key=itemgetter(1) )
xtoc = np.ones( Npt, int ) * -1
nincluster = np.zeros( C, int )
nall = 0
for (x,c), d in xcd:
if xtoc[x] < 0 and nincluster[c] < clustersize:
xtoc[x] = c
nincluster[c] += 1
nall += 1
if nall >= Npt: break
return xtoc
#...............................................................................
if __== "__main__":
import random
import sys
from scipy.spatial import distance
# http://docs.scipy.org/doc/scipy/reference/spatial.distance.html
Npt = 100
C = 3
dim = 3
seed = 1
exec( "\n".join( sys.argv[1:] )) # run this.py N= ...
np.set_printoptions( 2, threshold=200, edgeitems=5, suppress=True ) # .2f
random.seed(seed)
np.random.seed(seed)
X = np.random.uniform( size=(Npt,dim) )
centres = random.sample( X, C )
D = distance.cdist( X, centres )
xtoc = samesizecluster( D )
print "samesizecluster sizes:", np.bincount(xtoc)
# Npt=100 C=3 -> 32 34 34
Récemment, j'ai eu besoin de cela moi-même pour un ensemble de données peu volumineux. Ma réponse, bien qu'elle ait un temps de fonctionnement relativement long, est garantie de converger vers un optimum local.
def eqsc(X, K=None, G=None):
"equal-size clustering based on data exchanges between pairs of clusters"
from scipy.spatial.distance import pdist, squareform
from matplotlib import pyplot as plt
from matplotlib import animation as ani
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
def error(K, m, D):
"""return average distances between data in one cluster, averaged over all clusters"""
E = 0
for k in range(K):
i = numpy.where(m == k)[0] # indeces of datapoints belonging to class k
E += numpy.mean(D[numpy.meshgrid(i,i)])
return E / K
numpy.random.seed(0) # repeatability
N, n = X.shape
if G is None and K is not None:
G = N // K # group size
Elif K is None and G is not None:
K = N // G # number of clusters
else:
raise Exception('must specify either K or G')
D = squareform(pdist(X)) # distance matrix
m = numpy.random.permutation(N) % K # initial membership
E = error(K, m, D)
# visualization
#FFMpegWriter = ani.writers['ffmpeg']
#writer = FFMpegWriter(fps=15)
#fig = plt.figure()
#with writer.saving(fig, "ec.mp4", 100):
t = 1
while True:
E_p = E
for a in range(N): # systematically
for b in range(a):
m[a], m[b] = m[b], m[a] # exchange membership
E_t = error(K, m, D)
if E_t < E:
E = E_t
print("{}: {}<->{} E={}".format(t, a, b, E))
#plt.clf()
#for i in range(N):
#plt.text(X[i,0], X[i,1], m[i])
#writer.grab_frame()
else:
m[a], m[b] = m[b], m[a] # put them back
if E_p == E:
break
t += 1
fig, ax = plt.subplots()
patches = []
for k in range(K):
i = numpy.where(m == k)[0] # indeces of datapoints belonging to class k
x = X[i]
patches.append(Polygon(x[:,:2], True)) # how to draw this clock-wise?
u = numpy.mean(x, 0)
plt.text(u[0], u[1], k)
p = PatchCollection(patches, alpha=0.5)
ax.add_collection(p)
plt.show()
if __== "__main__":
N, n = 100, 2
X = numpy.random.Rand(N, n)
eqsc(X, G=3)
Puis-je vous proposer humblement d'essayer ce projet ekmeans .
A Java K-means Implémentation de clustering avec une option optionnelle d'égalité spéciale qui applique une contrainte de cardinalité égale sur les clusters tout en restant aussi cohérent spatialement que possible.
C'est encore expérimental, alors soyez conscient des bugs connus .
J'ai aussi du mal à résoudre cette question. Cependant, je me rends compte que j'ai utilisé le mauvais mot-clé tout ce temps. Si vous souhaitez que le nombre de membres du résultat ponctuel soit de la même taille, vous effectuez un regroupement, et non plus un regroupement. J'ai enfin réussi à résoudre le problème en utilisant un simple script python et postgis.
Par exemple, j'ai une table appelée tb_points qui a 4000 points de coordonnées, et vous voulez la diviser en 10 groupes de même taille, qui contiendront 400 points de coordonnées chacun. Voici l'exemple de la structure de la table
CREATE TABLE tb_points (
id SERIAL PRIMARY KEY,
outlet_id INTEGER,
longitude FLOAT,
latitide FLOAT,
group_id INTEGER
);
Ensuite, vous devez:
Voici l'implémentation en python:
import psycopg2
dbhost = ''
dbuser = ''
dbpass = ''
dbname = ''
dbport = 5432
conn = psycopg2.connect(Host = dbhost,
user = dbuser,
password = dbpass,
database = dbname,
port = dbport)
def fetch(sql):
cursor = conn.cursor()
rs = None
try:
cursor.execute(sql)
rs = cursor.fetchall()
except psycopg2.Error as e:
print(e.pgerror)
rs = 'error'
cursor.close()
return rs
def execScalar(sql):
cursor = conn.cursor()
try:
cursor.execute(sql)
conn.commit()
rowsaffected = cursor.rowcount
except psycopg2.Error as e:
print(e.pgerror)
rowsaffected = -1
conn.rollback()
cursor.close()
return rowsaffected
def select_first_cluster_id():
sql = """ SELECT a.outlet_id as ori_id, a.longitude as ori_lon,
a.latitude as ori_lat, b.outlet_id as dest_id, b.longitude as
dest_lon, b.latitude as dest_lat,
ST_Distance(CAST(ST_SetSRID(ST_Point(a.longitude,a.latitude),4326)
AS geography),
CAST(ST_SetSRID(ST_Point(b.longitude,b.latitude),4326) AS geography))
AS air_distance FROM tb_points a CROSS JOIN tb_points b WHERE
a.outlet_id != b.outlet_id and a.group_id is NULL and b.group_id is
null order by air_distance desc limit 1 """
return sql
def update_group_id(group_id, ori_id, limit_constraint):
sql = """ UPDATE tb_points
set group_id = %s
where outlet_id in
(select b.outlet_id
from tb_points a,
tb_points b
where a.outlet_id = '%s'
and a.group_id is null
and b.group_id is null
order by ST_Distance(CAST(ST_SetSRID(ST_Point(a.longitude,a.latitude),4326) AS geography),
CAST(ST_SetSRID(ST_Point(b.longitude,b.latitude),4326) AS geography)) asc
limit %s)
""" % (group_id, ori_id, limit_constraint)
return sql
def clustering():
data_constraint = [100]
n = 1
while n <= 10:
sql = select_first_cluster_id()
res = fetch(sql)
ori_id = res[0][0]
sql = update_group_id(n, ori_id, data_constraint[0])
print(sql)
execScalar(sql)
n += 1
clustering()
J'espère que cela aide
Regardez également l'arbre K-d qui partitionne les données jusqu'à ce que les membres de chaque partition soient inférieurs à un BUCKET_SIZE qui est une entrée pour l'algorithme.
Cela ne force pas les compartiments/partitions à avoir exactement la même taille, mais ils seront tous inférieurs à BUCKET_SIZE.