前言
在机器学习的各类算法中,分为两类:监督学习算法以及无监督学习算法,一个月前写的ID3决策树算法就是典型的监督学习算法。两者的区别就在于给定的样本是否已经明确具有类别。
今天,在这篇文章里,要给自己备忘一下聚类算法里面,简单但是却应用广泛的算法:k均值聚类算法。
K-means
首先,我们需要来明确一下分类与聚类两者的概念。
分类
在我看来,所谓分类,是根据给定类别的特征,把样本分到最为符合的类别里面,如垃圾分类,就是典型的分类,根据每种垃圾的特征,分为可回收、不可回收、厨余垃圾等等。这一概念里面隐含着一个特定前提,那就是类别已经事先给定了,从机器学习的角度来说,即是给出的训练样本里面包含的明确的类别。也就是说,倘若要让计算机学习分类垃圾,那么训练样本要包括每种垃圾的特征以及该垃圾具体属于哪一类。
聚类
而聚类,则是给定的样本没有事先确定类别,根据自己需要,确定类别数量,再把样本归到不同的类别里面。也就是说,同样是垃圾分类的例子,你给一堆垃圾,我可以根据可回收、不可回收分为聚类为两堆;也可以根据可回收、不可回收、厨余垃圾聚类为三堆。而其中聚类为同一堆的条件,我们可以理解为垃圾间的相似程度。
可以看到,从机器学习的角度来讲,分类是明显的监督学习的例子,聚类则是无监督学习的例子。
实现
那给我们一堆数据,如何实现聚类呢? 我们刚刚说到,判断是否聚为同一类的条件是样本间的相似性,那么,我们肯定需要该类别的标准。就拿代码来讲。
代码实现步骤如下:
初始化一定数量的二维坐标点(x,y),点数量可自定义,所有坐标点的初始类别都为0
然后根据自定义的类别数,比如说我需要把数据聚类成三类,则从上述坐标点中,随机取三个点。作为类别的中心点
迭代所有坐标点,分别与三个中心点计算平方距离(就是Δx^2+Δy^2),这一平方距离可以认为是与标准类别样本的差异度,取平方距离最小的类别作为该坐标点的类别
根据步骤三,可以得到每种分类的样本,然后,再从每种分类的样本中,重新计算该分类的类别中心。具体来说,比较简单的一种做法就是该类别所有样本的x与y,分别求和,然后除与该类别的样本数
迭代步骤3、4,直到每个类别的中心点不变或变化很小为止。
通常,把聚类中的类别叫做簇。
另外平方距离也只是计算差异度的一种比较方便的方法。
重新计算类别中心,亦即簇中心的方法也有很多,文中也仅仅是提及一种实现比较方便的方式
更详细的计算差异度的方法可以参考这篇文章:
更详细的计算类别中心的方法可以参考这篇文章:
代码的具体结果以用图像表示就是这样:
可以看到坐标点被聚类成四类
下面是具体的代码:
#include
#include
#include
#include
#include
#define max 100000000;
typedef struct
{
double x;
double y;
int centroid;//坐标点的类别
} Position;
//随机生成二维数据点
//len:坐标点个数
//range:坐标点取值范围
Position *randomPosition(int len, int range)
{
srand((unsigned)time(NULL));
Position *allPos = malloc(len * sizeof(Position));
int tempX;
int tempY;
for (int i = 0; i < len; ++i)
{
allPos[i].x = (double)rand() / 2147483647 * range;
allPos[i].y = (double)rand() / 2147483647 * range;
allPos[i].centroid = 0;
}
return allPos;
}
//centroidNum:类别(簇)数量
//len:坐标点数量
void kMeans(Position *allPos, int centroidNum, int len)
{
//随机生成簇中心点
Position *centroidPos = malloc(centroidNum * sizeof(Position));
for (int i = 0; i < centroidNum; ++i)
{
centroidPos[i] = allPos[rand() % len];
}
for (int t = 0; t < 500; ++t)
{
double sum = 0;
//对每个数据点进行分类
for (int i = 0; i < len; ++i)
{
int dis = max;
int newDis;
for (int x = 0; x < centroidNum; ++x)
{
//遍历所有簇中心取最近的簇
newDis = pow((allPos[i].x - centroidPos[x].x), 2) + pow((allPos[i].y - centroidPos[x].y), 2);
if (newDis < dis)
{
dis = newDis;
allPos[i].centroid = x;
}
}
}
//重新计算簇中心
//每个簇的计数器
int *clusterS_num = malloc(centroidNum * sizeof(int));
double *clusterS_sumX = malloc(centroidNum * sizeof(double));
double *clusterS_sumY = malloc(centroidNum * sizeof(double));
for (int i = 0; i < centroidNum; ++i)
{
//初始化计数器
clusterS_num[i] = clusterS_sumX[i] = clusterS_sumY[i] = 0;
}
for (int i = 0; i < len; i++)
{
clusterS_num[allPos[i].centroid]++;
clusterS_sumX[allPos[i].centroid] += allPos[i].x;
clusterS_sumY[allPos[i].centroid] += allPos[i].y;
}
//重新计算簇中心
for (int i = 0; i < centroidNum; ++i)
{
centroidPos[i].x = clusterS_sumX[i] / clusterS_num[i];
centroidPos[i].y = clusterS_sumY[i] / clusterS_num[i];
}
for (int i = 0; i < len; ++i)
{
sum = sum + pow((allPos[i].x - centroidPos[allPos[i].centroid].x), 2) + pow((allPos[i].y - centroidPos[allPos[i].centroid].y), 2);
}
free(clusterS_num);
free(clusterS_sumX);
free(clusterS_sumY);
}
}
int main()
{
Position *allPos = randomPosition(500, 1000);
kMeans(allPos, 4, 500);
}