K-means以及K-Means++

K-Means:

聚类算法有很多种(几十种),K-Means是聚类算法中的最常用的一种,算法最大的特点是简单,好理解,运算速度快,但是只能应用于连续型的数据,并且一定要在聚类前需要手工指定要分成几类!

连续性数据:

在统计学中,数据按变量值是否连续可分为连续数据与离散数据两种。
离散数据是指数值职能用自然数或整数单位计算,例如,企业职工人数,设备台数等,只能按计算量单位数计数,这种数据的数值一般用技术方法取得。
反之,在一定区间内可以任意取值的数据叫连续数据,其数值是连续不断的,相邻两个数值可作无限分割,即可取无限个数值。例如,生产零件的规格尺寸,人体测量的身高,体重,胸围等为连续数据,其数值只能用测量或计量的方法取得。

K-Means的算法如下:

1.随机在图中取K(这里K=2)个种子点。
2.然后对图中的所有点求到这K个种子点的距离,假如点Pi离种子点Si最近,那么Pi属于Si点群。
3.接下来,我们要移动种子点到属于他的“点群”的中心。
4.然后重复第2)和第3)步,直到,种子点没有移动

但是K-Means算法有两大缺陷:(都是和初始值有关的)

(1)K是事先给定的,这个K值的选定是非常难以估计的。很多时候,事先并不知道给定的数据集应该分成多少个类别才最合适。(ISODATA算法通过类的自动合并和分裂,得到较为合理的类型数目K)

(2)K-Means算法需要用初始随机种子点来搞,这个随机种子点太重要,不同的随机种子点会有得到完全不同的结果。(K-Means++算法可以用来解决这个问题,其可以有效地选择初始点)

K-Means++算法步骤:

(1)先从我们的数据库随机挑个随机点当“种子点”。
(2)对于每个点,我们都计算其和最近的一个“种子点”的距离D(x)并保存在一个数组里,然后把这些距离加起来得到Sum(D(x))。
(3)然后,再取一个随机值,用权重的方式来取计算下一个“种子点”。这个算法的实现是,先取一个能落在Sum(D(x))中的随机值Random,然后用Random -= D(x),直到其<=0,此时的点就是下一个“种子点”。
(4)重复第(2)和第(3)步直到所有的K个种子点都被选出来。
(5)进行K-Means算法。

这里需要说明的是在计算机中用随机数来代表概率事件。上面的三步就用了这一原理。随机抽取一个0-Sum(D(x))中的数,每次都减去相应的距离D(x),当打破Sum(D(x))>0,此时的点x就是下一个中心点。

K-Means++算法的实现

首先定义一个类,来保存文本文件中的每条数据(KmPoint.h 代码如下)

#ifndef KmPoint_h
#define KmPoint_h

#include <vector>

class KmPoint
{
//    friend std::ostream &operator<<(std::ostream &out, const KmPoint &item);
public:
    KmPoint():id(-1)
    {

    }

    KmPoint(const std::vector<double> &other, int other_id):data(other), id(other_id)
    {}

    const std::vector<double> &getData() const { return data; }

    std::vector<double> &getData() {return data; }

    int getId() { return id; }

private:
    std::vector<double> data;  //用来存储一行中的数据
    int id;    //在文本文件中是第几行数据
};

//std::ostream &operator<<(std::ostream &out, const KmPoint &item)
//{
//    for (int i = 0; i < item.data.size(); i++) {
//        out<<item.data[i]<<"  ";
//    }
//    return out;
//}
#endif /* KmPoint_h */

定义K-Means类,进行聚类(Kmeans.h 代码如下)

#ifndef Kmeans_h
#define Kmeans_h

#include "KmPoint.h"

const int k = 2;   //簇的个数

/*
 这里我们采用的是K-Means++算法,即在初始化中心点时比K-Means算法有改进(自动生成中心点)
 */
class Kmeans
{
public:
    Kmeans(const char *file);

    void improvementKmeans();
    void print(std::vector<KmPoint> points);
    void printClusters();
private:
    std::vector<KmPoint> points;    //存储文本中存储的数据
    std::vector<KmPoint> centers;   //存储K个中心点
    std::vector<KmPoint> clusters[k];     //存储k个分类
    int dim; //每行数据的维度
    int num; //一共有多少数据

private:

    //初始化中心点
    void initCenter();
    double getDistance(const KmPoint &lh, const KmPoint &rh);   //返回两个点之间的距离
    void minDist(KmPoint point, std::vector<KmPoint> centers, double &distance);  //返回该点到各个中心的最小距离
    int clusterIdOfPoint(std::vector<KmPoint> centers, KmPoint point); //返回该point点到哪个簇距离最短
    double getVar();  //得到此次聚类后误差平方和

    KmPoint getNewCenter(int h);   //得到第h簇的中心点
};
#endif /* Kmeans_h */

K-Means类的实现(K-Means.cpp 代码如下)

#include <stdio.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <math.h>
#include "Kmeans.h"

using namespace std;

void Kmeans::print(vector<KmPoint> points)
{
    for (int i = 0; i < points.size(); i++) {
        for (int j = 0; j < dim; j++) {
            cout<<(points[i].getData())[j]<<" ";
        }
        cout<<endl;
    }
}

Kmeans::Kmeans(const char *file)
{
    fstream input(file);
    //1.先查看一共有多少的数据,每个数据多少维度
    int lines = 0;
    char c;
    if (!input) {
        cerr<<"cannot open the file!"<<endl;
        return ;
    }

    int sum = 0;
    while (input.get(c)) {
        if (c == '\n') {
            lines++;
        }
        else if(c != ' '){
            sum++;
        }
    }
    num = lines;
    dim = sum / lines;
    input.close();

    fstream infile(file);
    for (int i = 0; i < num; i++) {
        string str;
        getline(infile, str);
        istringstream istr(str);

        vector<double> data;
        data.resize(dim);

        for (int j = 0; j < dim; j++) {
            istr>>data[j];
        }
        KmPoint point(data, i+1);
        points.push_back(point);
    }

#if 0
    print(points);
    initCenter();
#endif

}



double Kmeans::getDistance(const KmPoint &lh, const KmPoint &rh)
{
    double dst = 0.0;

    vector<double> ldata = lh.getData();
    vector<double> rdata = rh.getData();

//    cout<<ldata.size()<<endl;
//    cout<<rdata.size()<<endl;
    for (int i = 0; i < dim; i++) {
        dst += (ldata[i] - rdata[i]) * (ldata[i] - rdata[i]);
    }

    return sqrt(dst);
}



void Kmeans::minDist(KmPoint point, std::vector<KmPoint> centers, double &distance)
{
    distance = getDistance(point, centers[0]);
    double tempDistance = 0.0;
    for (int i = 1; i < centers.size(); i++) {
        if (tempDistance < distance) {
            distance = tempDistance;
        }
    }
}

void Kmeans::initCenter()
{
    //1.先随机产生第一个中心点
    KmPoint first_center = points[random() % num];
    centers.push_back(first_center);

#if 0
    cout<<"=====中心点:======"<<endl;
    print(centers);
#endif


    //2.产生之后的 k-1 个中心点
    vector<double> d;   //用来存储每个点到中心点的最近距离
    d.resize(num);

    double sum = 0.0;  //用来叠加各个最短距离

    for (int i = 1; i < k; i++) {
        for (int j = 0; j < num; j++) {
            minDist(points[j], centers, d[j]);
            sum += d[j];
        }

        sum = rand() / double(RAND_MAX) * sum;

        for (int j = 0 ; j < num; j++) {
            if ((sum - d[j]) > 0) {
                continue;
            }
            centers.push_back(points[j]);
            break;
        }

    }
#if 0
    cout<<"=====中心点:======"<<endl;
    print(centers);
#endif

}

int Kmeans::clusterIdOfPoint(vector<KmPoint> centers, KmPoint point)
{
    double distance = getDistance(point, centers[0]);

    int templabel = 0;
    double tempDistance = 0.0;

    for (int i = 1; i < k; i++)
    {
        tempDistance = getDistance(point, centers[i]);

        if (tempDistance < distance)
        {
            distance = tempDistance;
            templabel = i;
        }
    }
    return templabel;
}


double Kmeans::getVar()
{
    double var = 0.0;
    for (int i = 0; i < k; i++) {
        vector<KmPoint> oneCluster = clusters[i];
        for (int j = 0; j < oneCluster.size(); j++) {
            var += getDistance(oneCluster[j], centers[i]);
        }
    }

    return var;
}

KmPoint Kmeans::getNewCenter(int h)
{
    KmPoint newcenter;
    vector<double> &data = newcenter.getData();
    data.resize(dim);

    for (int i = 0; i < clusters[h].size(); i++) {
        for (int j = 0; j < dim; j++) {
            data[j] += (clusters[h][i].getData())[j];
        }
    }

    for (int i = 0; i < dim; i++) {
        data[i] /= clusters[h].size();
    }

    return newcenter;
}

void Kmeans::improvementKmeans()
{


    initCenter();


    int label = 0;  //设置一个标签,表示当前的点应该数据哪个簇

    for (int i = 0; i < num; i++)
    {
        label = clusterIdOfPoint(centers ,points[i]);
        clusters[label].push_back(points[i]);
    }


    double oldVar = -1;
    double newVar = getVar();

    cout<<"初始化时的整体误差平方和为:"<<newVar<<endl;


    int t = 0;
    while (fabs(newVar - oldVar) >= 1)   //当新旧函数值相差不到1,即准则函数值不发生明显变化时,算法终止
    {
        cout<<"第 "<<++t<<" 次迭代开始:"<<endl;
        //更新中心点,得到此次迭代的中心点
        for (int i = 0; i < k; i++)
        {
            centers[i] = getNewCenter(i);
        }

//        cout<<"=====中心点====="<<endl;
//        cout<<centers.size()<<endl;

        oldVar = newVar;
        newVar = getVar();

        cout<<newVar<<endl;

        //清空每个簇(非常重要)
        for (int i = 0; i < k; i++) {
            clusters[i].clear();
        }

        //根据新的簇心获得新的簇
        for (int i = 0; i < num; i++) {
            label = clusterIdOfPoint(centers ,points[i]);
            clusters[label].push_back(points[i]);
        }
        cout<<"此次迭代之后的整体误差平方和为:"<<newVar<<endl;
    }
}

void Kmeans::printClusters()
{
    for (int i = 0; i < k; i++) {
        cout<<"第 "<<i+1<<" 个簇"<<endl;
        print(clusters[i]);
    }
}

main.cpp代码:

#include <iostream>
#include "Kmeans.h"

using namespace std;

int main(int argc, const char * argv[]) {

    Kmeans km("/Users/fengjiakai/Desktop/Kmean-data.txt");

    km.improvementKmeans();
    km.printClusters();

    return 0;
}

这是小弟第一次写,主要目的是为了保存一下代码,为了方便自己以后查看,也可以方便大家!(代码是已经运行成功的,前面的理解都是我看别人的博客学会的。)

扫描二维码关注公众号,回复: 2131628 查看本文章

参考:

http://www.csdn.net/article/2012-07-03/2807073-k-means
http://www.jianshu.com/p/fc91fed8c77b
http://blog.csdn.net/qll125596718/article/details/8243404/

猜你喜欢

转载自blog.csdn.net/feng_jiakai/article/details/71124414