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;
- }
如果要使用更多的信息量参与计算,则需要修改的地方有:距离计算,类中心计算和判断是否稳定这些地方。