1. 简介:
K-MEANS算法是输入聚类个数k,以及包含 n个数据对象的数据库,输出满足方差最小标准k个聚类的一种算法。k-means 算法接受输入量 k ;然后将n个数据对象划分为 k个聚类以便使得所获得的聚类满足:同一聚类中的对象相似度较高;而不同聚类中的对象相似度较小。
聚类相似度是利用各聚类中对象的均值所获得一个“中心对象”(引力中心)来进行计算的。
2. 算法流程
1) 随机选取数据集中的k个数据点作为初始的聚类中心:
2) 分别计算每个数据点到每个中心的距离,选取距离最短的中心点作为其聚类中心:
3) 利用目前得到的聚类重新计算中心点:
4) 重复步骤2和3直到收敛(达到最大迭代次数或聚类中心不再移动)
3. 常见问题
1) 初始质心的选择
选择适当的初始质心是基本kmeans算法的关键步骤。常见的方法是随机的选取初始质心,但是这样簇的质量常常很差。处理选取初始质心问题的一种常用技术是:多次运行,每次使用一组不同的随机初始质心,然后选取具有最小SSE(误差的平方和)的簇集。这种策略简单,但是效果可能不好,这取决于数据集和寻找的簇的个数。
第二种有效的方法是,取一个样本,并使用层次聚类技术对它聚类。从层次聚类中提取K个簇,并用这些簇的质心作为初始质心。该方法通常很有效,但仅对下列情况有效:(1)样本相对较小,例如数百到数千(层次聚类开销较大);(2)K相对于样本大小较小
第三种选择初始质心的方法,随机地选择第一个点,或取所有点的质心作为第一个点。然后,对于每个后继初始质心,选择离已经选取过的初始质心最远的点。使用这种方法,确保了选择的初始质心不仅是随机的,而且是散开的。但是,这种方法可能选中离群点。此外,求离当前初始质心集最远的点开销也非常大。为了克服这个问题,通常该方法用于点样本。由于离群点很少(多了就不是离群点了),它们多半不会在随机样本中出现。计算量也大幅减少。
2) 距离的度量
常用的距离度量方法包括:欧几里得距离和余弦相似度。两者都是评定个体间差异的大小的。欧几里得距离度量会受指标不同单位刻度的影响,所以一般需要先进行标准化,同时距离越大,个体间差异越大;空间向量余弦夹角的相似度度量不会受指标刻度的影响,余弦值落于区间[-1,1],值越大,差异越小。
本次实验采用欧几里得距离度量。
3) 算法停止条件
计算各个聚类中心的中心向量值Zj(k+1),j=1.2.3…k,如果Zj(k+1)=Zj(k)则可以停止。
算法实现
1. 实验数据--data.txt:
实验数据为处理后的IRIS数据集。数据集包含150个数据集,分为3类,每类50个数据,每个数据包含4个属性。可通过花萼长度,花萼宽度,花瓣长度,花瓣宽度4个属性预测鸢尾花卉属于(Setosa,Versicolour,Virginica)三个种类中的哪一类。
l 文件内容:
K=3,D=4,N=150
5.1 3.5 1.4 0.2
4.9 3 1.4 0.2
4.7 3.2 1.3 0.2
4.6 3.1 1.5 0.2
5 3.6 1.4 0.2
5.4 3.9 1.7 0.4
4.6 3.4 1.4 0.3
5 3.4 1.5 0.2
…
l 数据说明:
第一行表示分为三类,4个特征,150个数据后面依次为数据信息。
2. 输出数据—result.txt
输出数据输出实验数据聚类后的分类信息,与真实信息对比来分析程序聚类准确率。
l 文件内容:
0
2
0
1
0
…
l 数据说明:
0,1,2依次对应表示Setosa,Versicolour,Virginica三类花。
3. 程序运行截图:
程序每次计算输出新的聚类中心点,中心向量值(即各点与聚类中心的距离和平均值),以及分类信息,当中心向量值不变,聚类完成
源代码
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
int K,N,D; //聚类的数目,数据量,数据的维数
float **data; //存放数据
int *in_cluster; //标记每个点属于哪个聚类
float **cluster_center; //存放每个聚类的中心点
float **array(int m,int n);
void freearray(float **p);
float **loadData(int *k,int *d,int *n);
float getDistance(float avector[],float bvector[],int n);
void cluster();
float getDifference();
void getCenter(int in_cluster[]);
int main()
{
int i,j,count=0;
float temp1,temp2;
data=loadData(&K,&D,&N);
printf("Data sets:\n");
for(i=0;i<N;i++)
for(j=0;j<D;j++){
printf("%-8.2f",data[i][j]);
if((j+1)%D==0) putchar('\n');
}
printf("-----------------------------\n");
srand((unsigned int)(time(NULL))); //随机初始化k个中心点
for(i=0;i<K;i++)
for(j=0;j<D;j++)
cluster_center[i][j]=data[(int)((double)N*rand()/(RAND_MAX+1.0))][j];
cluster(); //用随机k个中心点进行聚类
temp1=getDifference(); //第一次中心点和所属数据点的距离之和
count++;
printf("The difference between data and center is: %.2f\n\n", temp1);
getCenter(in_cluster);
cluster(); //用新的k个中心点进行第二次聚类
temp2=getDifference();
count++;
printf("The difference between data and center is: %.2f\n\n",temp2);
while(fabs(temp2-temp1)!=0){ //比较前后两次迭代,若不相等继续迭代
temp1=temp2;
getCenter(in_cluster);
cluster();
temp2=getDifference();
count++;
printf("The %dth difference between data and center is: %.2f\n\n",count,temp2);
}
printf("\nThe total number of cluster is: %d\n",count); //统计迭代次数
//system("pause"); //gcc编译需删除
return 0;
}
//动态创建二维数组
float **array(int m,int n)
{
int i;
float **p;
p=(float **)malloc(m*sizeof(float *));
p[0]=(float *)malloc(m*n*sizeof(float));
for(i=1;i<m;i++) p[i]=p[i-1]+n;
return p;
}
//释放二维数组所占用的内存
void freearray(float **p)
{
free(*p);
free(p);
}
//从data.txt导入数据,要求首行格式:K=聚类数目,D=数据维度,N=数据量
float **loadData(int *k,int *d,int *n)
{
int i,j;
float **arraydata;
FILE *fp;
if((fp=fopen("data.txt","r"))==NULL) fprintf(stderr,"cannot open data.txt!\n");
if(fscanf(fp,"K=%d,D=%d,N=%d\n",k,d,n)!=3) fprintf(stderr,"load error!\n");
arraydata=array(*n,*d); //生成数据数组
cluster_center=array(*k,*d); //聚类的中心点
in_cluster=(int *)malloc(*n * sizeof(int)); //每个数据点所属聚类的标志数组
for(i=0;i<*n;i++)
for(j=0;j<*d;j++)
fscanf(fp,"%f",&arraydata[i][j]); //读取数据点
return arraydata;
}
//计算欧几里得距离
float getDistance(float avector[],float bvector[],int n)
{
int i;
float sum=0.0;
for(i=0;i<n;i++)
sum+=pow(avector[i]-bvector[i],2);
return sqrt(sum);
}
//把N个数据点聚类,标出每个点属于哪个聚类
void cluster()
{
int i,j;
float min;
float **distance=array(N,K); //存放每个数据点到每个中心点的距离
//float distance[N][K]; //也可使用C99变长数组
FILE *fp2;
if((fp2=fopen("result.txt","w"))==NULL) {
printf("File cannot be opened/n");
exit(0);
}
for(i=0;i<N;++i){
min=9999.0;
for(j=0;j<K;++j){
distance[i][j] = getDistance(data[i],cluster_center[j],D);
//printf("%f\n", distance[i][j]);
if(distance[i][j]<min){
min=distance[i][j];
in_cluster[i]=j;
}
}
printf("data[%d] 属于类-%d\n",i,in_cluster[i]);
fprintf(fp2,"%d \n",in_cluster[i]);//写入文件
}
printf("-----------------------------\n");
free(distance);
}
//计算所有聚类的中心点与其数据点的距离之和
float getDifference()
{
int i,j;
float sum=0.0;
for(i=0;i<K;++i){
for(j=0;j<N;++j){
if(i==in_cluster[j])
sum+=getDistance(data[j],cluster_center[i],D);
}
}
return sum;
}
//计算每个聚类的中心点
void getCenter(int in_cluster[])
{
float **sum=array(K,D); //存放每个聚类中心点
//float sum[K][D]; //也可使用C99变长数组
int i,j,q,count;
for(i=0;i<K;i++)
for(j=0;j<D;j++)
sum[i][j]=0.0;
for(i=0;i<K;i++){
count=0; //统计属于某个聚类内的所有数据点
for(j=0;j<N;j++){
if(i==in_cluster[j]){
for(q=0;q<D;q++)
sum[i][q]+=data[j][q]; //计算所属聚类的所有数据点的相应维数之和
count++;
}
}
for(q=0;q<D;q++)
cluster_center[i][q]=sum[i][q]/count;
}
printf("The new center of cluster is:\n");
for(i = 0; i < K; i++)
for(q=0;q<D;q++){
printf("%-8.2f",cluster_center[i][q]);
if((q+1)%D==0) putchar('\n');
}
free(sum);
}