聚类算法-k-means算法
聚类介绍
什么是聚类
统计数据分析的一门技术,在许多领域受到广泛应用,包括机器学习,数据挖掘,模式识别,图像分析以及生物信息。聚类是把相似的对象通过静态分类的方法分成不同的组别或者更多的子集(subset),这样让在同一个子集中的成员对象都有相似的一些属性,常见的包括在坐标系中更加短的空间距离等。
聚类的应用
在商务上,聚类能帮助市场分析人员从客户基本库中发现不同的客户群,并且用购买模式来刻画不同的客户群的特征。在生物学上,聚类能用于推导植物和动物的分类,对基因进行分类,获得对种群中固有结构的认识。聚类在地球观测数据库中相似地区的确定,汽车保险单持有者的分组,及根据房子的类型、价值和地理位置对一个城市中房屋的分组上也可以发挥作用。聚类也能用于对Web上的文档进行分类,以发现信息。诸如此类,聚类有着广泛的实际应用。
k-means算法思想
先随机选取K个对象作为初始的聚类中心。然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。一旦全部对象都被分配了,每个聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是以下任何一个:
①没有(或最小数目)对象被重新分配给不同的聚类。
②没有(或最小数目)聚类中心再发生变化。
③误差平方和局部最小。
得到相互分离的球状聚类,在这些聚类中,均值点趋向收敛于聚类中心。 一般会希望得到的聚类大小大致相当,这样把每个观测都分配到离它最近的聚类中心(即均值点)就是比较正确的分配方案。
k-means工作流程
创建 k 个点作为起始质心(通常是随机选择)
当任意一个点的簇分配结果发生改变时(不改变时算法结束)
对数据集中的每个数据点
对每个质心
计算质心与数据点之间的距离
将数据点分配到距其最近的簇
对每一个簇, 计算簇中所有点的均值并将均值作为质心
k-means优缺点
优点:
①属于无监督学习,无须准备训练集
②原理简单,实现起来较为容易
③结果可解释性较好
缺点:
①聚类数目k是一个输入参数。选择不恰当的k值可能会导致糟糕的聚类结果。这也是为什么要进行特②征检查来决定数据集的聚类数目了。
③可能收敛到局部最小值, 在大规模数据集上收敛较慢
④对于异常点、离群点敏感
代码实现(C语言)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#define N 11
#define K 3
typedef struct
{
float x;
float y;
}Point;
int center[N]; /// 判断每个点属于哪个簇
Point point[N] = {
{2.0, 10.0},
{2.0, 5.0},
{8.0, 4.0},
{5.0, 8.0},
{7.0, 5.0},
{6.0, 4.0},
{1.0, 2.0},
{4.0, 9.0},
{7.0, 3.0},
{1.0, 3.0},
{3.0, 9.0}
};
Point mean[K]; /// 保存每个簇的中心点
float getDistance(Point point1, Point point2)
{
float d;
d = sqrt((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y));
return d;
}
/// 计算每个簇的中心点
void getMean(int center[N])
{
Point tep;
int i, j, count = 0;
for(i = 0; i < K; ++i)
{
count = 0;
tep.x = 0.0; /// 每算出一个簇的中心点值后清0
tep.y = 0.0;
for(j = 0; j < N; ++j)
{
if(i == center[j])
{
count++;
tep.x += point[j].x;
tep.y += point[j].y;
}
}
tep.x /= count;
tep.y /= count;
mean[i] = tep;
}
for(i = 0; i < K; ++i)
{
printf("The new center point of %d is : \t( %f, %f )\n", i+1, mean[i].x, mean[i].y);
}
}
/// 计算平方误差函数
float getE()
{
int i, j;
float cnt = 0.0, sum = 0.0;
for(i = 0; i < K; ++i)
{
for(j = 0; j < N; ++j)
{
if(i == center[j])
{
cnt = (point[j].x - mean[i].x) * (point[j].x - mean[i].x) + (point[j].y - mean[i].y) * (point[j].y - mean[i].y);
sum += cnt;
}
}
}
return sum;
}
/// 把N个点聚类
void cluster()
{
int i, j, q;
float min;
float distance[N][K];
for(i = 0; i < N; ++i)
{
min = 999999.0;
for(j = 0; j < K; ++j)
{
distance[i][j] = getDistance(point[i], mean[j]);
/// printf("%f\n", distance[i][j]); /// 可以用来测试对于每个点与3个中心点之间的距离
}
for(q = 0; q < K; ++q)
{
if(distance[i][q] < min)
{
min = distance[i][q];
center[i] = q;
}
}
printf("( %.0f, %.0f )\t in cluster-%d\n", point[i].x, point[i].y, center[i] + 1);
}
printf("-----------------------------\n");
}
int main()
{
int i, j, n = 0;
float temp1;
float temp2, t;
printf("----------Data sets----------\n");
for(i = 0; i < N; ++i)
{
printf("\t( %.0f, %.0f )\n", point[i].x, point[i].y);
}
printf("-----------------------------\n");
/*
可以选择当前时间为随机数
srand((unsigned int)time(NULL));
for(i = 0; i < K; ++i)
{
j = rand() % K;
mean[i].x = point[j].x;
mean[i].y = point[j].y;
}
*/
mean[0].x = point[0].x; /// 初始化k个中心点
mean[0].y = point[0].y;
mean[1].x = point[3].x;
mean[1].y = point[3].y;
mean[2].x = point[6].x;
mean[2].y = point[6].y;
cluster(); /// 第一次根据预设的k个点进行聚类
temp1 = getE(); /// 第一次平方误差
n++; /// n计算形成最终的簇用了多少次
printf("The E1 is: %f\n\n", temp1);
getMean(center);
cluster();
temp2 = getE(); /// 根据簇形成新的中心点,并计算出平方误差
n++;
printf("The E2 is: %f\n\n", temp2);
while(fabs(temp2 - temp1) != 0) /// 比较两次平方误差 判断是否相等,不相等继续迭代
{
temp1 = temp2;
getMean(center);
cluster();
temp2 = getE();
n++;
printf("The E%d is: %f\n", n, temp2);
}
printf("The total number of cluster is: %d\n\n", n); /// 统计出迭代次数
system("pause");
return 0;
}
参考资料:
https://blog.youkuaiyun.com/yanxiaopan/article/details/53712105
https://blog.youkuaiyun.com/triumph92/article/details/41128049
总结
K-means算法运行速度快,实现简便,容易理解,直到两次迭代误差的差值在一定阈值范围内就迭代停止得出结果。但K-means算法对具有变化大小,变化密度,非圆形状等特点的数据具有局限性。解决方法是增加K的大小,增加簇数量,使得数据的特征能够更加明显。