Golf K-means算法


10

K-means是一种标准的无监督聚类算法,在给定一组“点”和多个K聚类的情况下,它将将每个“点”分配给K个聚类之一。

K均值的伪码

注意,K-means有很多变体。您必须实现我在下面描述的算法。只要给定相同的初始点,您可以获得与该算法相同的结果,就可以对算法进行一些更改或使用内置函数。

在此挑战中,所有输入将是2D平面上的点(每个点由其在x和y中的坐标表示)。

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    

输入和输出

  • 您可以通过STDIN,或将K和P 用作函数参数等。
  • P和P中的点可以使用您选择的语言对集合/列表自然使用的任何结构表示。
  • K是严格的正整数。
  • 您可以假设输入有效。
  • P中至少总是有K点。
  • 您可以将群集输出到STDOUT,从函数中返回它们,等等。
  • 群集的顺序和群集内部的顺序并不重要。-您可以返回表示聚类的点组,也可以返回每个标记有聚类标识符(例如整数)的点。

测试用例

由于生成的群集取决于最初选择的点,因此您可能不会都获得相同的结果(或每次运行代码时都得到相同的结果)。

因此,仅将输出作为示例输出。

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]]]

计分

这是,因此最短的答案以字节为单位。


当结果与您的算法无法区分时,是否允许使用内置函数?
马丁·恩德

@MartinBüttner如果可以证明给定相同的初始点,那么它会收敛于相同的结果,是的。
致命

为每个点输出集群成员资格标签也可以吗?(例如,第一个群集的1所有点都带有标签,第二个群集的所有点都带有标签,2等等)
更加模糊的

@flawr是的,这是可以接受的。
致命

退化测试用例:K=2, P = [[1,1], [1,1], [1,1]]
彼得·泰勒

Answers:


4

Matlab,25个字节

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

给定n x 2矩阵(例如,每点一行[[4,8]; [15,16]; [23,42]; [-13.37,-12.1]; [666,-666]]),此函数将为每个输入点返回标签列表。


5

C ++,479个 474字节

大约是Matlab的20倍!

打高尔夫球

#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]);}}

该算法的输入/输出是struct P带有x和的一组点()y。并且输出是相同的集合,它们i的集合指示该点结束的输出群集的索引。

该多余i部分还用于标识群集。在主循环中,通过按与该点的接近程度对当前质心的副本进行排序来找到与每个点最接近的质心。

这通过保持相应质心的先前位置来处理退化的情况(空簇)(请参阅的定义P::n,它也返回到上一个质心的距离)。通过假设这些字符不会出现,可以节省一些字符。

不带高尔夫,带主

#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;
}

我知道我可能在这条评论中迟到了,但是您可以定义一个宏#define R p){return并将第二个参数更改为lp以便总共使用3次吗?
扎卡里

4

J,60 54字节

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

定义一个辅助动词p,该动词接受点和质心的列表,并根据最近的质心的索引对每个点进行分类。然后,它使用该方法来重复选择新质心的过程,方法是获取每个群集中点的平均值,直到收敛为止,然后对这些点进行分区以进行输出。

用法

k的值在LHS上以整数形式给出。点列表在RHS上以2d数组形式给出。在这里,它被指定为点列表,这些点被整形为5 x 2的2d数组。输出将是每个点所属的聚类的标签,其顺序与输入相同。

如果您希望使用可重复结果的固定种子,替换??.(?#)

   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

说明

[:(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

我会给+1,但我担心打破您的3k会诅咒我。
NoOneIsHere

3

CJam(60字节)

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

这是一个函数,其输入形式k p为堆栈。假定这些点用双精度而不是整数表示。它不会隐式假定点的尺寸,因此它在6维欧几里德空间中的聚类效果与在指定二维中一样好。

在线演示


2

Mathematica 14 12字节

由于允许内置,因此应该这样做。

FindClusters

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}}}


您不需要括号。f = FindClustersf[something]
NoOneIsHere

好的,谢谢,我不确定。
DavidC

1

果冻,24字节

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

在线尝试!

使用发布此挑战后实施的功能。据说,这不再是非竞争性的

说明

_ÆḊ¥þ³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字节

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)

在线尝试!

采用P作为基体,具有xy分别在第一和第二列的坐标。返回P并添加第一列,该列指示群集索引(整数)。

我必须w通过复制来重新定义源,nnet::which.is.max以符合在有联系的情况下随机选择集群的要求。否则,我会用which.minbase总共210个字节。仍然有打高尔夫球的空间,但是我不想对其进行过多的混淆,以使其他人有机会发现我的代码中可能存在的问题。


0

朱莉娅213字节

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

返回与长度相同的数组p,其中的整数表示相应元素p所属的簇。

我认为仍然存在很大的范围来优化字符计数。

(当然,我可以只使用Clustering.jl包来完成它)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.