Procedura di clustering in cui ciascun cluster ha un uguale numero di punti?


25

Ho alcuni punti in R p , e voglio raggruppare i punti in modo che:X={x1,...,xn}Rp

  1. Ciascun cluster contiene un numero uguale di elementi di . (Supponiamo che il numero di cluster divida .)Xn

  2. Ogni cluster è "spazialmente coeso" in un certo senso, come i cluster di medie.k

È facile pensare a molte procedure di clustering che soddisfano l'una o l'altra di queste, ma qualcuno sa come ottenere entrambe contemporaneamente?


2
È stata anche specificata la dimensione del cluster? Quindi, come detto, il problema mi sembra irrisolvibile. Considera il caso seguente con : X = { - 1 , 0,99 , 1 , 1,01 } . Se si desidera 2 cluster, si ottengono dimensioni diverse o non "coesivamente spaziali". O vuoi qualcosa del tipo, "il più spazialmente coeso possibile" - minimizzare la distanza massima intragruppo o giù di lì? L'altra soluzione sarebbe quella di consentire qualsiasi divisore di n come dimensione del cluster - ma poi c'è sempre la banale soluzione di n cluster di dimensione 1 . n=4,p=1X={1,0.99,1,1.01}nn1
Erik P.

1
Buon punto. Idealmente, vorrei qualcosa che fosse "il più spazialmente coeso possibile", pur soddisfacendo il pari vincolo di cardinalità. Ma sarei interessato a conoscere eventuali procedure che rendono anche altri compromessi qui, dal momento che forse possono essere adattati.
Non Durrett il

La suddivisione dei dati per quantili sarebbe sufficiente? Se i valori non sono monotonici l'uno rispetto all'altro, non riesco a vedere in che altro modo potrebbero essere "spazialmente coesi".
Celenius

4
Ci sono state alcune ricerche recenti sul clustering vincolato. Google google.com/search?q=constrained+k-means .
whuber

Solo un'idea non testata. Nel clustering, viene spesso utilizzata una cosiddetta statistica Silhouette. Ti mostra quanto bene un oggetto è raggruppato e qual è il migliore, vicino cluster in cui potrebbe essere registrato. Quindi, potresti iniziare con K-MEANS o altra classificazione con differenti cluster n . Quindi sposta gli oggetti non molto ben classificati (secondo la statistica) nei loro cluster migliori vicini con n minore fino a ottenere n uguale . Mi aspetto iterazioni: spostare alcuni oggetti, ricalcolare le statistiche, spostare alcuni oggetti, ecc. Sarà un processo di compromesso.
ttnphns,

Risposte:


6

Suggerisco un approccio in due fasi:

  1. ottenere una buona stima iniziale dei centri del cluster, ad es. usando mezzi K duri o sfocati.

  2. Utilizza l'assegnazione Global Neighbor più vicino per associare i punti ai centri del cluster: calcola una matrice di distanza tra ciascun punto e ciascun centro del cluster (puoi ridurre leggermente il problema calcolando solo le distanze ragionevoli), replicare ogni centro X del cluster e risolvere i lineari problema di assegnazione . Otterrai, per ogni centro cluster, esattamente X corrispondenze ai punti dati, in modo che, a livello globale, la distanza tra i punti dati e i centri cluster sia ridotta al minimo.

Si noti che è possibile aggiornare i centri cluster dopo il passaggio 2 e ripetere il passaggio 2 per eseguire sostanzialmente K-medie con un numero fisso di punti per cluster. Tuttavia, sarà una buona idea ottenere prima una buona ipotesi iniziale.


4

Prova questa variante di k-significa:

Inizializzazione :

  • scegli i kcentri dal set di dati in modo casuale, o ancora meglio usando la strategia kmeans ++
  • per ogni punto, calcola la distanza dal centro del cluster più vicino e costruisci un mucchio per questo
  • disegnare punti dall'heap e assegnarli al cluster più vicino, a meno che il cluster non sia già troppo pieno. In tal caso, calcolare il centro cluster più vicino successivo e reinserirlo nell'heap

Alla fine, dovresti avere un partizionamento che soddisfi i tuoi requisiti dello + -1 stesso numero di oggetti per cluster (assicurati che anche gli ultimi cluster abbiano il numero giusto. I primi mcluster dovrebbero avere ceiloggetti, il resto esattamente flooroggetti).

Iterazione :

Requisiti: un elenco per ciascun cluster con "proposte di scambio" (oggetti che preferirebbero trovarsi in un cluster diverso).

E step: calcola i centri cluster aggiornati come nei normali k-media

Passaggio M : scorrere tutti i punti (solo uno o tutti in un batch)

Calcola il centro cluster più vicino all'oggetto / a tutti i centri cluster più vicini ai cluster correnti. Se si tratta di un cluster diverso:

  • Se l'altro cluster è più piccolo del cluster corrente, spostalo nel nuovo cluster
  • Se esiste una proposta di scambio dall'altro cluster (o qualsiasi cluster con una distanza inferiore), scambiare le assegnazioni di cluster a due elementi (se esiste più di un'offerta, scegliere quella con il miglioramento maggiore)
  • in caso contrario, indicare una proposta di scambio per l'altro cluster

Le dimensioni del cluster rimangono invarianti (+ - la differenza ceil / floor), gli oggetti vengono spostati da un cluster all'altro solo se si traduce in un miglioramento della stima. Dovrebbe quindi convergere ad un certo punto come k-medie. Potrebbe essere un po 'più lento (cioè più iterazioni) però.

Non so se questo è stato pubblicato o implementato prima. È proprio quello che proverei (se provassi k-mean. Ci sono algoritmi di clustering molto migliori.)

Un buon punto di partenza potrebbe essere con l'implementazione di k-means in ELKI , che sembra già supportare tre diverse inizializzazioni (incluso k-mean ++), e gli autori hanno affermato di voler anche avere strategie di iterazione diverse, per coprire tutti i vari comuni varianti in modo modulare (es. Lloyd, MacQueen, ...).


2
Un approccio simile è incluso in ELKI come tutorial e nel modulo "extension" del tutorial: elki.dbs.ifi.lmu.de/wiki/Tutorial/SameSizeKMeans
Erich Schubert

3

Questo è un problema di ottimizzazione. Abbiamo una libreria java open source che risolve questo problema (clustering in cui la quantità per cluster deve essere compresa tra intervalli impostati). Avresti bisogno che il tuo numero totale di punti sia massimo di qualche migliaio, non più di 5000 o forse 10000.

La biblioteca è qui:

https://github.com/PGWelch/territorium/tree/master/territorium.core

La libreria stessa è configurata per problemi di tipo geografico / GIS - quindi vedrai riferimenti a X e Y, latitudini e longitudini, clienti, distanza e tempo, ecc. Puoi semplicemente ignorare gli elementi "geografici" e usarli come puro clusterer.

Fornisci un set di cluster di input inizialmente vuoti, ciascuno con una quantità target minima e massima. Il clusterer assegnerà punti ai cluster di input, utilizzando un algoritmo di ottimizzazione basato su euristica (swap, mosse, ecc.). Nell'ottimizzazione, in primo luogo dà la priorità a mantenere ciascun cluster nel suo intervallo di quantità minima e massima, quindi in secondo luogo minimizza le distanze tra tutti i punti del cluster e il punto centrale del cluster, quindi un cluster è spazialmente coeso.

Dai al risolutore una funzione metrica (cioè la funzione di distanza) tra i punti usando questa interfaccia:

https://github.com/PGWelch/territorium/blob/master/territorium.core/src/main/java/com/opendoorlogistics/territorium/problem/TravelMatrix.java

La metrica è in realtà strutturata in modo da restituire sia una distanza sia un "tempo", poiché è progettata per problemi geografici basati sui viaggi, ma per problemi di clustering arbitrario è sufficiente impostare "tempo" su zero e la distanza come metrica effettiva che si sta utilizzando tra punti.

Dovresti impostare il tuo problema in questa classe:

https://github.com/PGWelch/territorium/blob/master/territorium.core/src/main/java/com/opendoorlogistics/territorium/problem/Problem.java

I tuoi punti sarebbero i "Clienti" e la loro quantità sarebbe 1. Nella classe del cliente assicurati di impostare costPerUnitTime = 0 e costPerUnitDistance = 1 supponendo che stai restituendo la distanza metrica nel campo "distanza" restituito da TravelMatrix.

https://github.com/PGWelch/territorium/blob/master/territorium.core/src/main/java/com/opendoorlogistics/territorium/problem/Customer.java

Vedi qui per un esempio di esecuzione del solutore:

https://github.com/PGWelch/territorium/blob/master/territorium.core/src/test/java/com/opendoorlogistics/territorium/TestSolver.java



2

Di recente ne ho avuto bisogno da solo per un set di dati non molto grande. La mia risposta, sebbene abbia un tempo di esecuzione relativamente lungo, è garantita per convergere in un ottimale locale.

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 __name__ == "__main__":
    N, n = 100, 2    
    X = numpy.random.rand(N, n)
    eqsc(X, G=3)

1
Grazie per questo contributo, @ user2341646. Ti dispiacerebbe aggiungere qualche esposizione che spieghi cos'è questa soluzione, come funziona e perché è una soluzione?
gung - Ripristina Monica

OK. Fondamentalmente, l'algoritmo inizia con assegnazioni di appartenenza casuali, ma ci sono quasi G membri in un cluster e ci sono K cluster in generale. Definiamo la funzione di errore che misura le distanze medie tra i dati in un cluster, mediata su tutti i cluster. Esaminando sistematicamente tutte le coppie di dati, vediamo se lo scambio dell'appartenenza di questi due dati provoca un errore inferiore. In tal caso, aggiorniamo l'errore più basso possibile, altrimenti annulliamo il cambio di appartenenza. Lo facciamo fino a quando non ci sono più mosse rimaste per un intero passaggio.
Alexander Kain,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.