k-means(k均值聚類)演算法介紹及實現(c++)
阿新 • • 發佈:2018-12-30
基本介紹:
k-means 演算法接受輸入量 k ;然後將n個數據物件劃分為 k個聚類以便使得所獲得的聚類滿足:同一聚類中的物件相似度較高;而不同聚類中的物件相似度較小。聚類相似度是利用各聚類中物件的均值所獲得一個“中心物件”(引力中心)來進行計算的。
工作過程:
(1) 從 n個數據物件任意選擇 k 個物件作為初始聚類中心;
/*kmeans演算法實現(此處只考慮元組只有兩個屬性的情況) *@File:k_means.cpp *@Author:Cai0538 *@Create:2011-12-10 *@Last Modified:2011-12-10 */ #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)); } //根據質心,決定當前元組屬於哪個簇 int clusterOfTuple(Tuple means[],Tuple tuple){ float dist=getDistXY(means[0],tuple); float tmp; int label=0;//標示屬於哪一個簇 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++) { vector<Tuple> t = clusters[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){ vector<Tuple> clusters[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<3;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; float newVar=getVar(clusters,means); 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<3;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(){ 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); return 0; }
大家要執行該程式,要滿足如下條件:
輸入:
執行程式時,會提示輸出要讀入資料所在的檔案,比如在程式所在的當前目錄下的檔案data.txt。
data.txt中的資料格式可參考下面:
1 1
2 1
1 2
2 2
4 3
5 3
4 4
5 4
將如此格式的資料貼上在data.txt中即可。
輸出檔案後,回車即可看到相應的輸出結果。