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 item
public:
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;
}
如果要使用更多的信息量參與計算,則需要修改的地方有:距離計算,類中心計算和判斷是否穩定這些地方。