什么是 k-means 聚类算法?如何评估聚类的效果?
2026/7/5 6:31:10 网站建设 项目流程

一、k-means 是什么

一句话:把数据分成 k 组,让每组内的点尽量靠近自己的中心


直觉理解

想象你桌上有 100 颗糖豆散落一地,你想把它们分成 3 堆。你的做法大概是:

  1. 随便放 3 个碗在桌上当"堆心"
  2. 每颗糖豆归到离它最近的那个碗
  3. 根据分好的糖豆,把每个碗移到这一堆的中心位置
  4. 糖豆重新按最近距离归碗
  5. 反复 3、4 步,直到碗不再移动

这就是 k-means。


算法步骤

输入:数据集 D,分组数 k 输出:k 个簇及其中心点 1. 从 D 中随机选 k 个点作为初始中心 2. 重复: a) 分配:把每个数据点归到距离最近的中心所在的簇 b) 更新:重新计算每个簇的中心(所有点的均值) 3. 直到中心不再变化(或变化小于阈值)

用一个简单例子走一遍:

数据点: A(1,1) B(2,1) C(4,3) D(5,3) k=2 第1轮:随机选中心 C1=A(1,1), C2=D(5,3) 分配:A→簇1, B→簇1(离C1近), C→簇2(离C2近), D→簇2 更新:C1=(1.5,1), C2=(4.5,3) 第2轮: 分配:A→簇1, B→簇1, C→簇2, D→簇2 (没变) 更新:C1=(1.5,1), C2=(4.5,3) (没变) 结束,分两组:{A,B} 和 {C,D}

数学形式

目标函数(惯性 / Inertia):

J=∑i=1k∑x∈Ci∥x−μi∥2J = \sum_{i=1}^{k} \sum_{x \in C_i} \|x - \mu_i\|^2J=i=1kxCixμi2

其中μi\mu_iμi是第iii个簇的中心,CiC_iCi是第iii个簇的所有点。

k-means 本质上就是在最小化这个值——让每个点到自己簇中心的距离平方和尽量小。

时间复杂度:O(n · k · d · t),n=样本数,k=簇数,d=维度,t=迭代轮数。通常很快。


三个老问题

问题说明应对
k 怎么选需要事先指定分组数肘部法则、轮廓系数、Gap Statistic
初始中心敏感不同起点可能收敛到不同结果k-means++(优选初始点)、多次运行取最优
只适合球形簇只按距离划分,组不了月牙形、环形等异形簇换 DBSCAN、谱聚类等

k-means++ 初始化:不随机选,而是让第一个中心随机选,之后每个新中心尽量离已有中心远一些。这样起步就更稳,收敛更快。


代码示例

fromsklearn.clusterimportKMeansfromsklearn.datasetsimportmake_blobsimportmatplotlib.pyplotasplt# 生成模拟数据X,_=make_blobs(n_samples=300,centers=4,random_state=42)# 聚类km=KMeans(n_clusters=4,init='k-means++',n_init=10,random_state=42)labels=km.fit_predict(X)# 可视化plt.scatter(X[:,0],X[:,1],c=labels,cmap='viridis',s=30)plt.scatter(km.cluster_centers_[:,0],km.cluster_centers_[:,1],c='red',marker='X',s=200,label='中心点')plt.legend()plt.show()

二、如何评估聚类效果

聚类没有标准答案(无监督学习),所以评估比分类/回归要棘手。分两大类:


内部指标(不需要真实标签)

1. 惯性 / SSE(越小越好)

就是上面那个目标函数 J 本身——每个点到自己簇中心的距离平方和。

  • 越小说明簇内越紧凑
  • 但 k 越大必然越小,k=n 时为 0,所以不能直接比
2. 肘部法则(选 k 用)
k=1 SSE=1000 k=2 SSE=400 k=3 SSE=200 k=4 SSE=180 ← 下降变缓的拐点 k=5 SSE=170 k=6 SSE=165

画一条 k-SSE 曲线,找"拐弯"的地方——拐点之前加 k 收益大,拐点之后加了也差不多。

SSE |\ | \ | \___ | \_______ ← 拐点(肘部)就是合适的 k +----------→ k

局限:拐点有时候不明显,人为判断有主观性。

3. 轮廓系数(Silhouette Score)

取值[-1, 1],越大越好。

每个点算一个轮廓值:

s(i)=b(i)−a(i)max⁡(a(i),b(i))s(i) = \frac{b(i) - a(i)}{\max(a(i), b(i))}s(i)=max(a(i),b(i))b(i)a(i)

  • a(i):点 i 到同簇其他点的平均距离(越小越好,说明自己簇很紧)
  • b(i):点 i 到最近其他簇的平均距离(越大越好,说明跟别的簇远)

直观理解:

轮廓值含义
接近 1这个点跟自己人很近,跟外人很远,分对了
接近 0在两个簇的边界上,分得不明确
接近 -1跟自己人远,跟外人近,可能分错了
fromsklearn.metricsimportsilhouette_scoreforkinrange(2,10):km=KMeans(n_clusters=k,n_init=10,random_state=42)labels=km.fit_predict(X)score=silhouette_score(X,labels)print(f"k={k}, 轮廓系数={score:.3f}")

选轮廓系数最大的 k。

4. CH 指数(Calinski-Harabasz Index)

簇间分散度 / 簇内紧凑度,越大越好。计算比轮廓系数快。

5. Davies-Bouldin 指数

每个簇跟"最相似"的簇之间的相似度取平均,越小越好


外部指标(有真实标签时用)

如果碰巧知道真实分类(比如用标注数据做实验),可以对比:

指标思路取值
ARI(调整兰德指数)聚类结果和真实标签的一致程度,排除了随机猜的干扰[-1, 1],1=完全一致
NMI(归一化互信息)两种分组共享了多少信息量[0, 1],1=完全一致
V-measure均匀性和完整性的调和平均[0, 1]
fromsklearn.metricsimportadjusted_rand_score,normalized_mutual_info_score ari=adjusted_rand_score(y_true,labels)# y_true 是真实标签nmi=normalized_mutual_info_score(y_true,labels)

指标对比速查

指标需要标签?衡量什么越大/小越好选 k 适用
SSE / Inertia不需要簇内紧凑度越小越好配合肘部法则
轮廓系数不需要紧凑 + 分离越大越好直接选最大
CH 指数不需要簇间/簇内比越大越好直接选最大
Davies-Bouldin不需要簇间相似度越小越好直接选最小
ARI需要与真实标签一致度越大越好-
NMI需要与真实标签信息重叠越大越好-

实际建议

  1. 先用肘部法则粗选 k,再用轮廓系数精细确认
  2. 如果数据量大,轮廓系数计算慢,用 CH 指数替代
  3. 有真实标签时一定看 ARI/NMI,比内部指标可靠
  4. 指标只是参考——最终还是要看业务上分出来的簇是否有意义

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询