最近在苦於思考kmeans算法的MPI並行化,花了兩天的時間把該算法看懂和實現了串行版。
聚類問題就是給定一個元素集合V,其中每個元素具有d個可觀察屬性,使用某種算法將V划分成k個子集,要求每個子集內部的元素之間相異度盡可能低,而不同子集的元素相異度盡可能高。
下面是google到該算法的一個流程圖,表意清楚:
1、隨機選取數據集中的k個數據點作為初始的聚類中心:
2、分別計算每個數據點到每個中心的距離,選取距離最短的中心點作為其聚類中心:
3、利用目前得到的聚類重新計算中心點:
4、重復步驟2和3直到收斂(達到最大迭代次數或聚類中心不再移動):
code:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <math.h> 4 #include <time.h> 5 6 int K,N,D; //聚類的數目,數據量,數據的維數 7 float **data; //存放數據 8 int *in_cluster; //標記每個點屬於哪個聚類 9 float **cluster_center; //存放每個聚類的中心點 10 11 float **array(int m,int n); 12 void freearray(float **p); 13 float **loadData(int *k,int *d,int *n); 14 float getDistance(float avector[],float bvector[],int n); 15 void cluster(); 16 float getDifference(); 17 void getCenter(int in_cluster[]); 18 19 int main() 20 { 21 int i,j,count=0; 22 float temp1,temp2; 23 data=loadData(&K,&D,&N); 24 printf("Data sets:\n"); 25 for(i=0;i<N;i++) 26 for(j=0;j<D;j++){ 27 printf("%-8.2f",data[i][j]); 28 if((j+1)%D==0) putchar('\n'); 29 } 30 printf("-----------------------------\n"); 31 32 srand((unsigned int)(time(NULL))); //隨機初始化k個中心點 33 for(i=0;i<K;i++) 34 for(j=0;j<D;j++) 35 cluster_center[i][j]=data[(int)((double)N*rand()/(RAND_MAX+1.0))][j]; 36 37 cluster(); //用隨機k個中心點進行聚類 38 temp1=getDifference(); //第一次中心點和所屬數據點的距離之和 39 count++; 40 printf("The difference between data and center is: %.2f\n\n", temp1); 41 42 getCenter(in_cluster); 43 cluster(); //用新的k個中心點進行第二次聚類 44 temp2=getDifference(); 45 count++; 46 printf("The difference between data and center is: %.2f\n\n",temp2); 47 48 while(fabs(temp2-temp1)!=0){ //比較前后兩次迭代,若不相等繼續迭代 49 temp1=temp2; 50 getCenter(in_cluster); 51 cluster(); 52 temp2=getDifference(); 53 count++; 54 printf("The %dth difference between data and center is: %.2f\n\n",count,temp2); 55 } 56 57 printf("\nThe total number of cluster is: %d\n",count); //統計迭代次數 58 //system("pause"); //gcc編譯需刪除 59 return 0; 60 } 61 62 63 //動態創建二維數組 64 float **array(int m,int n) 65 { 66 int i; 67 float **p; 68 p=(float **)malloc(m*sizeof(float *)); 69 p[0]=(float *)malloc(m*n*sizeof(float)); 70 for(i=1;i<m;i++) p[i]=p[i-1]+n; 71 return p; 72 } 73 74 //釋放二維數組所占用的內存 75 void freearray(float **p) 76 { 77 free(*p); 78 free(p); 79 } 80 81 //從data.txt導入數據,要求首行格式:K=聚類數目,D=數據維度,N=數據量 82 float **loadData(int *k,int *d,int *n) 83 { 84 int i,j; 85 float **arraydata; 86 FILE *fp; 87 if((fp=fopen("data.txt","r"))==NULL) fprintf(stderr,"cannot open data.txt!\n"); 88 if(fscanf(fp,"K=%d,D=%d,N=%d\n",k,d,n)!=3) fprintf(stderr,"load error!\n"); 89 arraydata=array(*n,*d); //生成數據數組 90 cluster_center=array(*k,*d); //聚類的中心點 91 in_cluster=(int *)malloc(*n * sizeof(int)); //每個數據點所屬聚類的標志數組 92 for(i=0;i<*n;i++) 93 for(j=0;j<*d;j++) 94 fscanf(fp,"%f",&arraydata[i][j]); //讀取數據點 95 return arraydata; 96 } 97 98 //計算歐幾里得距離 99 float getDistance(float avector[],float bvector[],int n) 100 { 101 int i; 102 float sum=0.0; 103 for(i=0;i<n;i++) 104 sum+=pow(avector[i]-bvector[i],2); 105 return sqrt(sum); 106 } 107 108 //把N個數據點聚類,標出每個點屬於哪個聚類 109 void cluster() 110 { 111 int i,j; 112 float min; 113 float **distance=array(N,K); //存放每個數據點到每個中心點的距離 114 //float distance[N][K]; //也可使用C99變長數組 115 for(i=0;i<N;++i){ 116 min=9999.0; 117 for(j=0;j<K;++j){ 118 distance[i][j] = getDistance(data[i],cluster_center[j],D); 119 //printf("%f\n", distance[i][j]); 120 if(distance[i][j]<min){ 121 min=distance[i][j]; 122 in_cluster[i]=j; 123 } 124 } 125 printf("data[%d] in cluster-%d\n",i,in_cluster[i]+1); 126 } 127 printf("-----------------------------\n"); 128 free(distance); 129 } 130 131 //計算所有聚類的中心點與其數據點的距離之和 132 float getDifference() 133 { 134 int i,j; 135 float sum=0.0; 136 for(i=0;i<K;++i){ 137 for(j=0;j<N;++j){ 138 if(i==in_cluster[j]) 139 sum+=getDistance(data[j],cluster_center[i],D); 140 } 141 } 142 return sum; 143 } 144 145 //計算每個聚類的中心點 146 void getCenter(int in_cluster[]) 147 { 148 float **sum=array(K,D); //存放每個聚類中心點 149 //float sum[K][D]; //也可使用C99變長數組 150 int i,j,q,count; 151 for(i=0;i<K;i++) 152 for(j=0;j<D;j++) 153 sum[i][j]=0.0; 154 for(i=0;i<K;i++){ 155 count=0; //統計屬於某個聚類內的所有數據點 156 for(j=0;j<N;j++){ 157 if(i==in_cluster[j]){ 158 for(q=0;q<D;q++) 159 sum[i][q]+=data[j][q]; //計算所屬聚類的所有數據點的相應維數之和 160 count++; 161 } 162 } 163 for(q=0;q<D;q++) 164 cluster_center[i][q]=sum[i][q]/count; 165 } 166 printf("The new center of cluster is:\n"); 167 for(i = 0; i < K; i++) 168 for(q=0;q<D;q++){ 169 printf("%-8.2f",cluster_center[i][q]); 170 if((q+1)%D==0) putchar('\n'); 171 } 172 free(sum); 173 }
該程序支持不同維度的數據集,一個示例的數據集 data.txt如下:
K=3,D=3,N=15
-25 22.2 35.34
31.2 -14.4 23
32.02 -23 24.44
-25.35 36.3 -33.34
-20.2 27.333 -28.22
-15.66 17.33 -23.33
26.3 -31.34 16.3
-22.544 16.2 -32.22
12.2 -15.22 22.11
-41.241 25.232 -35.338
-22.22 45.22 23.55
-34.22 50.14 30.98
15.23 -30.11 20.987
-32.5 15.3 -25.22
-38.97 20.11 33.22