web-dev-qa-db-fra.com

Variation de l'algorithme K-means avec une taille de cluster égale

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

45
pixelistik

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 ).

12
Fred Foo

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.

5
Erich Schubert

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.

5
Michael J. Barber

Essayez cette variation k-means:

Initialisation:

  • choisissez k centres de l'ensemble de données au hasard, ou mieux en utilisant la stratégie kmeans ++
  • pour chaque point, calculez la distance jusqu'au centre de cluster le plus proche et créez un tas pour
  • dessinez des points du tas et affectez-les au cluster le plus proche, sauf si le cluster est déjà saturé. Si tel est le cas, calculez le centre de cluster le plus proche et réinsérez-le dans le tas

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:

  • Si l'autre cluster est plus petit que le cluster actuel, déplacez-le simplement vers le nouveau cluster
  • S'il y a une proposition d'échange de l'autre cluster (ou de tout cluster avec une distance inférieure), échangez les affectations de cluster à deux éléments (s'il y a plus d'une offre, choisissez celle qui présente la plus grande amélioration)
  • sinon, indiquez une proposition de swap pour l'autre cluster

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.)

3
Anony-Mousse

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()

2
Nate

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

2
sclv

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.

  • Créez une liste de tuples (item_id, cluster_id, distance)
  • Trier les tuples en fonction de la distance
  • Pour chaque élément (item_id, cluster_id, distance) dans la liste triée de tuples:
    • si nombre d'éléments dans cluster_id dépasse S ne fait rien
    • sinon ajoutez item_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
2
Łukasz Kidziński

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: enter image description here

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.

2
BooksE

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
1
denis

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)
1
user2341646

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 .

0

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:

  1. Trouvez la première coordonnée qui sera votre point de départ
  2. Trouvez la coordonnée la plus proche de votre point de départ, par ordre croissant de distance, limitez le résultat par le nombre de votre membre préféré (dans ce cas 400)
  3. Mettez à jour le résultat en mettant à jour la colonne group_id
  4. Effectuez 3 étapes au-dessus de 10 fois pour le reste des données, dont la colonne group_id est toujours NULL

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

0
techamateur

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.

0
Ash