
简介这份KMeans聚类源码包包含MATLAB脚本与Excel数据文件面向机器学习初学者和故障诊断相关研究人员适合用于探索无监督聚类流程、自动识别设备异常发热等故障类型。压缩包共2个文件1个xlsx数据文件、1个m脚本整体仅57KB小巧紧凑其中xlsx文件提供异常加热片段样本m脚本包含从数据读取、聚类计算到结果可视化的完整过程。已有4507人学习下载。脚本实现了KMeans核心迭代逻辑并引入Calinski-Harabasz指数动态评估最佳聚类个数避免人为指定簇数的偏差同时生成二维/三维散点图以不同颜色展示聚类分布还能结合直方图、箱线图等统计图表分析簇内特征帮助深入理解聚类结果。该源码包特别适合课程实验、毕业设计或企业故障预警预研可直接迁移思路到其他聚类任务是学习KMeans与故障分类的实用参考。1. Kmeans聚类样本与可视化源码先解决“这个聚类准不准”的问题很多人第一次跑通Kmeans聚类用的还是scikit-learn三行代码拟合、预测、拿到labels。但问题随之而来——聚类结果到底对不对Kmeans没有标签给你对答案唯一可靠的检验手段是回到数据分布本身。这正是“样本可视化源码”这套组合的价值样本负责构造你知道标准答案的测试集可视化源码负责把每一轮迭代的分配结果摊开在人眼前。这篇文章从Kmeans聚类的目标函数讲起落到样本如何构造、源码怎么写、可视化怎么画最后给出一套可复现的验证清单。适合想绕开sklearn黑盒、把聚类过程吃透的人也适合正在做python数据分析与可视化、需要给业务方解释聚类依据的人。2. Kmeans聚类原理与样本侧设计k值、距离和欧氏前提2.1 Kmeans为什么默认配欧氏距离SSE目标和凸簇假设Kmeans聚类的目标函数是簇内平方和SSE也就是每个样本到它所属质心距离的平方和。最小化SSE等价于在欧氏空间里把数据切分成k个“以质心为代表点”的区域这也是“欧氏聚类”这一说法的来源。需要注意这里用的是欧氏距离的平方不是欧几里得范数本身如果你换成曼哈顿距离聚类结果就不再具备“均值是最优代表点”的性质质心更新那一步会直接失真。因为目标函数建立在距离平方之上Kmeans对簇形状有隐含假设簇最好是凸的、大小相近、方差各向一致。遇到螺旋形、月牙形数据Kmeans会把本来连在一起的簇拦腰截断这时层次聚类python实现里基于连通性的方法更合适。所以一个合格的Kmeans项目第一步不是调参而是确认数据形态配得上这个算法生成样本时也要刻意选边界清晰的簇才能验证源码行为是否符合预期。2.2 合成样本与真实样本make_blobs到StandardScaler样本构造最直接的方式是sklearn.datasets.make_blobs。它按参数生成几簇高斯点天然满足凸簇假设是验证Kmeans源码正确性的标准测试集。常用参数如下表参数含义建议值说明n_samples样本总量300~1000太少看不出聚类边界太多增加可视化负担centers簇数量或中心点坐标3~5与算法里的k保持一致方便对照cluster_std簇内标准差0.5~2.0超过2.0时簇间重叠严重SSE会急剧上升random_state随机种子固定值保证样本可复现排错时不发生“这次不一样”用真实样本时先做标准化比选距离更关键。Kmeans对量纲敏感一个“年龄”是两位数、一个“年收入”是六位数后者会主导距离计算聚类结果几乎只由高方差特征决定。常见做法是先对特征做StandardScaler或RobustScaler再进聚类做可视化时还要把聚类坐标还原回原始空间否则图画出来单位是“标准化后的数值”业务方看不懂。import numpy as np from sklearn.datasets import make_blobs from sklearn.preprocessing import StandardScaler X, y_true make_blobs(n_samples500, centers4, cluster_std1.2, random_state42) X_scaled StandardScaler().fit_transform(X)这里的y_true只是用来事后对比不参与聚类过程cluster_std建议从0.5开始调逐步放大到2.0观察Kmeans在高重叠样本下的边界变化这比单独一组“完美数据”更能暴露算法的局限。2.3 肘部法则与轮廓系数k值聚类的样本侧判断k是Kmeans唯一必须手动指定的超参k值聚类最常用的判断工具是肘部法则。做法是跑k从2到10记录每个k对应的SSE然后找“下降速度明显变缓”的拐点。SSE永远不会随着k增加而上升所以拐点比绝对值重要。from sklearn.cluster import KMeans import matplotlib.pyplot as plt inertias [] for k in range(2, 11): km KMeans(n_clustersk, n_init10, random_state42) km.fit(X_scaled) inertias.append(km.inertia_) plt.plot(range(2, 11), inertias, markero) plt.xlabel(k) plt.ylabel(SSE) plt.title(Elbow Method) plt.show()这段代码里n_init10表示每个k用10个随机起点跑10次取SSE最小的结果避免劣质初始质心影响肘部位置的判断。样本量不足时肘部往往不明显这时要结合轮廓系数看轮廓系数的计算逻辑和判读细节放在最后一章这里先记住它的用途——给“肘部不太明显”的样本一个定量的第二意见。3. 从零实现Kmeans聚类源码初始化、迭代与收敛判断3.1 k-means初始化源码打破朴素随机采样的不对称朴素随机初始化是从样本里均匀抽k个点当质心很容易出现两个质心落进同一簇的尴尬局面导致最终聚类结果只是局部最优。k-means的思路是第一个质心随机选之后的每个质心按“距离已有质心越远、被选中概率越大”来抽样。import numpy as np def kmeans_plusplus_init(X, k, random_state42): rng np.random.default_rng(random_state) n_samples X.shape[0] centers [X[rng.integers(0, n_samples)]] for _ in range(1, k): # 每个样本到最近质心的距离平方 dist np.min( np.array([np.sum((X - c) ** 2, axis1) for c in centers]), axis0 ) # 按距离平方占总和的比例采样 prob dist / dist.sum() centers.append(X[rng.choice(n_samples, pprob)]) return np.array(centers)逻辑说明dist是当前所有样本到最近质心的欧氏距离平方和prob把它归一化成概率分布距离越远的样本越有可能被选为下一个质心。random_state固定后整条初始化链可复现这对调试可视化源码非常关键——如果你每次跑图里的质心位置都变很难判断是算法问题还是随机性问题。3.2 主循环源码分配阶段和更新阶段的向量化Kmeans每一次迭代只有两步分配assignment和更新update。分配阶段计算每个样本到k个质心的距离取最近的质心作为簇标签更新阶段把每个簇内样本的均值作为新质心。收敛条件看质心位移如果相邻两次质心的欧氏距离总和小于tol就停止迭代。import numpy as np def kmeans(X, k, max_iter300, tol1e-4, random_state42): centers kmeans_plusplus_init(X, k, random_state) n_samples X.shape[0] labels np.zeros(n_samples, dtypeint) for i in range(max_iter): # 分配阶段X[:, None, :] 扩展为 (n, 1, d) # centers[None, :, :] 扩展为 (1, k, d) distances np.linalg.norm( X[:, None, :] - centers[None, :, :], axis2 ) new_labels np.argmin(distances, axis1) # 更新阶段每个簇内样本取均值 new_centers np.array([ X[new_labels j].mean(axis0) for j in range(k) ]) # 质心位移小于 tol 则收敛 shift np.linalg.norm(new_centers - centers) centers new_centers labels new_labels if shift tol: break return labels, centers参数说明max_iter300是安全上限防止不收敛时死循环tol1e-4是质心位移的绝对阈值样本量级大或特征方差高时建议改成1e-6X[:, None, :] - centers[None, :, :]这一步是向量化广播避免写双层for循环样本量大时速度差距是数量级的。空簇问题也要处理如果某个j没有样本X[new_labels j].mean(axis0)会出现NaN常见做法是保留上一轮的质心或原地随机重采样。3.3 迭代参数怎么调max_iter、tol与n_init的边界参数作用典型值调参方向k簇数聚类唯一必选超参肘部/业务决定偏大时SSE下降但可解释性变差max_iter最大迭代轮数300数据量大或初始化差时提到1000tol质心位移阈值1e-4追求稳定结果时收紧到1e-6n_init重启次数sklearn参数10样本分布重叠严重时加到25一个容易被忽略的边界是tol只监控质心位移不监控标签变化。某些数据形态下质心位移很小但簇边缘样本还在抖动。要抓这种问题可以在迭代里同时记录new_labels ! labels的比例把这个比例画成曲线比单看SSE更直观。这也是为什么后面要写可视化源码——中间状态值得被看见。4. 可视化源码散点图、簇边界与质心轨迹的绘制4.1 静态可视化源码散点着色的三个细节最常见的Kmeans可视化是二维散点图每个簇一种颜色质心用星号标出。不加修饰的三行代码能出图但要“能看出聚类质量”有三个细节必须处理颜色循环要稳定、质心要压在最上层、半透明避免样本点互相遮挡。import matplotlib.pyplot as plt import numpy as np def plot_kmeans_result(X, labels, centers, axNone): if ax is None: ax plt.gca() n_clusters len(centers) colors plt.cm.Set1(np.linspace(0, 1, n_clusters)) for i, color in enumerate(colors): mask labels i ax.scatter(X[mask, 0], X[mask, 1], c[color], s30, alpha0.7, labelfCluster {i}) ax.scatter(centers[:, 0], centers[:, 1], marker*, s320, cblack, edgecolorswhite, linewidths1.5, zorder3, labelCentroids) ax.legend() return ax参数说明alpha0.7让重叠样本呈现密度感簇密集时不会一团黑zorder3保证质心星标不被普通样本覆盖s320的星标配白边在浅色背景下依然醒目。plt.cm.Set1的颜色表最多支持9类簇数超过9时要换成plt.cm.tab20否则颜色会重复循环误导看图的人。4.2 簇边界绘制Voronoi图的网格近似散点图只能看到样本被分成几堆看不出“决策边界”在哪。Kmeans的边界其实是质心之间的垂直平分线也就是Voronoi图。做法是在画布上铺一层密集网格对每个网格点执行argmin预测它属于哪个簇再用等高线填充。from matplotlib.colors import ListedColormap def plot_kmeans_boundary(X, labels, centers, axNone, grid_step0.1): if ax is None: ax plt.gca() x_min, x_max X[:, 0].min() - 1, X[:, 0].max() 1 y_min, y_max X[:, 1].min() - 1, X[:, 1].max() 1 xx, yy np.meshgrid( np.arange(x_min, x_max, grid_step), np.arange(y_min, y_max, grid_step) ) grid np.c_[xx.ravel(), yy.ravel()] dists np.linalg.norm( grid[:, None, :] - centers[None, :, :], axis2 ) grid_labels np.argmin(dists, axis1).reshape(xx.shape) cmap ListedColormap(plt.cm.Set1.colors[:len(centers)]) ax.contourf(xx, yy, grid_labels, cmapcmap, alpha0.15) return ax网格密度由grid_step控制0.1对多数样本规模够用太密会让边界锯齿感消失但计算量翻几倍太稀则边界看起来像马赛克。contourf的alpha0.15必须比散点的alpha低否则边界色块会盖住样本。边界图最大的价值是暴露“Kmeans默认所有边界都是直线”这件事——如果你看到真实业务数据里簇与簇有明显弧形边界就该怀疑Kmeans是不是选错了。4.3 动态可视化把每一次迭代留在画布上静态图展示结果动态图展示过程。Kmeans迭代过程可视化是理解“收敛”的最佳工具质心每次移动一小步样本归属跟着翻转。做法是把每轮迭代的质心位置和簇标签存下来最后一次性渲染成子图序列。def kmeans_with_trace(X, k, max_iter20): centers kmeans_plusplus_init(X, k) trace [] for i in range(max_iter): distances np.linalg.norm( X[:, None, :] - centers[None, :, :], axis2 ) labels np.argmin(distances, axis1) trace.append((labels.copy(), centers.copy())) new_centers np.array([ X[labels j].mean(axis0) for j in range(k) ]) if np.linalg.norm(new_centers - centers) 1e-4: centers new_centers break centers new_centers return labels, centers, trace fig, axes plt.subplots(2, 3, figsize(18, 10)) axes axes.ravel() for idx, (lab, cen) in enumerate(trace[:6]): ax axes[idx] plot_kmeans_result(X, lab, cen, axax) ax.set_title(fIteration {idx}) plt.tight_layout() plt.show()这里把收敛阈值临时放宽到1e-4并限制max_iter20保证能截到中间帧。子图标题里的迭代次数直接从enumerate取不用手动打断。观察质心轨迹时重点看前两三轮如果质心在第一轮就出现大幅跳跃说明初始化点的位置离数据重心很远如果好几轮都在原地微调说明迭代已经进入慢速收敛区再跑下去边际收益很低。4.4 高维样本的可视化PCA投影与降维陷阱业务里很少有干净的二维数据可视化前必须先降维。最常见做法是PCA降到前两个主成分画二维平面图。但PCA是全局线性投影它会破坏原本的簇结构两个在三维空间里分开的簇投影到某个二维平面后可能完全重叠。from sklearn.decomposition import PCA pca PCA(n_components2) X_pca pca.fit_transform(X_scaled) labels_pca, centers_pca kmeans(X_pca, k4) plot_kmeans_result(X_pca, labels_pca, centers_pca) plt.xlabel(PC1) plt.ylabel(PC2) plt.show()这段代码前必须想清楚一个顺序问题你是先聚类再投影还是先投影再聚类两者结果不同。先投影会丢掉部分方差信息聚类结果可能在原空间里不是最优先聚类再对质心和样本做投影画出来是准的但簇密度区域可能重叠。我一般会两个顺序都跑一遍把两张图并排放。如果投影后簇结构完全消失而轮廓系数仍然很高说明簇结构藏在被PCA砍掉的低方差维度里这时换t-SNE或UMAP看局部结构而不是直接信PCA的“空隙”。5. 用轮廓系数和可视化交叉验证Kmeans聚类效果5.1 轮廓系数的读法从平均分到单样本分轮廓系数silhouette score对每个样本计算一个a到同簇其他样本的平均距离和b到最近其他簇样本的平均距离得分为(b - a) / max(a, b)。这个值的范围是[-1, 1]接近1说明样本离自己的簇中心近、离隔壁簇远接近0说明样本处在两个簇的交界归属模棱两可负值说明它可能被分错了簇。把k从2到10都算一遍平均分最高分对应的k就是轮廓系数的建议值。from sklearn.metrics import silhouette_score sil_scores {} for k in range(2, 11): km KMeans(n_clustersk, n_init10, random_state42) labels km.fit_predict(X_scaled) sil_scores[k] silhouette_score(X_scaled, labels) best_k max(sil_scores, keysil_scores.get) print(f轮廓系数最优 k {best_k}, score {sil_scores[best_k]:.4f}) # 查看单个样本的轮廓系数负值样本标记为噪声 from sklearn.metrics import silhouette_samples sample_sil silhouette_samples(X_scaled, labels) outliers np.where(sample_sil 0)[0] print(f负轮廓系数样本数: {len(outliers)})单独看平均分会掩盖局部问题。silhouette_samples能给出每个样本的分数把负分样本在原图上用圆圈标出来比任何统计量都直观。真实项目中我通常把负分样本抽出来检查是否属于业务噪声——它们往往对应录入错误、极端值或聚类簇之间本就不该有清晰界限的情况。5.2 可视化验证清单最后给出一个我每次跑完Kmeans可视化源码头都会过的清单共四步先看静态簇图各簇半径是否相近是否存在几乎空的簇。空簇说明k设大了或样本分布太偏。再看边界图簇边界是否有明显直角、楔形不自然切割如果有考虑换DBSCAN或层次聚类。对照迭代轨迹图确认质心移动是“先快后慢”如果一直在震荡检查tol是不是设得太松或初始化点太差。把轮廓系数按k画折线图与肘部法则的拐点放在同一张画布里两个子图横轴对齐。两边给出的最优k一致才有底气定k。这个方法配合make_blobs合成样本能在一小时内验证你手写的Kmeans源码、matplotlib可视化逻辑、sklearn标准实现三者是否行为一致。之后再换真实样本才算真正把“样本可视化源码”这套组合落地成可复用的聚类实验台。本文还有配套的精品资源点击获取