在数据挖掘中,K-Means(K均值)是一种用来计算数据聚集的算法。具体来说,K-Means要解决的问题如下图所示
凭肉眼可以看出,大致可以分为4个点群。但是怎么通过计算机找出这几个点群呢?这就是K-Means要解决的问题。
普通的K-Means算法的步骤如下
(1)随机在图中取K个种子点
(2)对图中的每个点求到这K个点的距离,假设点距离种子点最近,那么属于点群。
(3)接下来,移动种子点到属于它点群的中心
(4)重复(2)(3)步骤,直到种子点没有移动
求点群中心的算法主要有如下三种
(1)Minkowski Distance公式——λ可以随意取值
(2)Euclidean Distance公式——也就是第一个公式λ=2的情况
(3)CityBlock Distance公式——也就是第一个公式λ=1的情况
这三种方法的收敛方式有差别,如下图
以二维点集为例说明:data.txt
15 0 0 1 0 0 1 -1 0 0 -1 10 0 11 0 9 0 10 1 10 -1 -10 0 -11 0 -9 0 -10 1 -10 -1
代码:
#include <iostream> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <math.h> #include <vector> #include <time.h> using namespace std; const int NUM = 3; //定义划分簇的数目 //数据向量表示 struct Vect { double x; double y; }; double getDist(Vect A,Vect B) //计算距离的平方 { return (A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y); } //计算每个簇的中心,平均距离表示 Vect getMeansC(vector<Vect> t) { int num = t.size(); double meansX = 0; double meansY = 0; for(int i=0; i<num; i++) { meansX += t[i].x; meansY += t[i].y; } Vect c; c.x = meansX / num; c.y = meansY / num; return c; } //获取算法的准则函数值,当准则函数收敛时算法停止 double getE(vector<Vect> classes[],Vect means[]) { double sum = 0; for(int i=0; i<NUM; i++) { vector<Vect> v = classes[i]; for(int j=0; j<v.size(); j++) sum += getDist(v[j],means[i]); } return sum; } int searchMinC(Vect t,Vect means[NUM]) { int c = 0; double d = getDist(t,means[0]); for(int i=1; i<NUM; i++) { double tmp = getDist(t,means[i]); if(tmp < d) { c = i; d = tmp; } } return c; } void kMeans(vector<Vect> init) { Vect means[NUM]; vector<Vect> classes[NUM]; double newE,oldE = -1; srand(time(NULL)); for(int i=0; i<NUM; i++) { int c = rand() % init.size(); classes[i].push_back(init[c]); means[i] = getMeansC(classes[i]); //计算当前每个簇的中心点 } newE = getE(classes,means); //计算当前准则函数值 for(int i=0; i<NUM; i++) classes[i].clear(); vector<Vect> ans[NUM]; while(fabs(newE - oldE) >= 1) { for(int i=0; i<init.size(); i++) { int toC = searchMinC(init[i],means); classes[toC].push_back(init[i]); } for(int i=0; i<NUM; i++) ans[i] = classes[i]; for(int i=0; i<NUM; i++) means[i] = getMeansC(classes[i]); oldE = newE; newE = getE(classes,means); for(int i=0; i<NUM; i++) classes[i].clear(); } //输出最终结果 for(int i=0; i<NUM; i++) { cout<<"类"<<i+1<<":"<<endl; for(int j=0; j<ans[i].size(); j++) cout<<ans[i][j].x<<" "<<ans[i][j].y<<endl; cout<<endl; } //每个类的中心点坐标 for(int i=0; i<NUM; i++) cout<<"means["<<i+1<<"]: "<<means[i].x<<" "<<means[i].y<<endl; } int main() { int n; freopen("data.txt","r",stdin); scanf("%d",&n); vector<Vect> init; while(n--) { Vect t; scanf("%lf%lf",&t.x,&t.y); init.push_back(t); } kMeans(init); return 0; }
实际上,K-Means有两个很大的缺陷,这两个缺陷都与K值有关的。
(1)在K-Means算法中,K值是事先固定的,我们无法知道最适合的K(ISODATA算法可以找到合适的K)
(2)K-Means需要用随机种子来做,这个随机种子的选取很重要,不同随机种子可能会得到完全不同的结果
针对缺陷(2)K-Means有一个改进的版本,即K-Means++算法,又叫Lloyd算法。
相对K-Means算法来说,K-Means++算法主要是选择了很合理的种子,然后后面基本跟K-Means没有什么区别。
K-Means++算法的步骤大致如下:
(1)从输入的数据点集合中随机选择一个点作为第一个种子点
(2)计算每个点与最近一个种子点的距离。对于一个点,计算其与所有已有种子点的距离,选出最短的距离
D(X),保存到数组中。计算这个数组中元素的总和Sum(D(x))。
(3)取一个随机值,用权重的方式来计算下一个种子点。先取一个大于等于0、小于等于Sum(D(x))的随机值
Random;将Random依次减去数组元素D(X),若Random<=0,则当前数组元素对应的点即为下一个种点。
(4)重复(2)(3)步骤,直到K个种子点都被选出来。
(5)进行K-Means算法。
代码:
#include <iostream> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <math.h> #include <vector> #include <time.h> using namespace std; const int NUM = 3; //定义划分簇的数目 //数据向量表示 struct Vect { double x; double y; }; double getDist(Vect A,Vect B) //计算距离的平方 { return (A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y); } //计算每个簇的中心,平均距离表示 Vect getMeansC(vector<Vect> t) { int num = t.size(); double meansX = 0; double meansY = 0; for(int i=0; i<num; i++) { meansX += t[i].x; meansY += t[i].y; } Vect c; c.x = meansX / num; c.y = meansY / num; return c; } //获取算法的准则函数值,当准则函数收敛时算法停止 double getE(vector<Vect> classes[],Vect means[]) { double sum = 0; for(int i=0; i<NUM; i++) { vector<Vect> v = classes[i]; for(int j=0; j<v.size(); j++) sum += getDist(v[j],means[i]); } return sum; } int searchMinC(Vect t,Vect means[],int n,double *D) { int c = 0; double d = getDist(t,means[0]); for(int i=1; i<n; i++) { double tmp = getDist(t,means[i]); if(tmp < d) { c = i; d = tmp; } } if(D) *D = d; return c; } //找最优的种子点 void Find(vector<Vect> init,Vect means[]) { srand(time(NULL)); double *D = new double[init.size()]; means[0] = init[rand()%init.size()]; for(int i=1; i<NUM; i++) { double sum = 0; for(int j=0; j<init.size(); j++) { searchMinC(init[j],means,i,D + j); sum += D[j]; } sum = rand() % (int)sum; for(int j=0; j<init.size(); j++) { if((sum -= D[j]) > 0) continue; means[i] = init[j]; break; } } delete[] D; } void kMeans(vector<Vect> init) { Vect means[NUM]; vector<Vect> classes[NUM]; double newE,oldE = -1; Find(init,means); newE = getE(classes,means); //计算当前准则函数值 for(int i=0; i<NUM; i++) classes[i].clear(); vector<Vect> ans[NUM]; while(fabs(newE - oldE) >= 1) { for(int i=0; i<init.size(); i++) { int toC = searchMinC(init[i],means,NUM,NULL); classes[toC].push_back(init[i]); } for(int i=0; i<NUM; i++) ans[i] = classes[i]; for(int i=0; i<NUM; i++) means[i] = getMeansC(classes[i]); oldE = newE; newE = getE(classes,means); for(int i=0; i<NUM; i++) classes[i].clear(); } //输出最终结果 for(int i=0; i<NUM; i++) { cout<<"类"<<i+1<<":"<<endl; for(int j=0; j<ans[i].size(); j++) cout<<ans[i][j].x<<" "<<ans[i][j].y<<endl; cout<<endl; } //每个类的中心点坐标 for(int i=0; i<NUM; i++) cout<<"means["<<i+1<<"]: "<<means[i].x<<" "<<means[i].y<<endl; } int main() { int n; freopen("data.txt","r",stdin); scanf("%d",&n); vector<Vect> init; while(n--) { Vect t; scanf("%lf%lf",&t.x,&t.y); init.push_back(t); } kMeans(init); return 0; }
原文地址:http://blog.csdn.net/acdreamers/article/details/27213347