http://blog.csdn.net/cai0538/article/details/7061922
这是一篇关于k均值算法的小文章,程序使用的是上一个人的,不过,为程序稍微多做了一些注解,方便理解。
同时也参考了
http://www.cnblogs.com/CBDoctor/archive/2011/10/24/2222358.html
K-means算法是最简单的一种聚类算法。算法的目的是使各个样本与所在类均值的误差平方和达到最小(这也是评价K-means算法最后聚类效果的评价标准)
K-means聚类算法的一般步骤:
- 初始化。输入基因表达矩阵作为对象集X,输入指定聚类类数N,并在X中随机选取N个对象作为初始聚类中心。设定迭代中止条件,比如最大循环次数或者聚类中心收敛误差容限。
- 进行迭代。根据相似度准则将数据对象分配到最接近的聚类中心,从而形成一类。初始化隶属度矩阵。
- 更新聚类中心。然后以每一类的平均向量作为新的聚类中心,重新分配数据对象。
- 反复执行第二步和第三步直至满足中止条件。 #include <iostream> #include <fstream> #include <vector> #include <math.h> #define k 3 using namespace std; //存放元组的属性信息 struct Tuple{ float attr1; float attr2; }; //计算两个元组间的欧几里距离 float getDistXY(Tuple t1, Tuple t2) { return sqrt((t1.attr1 - t2.attr1) * (t1.attr1 - t2.attr1) + (t1.attr2 - t2.attr2) * (t1.attr2 - t2.attr2)); } //根据质心,决定当前元组属于哪个簇, 其中means[]数组保存到额是每个簇的质心 //这个函数大概是让每一个元素与所有的质心通过距离比较,计算它应该属于哪个簇。返回簇的索引 int clusterOfTuple(Tuple means[],Tuple tuple){ float dist=getDistXY(means[0],tuple); float tmp; int label=0;//标示属于哪一个簇 //只有k个簇,所以对于每一个元素只需要判断它距离哪一个簇的质心最近就好了。 for(int i=1;i<k;i++){ tmp=getDistXY(means[i],tuple); if(tmp<dist) {dist=tmp;label=i;} } return label; } //获得给定簇集的平方误差 float getVar(vector<Tuple> clusters[], Tuple means[]){ float var = 0; for (int i = 0; i < k; i++) { //计算第k个簇中的平方误差之和 vector<Tuple> t = clusters[i]; //获得第i簇的所有元素, means[i]表示该簇的质心 for (int j = 0; j< t.size(); j++) { var += getDistXY(t[j], means[i]); } } //cout<<"sum:"<<sum<<endl; return var; } //获得当前簇的均值(质心) Tuple getMeans(vector<Tuple> cluster){ int num = cluster.size(); double meansX = 0, meansY = 0; Tuple t; for (int i = 0; i < num; i++) { meansX += cluster[i].attr1; meansY += cluster[i].attr2; } t.attr1 = meansX / num; t.attr2 = meansY / num; return t; //cout<<"sum:"<<sum<<endl; } void KMeans(vector<Tuple> tuples){ //tuples 包含所有的数据集,而我们要把它分成k簇,没一簇包含几个数据,所以在这里定义了的是大小为k的vector,大小会自增长, //这k个簇分别对应的质心为means[k]中的元素。 vector<Tuple> clusters[k]; //k个元组,表示k个簇的质心 Tuple means[k]; int i=0; //默认一开始将前K个元组的值作为k个簇的质心(均值) for(i=0;i<k;i++){ means[i].attr1=tuples[i].attr1; means[i].attr2=tuples[i].attr2; } int lable=0; //根据默认的质心给簇赋值。遍历,分别求出每个元素所属于的簇,并压入相应的簇的数组 for(i=0;i!=tuples.size();++i){ lable=clusterOfTuple(means,tuples[i]); clusters[lable].push_back(tuples[i]); } //输出刚开始的簇 for(lable=0;lable<k;lable++){ cout<<"第"<<lable+1<<"个簇:"<<endl; vector<Tuple> t = clusters[lable]; for (i = 0; i< t.size(); i++) { cout<<"("<<t[i].attr1<<","<<t[i].attr2<<")"<<" "; } cout<<endl; } float oldVar=-1; //该函数求出对于所有簇,假如说k=3,也就是3个簇平方误差之和。 float newVar=getVar(clusters,means); //第一次newVal和oldVal都是在while循环外获得的,但是第二次就是在循环内部获得的。 //从循环内跳出到执行while判决,则说明上次的簇的质心和簇都已经建立完毕 //如果判断为假,说明还要继续进入循环,修改簇的质心和簇。 while(abs(newVar - oldVar) >= 1) //当新旧函数值相差不到1即准则函数值不发生明显变化时,算法终止 { //在更新每个簇的中心以后,需要清空每个簇。这是为了在新的中心确定以后,继续以此迭代往相应的簇中添加数据。 for (i = 0; i < k; i++) //更新每个簇的中心点 { means[i] = getMeans(clusters[i]); //cout<<"means["<<i<<"]:"<<means[i].attr1<<" "<<means[i].attr2<<endl; } oldVar = newVar; newVar = getVar(clusters,means); //计算新的准则函数值 for (i = 0; i < k; i++) //清空每个簇 { clusters[i].clear(); } //根据新的质心获得新的簇 for(i=0;i!=tuples.size();++i){ lable=clusterOfTuple(means,tuples[i]); clusters[lable].push_back(tuples[i]); } //输出当前的簇 for(lable=0;lable<k;lable++){ cout<<"第"<<lable+1<<"个簇:"<<endl; vector<Tuple> t = clusters[lable]; for (i = 0; i< t.size(); i++) { cout<<"("<<t[i].attr1<<","<<t[i].attr2<<")"<<" "; } cout<<endl; } } } int main(void){ char fname[256]; cout<<"请输入存放数据的文件名: "; cin>>fname; cout<<endl; ifstream infile; infile.open(fname,ios::in); if(!infile){ cout<<"不能打开输入的文件"<<fname<<endl; return 0; } int count=0; vector<Tuple> tuples; Tuple tuple; //从文件流中读入数据 while(!infile.eof()){ count++; if(count%2==1) infile>>tuple.attr1; else { infile>>tuple.attr2; tuples.push_back(tuple); } } //int k; //cout<<"请输入期望的簇的个数:" //cin>>k; //cout<<endl; //输出文件中的元组信息 for(vector<Tuple>::size_type ix=0;ix!=tuples.size();++ix) cout<<"("<<tuples[ix].attr1<<","<<tuples[ix].attr2<<")"<<" "; cout<<endl; KMeans(tuples); cout<<"请观看结果"; int b; cin>>b; if (0 == b) { return 0; } return 0; }
同时也参考了
http://www.cnblogs.com/leoo2sk/archive/2010/09/20/k-means.html
其中上文的例子生动有趣,顺便也调侃了一下中国足球,当然这又是后话了。
所谓聚类问题,就是给定一个元素集合D,其中每个元素具有n个可观察属性,使用某种算法将D划分成k个子集,要求每个子集内部的元素之间相异度尽可能低,而不同子集的元素相异度尽可能高。其中每个子集叫做一个簇。
k均值算法的计算过程非常直观:1、从D中随机取k个元素,作为k个簇的各自的中心。
2、分别计算剩下的元素到k个簇中心的相异度,将这些元素分别划归到相异度最低的簇。
3、根据聚类结果,重新计算k个簇各自的中心,计算方法是取簇中所有元素各自维度的算术平均数。
4、将D中全部元素按照新的中心重新聚类。
5、重复第4步,直到聚类结果不再变化。
6、将结果输出。
希望能够帮助读者快速的理解k-均值算法