TL;DR
- 场景:用 sklearn 做 KMeans 聚类,想解释中心点/损失,并用指标选 K。
- 结论:inertia_ 只能“越小越好但不可比”,选 K 更应看 -silhouette_score 的峰值;代码里要修正 idxmin/变量名混用。
- 产出:一套可复用的“属性解读 + 选 K 曲线 + 版本差异 + 报错排查”模板。
sklearn实现 K-Means
cluster.cluster_centers_
centroid = cluster.cluster_centers_
centroid
centroid.shape
运行结果如下图所示:
cluster.inertia_
查看总距离的平方和:
inertia = cluster.inertia_
inertia
运行结果如下图所示:
如果我们把簇的数量换成 4,Inertia 会怎么样?
n_clusters = 4
cluster_ = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
inertia_ = cluster_.inertia_
inertia_
运行结果如下图所示:
如果换成 5:
n_clusters = 5
cluster_ = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
inertia_ = cluster_.inertia_
inertia_
执行结果如下图所示:
如果换成 6:
n_clusters = 6
cluster_ = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
inertia_ = cluster_.inertia_
inertia_
执行结果如下图所示:
聚类算法模型评估:轮廓系数
不同于分类模型和回归,聚类算法的模型评估不是一件简单的事,在分类中,有直接结果(标签)的输出,并且分类结果有正负之分,所以我们使用预测的准确度,混淆矩阵,ROC 曲线等等指标来进行评估,但无论如何评估,都是在”模型找到正确答案“的能力。而回归中,由于要拟合数据,我们有 SSE 均方误差,有损失函数来衡量模型的拟合程度,但这些衡量指标都不能够使用于聚类。
如何衡量聚类算法的效果
聚类模型的结果不是某种标签输出,并且聚类的结果是不确定的,其优劣由业务需求或算法需求来决定,并且永远没有正确答案。那我们如何衡量聚类的效果呢? 记得我们说过,KMeans 的目标是确保“簇内差异小,簇外差异大”,我们就可以通过衡量簇内差异来衡量聚类的效果。我们刚才说过,Inertia 是用距离来衡量簇内差异的指标,因此,我们是否可以使用 Inertia 来作为聚类的衡量指标呢?Inertia 越小模型越好。 可以,但是这个指标的缺点和极限太大了:
- 它不是有界的,我们只知道,Inertia 是越小越好,是 0 最好,但我们不知道,一个较小的Inertia究竟有没有达到模型的极限,是否能够继续提高。
- 它的计算太容易受到特征数目的影响,数据维度很大的时候,Inertia 的计算量会陷入维度诅咒之中,计算量会爆炸,不适合用来一次次评估模型。
- 它会收到超参数 K 的影响,在我们之前的尝试中其实我们已经发现,随着 K 的越大,Intertia 会越来越小,但是这不能代表我们的模型越来越好。
- Inertia 作为评估指标,会让聚类算法在一些细长簇,环形簇,或者不规则的表现不佳。
那我们可以使用什么指标呢?聚类是没有标签的,即不知道真实答案的预测算法,我们必须完全依赖评价簇内稠密程度(簇内差异小)和簇间的离散程度(簇外差异大)来评估聚类的效果。其中轮廓系数是最常用的聚类算法的评价指标。它是对每个样本来定义的,它能够同时衡量:
- 样本与其自身所在的簇中的其他样本的相似度 a,等于样本与同一簇所有其他点之间的平均距离
- 样本与其他簇中的样本相似度 b ,等于样本与下一个最近的簇中的所有点之间的平均距离
根据聚类的要求:“簇内差异小,簇外差异大”,我们希望 b 永远大于 a,并且大的越多越好,单个样本的轮廓系数为:
这个公式可以被解析为:
很容易理解的轮廓系数范围是:(-1,1):
- 轮廓系数越接近 1:样本与自己所在簇中的样本很相似,并且与其他簇中的样本不相似
- 轮廓系数为 0 时,两个簇中的样本相似度一致,两个簇本应该是一个簇。
- 轮廓为负时:样本点与簇外的样本更相似。
如果一个簇中的大多数样本具有比较高的轮廓系数,则簇会有较高的总轮廓系数,则整个数据集的平均轮廓系数较高,则聚类时合适的。 如果许多样本点具有低轮廓系数甚至负值,则聚类是不合适的,聚类的超参数 K 可能设定的太大或者太小。
在 sklearn 中,我们使用模块 metrics 中的类 sihouette_score 来计算轮廓系数,它返回的是一个数据集中,所有样本的轮库系数的均值,但我们还有同在 metrics 模块中的 sihouette_sample,它的参数与轮廓系数一致,但返回的数据集中每个样本自己的轮廓系数:
from sklearn.metrics import silhouette_score
from sklearn.metrics import silhouette_samples
X
y_pred
执行结果如下图所示:
观察一下不同的 K ,轮廓系数发生了什么变化?
#观察一下不同的K下,轮廓系数发生什么变化?
cluster = KMeans(n_clusters=3, random_state=0).fit(X)
silhouette_score(X,cluster_.labels_) #计算所有样本的轮廓系数均值。
silhouette_samples(X,cluster.labels_) #计算每个样本的轮廓系数。
运行之后生成的结果如下:
我们继续编写代码:
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
# 定义存储评分的列表
score = []
# 进行 KMeans 聚类并计算 silhouette_score
for i in range(2, 100):
cluster = KMeans(n_clusters=i, random_state=0).fit(X)
score.append(silhouette_score(X, cluster.labels_))
# 绘制 silhouette_score 的变化曲线
plt.plot(range(2, 100), score)
# 找到 silhouette_score 最小值对应的索引,并绘制一条垂直线
plt.axvline(pd.DataFrame(score).idxmin()[0] + 2, ls=':')
# 添加标题和坐标轴标签
plt.title('Silhouette Score vs Number of Clusters')
plt.xlabel('Number of Clusters')
plt.ylabel('Silhouette Score')
# 显示图形
plt.show()
轮廓系数(Silhouette Coefficient)是一种常用的聚类效果评估指标,具有以下显著优点:
-
取值范围明确:
- 数值范围固定在[-1,1]之间,使得评估结果具有可比性
- 值越接近1表示聚类效果越好
- 值为0表示样本点在簇边界上
- 负值表示可能被分错了簇
-
适用性广泛:
- 不要求数据服从特定分布,适用于各种形态的数据集
- 对球形簇、凸形簇等常见聚类形态都能有效评估
- 特别适合评估K-means等基于距离的聚类算法效果
-
直观解释性强:
- 同时考虑了簇内紧密度和簇间分离度
- 通过计算样本与同簇其他点的平均距离(a)和与最近其他簇点的平均距离(b)得出
- 计算公式为:s = (b - a)/max(a,b)
然而,轮廓系数也存在一些局限性:
-
对特定聚类类型的评估偏差:
- 对于DBSCAN等基于密度的聚类算法,可能给出虚高的评分
- 在评估不规则形状的簇时表现不佳
- 当数据中存在噪声点时,评估结果可能失真
-
最佳适用场景:
- 在簇间边界清晰、各簇密度相近时表现最好
- 适用于评估预先确定簇数量的聚类算法
- 对于层次聚类等不确定簇数量的算法评估效果有限
-
计算复杂度问题:
- 需要计算所有样本点之间的距离,时间复杂度较高
- 在大规模数据集上计算代价较大
在实际应用中,建议将轮廓系数与其他评估指标(如Calinski-Harabasz指数、Davies-Bouldin指数等)结合使用,以获得更全面的聚类效果评估。同时要注意,对于特殊形态的数据集或特定类型的聚类算法,可能需要选用更适合的评估方法。
错误速查
| 症状 | 根因 | 定位 | 修复 |
|---|---|---|---|
运行 silhouette_score(X, cluster_.labels_) 报错或结果异常 | 变量混用:cluster 与 cluster_ 不一致 | 检查 fit(X) 的返回对象到底赋给了谁 | 统一变量名:cluster = KMeans(...).fit(X) 后用 cluster.labels_ |
| 选 K 画线落在“最差点”而不是“最好点” | 用了 idxmin();轮廓系数应取最大值附近 | 看代码:pd.DataFrame(score).idxmin() | 改为 idxmax(),并明确“取峰值附近作为候选 K” |
| 同一份数据在不同机器/版本下 KMeans 结果不一致 | sklearn 1.4 起 n_init 默认 'auto';初始化次数/策略变化 | 打印 sklearn 版本与 KMeans 参数 | 显式指定 n_init、init、random_state(跨版本对比必须固定)。 |
inertia 很大、肘部不明显 | 特征未标准化/量纲差异大;高维导致距离主导 | 观察特征量纲与方差;对比标准化前后曲线 | 先做标准化/归一化;再看 inertia/轮廓曲线 |
silhouette_score 计算很慢 | 轮廓系数需要大量距离计算,样本大时成本高 | K 扫描范围过大(2..100)且 n 较大 | 缩小 K 搜索区间;先抽样评估,再全量验证 |
silhouette 出现大量负值 | K 不合适/簇重叠严重/数据不适合 KMeans(非凸形簇、噪声多) | 用 silhouette_samples 看分布,定位负值集中簇 | 调整 K、换特征工程;必要时改用 GMM/DBSCAN 等算法 |
其他系列
🚀 AI篇持续更新中(长期更新)
AI炼丹日志-29 - 字节跳动 DeerFlow 深度研究框斜体样式架 私有部署 测试上手 架构研究,持续打造实用AI工具指南! AI研究-132 Java 生态前沿 2025:Spring、Quarkus、GraalVM、CRaC 与云原生落地
💻 Java篇持续更新中(长期更新)
Java-218 RocketMQ Java API 实战:同步/异步 Producer 与 Pull/Push Consumer MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务已完结,Dubbo已完结,MySQL已完结,MongoDB已完结,Neo4j已完结,FastDFS 已完结,OSS已完结,GuavaCache已完结,EVCache已完结,RabbitMQ已完结,RocketMQ正在更新... 深入浅出助你打牢基础!
📊 大数据板块已完成多项干货更新(300篇):
包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈! 大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解