數據挖掘十大經典算法[0]-K-Means算法

    K-Means算法的輸入N,K和一個size爲N的向量組vector.輸出K個兩兩互不相交的向量組.其本質是將給定的向量組劃分紅K個類別,使得同類別的向量類似度比較大,而不一樣類別的向量之間的類似度較小.
    好比如下這個圖,人肉眼能看出有四個點團,但計算機不知道,爲了讓計算機明白這一點,能夠將點的座標提取到向量組中,而向量之間的類似度定義爲點之間的距離的相反數或者倒數.從而將這些點分開.
    實現過程:
    (1)從n個數據對象任意選擇k個對象做爲初始聚類中心;
    (2)根據每一個聚類對象的均值(中心對象),計算每一個對象與這些中心對象的距離,並根據最小距離從新對相應對象進行劃分;
    (3)從新計算每一個(有變化)聚類的均值(中心對象);
    (4)計算標準測度函數,當知足必定條件,如函數收斂時,則算法終止,若是條件不知足則回到步驟(2).
    實際應用中的問題:
    事實上,我是一個作ACM的選手,因此我比較感興趣的是K-Means可否求得一個最優解.對於這樣一個問題:從N個點取出K個做爲核心,定義兩個向量之間的類似度函數f(vector1,vector2),使得全部點與其所對應的核心的類似度之和最大.然而事實讓我大失所望,K-Means算法對種子點的選取十分敏感,不一樣的種子會致使不一樣的解.html

#include<math.h>
#include<stdio.h>
#include<string.h>
#define Convergence (fabs(last-cur)<1e-8)
#define dist(a,b) (sqrt((x[a]-px[b])*(x[a]-px[b])+(y[a]-py[b])*(y[a]-py[b])))
int x[50000],y[50000],qx[50000],qy[50000],px[100],py[100],assign[50000];
int main()
{
 freopen("data.txt","r",stdin);
 FILE *fp=fopen("output.txt","w");
 int N,K,i,j,k;
 double ave=0,MIN=1e15;
 scanf("%d%d",&N,&K);
 for (i=1;i<=N;i++) scanf("%d%d",&x[i],&y[i]);
 for (int asd=0;asd<N;asd++)
 {
 printf("Executing case #%d\n",asd);
 if (asd) printf("Current Average:%.6lf\n",ave/asd);
 printf("Current Minimize:%.6lf\n",MIN);
 printf("----------------------------------------\n");
 fprintf(fp,"Executing case #%d\n",asd);
 if (asd) fprintf(fp,"Current Average:%.6lf\n",ave/asd);
 fprintf(fp,"Current Minimize:%.6lf\n",MIN);
 fprintf(fp,"----------------------------------------\n");
 for (i=1;i<=K;i++)
 {
  px[i]=x[(i+asd)%N+1];
  py[i]=y[(i+asd)%N+1];
 }
 double last=1e15,cur=0;
 while (!Convergence)
 {
  printf("%.6lf\n",last);
  last=cur;
  for (i=1;i<=N;i++)
  {
   double Min=1e15;
   int v;
   for (j=1;j<=K;j++)
   {
    double d=dist(i,j);
    if (d<Min)
    {
     Min=d;
     v=j;
    }
   }
   assign[i]=v;
  }
  for (i=1;i<=K;i++)
  {
   int cnt=0;
   for (j=1;j<=N;j++)
    if (assign[j]==i)
    {
     qx[++cnt]=x[j];
     qy[ cnt ]=y[j];
    }
   double Min=1e15;
   int v;
   for (j=1;j<=cnt;j++)
   {
    double tmp=0;
    for (k=1;k<=cnt;k++)
     tmp+=(sqrt((qx[j]-qx[k])*(qx[j]-qx[k])+(qy[j]-qy[k])*(qy[j]-qy[k])));
    if (tmp<Min)
    {
     Min=tmp;
     v=j;
    }
   }
   px[i]=qx[v];
   py[i]=qy[v];
  }
  cur=0;
  for (i=1;i<=N;i++) cur+=dist(i,assign[i]);
 }
 ave+=cur;
 MIN=MIN<cur ? MIN:cur;
 }
 printf("Total average:%.6lf\n",ave/N);
 printf("Total MIN:%.6lf\n",MIN);
 fprintf(fp,"Total average:%.6lf\n",ave/N);
 fprintf(fp,"Total MIN:%.6lf\n",MIN);
 return 0;
}
View Code

    運行結果如圖所示:算法

K-Means[1]
    另外一個問題是算法的收斂速度,從新算了一下,結果以下圖所示:dom

K-Means[0]
    這個結果讓我大吃一驚啊,每次迭代以後更新量都很小,並且最終的值(9259914.963696)跟第一個有意義的值(10352922.175732)相差並非不少.後來我仔細想了一下,應該是跟輸入數據有關,個人數據徹底是在必定範圍內隨機生成的,分佈比較均勻,因此即便隨便選也能夠獲得至關不錯的效果,這是我生成數據的程序:ide

program makedata;
var i,N,K:longint;
begin
assign(output,'data.txt');
rewrite(output);
    randomize;
    N:=random(10000);
    K:=random(10000);
    writeln(N,' ',K);
    for i:=1 to N do
        writeln(random(10000),' ',random(10000));
close(output);
end.
View Code

    因而我從新寫了makedada程序,想法是先隨機生成K個核心,再在其周圍生成其餘的點:函數

#include<stdio.h>
#include<time.h>
#include<math.h>
#include<stdlib.h>
int main()
{
 srand(unsigned(time(0)));
 freopen("data.txt","w",stdout);
 printf("15000 15\n");
 for (int i=1;i<=15;i++)
 {
  int X=rand()%1000000,Y=rand()%1000000;
  for (int j=1;j<=1000;j++)
  {
   int dx=rand()%10000,dy=rand()%10000;
   if (rand()&1) dx*=-1;
   if (rand()&1) dy*=-1;
   printf("%d %d\n",X+dx,Y+dy);
  }
 }
 return 0;
}
View Code

    再從新運行一下,獲得以下結果:spa

K-Means[2]
    能夠看出,收斂的速度仍是能夠的,並且最終結果幾乎只有最初解得一半.
    初除此以外,還有一個重要問題,核心數K是做爲輸入給定的,而在實際應用中是沒法預知的.對此能夠用ISODATA算法做爲補充.code

相關文章
相關標籤/搜索