在数据挖掘中,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