【转】K-Means聚类算法原理及实现

k-means 聚类算法原理:

  1、从包含多个数据点的数据集 D 中随机取 k 个点,作为 k 个簇的各自的中心。

  2、分别计算剩下的点到 k 个簇中心的相异度,将这些元素分别划归到相异度最低的簇。两个点之间的相异度大小采用欧氏距离公式衡量,对于两个点 T0(x1,y2)和 T1(x2,y2),T0 和 T1 之间的欧氏距离为:

 

欧氏距离越小,说明相异度越小

  3、根据聚类结果,重新计算 k 个簇各自的中心,计算方法是取簇中所有点各自维度的算术平均数。

  4、将 D 中全部点按照新的中心重新聚类。

  5、重复第 4 步,直到聚类结果不再变化。

  6、将结果输出。

  举例说明, 假设包含 9 个点数据 D 如下(见 simple_k-means.txt), 从 D 中随机取 k 个元素,作为 k 个簇的各自的中心, 假设选 k=2, 即将如下的 9 个点聚类成两个类(cluster)

 


  1.假设选 C0(1 1)和 C1(2 1)前两个点作为两个类的簇心。
  2. 分别计算剩下的点到 k 个簇中心的相异度,将这些元素分别划归到相异度最低的簇。结果为:


  3.根据 2 的聚类结果,重新计算 k 个簇各自的中心,计算方法是取簇中所有元素各自维度的算术平均数。
C0 新的簇心为: 1.0,1.5
C1 新的簇心为: 5.857142857142857, 5.714285714285714
  4.将 D 中全部元素按照新的中心重新聚类。

  5.重复第 4 步,直到聚类结果不再变化。当每个簇心点前后移动的距离小于某个阈值t的时候,就认为聚类已经结束了,不需要再迭代,这里的值选t=0.001,距离计算采用欧氏距离。
C0 的簇心为: 1.6666666666666667, 1.75
C1 的簇心为: 7.971428571428572, 7.942857142857143

 


C0 的簇心为: 1.777777777777778, 1.7916666666666667
C1 的簇心为: 8.394285714285715, 8.388571428571428


C0 的簇心为: 1.7962962962962965, 1.7986111111111114
C1 的簇心为: 8.478857142857143, 8.477714285714285


C0 的簇心为: 1.799382716049383, 1.7997685185185184
C1 的簇心为: 8.495771428571429, 8.495542857142857


C0 的簇心为: 1.7998971193415638, 1.7999614197530864
C1 的簇心为: 8.499154285714287, 8.499108571428572

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <cmath>
 
using namespace std;
 
class Cluster//聚类,每个聚类都包含两个属性,一个是簇心的属性(维数),另一个是距离本簇心最近的样本点
{
public:
    vector <double> centroid;//存放簇心的属性(维数)
    vector <int> samples;//存放属于相同簇心样本的下标
};
 
double CalculateDistance(vector<double> a, vector<double> b)//计算两个向量之间的距离
{
    int len1 = a.size();
    int len2 = b.size();
    if(len1 != len2)
        cerr<<"Dimensions of two vectors must be same!!
";
    double temp = 0;
    for(int i = 0; i  < len1; ++i)
        temp += pow(a[i]-b[i], 2);
    return sqrt(temp);
}
 
//max_iteration表示最大的迭代次数,min_move_distance
vector<Cluster> KMeans(vector<vector<double> >data_set, int k, int max_iteration, double threshold)
{
    int row_number = data_set.size();//数据的个数
    int col_number = data_set[0].size();//每个向量(属性)的维数
 
    //初始随机选取k个质心
    vector<Cluster> cluster(k);//存放k个簇心。vector<T> v(n,i)形式,v包含n 个值为 i 的元素
    srand((int)time(0));
    for(int i = 0; i < k; ++i)
    {
        int c = rand()%row_number;
        cluster[i].centroid = data_set[c];//把第c个作为簇心,并把它相应的属性赋值给centroid
    }
 
    //iteration
    int iter = 0;
    while(iter < max_iteration)
    {
        iter++;
        for(int i = 0; i < k; ++i)
            cluster[i].samples.clear();
        //找出每个样本点所属的质心
        for(int i = 0; i < row_number; ++i)
        {
            double min_distance = INT_MAX;
            int index = 0;
            //计算离样本点i最近的质心
            for(int j = 0; j < k; ++j)
            {
                double temp_distance = CalculateDistance(data_set[i], cluster[j].centroid);
                if(min_distance > temp_distance)
                {
                    min_distance = temp_distance;
                    index = j;
                }
            }
            cluster[index].samples.push_back(i);//把第i个样本点放入,距离其最近的质心的samples
        }
 
        double max_move_distance = INT_MIN;
        //更新簇心
        for(int i = 0; i < k; ++i)
        {
            vector<double> temp_value(col_number, 0.0);
            for(int num = 0; num < cluster[i].samples.size(); ++num)//计算每个样本的属性之和
            {
                int temp_same = cluster[i].samples[num];
                for(int j = 0; j < col_number; ++j)
                    temp_value[j] += data_set[temp_same][j];
            }
            vector<double> temp_centroid = cluster[i].centroid;
            for(int j = 0; j < col_number; ++j)
                cluster[i].centroid[j] = temp_value[j]/cluster[i].samples.size();
            //计算从上一个簇心移动到当前新的簇心的距离
            double temp_distance = CalculateDistance(temp_centroid, cluster[i].centroid);
            if(max_move_distance < temp_distance)
                max_move_distance = temp_distance;
        }
        if(max_move_distance < threshold)
            break;
    }
    return cluster;
}
 
int main()
{
    int threshold = 0.001;//当从上一个簇心移动到当前粗心的距离几乎不变时,可以结束。这里用threshold作为阈值
    vector <vector<double> >data_set(9, vector<double>(2, 0.0));
    int point_number;
    cin>>point_number;
    for(int i = 0; i < point_number; ++i)
    {
        for(int j = 0; j < 2; ++j)
            cin>>data_set[i][j];
    }
 
    int col = data_set[0].size();
    vector<Cluster> cluster_res = KMeans(data_set, 2, 200, threshold);
    for(int i = 0; i < cluster_res.size(); ++i)
    {
        cout<<"Cluster "<<i<<" : "<<endl;
        cout<<"	"<<"Centroid: ";//<<endl;
        cout<<"(";
        for(int j = 0; j < cluster_res[i].centroid.size()-1; ++j)
            cout<< cluster_res[i].centroid[j]<<",";
        cout<<cluster_res[i].centroid[cluster_res[i].centroid.size()-1]<<")"<<endl;
        cout<<"	"<<"Samples: ";
        for(int j = 0; j < cluster_res[i].samples.size(); ++j)
        {
            int c = cluster_res[i].samples[j];
            cout<<"(";
            for(int m = 0; m < col-1; ++m)
                cout<<data_set[c][m]<<",";
            cout<<data_set[c][col-1]<<")  ";
        }
        cout<<endl;
    }
    return 0;
}
 
/**
1 1
2 1
1 2
2 2
3 3
8 8
8 9
9 8
9 9
*/

  运行结果:

转发自:https://blog.csdn.net/hearthougan/article/details/52932452

原文地址:https://www.cnblogs.com/wuyepeng/p/9795518.html