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