K均值(K-Means)算法是无监督的聚类方法,实现起来比较简单,聚类效果也比较好,因此应用很广泛。K-Means算法针对不同应用场景,有不同方面的改进。我们从最传统的K-Means算法讲起,然后在此基础上介绍初始化质心优化K-Means++算法,距离计算优化Elkan K-Means算法和大样本情况下Mini Batch K-Means算法。
K-Means算法的思想很简单,对于给定的样本集,按照样本之间的距离大小,将样本集划分为K个簇。让簇内的点尽可能紧密的连在一起,而让簇间的距离尽量的大,下面我们引入K-Means目标函数。
假设样本集输入变量为(x1,x2,x3,…,xm),样本集划分为K个簇(C1,C2,C3,…,Ck),则我们的目标是最小化平方误差E。
E = ∑ i = 1 k ∑ x ∈ C i ∣ ∣ x − μ i ∣ ∣ 2 E=\sum _{i=1}^{k} \sum _{x \in C_i}||x-\mu _i||^2 E=i=1∑kx∈Ci∑∣∣x−μi∣∣2
其中μi是簇Ci的均值向量,也可称作质心,表达式为
μ i = 1 ∣ C i ∣ ∑ x ∈ C i x \mu _i=\frac{1}{|C_i|}\sum _{x \in C_i}x μi=∣Ci∣1x∈Ci∑x
如果直接求解上述最小值的话,那么为NP Hard问题,因此K-Means算法采用启发式的迭代方法。下面我们通过一个简单聚类来介绍K-Means算法迭代过程。
假设输入样本集 D = x 1 , x 2 , … , x m D={x_1,x_2,…,x_m} D=x1,x2,…,xm,聚类簇数为K,最大迭代次数为N。输出的簇划分为 C = C 1 , C 2 , … , C m C={C_1,C_2,…,C_m} C=C1,C2,…,Cm。
从数据集D中随机选择K个样本作为初始的质心向量$ \mu={ \mu_1,\mu_2,\mu_3,…,\mu_k }$。
迭代 n = 1 , 2 , … , N n=1,2,…,N n=1,2,…,N。
d i j = ∣ ∣ x i − μ j ∣ ∣ 2 d_{ij}=||x_i-\mu_j||^2 dij=∣∣xi−μj∣∣2
μ j = 1 ∣ C j ∣ ∑ x ∈ C j x \mu_j=\frac{1}{|C_j|}\sum_{x\in C_j}x μj=∣Cj∣1x∈Cj∑x
输出K个划分簇 C C C, C = { C 1 , C 2 , C 3 , … , C k } C=\{C_1,C_2,C_3,…,C_k \} C={C1,C2,C3,…,Ck}。
对于K-Means算法,首先要注意K值的选择和K个初始化质心的选择。
如果是完全随机的选择, 算法的收敛可能很慢。我们在此介绍K-Means++算法,针对随机初始化质心进行优化,具体算法流程如下所示。
D ( x ) = arg min r = 1 k s e l e c t e d ∣ ∣ x i − μ r ∣ ∣ 2 D(x)=\arg \min_{r=1}^{k_{selected}}||x_i-\mu_r||^2 D(x)=argr=1minkselected∣∣xi−μr∣∣2
传统K-Means算法中,我们每次迭代时都要计算所有样本点到所有质心之间的距离,那么有没有什么方法来减少计算次数呢? Elkan K-Means算法提出利用两边之和大于第三边、两边之差小于第三边的三角形特性来减少距离的计算。
Elkan K-Means迭代速度比传统K-Means算法迭代速度有较大提高,但如果我们的样本特征是稀疏的,或者有缺失值的话,此种方法便不再使用。
传统的K-Means算法中需要计算所有样本点到所有质心的距离,计算复杂度较高。如果样本量非常大的情况下,比如数据量达到10万,特征在100以上,此时用传统K-Means算法非常耗时。故此针对大样本情况下采用Mini Batch K-Means算法。
Mini Batch K-Means采用无放回随机采样的方法从样本集中选取部分数据,然后用选取的数据进行传统的K-Means算法训练。然后进行迭代并更新质心,直到质心稳定或达到指定的迭代次数。
Mini Batch K-Means可以避免样本量太大带来的计算问题,算法收敛速度也能够加快,当然带来的代价就是我们的聚类精确度降低。为增加算法的准确性,我们可以多训练几次Mini Batch K-Means算法,用不同的随机采样集来得到聚类簇,选择其中最优的聚类簇。
我们经常需要通过改变参数来让模型达到聚类结果,具体参数设置可参考sklearn官方教程。
from sklearn.cluster import KMeans
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
#load iris
iris=load_iris()
X=iris.data[:,:2]
print(X.shape)
#150,2
#plot data
plt.figure()
plt.scatter(X[:,0],X[:,1],c='blue',
marker='o',label='point')
plt.legend(loc=2)
plt.show()
# fit data
kmeans=KMeans(n_clusters=3)
kmeans.fit(X)
label_pred=kmeans.labels_
#plot answer
plt.figure()
x0 = X[label_pred == 0]
x1 = X[label_pred == 1]
x2 = X[label_pred == 2]
plt.scatter(x0[:, 0], x0[:, 1], c = "red",
marker='o', label='label0')
plt.scatter(x1[:, 0], x1[:, 1], c = "green",
marker='*', label='label1')
plt.scatter(x2[:, 0], x2[:, 1], c = "blue",
marker='+', label='label2')
plt.legend(loc=2)
plt.show()
聚类效果较优。
原理简单,实现容易,收敛速度快。
需要调整的参数较少,通常只需要调整簇数K。
更多内容请关注公众号谓之小一,若有疑问可在公众号后台提问,随时回答,欢迎关注,内容转载请注明出处。
参考