什么是 k-means 聚类算法?如何评估聚类的效果? 一、k-means 是什么一句话把数据分成 k 组让每组内的点尽量靠近自己的中心。直觉理解想象你桌上有 100 颗糖豆散落一地你想把它们分成 3 堆。你的做法大概是随便放 3 个碗在桌上当堆心每颗糖豆归到离它最近的那个碗根据分好的糖豆把每个碗移到这一堆的中心位置糖豆重新按最近距离归碗反复 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) k2 第1轮随机选中心 C1A(1,1), C2D(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}数学形式目标函数惯性 / InertiaJ∑i1k∑x∈Ci∥x−μi∥2J \sum_{i1}^{k} \sum_{x \in C_i} \|x - \mu_i\|^2Ji1∑k​x∈Ci​∑​∥x−μi​∥2其中μ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_samples300,centers4,random_state42)# 聚类kmKMeans(n_clusters4,initk-means,n_init10,random_state42)labelskm.fit_predict(X)# 可视化plt.scatter(X[:,0],X[:,1],clabels,cmapviridis,s30)plt.scatter(km.cluster_centers_[:,0],km.cluster_centers_[:,1],cred,markerX,s200,label中心点)plt.legend()plt.show()二、如何评估聚类效果聚类没有标准答案无监督学习所以评估比分类/回归要棘手。分两大类内部指标不需要真实标签1. 惯性 / SSE越小越好就是上面那个目标函数 J 本身——每个点到自己簇中心的距离平方和。越小说明簇内越紧凑但 k 越大必然越小kn 时为 0所以不能直接比2. 肘部法则选 k 用k1 SSE1000 k2 SSE400 k3 SSE200 k4 SSE180 ← 下降变缓的拐点 k5 SSE170 k6 SSE165画一条 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):kmKMeans(n_clustersk,n_init10,random_state42)labelskm.fit_predict(X)scoresilhouette_score(X,labels)print(fk{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 ariadjusted_rand_score(y_true,labels)# y_true 是真实标签nminormalized_mutual_info_score(y_true,labels)指标对比速查指标需要标签衡量什么越大/小越好选 k 适用SSE / Inertia不需要簇内紧凑度越小越好配合肘部法则轮廓系数不需要紧凑 分离越大越好直接选最大CH 指数不需要簇间/簇内比越大越好直接选最大Davies-Bouldin不需要簇间相似度越小越好直接选最小ARI需要与真实标签一致度越大越好-NMI需要与真实标签信息重叠越大越好-实际建议先用肘部法则粗选 k再用轮廓系数精细确认如果数据量大轮廓系数计算慢用 CH 指数替代有真实标签时一定看 ARI/NMI比内部指标可靠指标只是参考——最终还是要看业务上分出来的簇是否有意义