KMeans演算法是很典型的基於距離的聚類演算法,採用距離作為相似性的評價指標,即認為兩個對象的距離越近,其相似性就越大。該演算法認為簇是由距離靠近的對象組成的,因此把得到緊湊且獨立的簇作為最終目標。
k-means 演算法基本步驟
(1) 從 n個資料對象任意選擇
k 個對象作為初始聚類中心;
(2) 根據每個聚類對象的均值(中心對象),計算每個對象與這些中心對象的距離;並根據最小距離重新對相應對象進行劃分;
(3) 重新計算每個(有變化)聚類的均值(中心對象);
(4) 計算標準測度函數,當滿足一定條件,如函數收斂時,則演算法終止;如果條件不滿足則回到步驟(2)。
以下是C++實現:
標頭檔kmeans.h:
#ifndef KMEANS#define KMEASN/*********************************************\ * KMeans 聚類演算法 * 1.資料格式為文字檔,每行一條資料 * 2.開頭為資料名,string類型。後面緊接著是屬性資訊, 此處為double,每個屬性之間用tab或空格隔開。 * 3.每個資料在計算時僅使用了第一列的屬性資訊 * 4.距離使用的是絕對值距離 * 5.如果使用更多的屬性,則需要修改相應的函數 * 6.測試中顯示了每次的計算結果,不需要的話可以在 KMeans::cluster()中刪除printResult()即可。 * Author: yuyang * Date: 2013-03-30/ ********************************************/#include <vector>#include <iostream>using namespace std;///需要聚類的資料類型class Item{///data itempublic: string name; vector<double> vovalue;};bool operator==(const Item &ia, const Item &ib);bool operator!=(const Item &ia, const Item &ib);class KMeans{public: KMeans(int km=2); int setData(string name);///讀入資料 void cluster();///聚類函數 void printData();///輸出未經處理資料 void printResult();///輸出聚類結果 virtual ~KMeans();protected: int ptcDistence(const Item & item);///某個點到類中心的距離 ///返回到最近的類的類編號 int stable();///判斷是否收斂private: void classify();///將每個資料放到最近的類中 void init();///初始化,選取前k個資料作為k類 void calCen();///計算當前每個類的中心 int k; vector<Item> datas;///儲存的未經處理資料 vector<double> ocen;///本輪聚類前類中心 vector<double> cen;///本輪聚類後的聚類中心,size=k vector<int> *kclass;///kclass中有k個指標,分別指向k個vector<int>, ///每個vector<int>為該類在vector<Item>中的編號 static const int READ_ERR=1; static const int OK=0; static const double AC_ERR = 1E-3;};#endif // KMEANS
實現檔案kmeans.cpp:
#include "kmeans.h"#include <fstream>#include <sstream>#include <cmath>#include <algorithm>///判斷2個Item是否相等bool operator==(const Item &ia, const Item &ib){ if(abs(ia.vovalue[0] - ib.vovalue[0])<1E-4) return true; return false;}bool operator!=(const Item &ia, const Item &ib){ return !(ia==ib);}KMeans::KMeans(int km): k(km),ocen(km),cen(km){ kclass = new vector<int> [k];}KMeans::~KMeans(){ delete[] kclass; cout<<"KMeans bye."<<endl;}int KMeans::setData(string name){ fstream inf; inf.open(name.c_str(),ios::in); if(!inf) { cerr<<"open file "<<name<<" error !"<<endl; return KMeans::READ_ERR; } string line; while(getline(inf,line)) { istringstream is(line); string iname,ivalue; Item it; is>>it.name;///get class name double d; while(is>>d)///get value { it.vovalue.push_back(d); } datas.push_back(it); } inf.close(); return KMeans::OK;}void KMeans::printData(){ vector<Item>::iterator it = datas.begin(); for(; it!=datas.end(); ++it) { Item i = *it; cout<<i.name<<"\t"; vector<double>::iterator id=i.vovalue.begin(); for(; id!=i.vovalue.end(); ++id) { cout<<*id<<"\t"; } cout<<"\n"; } cout<<endl;}int KMeans::stable()///判斷是否收斂{ for(int i=0; i<k; ++i) if(abs(cen[i]-ocen[i])>AC_ERR)//cen[i]!=ocen[i] return false; return true;}void KMeans::printResult(){///output cluster result for(int i=0; i<k; ++i) { int siz = (kclass+i)->size(); double center=cen[i]; cout<<"class : "<< i <<" ,size= " <<siz <<" , class center: "<<center <<endl; for(int j=0; j<siz; ++j) { int index = (kclass+i)->at(j); cout<<datas[index].name<<"\t"; } cout<<endl<<endl; }}///返回到最近的類的類編號int KMeans::ptcDistence(const Item & item){///某個點到類中心的距離 double d=10E9 ;//= new double[k]; int c=0; for(int i=0; i<k; ++i) {///item 到第i個類中心的距離 double poi = item.vovalue[0]; double poc = cen[i]; if(d>abs(poi-poc)) { d = abs(poi-poc);///絕對值距離 c = i; } } return c;}void KMeans::classify()///將每個資料放到最近的類中{ ///清空上次分類資訊 for(int i=0; i<k; ++i) { (kclass+i)->clear(); } unsigned int iiter = 0;//datas.begin(); for(; iiter<datas.size(); ++iiter) { int c = ptcDistence(datas[iiter]); ///將第i個資料分到第c個類中 (kclass+c)->push_back(iiter); }}void KMeans::init(){ ///選取前k個互不相等的資料作為初始的k個類 for(int i=0,cnt=0; cnt<k && i<datas.size(); ++i) { vector<Item>::iterator it= find(datas.begin(),datas.begin()+i,datas.at(i)); if(it!=datas.begin()+i)///find one continue; (kclass+cnt)->push_back(i); cen[cnt] = datas.at(i).vovalue[0];///類中心 ++cnt; }}void KMeans::calCen(){///計算當前每個類的中心 for(int i=0; i<k; ++i) {///class i double sum=0; int classnum = (kclass+i)->size(); for(int j=0; j<classnum; ++j) { int index = (*(kclass+i))[j]; sum += datas[index].vovalue[0]; } ocen[i] = cen[i]; ///save old value cen[i] = sum/classnum; }}/*** 核心聚類函數 **************************\ * 1.初始化 * 2.判斷類中心是否穩定 * 3.如果已經穩定,則結束演算法 * 4.如果不穩定,調用分類函數classify() * 5.轉2\********************************************/void KMeans::cluster(){ init(); int round=0; cout<<"init:"<<endl; printResult(); while(!stable()) { cout<<"round = "<<++round<<endl; classify(); calCen(); printResult(); }}
測試案例:
#include <iostream>#include "kmeans.h"using namespace std;int main(){ string file="a.txt"; KMeans km(2); km.setData(file); km.printData(); km.cluster(); cout<<"\nfinished..."<<endl; //km.printResult(); return 0;}
如果要使用更多的資訊量參與計算,則需要修改的地方有:距離計算,類中心計算和判斷是否穩定這些地方。