K-Means 算法的主要缺点:算法性能受类别个数 k 值、初始点值、异常点值影响很大。本文针对以上 k-means 算法主要缺点,详解 k-means 算法优化方案。
一、make_blobs 函数库
make_blobs() 是 sklearn.datasets 中的一个函数,主要功能是:生成聚类数据集。
主要参数:
(1)n_samples:样本数据量,默认值 100;
(2)n_features:样本维度,默认值 2;
(3)centers:聚类中心的个数,可以理解为 label 的种类数,默认值 3;
(4)cluster_std:数据集的标准差,默认值 1.0;
(5)shuffle:洗牌操作,boolean 类型,默认值 True;
(6)random_state:随机数种子,设置不同的种子会产生不同的样本集合。
(参考:sklearn 中的 make_blobs()函数详解)
二、K-Means 算法优化方案
1. 解决 k-means 算法性能受异常点影响较大
(1)数据预处理
k-means 的本质是基于欧氏距离的数据划分算法,均值和方差大的维度将对数据的聚类产生决定性影响。所以未做归一化处理和统一单位的数据是无法直接参与运算和比较的。常见的数据预处理方式有:数据归一化、数据标准化。
(参考:【机器学习】K-means(非常详细))
常见的数据归一化和标准化方法详见:【20210914】【数据分析】使用Python对数据进行标准化(归一化)
(2)异常值检测
离群点或噪声数据会对均值产生较大影响,很容易导致中心偏移,所以要首先进行异常点检测。
(参考:异常点检测算法小结)
(参考:四种检测异常值的常用技术简述)
(参考:有哪些比较好的做异常值检测的方法?)
2. 解决 k-means 算法性能受模型参数 k 影响较大(参数调优)
(1)手肘法
(参考:kmeans最优k值的确定方法-手肘法和轮廓系数法)
手肘法的核心思想:随着聚类数 k 的增大,样本划分会更加精细,每个簇的聚合程度会逐渐提高,那么误差平方和 SSE 自然会逐渐减小。
当 k 小于真实聚类数时,增大 k 值会大幅增加每个簇的聚合程度,所以 SSE 的下降幅度会很大;当 k 到达真实聚类数时,再增加 k 所得到的聚合程度会迅速变小,所以 SSE 的下降幅度会骤减,最终趋于平缓。拐点处就对应了最优的真实聚类数。
'''
功能:使用手肘法,寻找最优的 k-means 算法参数 k
'''
import numpy as np
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
import warnings
warnings.filterwarnings('ignore')
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
#导入数据集
x, y = make_blobs(n_samples=500, n_features=2, centers=4, random_state=1)
# 作图查看数据集分布
plt.figure()
plt.scatter(x[:, 0], x[:, 1], c=y)
plt.show()
# 使用手肘法确认 k-means 算法最优参数
k = np.arange(1, 11)
inertia = []
for i in k:
model = KMeans(n_clusters=i)
model.fit(x)
y_pre = model.predict(x)
centroid = model.cluster_centers_
inertia.append(model.inertia_)
# 绘制当前模型下的聚类中心
plt.figure()
plt.scatter(x[:, 0], x[:, 1], c=y_pre)
plt.scatter(centroid[:, 0], centroid[:, 1], marker='x', s=100, c='black')
plt.show()
# 绘制样本离最近聚类中心距离总和(inertias)
plt.figure()
plt.plot(inertia)
plt.xlabel('k')
plt.ylabel('距离和')
plt.title('不同k值下,样本点到质心的距离之和')
plt.show()
(参考:K-means 聚类算法)
# 使用最优的k参数,训练k-means模型,并预测
model_best = KMeans(n_clusters=3)
model_best.fit(x)
y_pre_best = model_best.predict(x)
# 绘制聚类结果
plt.figure()
plt.scatter(x[:, 0], x[:, 1], c=y_pre_best)
plt.show()
手肘法的核心指标:SSE(sum of squared errors, 误差平方和)。
其中,Ci 表示第 i 个簇;p 是 Ci 中的样本点;mi 是 Ci 的质心(Ci 中所有样本的均值);SSE 是所有样本的聚类误差,代表了聚类效果的好坏。
手肘法的缺点在于不够自动化,参数选择需要人工看,有一些数据集的手肘曲线也不太容易确定参数 k。对此,有优化的参数确定方法 —— 斜率判断法。该方法认为斜率基本不变的点为最优的模型参数:找到第一个与下一个点之间斜率小于一个阈值的点,把这个点作为拐点。(参考:kmeans的手肘法)
(2)轮廓系数(silhouette coefficient)
定义样本 i 的轮廓系数:
其中,ai 为样本 i 的簇内不相似度,物理意义为:样本 i 到同簇其他样本的平均距离;bij 为样本 i 的簇间不相似度,物理意义为:样本 i 到其他某簇 Cj 的所有样本的平均距离。ai 越小,说明样本 i 越应该被聚类到该簇;bi 越大,表示样本 i 越不属于其他簇。
轮廓系数范围为 [-1, 1],该值越大越合理;si 接近 1,说明样本 i 聚类合理;si 接近 -1,说明样本 i 更应该分类到其他的簇;si 近似为 0,表示该样本 i 在两个簇的边界上。
sklearn.metrics.silhouette_score 为 sklearn 中求轮廓系数的 API。
(参考:kmeans最优k值的确定方法-手肘法和轮廓系数法)
# 使用轮廓系数法,确认 k-means 算法最优参数
k = np.arange(2, 11)
silhouette_scores = []
for i in k:
model = KMeans(n_clusters=i)
model.fit(x)
y_pre = model.predict(x)
centroid = model.cluster_centers_
silhouette_scores.append(metrics.silhouette_score(x, y_pre))
# 绘制当前模型下的聚类中心
plt.figure()
plt.scatter(x[:, 0], x[:, 1], c=y_pre)
plt.scatter(centroid[:, 0], centroid[:, 1], marker='x', s=100, c='black')
plt.show()
# 绘制不同 k 值下的轮廓系数曲线
K = list(range(2, 11))
plt.figure()
plt.plot(K, silhouette_scores)
plt.xlabel('k')
plt.ylabel('轮廓系数')
plt.title('不同k值下的轮廓系数')
plt.show()
# 使用最优的k参数,训练k-means模型,并预测
model_best = KMeans(n_clusters=4)
model_best.fit(x)
y_pre_best = model_best.predict(x)
# 绘制聚类结果
plt.figure()
plt.scatter(x[:, 0], x[:, 1], c=y_pre_best)
plt.show()
(参考:聚类性能评估-轮廓系数)
(参考:样本轮廓系数(原理、sklearn.metrics.silhouette_score、silhouette_samples参数介绍))
(3)其他方法
另外还有:mclust包、Nbclust包、Calinsky criterion、Gap Statistic、PAM、Affinity propagation(AP) clustering、clustergram、层次聚类等方法。
(参考:【机器学习】确定最佳聚类数目的10种方法)
3. 解决 k-means 算法性能受初始点选取影响较大(算法调优)
k-means 算法的初始簇中心是随机选取的,因此最终求得的簇的划分与随机选取的簇中心有关,这可能造成多种 k 个簇的划分情况,这是因为 k-means 算法收敛到了局部最小值,而非全局最小值。
(1)二分 k-means 算法
二分 k-means 算法解决 k-means 算法对初始簇心比较敏感的问题,是一种弱化初始质心的一种算法。
从二分 k-means 算法原理出发,相较普通 k-means,二分 k-means 算法不需要一次性初始化所有的簇中心,而是首先初始化一个,经过多次迭代得到其他的簇中心。这样做的好处在于不会收敛到局部最小值。具体思路步骤如下:
第一步:将所有样本作为一个簇,放在一个队列中;
第二步:从队列中选择一个簇进行 k-means (k=2) 算法划分,划分为两个簇,并将划分生成的子簇放在队列中;
第三步:循环迭代第二步,直到达到循环结束条件(结束条件通常有:簇的个数、最小 SSE、迭代次数等);
循环结束之后,队列中的簇就是最终得分类簇集合。
(从队列中选择簇进行二分,一般有两种方式:选择 SSE 最大的簇、选择样本数量最多的簇。)
(参考:05 聚类算法 - 二分K-Means、K-Means++、K-Means||、Canopy、Mini Batch K-Means算法)
(参考:kmeans算法理解及代码实现)
(2)k-means++ 算法
k-means++ 解决 k-means 算法对初始簇心比较敏感的问题,它和 k-means 算法的主要区别在于对初始 k 个簇心的选择方面:k-means 算法随即给定初始簇心,k-means++ 采用一些列步骤给定 k 个初始簇心:
第一步:从样本集中任选一个样本作为第一个聚类中心;
第二步:计算其余每个点 xi 到已有聚类中心点的距离和 D(xi),并以一定概率选择新的聚类中心;(这种方式使得离得越远的样本,越容易被选取为新的聚类中心,满足我们的需求)
第三步:重复第二步,直到找到 k 个聚类中心点;
第四步:使用找到的 k 个聚类中心点,进行 k-means 算法迭代。
(k-means 算法不可并行,第 k 个聚类中心的选择依赖前 k-1 个聚类中心。)
(参考:05 聚类算法 - 二分K-Means、K-Means++、K-Means||、Canopy、Mini Batch K-Means算法)
(参考:【机器学习】K-means(非常详细))
(3)Mini-Batch k-means 算法
Mini-Batch k-means 算法解决 k-means 算法收敛需要一定时间的问题,是 k-means 算法的一种优化方式,它采用小规模的数据子集(每次训练使用的数据集是随机抽取的数据子集),减少了计算时间,同时试图优化目标函数。
Mini-Batch k-means 算法可以减少 k-means 算法的收敛时间,聚类效果略差于标准 k-means 算法。算法步骤如下:
第一步:首先选取部分数据集,使用 k-means 算法构建出 k 个聚簇点的模型;
第二步:继续抽取训练数据集中的部分数据集样本数据,并将其添加到模型中,分配给距离最近的簇中心点;
第三步:更新簇的质心;
循环第二步和第三步,直到达到循环结束条件。
(参考:05 聚类算法 - 二分K-Means、K-Means++、K-Means||、Canopy、Mini Batch K-Means算法)
三、K-Means 算法模型评估
k-means 算法评估分为两种:外部评估和内部评估。外部评估用于真实的分群 label 已知的情况;内部评估用于真实的分群 label 未知的情况。
外部评估有:Homogeneity,completeness,v-measure,Fowlkes-Mallows scores 等;
内部评估有:轮廓系数、DBI 戴维森堡丁指数、Calinski Harabasz Index 等。
(参考:这是一份全面的聚类算法及其改进的算法总结)
(参考:常用聚类(K-means,DBSCAN)以及聚类的度量指标)
四、知识点
1. 使用轮廓系数评估 k-means 算法性能时,k 要从 2 开始选,选择 1 会报错:ValueError: Number of labels is 1. Valid values are 2 to n_samples - 1 (inclusive)
(参考:聚类学习-轮廓系数)