Golf l'algoritmo K-significa


10

K-mean è un algoritmo di clustering standard non supervisionato, che, dato un insieme di "punti" e un numero di cluster K, assegnerà ciascun "punto" a uno dei cluster K.

Pseudo-codice di K-significa

Nota che ci sono molte varianti di K-medie. Devi implementare l'algoritmo che sto descrivendo di seguito. Potresti avere qualche variazione sull'algoritmo o utilizzare gli incorporati purché otterrai lo stesso risultato di questo algoritmo, dato gli stessi punti iniziali.

In questa sfida, tutti gli input saranno punti sul piano 2D (ogni punto è rappresentato dalle sue coordinate in xey).

Inputs: K, the number of clusters
        P, the set of points

Choose K points of P uniformly at random
Each chosen point is the initial centroid of its cluster

Loop:
     For each point in P:
         Assign to the cluster whose centroid is the nearest (Euclidean distance)
         In case of a tie, any of the tied cluster can be chosen

     Recompute the centroid of each cluster:
         Its x coordinate is the average of all x's of the points in the cluster
         Its y coordinate is the average of all y's of the points in the cluster

Until the clusters don't change from one iteration to the next

Output: the set of clusters    

Ingressi e uscite

  • Puoi prendere K e P attraverso STDIN, o come argomento di funzione, ecc.
  • P e i punti in P possono essere rappresentati utilizzando qualsiasi struttura naturale per set / elenchi nella lingua prescelta.
  • K è un numero intero strettamente positivo.
  • Si può presumere che gli input siano validi.
  • Ci saranno sempre almeno K punti in P.
  • È possibile generare i cluster in STDOUT, restituirli da una funzione, ecc.
  • L'ordinamento dei cluster e l'ordinamento all'interno dei cluster non è importante. -Puoi restituire gruppi di punti per rappresentare i cluster o ogni punto etichettato con un identificatore per il cluster (ad esempio un numero intero).

Casi test

Poiché i cluster risultanti dipendono da quali punti sono stati inizialmente scelti, potresti non ottenere tutti gli stessi risultati (o lo stesso risultato ogni volta che esegui il codice).

Pertanto, prendi l'output solo come output di esempio.

Input:
  K = 1
  P = [[1,2.5]]
Output:
  [[[1,2.5]]]

Input:
  K = 3
  P = [[4,8], [15,16], [23,42], [-13.37,-12.1], [666,-666]]
Output:
  [[[666,-666]],[[-13.37,-12.1],[4,8]],[[15,16],[23,42]]]

Input:
  K = 2
  P = [[1,1], [1,1], [1,1]]
Output:
  [[[1,1]],[[1,1],[1,1]]]

punteggio

Questo è , quindi vince la risposta più breve in byte.


Gli incorporamenti sono consentiti quando i risultati non sono distinguibili dal tuo algoritmo?
Martin Ender,

@ MartinBüttner, se puoi giustificarlo, dato gli stessi punti iniziali, converge allo stesso risultato, sì.
Fatalizza

Sarebbe anche accettabile produrre etichette di appartenenza a un cluster per ogni punto? (Ad esempio, tutti i punti del primo cluster hanno un'etichetta 1, tutti i punti del secondo hanno un'etichetta, 2ecc.)
flawr

@flawr Sì, questo è accettabile.
Fatalizza

Test case degenerata: K=2, P = [[1,1], [1,1], [1,1]].
Peter Taylor,

Risposte:


4

Matlab, 25 byte

@(x,k)kmeans(x,k,'S','u')

Data una n x 2matrice (una riga per punto ad es. [[4,8]; [15,16]; [23,42]; [-13.37,-12.1]; [666,-666]]) Questa funzione restituisce un elenco di etichette per ciascun punto di input.


5

C ++, 479 474 byte

Solo ~ 20 volte tanto quanto Matlab!

golfed

#define V vector<P>
#define f float
struct P{f x,y,i=0;f d(P&p){return(p.x-x)*(p.x-x)+(p.y-y)*(p.y-y);}f n(P&p){return i?x/=i,y/=i,d(p):x=p.x,y=p.y,0;}f a(P&p){x+=p.x,y+=p.y,i++;}};P z;int l(P a,P b){return a.d(z)<b.d(z);}f m(f k,V&p){f s=p.size(),i,j=0,t=1;V c(k),n=c,d;for(random_shuffle(p.begin(),p.end());j<k;c[j].i=j++)c[j]=p[j];for(;t;c=n,n=V(k)){for(i=0;i<s;i++)d=c,z=p[i],sort(d.begin(),d.end(),l),j=d[0].i,p[i].i=j,n[j].a(p[i]);for(j=t=0;j<k;j++)t+=n[j].n(c[j]);}}

L'input / output dell'algoritmo è un insieme di punti ( struct P) con xe y; e l'output è lo stesso set, con il loro iset per indicare l'indice del cluster di output in cui termina il punto.

Tale extra iviene utilizzato anche per identificare i cluster. Nel ciclo principale, il centroide più vicino a ciascun punto viene trovato ordinando una copia dei centroidi correnti per vicinanza a quel punto.

Questo gestisce casi degenerati (cluster vuoti) mantenendo la posizione precedente dei centroidi corrispondenti (vedere la definizione di P::n, che restituisce anche la distanza dal centroide precedente). Alcuni caratteri potrebbero essere salvati assumendo che questi non si formino.

Ungolfed, con principale

#include <cstdio>
#include <ctime>
#include <cstdlib>
#include <vector>
#include <algorithm>
using namespace std;

#define V vector<P>
#define f float
struct P{
    f x,y,i=0;
    f d(P&p){return(p.x-x)*(p.x-x)+(p.y-y)*(p.y-y);} // distance squared
    f n(P&p){return i?x/=i,y/=i,d(p):x=p.x,y=p.y,0;} // normalize-or-reset
    f a(P&p){x+=p.x,y+=p.y,i++;}                     // add coordinates
};
P z;int l(P a,P b){return a.d(z)<b.d(z);}            // closer-to-z comparator 
f m(f k,V&p){
    f s=p.size(),i,j=0,t=1;V c(k),n=c,d;
    for(random_shuffle(p.begin(),p.end());j<k;c[j].i=j++)
        c[j]=p[j];                                // initial random assignment
    for(;t;c=n,n=V(k)){                           
        for(i=0;i<s;i++)                          // assign to clusters
            d=c,z=p[i],sort(d.begin(),d.end(),l),
            j=d[0].i,p[i].i=j,n[j].a(p[i]);       // and add those coords
        for(j=t=0;j<k;j++)t+=n[j].n(c[j]);        // normalize & count changes
    }        
}

int main(int argc, char **argv) {
    srand((unsigned long)time(0));

    int k;
    V p;
    sscanf(argv[1], "%d", &k);
    printf("Input:\n");
    for (int i=2,j=0; i<argc; i+=2, j++) {
        P n;
        sscanf(argv[i], "%f", &(n.x));
        sscanf(argv[i+1], "%f", &(n.y));
        p.push_back(n);
        printf("%d : %f,%f\n", j, p[j].x, p[j].y);
    }

    m(k,p);
    printf("Clusters:\n");
    for (int q=0; q<k; q++) {
        printf("%d\n", q);
        for (unsigned int i=0; i<p.size(); i++) {
            if (p[i].i == q) printf("\t%f,%f (%d)\n", p[i].x, p[i].y, i);
        }
    }
    return 0;
}

So che potrei essere in ritardo in questo commento, ma potresti definire una macro #define R p){returne modificare il secondo argomento lin pmodo da poterlo utilizzare tre volte in totale?
Zacharý,

4

J, 60 54 byte

p=:[:(i.<./)"1([:+/&.:*:-)"1/
]p](p(+/%#)/.[)^:_(?#){]

Definisce un verbo helper pche accetta un elenco di punti e centroidi e classifica ogni punto in base all'indice del centroide più vicino. Quindi, lo usa per ripetere il processo di scelta del nuovo centroide prendendo le medie dei punti in ciascun cluster fino a quando non converge, e quindi per partizionare i punti per l'output.

uso

Il valore di k è dato come intero sull'LHS. L'elenco dei punti è indicato come un array 2d sull'RHS. Qui viene specificato come un elenco di punti che viene rimodellato in un array 2d di 5 x 2. L'output sarà l'etichetta per il quale cluster ogni punto appartiene nello stesso ordine dell'input.

Se desideri utilizzare un seme fisso per risultati riproducibili, sostituisci ?con un ?.a (?#).

   p =: [:(i.<./)"1([:+/&.:*:-)"1/
   f =: ]p](p(+/%#)/.[)^:_(?#){]
   3 f (5 2 $ 4 8 15 16 23 42 _13.37 _12.1 666 _666)
0 1 1 0 2

Spiegazione

[:(i.<./)"1([:+/&.:*:-)"1/  Input: points on LHS, centroids on RHS
           (          )"1/  Form a table between each point and centroid and for each
                     -        Find the difference elementwise
            [:     *:         Square each
              +/&.:           Reduce using addition
                              Apply the inverse of square (square root) to that sum
[:(     )"1                 For each row of that table
     <./                      Reduce using min
   i.                         Find the index of the minimum in that row
                            Returns a list of indices for each point that shows
                            which centroid it belongs to

]p](p(+/%#)/.[)^:_(?#){]  Input: k on LHS, points on RHS
                    #     Count the number of points
                   ?      Choose k values in the range [0, len(points))
                          without repetition
                       ]  Identity function, get points
                      {   Select the points at the indices above
  ]                       Identity function, get points
   (         )^:_         Repeat until convergence
    p                       Get the labels for each point
             [              Identity function, get points
           /.               Partition the points using the labels and for each
      +/                      Take the sums of points elementwise
         #                    Get the number of points
        %                     Divide sum elementwise by the count
                            Return the new values as the next centroids
]                         Identity function, get points
 p                        Get the labels for each point and return it

Darei +1, ma ho paura che rompere il tuo 3k mi maledirà.
NoOneIsHere

3

CJam (60 byte)

{:Pmr<1/2P,#{:z{_:+\,/}f%:C,{P{C\f{.-Yf#:+}_:e<#1$=},\;}%}*}

Questa è una funzione che prende il suo input nel modulo k pnello stack. Presuppone che i punti siano rappresentati con doppi, non con ints. Non assume implicitamente nulla sulla dimensione dei punti, quindi si raggrupperebbe ugualmente bene nello spazio euclideo 6-D come nella 2-D specificata.

Demo online


2

Mathematica 14 12 byte

Poiché gli incorporati sono consentiti, questo dovrebbe farlo.

FindClusters

Esempio

FindClusters[{{4, 8}, {15, 16}, {23, 42}, {-13.37, -12.1}, {666, -666}}, 3]

{{{4, 8}, {-13.37, -12.1}}, {{15, 16}, {23, 42}}, {{666, -666}}}


Non hai bisogno delle parentesi. f = FindClusters, f[something].
NoOneIsHere

ok, grazie non ne ero sicuro.
DavidC,

1

Gelatina , 24 byte

_ÆḊ¥þ³i"Ṃ€$
ẊḣµÇÆmƙ³µÐLÇ

Provalo online!

Utilizza le funzionalità implementate dopo la pubblicazione di questa sfida. Presumibilmente, questo non è più in competizione .

Spiegazione

_ÆḊ¥þ³i"Ṃ€$  Helper link. Input: array of points
             (Classify) Given a size-k array of points, classifies
             each point in A to the closet point in the size-k array
    þ        Outer product with
     ³       All points, P
   ¥         Dyadic chain
_              Subtract
 ÆḊ            Norm
          $  Monadic chain
      i"     Find first index, vectorized
        Ṃ€   Minimum each

ẊḣµÇÆmƙ³µÐLÇ  Main link. Input: array of points P, integer k
  µ           Start new monadic chain
Ẋ               Shuffle P
 ḣ              Take the first k
        µ     Start new monadic chain
   Ç            Call helper (Classify)
      ƙ         Group with those values the items of
       ³        All points, P
    Æm            Take the mean of each group
         ÐL   Repeat that until the results converge
           Ç  Call helper (Classify)

1

R , 273 byte

function(K,P,C=P[sample(nrow(P),K),]){while(T){D=C
U=sapply(1:nrow(P),function(i)w(dist(rbind(P[i,],C))[1:K]))
C=t(sapply(1:K,function(i)colMeans(P[U==i,,drop=F])))
T=isTRUE(all.equal(C,D))}
cbind(U,P)}
w=function(x,y=seq_along(x)[x==min(x)])"if"(length(y)>1,sample(y,1),y)

Provalo online!

Prende Pcome matrice, con xe ycoordinate rispettivamente nella prima e nella seconda colonna. Restituisce Pcon l'aggiunta di una prima colonna che indica l'indice del cluster (intero).

Ho dovuto ridefinire wcopiando la fonte da nnet::which.is.maxper conformarmi al requisito che il cluster viene scelto casualmente in caso di vincoli. Altrimenti avrei usato which.minda baseper un totale di 210 byte. C'è ancora spazio per giocare a golf, ma non volevo offuscarlo troppo per dare agli altri la possibilità di individuare possibili problemi all'interno del mio codice.


0

Julia 213 byte

function f(p,k)
A=0
P=size(p,1)
c=p[randperm(P)[1:k],:]
while(true)
d=[norm(c[i]-p[j]) for i in 1:k, j in 1:P]
a=mapslices(indmin,d,1)
a==A&&return a
A=a
c=[mean(p[vec(a.==i),:],1) for i in 1:k]
end
end

Restituisce un array della stessa lunghezza di p, con numeri interi che indicano a quale cluster pappartiene l'elemento corrispondente .

Penso che ci sia ancora abbastanza spazio per ottimizzare il conto alla rovescia del personaggio.

(Ovviamente potrei semplicemente usare il pacchetto Clustering.jl per farlo in modo banale)

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.