k-means聚类算法详解(Python实现)
k-means聚类的基本原理
假定给定数据样本 X,包含了 n 个对象 X={X1, X2, …, Xn},其中每个对象都具有 m 个维度的属性。k-means 算法的目标是将 n 个对象依据对象间的相似性聚集到指定的 k 个类簇中,每个对象属于且仅属于一个其到类簇中心距离最小的类簇中。对于 k-means,首先需要初始化 k 个聚类中心 {C1, C2, …, Ck},1<k≤n,然后通过计算每一个对象到第一个聚类中心的欧几里得距离:

式中:
- Xi 表示第 i 个对象,1≤i≤n;
- Cj 表示第 j 个聚类中心,1≤j≤k;
- Xit 表示第 i 个对象的第 t 个属性,1≤t≤m;
- Cjt 表示第 j 个聚类中心的第 t 个属性。
依次比较每一个对象到每一个聚类中心的距离,将对象分配到距离最近的聚类中心的类簇中,得到 k 个类簇 {S1, S2,…, Sk}。
k-means 算法用中心定义了类簇的原理,类簇中心就是类簇内所有对象在各个维度的均值,其公式为:

式中,Ct 表示第 t 个聚类中心,1≤t≤K,|St| 表示第 t 个类簇中对象的个数,Xi 表示第 t 个类簇中第 i 个对象,1≤i≤|St|。
k-means算法流程
k-means 聚类适合分离的数据。当数据点重叠时,此聚类并不适用。与其他聚类技术相比,k-means 聚类速度更快,它提供了数据点之间的强大耦合。k-means 聚类在许多应用中都很有用:
- 基于客户的使用情况来放置移动式塔楼;
- 在城市的不同地方开设食品店;
- 可根据犯罪率和人口分布将警察分配到不同的地点;
- 可以根据覆盖最大城市范围的方式建立医院;
- 等等。
将聚类质心移到每个簇的数据中心,因此,可以使用训练数据集来生成聚类。当从测试数据中获取实例时,可以将它们分配到相应的簇中。
在某些情况下,K 没有明确定义,所以必须考虑 K 的最佳数量(后续讨论选取 K 值的解决方法)。当解决监督学习问题时,要考虑成本函数,并且会尽量最小化任务的成本。
k-means 是一个反复选代的过程,其算法分为以下 4 个步骤:
- 选取数据空间中的 K 个对象作为初始中心,每个对象代表一个聚类中心;
- 对于样本中的数据对象,根据它们与这些聚类中心的欧氏距离,按距离最近的准则将它们分到距离它们最近的聚类中心(最相似)所对应的类;
- 更新聚类中心,将每个类别中所有对象所对应的均值作为该类别的聚类中心,计算目标函数的值;
- 判断聚类中心和目标函数的值是否发生改变,若不变,则输出结果,若改变,则返回步骤 2。
k-means随机分配聚类质心
下面通过一个例子来实现利用 Python 为随机数据分配聚类质心。【实例】利用 k-means 聚类法为随机数据分配质心。首先我们随机生成 200 个点,就取(0,2000)范围内的,并确定质心个数,这里就取 3 个质心,也是随机生成(可以根据需求改变)如下:
import random import matplotlib.pyplot as plt random_x = [random.randint(0,2000) for _ in range(200)] random_y = [random.randint(0,2000) for _ in range(200)] random_points = [(x, y) for x, y in zip(random_x, random_y)] def generate_random_point(min_,max_): return random.randint(min_,max_), random.randint(min_,max_) k1,k2,k3 = generate_random_point(-100,100), generate_random_point(-100,100), generate_random_point(-100,100) plt.scatter(k1[0],k1[1],color = 'red',s=100) plt.scatter(k2[0],k2[1],color = 'blue',s=100) plt.scatter(k3[0],k3[1],color = 'green',s=100) plt.scatter(random_x,random_y)执行效果如下图所示:

图 3 确定质心个数
接着导入 numpy 来计算各个点与质心的距离,并根据每个点与质心的距离分类,与第一个点近则分配在列表的第一个位置,离第二个近则分配到第二个位置,以此类推,如下代码所示:
import numpy as np def dis(p1, p2): # 这里的 p1, p2 是一个列表 [number1, number2] 的距离计算 return np.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2) random_points = [(x, y) for x, y in zip(random_x, random_y)] # 将 100 个随机点塞进列表 groups = [[], [], []] # 100 个点分成三类 for p in random_points: distances = [dis(p, k) for k in [k1, k2, k3]] # k1, k2, k3 是随机生成的三个点 min_index = np.argmin(distances) # 取距离最近质心的下标 groups[min_index].append(p) groups运行程序,输出如下:
[[(285,1802),
(1320,1267),
(828,1232),
(884,1937),
(247,1345),
(688,206),
(1998,430),
(1146,327),
(1755,571),
(1944,1503),
(1149,511),
(361,1929),
(259,1),
…
(862,1866),
(1011,1427),
(15,230)],
[],
[]]
previous_kernels = [k1,k2,k3] circle_number = 10 for n in range(circle_number): plt.close() kernel_colors = ['red','yellow','green'] new_kernels = [] plt.scatter(previous_kernels[0][0],previous_kernels[0][1],color = kernel_colors[0],s=200) plt.scatter(previous_kernels[1][0],previous_kernels[1][1],color = kernel_colors[1],s=200) plt.scatter(previous_kernels[2][0],previous_kernels[2][1],color = kernel_colors[2],s=200) groups = [[],[],[]] for p in random_points: distances = [dis(p,k) for k in previous_kernels] min_index = np.argmin(distances) groups[min_index].append(p) print('第{}次'.format(n+1)) for i,g in enumerate(groups): g_x = [_x for _x,_y in g] g_y = [_y for _x,_y in g] n_k_x,n_k_y = np.mean(g_x),np.mean(g_y) new_kernels.append([n_k_x,n_k_y]) print('三个点之前的质心和现在的质心距离:{}'.format(dis(previous_kernels[i], [n_k_x,n_k_y]))) plt.scatter(g_x,g_y,color = kernel_colors[i]) plt.scatter(n_k_x,n_k_y,color = kernel_colors[i],alpha = 0.5,s=200) previous_kernels = new_kernels运行程序,输出如下:
第1次
三个点之前的质心和现在的质心距离:344.046783724601
三个点之前的质心和现在的质心距离:178.67567512699137
三个点之前的质心和现在的质心距离:85.51258602308063
第2次
三个点之前的质心和现在的质心距离:223.75162213961798
三个点之前的质心和现在的质心距离:41.23571511332308
三个点之前的质心和现在的质心距离:132.0752155320645
...
第9次
三个点之前的质心和现在的质心距离:0.0
三个点之前的质心和现在的质心距离:0.0
三个点之前的质心和现在的质心距离:0.0
第10次
三个点之前的质心和现在的质心距离:0.0
三个点之前的质心和现在的质心距离:0.0
三个点之前的质心和现在的质心距离:0.0

图 4 聚类效果
这里设置了总共迭代 10 次,可以看到在迭代到第 8 次的时候就找到了最优的质点,如图 4 所示。
k-means算法的优缺点
在 Python 中,k-means 算法的优点主要有:- 在处理大数据集时,该算法是相对可扩展性的,并且具有较高的效率;
- 算法复杂度为 O(nkt)。其中,n 为数据集中对象的数目,k 为期望得到的簇的数目,t 为迭代的次数。
k-means 算法的应用局限性表现在:
- 用户必须事先指定聚类簇的个数;
- 常常终止于局部最优;
- 只适用于数值属性聚类(计算均值有意义);
- 对噪声和异常数据也很敏感;
- 不适合用于发现非凸形状的聚类簇。