码迷,mamicode.com
首页 > 其他好文 > 详细

K-Means聚类算法

时间:2014-06-05 07:39:11      阅读:733      评论:0      收藏:0      [点我收藏+]

标签:c   style   class   blog   code   a   

在数据挖掘中,K-Means(K均值)是一种用来计算数据聚集的算法。具体来说,K-Means要解决的问题如下图所示

 

bubuko.com,布布扣

 

凭肉眼可以看出,大致可以分为4个点群。但是怎么通过计算机找出这几个点群呢?这就是K-Means要解决的问题。

 

普通的K-Means算法的步骤如下

 

  (1)随机在图中取K个种子点

  (2)对图中的每个点求到这K个点的距离,假设点bubuko.com,布布扣距离种子点bubuko.com,布布扣最近,那么bubuko.com,布布扣属于bubuko.com,布布扣点群。

  (3)接下来,移动种子点到属于它点群的中心

  (4)重复(2)(3)步骤,直到种子点没有移动

 

求点群中心的算法主要有如下三种

 

  (1)Minkowski Distance公式——λ可以随意取值

 

      bubuko.com,布布扣

 

  (2)Euclidean Distance公式——也就是第一个公式λ=2的情况

 

      bubuko.com,布布扣

 

  (3)CityBlock Distance公式——也就是第一个公式λ=1的情况

 

      bubuko.com,布布扣

 

这三种方法的收敛方式有差别,如下图

 

    bubuko.com,布布扣

 

以二维点集为例说明: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;
}


 

 

K-Means聚类算法,布布扣,bubuko.com

K-Means聚类算法

标签:c   style   class   blog   code   a   

原文地址:http://blog.csdn.net/acdreamers/article/details/27213347

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!