k-means聚类也称k均值算法,自顶而下的聚类算法,根据元素对类的所属是否为而至,又分为硬聚类和软聚类。
在普通的k均值算法中,元素和类用相同的向量形式表达。类是质心,即类中所有元素的均值,初始时可将元素随意(或根据启发性信息)分为k个组,计算出各个组的之心,然后按照以下的算法进行类间元素的调整:
硬k均值聚类算法
1 初始化k个组的质心向量
2 while 还可以继续改进 do
3 for 每个元素(文档)d do
4 找出质心向量与d最相似的组c
5 将d调整到组c之中
6 end for
7 for 每个组c do
8 重新计算质心向量
9 end for
10 end while
软k均值聚类算法
相对于硬k均值聚类,删去3-6步,在第8步计算质心向量时,采用下式计算质心偏移量,注意:每个元素对每个组的偏移量都有贡献, 但贡献的大小不同,离组的质心越近,贡献越大。
相关链接:K-means聚类算法原理和C++实现
学习笔记:
先附上上一段链接中的代码:
#include "stdafx.h"
#include<iostream>
#include<cmath>
#include<vector>
#include<ctime>
using namespace std;
typedef unsigned int uint;
struct Cluster
{
vector<double> centroid;
vector<uint> samples;
};
double cal_distance(vector<double> a, vector<double> b)
{
uint da = a.size();
uint db = b.size();
if (da != db) cerr << "Dimensions of two vectors must be same!!\n";
double val = 0.0;
for (uint i = 0; i < da; i++)
{
val += pow((a[i] - b[i]), 2);
}
return pow(val, 0.5);
}
vector<Cluster> k_means(vector<vector<double> > trainX, uint k, uint maxepoches)
{
const uint row_num = trainX.size();
const uint col_num = trainX[0].size();
/*初始化聚类中心*/
vector<Cluster> clusters(k);
uint seed = (uint)time(NULL);
for (uint i = 0; i < k; i++)
{
srand(seed);
int c = rand() % row_num;
clusters[i].centroid = trainX[c];
seed = rand();
}
/*多次迭代直至收敛,本次试验迭代100次*/
for (uint it = 0; it < maxepoches; it++)
{
/*每一次重新计算样本点所属类别之前,清空原来样本点信息*/
for (uint i = 0; i < k; i++)
{
clusters[i].samples.clear();
}
/*求出每个样本点距应该属于哪一个聚类*/
for (uint j = 0; j < row_num; j++)
{
/*都初始化属于第0个聚类*/
uint c = 0;
double min_distance = cal_distance(trainX[j], clusters[c].centroid);
for (uint i = 1; i < k; i++)
{
double distance = cal_distance(trainX[j], clusters[i].centroid);
if (distance < min_distance)
{
min_distance = distance;
c = i;
}
}
clusters[c].samples.push_back(j);
}
/*更新聚类中心*/
for (uint i = 0; i < k; i++)
{
vector<double> val(col_num, 0.0);
for (uint j = 0; j < clusters[i].samples.size(); j++)
{
uint sample = clusters[i].samples[j];
for (uint d = 0; d < col_num; d++)
{
val[d] += trainX[sample][d];
if (j == clusters[i].samples.size() - 1)
clusters[i].centroid[d] = val[d] / clusters[i].samples.size();
}
}
}
}
return clusters;
}
int main()
{
vector<vector<double> > trainX(9, vector<double>(1, 0));
//对9个数据{1 2 3 11 12 13 21 22 23}聚类
double data = 1.0;
for (uint i = 0; i < 9; i++)
{
trainX[i][0] = data;
if ((i + 1) % 3 == 0) data += 8;
else data++;
}
/*k-means聚类*/
vector<Cluster> clusters_out = k_means(trainX, 3, 100);
/*输出分类结果*/
for (uint i = 0; i < clusters_out.size(); i++)
{
cout << "Cluster " << i << " :" << endl;
/*子类中心*/
cout << "\t" << "Centroid: " << "\n\t\t[ ";
for (uint j = 0; j < clusters_out[i].centroid.size(); j++)
{
cout << clusters_out[i].centroid[j] << " ";
}
cout << "]" << endl;
/*子类样本点*/
cout << "\t" << "Samples:\n";
for (uint k = 0; k < clusters_out[i].samples.size(); k++)
{
uint c = clusters_out[i].samples[k];
cout << "\t\t[ ";
for (uint m = 0; m < trainX[0].size(); m++)
{
cout << trainX[c][m] << " ";
}
cout << "]\n";
}
}
system("pause");
return 0;
}
运行结果:
代码理解:
定义结构体Cluster,含有double类型向量centroid和uint类型向量samples
对数据 vector<vector > trainX(9, vector(1, 0))赋初值
调用函数k_means进行k-means聚类
输出分类结果
详细理解函数:
vector<Cluster> k_means(vector<vector<double> > trainX, uint k, uint maxepoches)
//返回类型为分类向量,输入参数分别为数据、设定分类数与迭代次数
{
计算训练数据矩阵的行(row_num)与列(col_num)(示例中为9*1);
初始化聚类中心:随机分入k类中;
迭代(这里最好优化成收敛,示例的迭代次数为100)
{
每一次重新计算样本点所属类别之前,清空原来样本点信息;
对每一个样本点(row_num),计算它与各类间距,判断其应属于哪一聚类;
对每一类更新聚类中心
{
对此类中的每一个样本(clusters[i].samples.size)
{
对样本的每一列(col_num)
{
迭代计算此类的样本总值;
若到达此类的最后一个样本(j == clusters[i].samples.size() - 1)
计算均值;
}
}
}
}
}
缺陷:
由于失真函数是非凸函数,有可能陷入局部收敛,可以多运行几次k-means(采用不同的随机初始聚类中心),然后从多次结果中选出失真函数最小的聚类结果。参照上述链接。