Kmeans算法介绍及其实现


KMeans算法的基本思想是初始随机给定K个簇中心,按照最邻近原则把待分类样本点分到各个簇。然后按平均法重新计算各个簇的质心,从而确定新的簇心。一直迭代,直到簇心的移动距离小于某个给定的值。
 

K-Means聚类算法主要分为三个步骤:
(1)第一步是为待聚类的点寻找聚类中心
(2)第二步是计算每个点到聚类中心的距离,将每个点聚类到离该点最近的聚类中去
(3)第三步是计算每个聚类中所有点的坐标平均值,并将这个平均值作为新的聚类中心
反复执行(2)、(3),直到聚类中心不再进行大范围移动或者聚类次数达到要求为止

 

下图展示了对n个样本点进行K-means聚类的效果,这里k取2:
(a)未聚类的初始点集
(b)随机选取两个点作为聚类中心
(c)计算每个点到聚类中心的距离,并聚类到离该点最近的聚类中去
(d)计算每个聚类中所有点的坐标平均值,并将这个平均值作为新的聚类中心
(e)重复(c),计算每个点到聚类中心的距离,并聚类到离该点最近的聚类中去
(f)重复(d),计算每个聚类中所有点的坐标平均值,并将这个平均值作为新的聚类中心

%随机获取150个点
X = [randn(50,2)+ones(50,2);randn(50,2)-ones(50,2);randn(50,2)+[ones(50,1),-ones(50,1)]];
 
opts = statset('Display','final');
 
%调用Kmeans函数
%X N*P的数据矩阵
%Idx N*1的向量,存储的是每个点的聚类标号
%Ctrs K*P的矩阵,存储的是K个聚类质心位置
%SumD 1*K的和向量,存储的是类间所有点与该类质心点距离之和
%D N*K的矩阵,存储的是每个点与所有质心的距离;
 
[Idx,Ctrs,SumD,D] = kmeans(X,3,'Replicates',3,'Options',opts);
 
%画出聚类为1的点。X(Idx==1,1),为第一类的样本的第一个坐标;X(Idx==1,2)为第二类的样本的第二个坐标
plot(X(Idx==1,1),X(Idx==1,2),'r.','MarkerSize',14)
hold on
plot(X(Idx==2,1),X(Idx==2,2),'b.','MarkerSize',14)
hold on
plot(X(Idx==3,1),X(Idx==3,2),'g.','MarkerSize',14)
 
%绘出聚类中心点,kx表示是圆形
plot(Ctrs(:,1),Ctrs(:,2),'kx','MarkerSize',14,'LineWidth',4)
plot(Ctrs(:,1),Ctrs(:,2),'kx','MarkerSize',14,'LineWidth',4)
plot(Ctrs(:,1),Ctrs(:,2),'kx','MarkerSize',14,'LineWidth',4)
 
legend('Cluster 1','Cluster 2','Cluster 3','Centroids','Location','NW')


#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
#include <math.h>
#include <stdlib.h>
#define k 3//簇的数目
using namespace std;
//存放元组的属性信息
typedef vector<double> Tuple;//存储每条数据记录

int dataNum;//数据集中数据记录数目
int dimNum;//每条记录的维数

//计算两个元组间的欧几里距离
double getDistXY(const Tuple& t1, const Tuple& t2) 
{
	double sum = 0;
	for(int i=1; i<=dimNum; ++i)
	{
		sum += (t1[i]-t2[i]) * (t1[i]-t2[i]);
	}
	return sqrt(sum);
}

//根据质心,决定当前元组属于哪个簇
int clusterOfTuple(Tuple means[],const Tuple& tuple){
	double dist=getDistXY(means[0],tuple);
	double 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;	
}
//获得给定簇集的平方误差
double getVar(vector<Tuple> clusters[],Tuple means[]){
	double 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(const vector<Tuple>& cluster){
	
	int num = cluster.size();
	Tuple t(dimNum+1, 0);
	for (int i = 0; i < num; i++)
	{
		for(int j=1; j<=dimNum; ++j)
		{
			t[j] += cluster[i][j];
		}
	}
	for(int j=1; j<=dimNum; ++j)
		t[j] /= num;
	return t;
	//cout<<"sum:"<<sum<<endl;
}

void print(const vector<Tuple> clusters[])
{
	for(int lable=0; lable<k; lable++)
	{
		cout<<"第"<<lable+1<<"个簇:"<<endl;
		vector<Tuple> t = clusters[lable];
		for(int i=0; i<t.size(); i++)
		{
			cout<<i+1<<".(";
			for(int j=0; j<=dimNum; ++j)
			{
				cout<<t[i][j]<<", ";
			}
			cout<<")\n";
		}
	}
}

void KMeans(vector<Tuple>& tuples){
	vector<Tuple> clusters[k];//k个簇
	Tuple means[k];//k个中心点
	int i=0;
	//一开始随机选取k条记录的值作为k个簇的质心(均值)
	srand((unsigned int)time(NULL));
	for(i=0;i<k;){
		int iToSelect = rand()%tuples.size();
		if(means[iToSelect].size() == 0)
		{
			for(int j=0; j<=dimNum; ++j)
			{
				means[i].push_back(tuples[iToSelect][j]);
			}
			++i;
		}
	}
	int lable=0;
	//根据默认的质心给簇赋值
	for(i=0;i!=tuples.size();++i){
		lable=clusterOfTuple(means,tuples[i]);
		clusters[lable].push_back(tuples[i]);
	}
	double oldVar=-1;
	double newVar=getVar(clusters,means);
	cout<<"初始的的整体误差平方和为:"<<newVar<<endl; 
	int t = 0;
	while(abs(newVar - oldVar) >= 1) //当新旧函数值相差不到1即准则函数值不发生明显变化时,算法终止
	{
		cout<<"第 "<<++t<<" 次迭代开始:"<<endl;
		for (i = 0; i < k; i++) //更新每个簇的中心点
		{
			means[i] = getMeans(clusters[i]);
		}
		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]);
		}
		cout<<"此次迭代之后的整体误差平方和为:"<<newVar<<endl; 
	}

	cout<<"The result is:\n";
	print(clusters);
}
int main(){

	char fname[256];
	cout<<"请输入存放数据的文件名: ";
	cin>>fname;
	cout<<endl<<" 请依次输入: 维数 样本数目"<<endl;
	cout<<endl<<" 维数dimNum: ";
	cin>>dimNum;
	cout<<endl<<" 样本数目dataNum: ";
	cin>>dataNum;
	ifstream infile(fname);
	if(!infile){
		cout<<"不能打开输入的文件"<<fname<<endl;
		return 0;
	}
	vector<Tuple> tuples;
	//从文件流中读入数据
	for(int i=0; i<dataNum && !infile.eof(); ++i)
	{
		string str;
		getline(infile, str);
		istringstream istr(str);
		Tuple tuple(dimNum+1, 0);//第一个位置存放记录编号,第2到dimNum+1个位置存放实际元素
		tuple[0] = i+1;
		for(int j=1; j<=dimNum; ++j)
		{
			istr>>tuple[j];
		}
		tuples.push_back(tuple);
	}

	cout<<endl<<"开始聚类"<<endl;
	KMeans(tuples);
	return 0;
}



你可能感兴趣的:(Kmeans算法介绍及其实现)