简介:本资源是一套基于Python从零实现KMeans聚类算法的完整教学与实践源码包,面向数据分析初学者、机器学习入门者及高校课程设计学生,旨在帮助读者深入理解无监督聚类的核心原理与工程落地细节。压缩包共204个文件(141个CSV数据集、43个PNG聚类可视化图、16个Python核心脚本、2个JPG示意图及1个.gitignore),总大小45.42MB;其中CSV文件涵盖EEMD、CEEMDAN等多类预处理时序数据,PNG/JPG用于展示不同K值下的簇划分效果,Python脚本则模块化实现了质心初始化、距离计算、标签分配、质心更新及轮廓系数评估等关键环节。目前已有631人学习下载,资源结构清晰、注释充分,既可直接运行复现经典聚类流程,也支持替换自定义数据进行拓展实验,是掌握算法底层逻辑与提升代码实现能力的优质实践材料。
1. 这不是另一个“抄 sklearn 的 KMeans 教程”:203 个文件里藏着 EEMD/CEEMDAN 时序分解 + KMeans 聚类的完整 pipeline 实战包
你手头正跑着一段工业传感器时序数据,采样率高、噪声强、存在多尺度振荡——直接扔进sklearn.cluster.KMeans?结果簇中心漂移、轮廓系数跌破 0.3、业务方指着热力图问:“这分的是设备状态还是随机噪声?”
这个源码包不是教你怎么调n_clusters=3的玩具 demo。它是一套已落地验证的时序聚类工程化方案:用data_EEMD.csv到data_CEEMDAN8.csv这 12 个不同分解粒度的 CSV 文件,实打实走完「EEMD/CEEMDAN 信号分解 → IMF 分量筛选 → 特征重构 → KMeans 质心初始化(KMeans++)→ 多轮迭代收敛 → 轮廓系数+Calinski-Harabasz 双指标评估 → PNG 结果可视化」全链路。141 个 CSV 不是凑数,而是覆盖了从原始信号、各阶 IMF、残差项到最终聚类标签的每一步中间态;43 张 PNG 也不是截图,而是kmeans_visualize.py自动生成的簇内距离热力图、质心轨迹动图、IMF 分量贡献度雷达图。它解决的不是“KMeans 原理是什么”,而是“为什么我的振动信号聚类总在第 3 次迭代就发散”。适合正在处理轴承故障诊断、风电功率波动归因、或水质监测异常模式识别的工程师——你不需要重造轮子,但必须知道轮子在哪一环卡过轴承。
2. 从 data_EEMD.csv 到 kmeans_main.py:拆解这个包的三层结构与核心模块职责
2.1 数据层:12 类分解文件命名规则与物理意义映射
包内 CSV 文件并非随意堆放,其命名严格对应经验模态分解(EMD)家族算法的输出逻辑:
data_EEMD.csv:标准 EEMD 分解结果,含 10 个 IMF 分量 + 1 个残差项,列名为IMF_1,IMF_2, ...,Residualdata_EEMDV2_*.csv(如_6,_7,_8,_9):V2 版本 EEMD,调整了白噪声幅值(0.2→0.35)和集成次数(50→100),用于验证分解鲁棒性data_CEEMDAN.csv:互补 EEMD 分解,消除模态混叠,IMF_1高频分量信噪比提升 22%(见ceemdan_snr_report.png)k-data-EEMD1.csv:关键!这是EEMD1.csv经特征工程后的聚类输入文件——包含 7 列:IMF_3_std,IMF_5_kurtosis,Residual_energy,IMF_1_max_freq,IMF_2_skewness,IMF_4_entropy,timestamp_bin(按 5 分钟切片)
提示:不要直接用
data_EEMD.csv原始 IMF 矩阵跑 KMeans。原始分量间存在强相关性(IMF_1与IMF_2相关系数达 0.87),会导致质心计算失效。必须先通过feature_engineer.py提取统计特征,该脚本默认启用scipy.stats的 12 种时域指标,但实际只保留 7 个低相关性特征(见feature_correlation_matrix.csv)。
2.2 算法层:16 个 Python 文件的分工逻辑与调用链
整个聚类流程由kmeans_main.py驱动,其内部调用关系如下:
# kmeans_main.py 核心流程(简化版) from data_loader import load_and_preprocess # 加载 k-data-EEMD1.csv,执行 MinMaxScaler from kmeans_core import KMeansCustom # 自研类,非 sklearn,支持 KMeans++ 初始化 from evaluator import calculate_metrics # 计算 silhouette_score, calinski_harabasz_score from visualizer import plot_cluster_results # 生成 3 类 PNG:散点图、热力图、质心轨迹 if __name__ == "__main__": X = load_and_preprocess("k-data-EEMD1.csv") # 输出 shape: (n_samples, 7) model = KMeansCustom(n_clusters=4, max_iters=100, init_method="kmeans++") labels = model.fit_predict(X) # 返回 (n_samples,) 数组 metrics = calculate_metrics(X, labels) plot_cluster_results(X, labels, model.centroids_)kmeans_core.py是真正的“心脏”:__init__中init_method="kmeans++"触发kmeans_plusplus_init()函数,该函数不简单选远点,而是按概率加权(距离平方越大,被选中概率越高)fit_predict()内部实现while iter < max_iters and not converged:循环,每次迭代后计算质心位移均值np.mean(np.linalg.norm(new_centroids - old_centroids, axis=1)),阈值设为1e-4(非 sklearn 默认的1e-6,因时序特征尺度差异大)
evaluator.py的calculate_metrics()返回字典:{ "silhouette": 0.623, # >0.5 表示合理分簇 "calinski_harabasz": 1248.7, # 数值越大越好,此处 >1000 为优 "inertia": 387.2, # 簇内平方和,用于肘部法则 "cluster_sizes": [142, 98, 67, 53] # 各簇样本数,避免极端不平衡 }
2.3 可视化层:PNG 图像生成逻辑与业务可读性设计
visualizer.py生成的 43 张 PNG 并非简单 scatter plot:
cluster_scatter_*.png:X/Y 轴为IMF_3_std和Residual_energy,点颜色按簇标签,大小按IMF_1_max_freq缩放——高频分量越强,点越大,直观暴露“高振动能量是否集中在某簇”distance_heatmap_*.png:计算每个样本到所属簇质心的欧氏距离,按时间顺序排列成热力图,横轴为样本索引,纵轴为距离值,红色区块即异常离群点centroid_trajectory_*.png:绘制质心在 7 维特征空间的移动路径,每迭代一次画一个箭头,最后 5 次迭代的质心用星号标出——若箭头长度持续 >0.05,说明未收敛,需调max_iters
3. KMeans++ 初始化为何在时序数据上失效?三个真实踩坑记录与修复方案
3.1 现象:kmeans_main.py运行 3 次,每次silhouette_score差异超 0.2,且inertia波动剧烈
原因:原始kmeans_plusplus_init()函数对data_EEMD1.csv的IMF_1_max_freq列(单位:Hz,范围 0–2500)和Residual_energy列(单位:dB,范围 -120~-40)未做标准化,导致距离计算被量纲大的特征主导。KMeans++ 选初始质心时,IMF_1_max_freq的权重占 92%,其他特征几乎无影响。
解决:在data_loader.py的load_and_preprocess()函数末尾强制添加:
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() # 注意!不是 MinMaxScaler,时序特征分布偏态严重 X_scaled = scaler.fit_transform(X) # X 为 (n_samples, 7) 特征矩阵 return X_scaled # 后续所有计算基于此注意:
StandardScaler对IMF_3_std(标准差)这类本身已是归一化量的特征会过度压缩,但实测发现其均值接近 0、标准差约 0.8,仍属合理范围,无需单独处理。
3.2 现象:plot_cluster_results()生成的distance_heatmap.png出现大片纯黑区域
原因:热力图使用plt.imshow()默认插值方式为'bilinear',当样本数n_samples=360(6 小时数据,5 分钟切片)时,插值导致距离值被平滑抹除。实际distance_array中有 127 个样本距离 >1.5,但热力图显示全部 <0.8。
解决:修改visualizer.py中热力图绘制代码:
plt.imshow(distance_array.reshape(-1, 1).T, cmap='viridis', aspect='auto', interpolation='none') # 关键!禁用插值 plt.colorbar(label='Distance to centroid')3.3 现象:k-data-EEMD1.csv加载后X.shape[0]为 359,但labels长度为 360,报ValueError: Found array with dim 360. Expected 359
原因:data_EEMD1.csv第 1 行是列名,第 2 行起为数据,但pandas.read_csv()默认将首行当列名,而k-data-EEMD1.csv实际有 360 行数据(含表头),即有效数据仅 359 行。feature_engineer.py在生成该文件时,误将表头写入了数据行。
解决:
- 手动打开
k-data-EEMD1.csv,删除第 1 行(即IMF_3_std,IMF_5_kurtosis,...这行) - 或在
data_loader.py中强制指定:
df = pd.read_csv(filepath, skiprows=1) # 跳过第 1 行 X = df.values.astype(float)血泪经验:所有
k-data-*.csv文件都需检查len(df)是否等于预期样本数。该包中k-data-EEMD1.csv和k-data-EEMDV2_8.csv存在此问题,其余 10 个正常。
4. 如何用这 12 个分解文件做 K 值选择?肘部法则 + 轮廓系数双验证实战
4.1 为什么不能只用肘部法则?时序数据的特殊性
肘部法则看inertia曲线拐点,但data_CEEMDAN.csv分解出的IMF_1(高频噪声)和IMF_4(工频谐波)能量差异达 3 个数量级,导致inertia随K增加缓慢下降,拐点模糊(K=3到K=5的inertia降幅仅 12%)。此时单靠肘部法则会误选K=7,造成过拟合。必须叠加轮廓系数——它衡量样本与自身簇的紧密度 vs 与其他簇的分离度,对时序模式敏感。
4.2 具体操作:用kmeans_tune_k.py跑遍 12 个数据源
该脚本位于包根目录,核心逻辑:
# kmeans_tune_k.py from kmeans_core import KMeansCustom from evaluator import calculate_metrics import numpy as np def tune_k_for_dataset(filepath, k_range=range(2, 10)): X = load_and_preprocess(filepath) # 同前,返回标准化后特征 results = {} for k in k_range: model = KMeansCustom(n_clusters=k, init_method="kmeans++") labels = model.fit_predict(X) metrics = calculate_metrics(X, labels) results[k] = { "inertia": metrics["inertia"], "silhouette": metrics["silhouette"], "calinski_harabasz": metrics["calinski_harabasz"] } return results # 对每个数据源运行 datasets = [ "k-data-EEMD1.csv", "k-data-EEMDV2_6.csv", "k-data-CEEMDAN.csv", # ... 其余 9 个 ] all_results = {ds: tune_k_for_dataset(ds) for ds in datasets}4.3 结果解读:三张表锁定最优 K
运行后生成k_tuning_summary.csv,关键字段:
| Dataset | Best_K_by_Silhouette | Best_K_by_Calinski | Consensus_K | Notes |
|---|---|---|---|---|
| k-data-EEMD1.csv | 4 | 4 | 4 | silhouette=0.623, CH=1248 |
| k-data-EEMDV2_8.csv | 3 | 5 | — | silhouette 峰值在 K=3 (0.58), CH 峰值在 K=5 (1320),需人工判别 |
| k-data-CEEMDAN.csv | 4 | 4 | 4 | 两指标峰值重合,置信度最高 |
提示:当
Best_K_by_Silhouette != Best_K_by_Calinski时(如EEMDV2_8),优先选silhouette更高的 K,因为轮廓系数直接反映业务可解释性——K=3时,cluster_sizes=[180,120,60],而K=5时出现[95,82,73,65,45],最小簇仅 45 样本,难以支撑故障模式分析。
4.4 进阶技巧:用kmeans_stability.py验证 K 的鲁棒性
仅看单次运行结果不够。该脚本对同一数据集重复运行 50 次 KMeans(每次random_state不同),统计:
stability_score:各次运行中,同一样本被分到相同簇的频率均值(>0.85 为稳定)consensus_matrix:构建n_samples x n_samples矩阵,M[i,j]=1若样本 i,j 在 ≥40 次运行中同簇,否则 0
运行python kmeans_stability.py --dataset k-data-CEEMDAN.csv --k 4,输出:
Stability score for K=4: 0.892 Consensus matrix density: 0.341 (higher = more stable clustering)若stability_score < 0.75,说明 K 值受初始化影响过大,需换数据源(如从EEMD切到CEEMDAN)或增加特征维度。
5. 把 PNG 可视化变成业务语言:三步导出可汇报的聚类结论
5.1 从cluster_scatter_*.png提炼设备状态标签
cluster_scatter_EEMD1.png中,4 个簇的空间分布具有明确物理含义:
- 簇 0(蓝色):
IMF_3_std低(<0.15)、Residual_energy高(>-65 dB)→ 设备稳态运行,残差能量主导 - 簇 1(橙色):
IMF_3_std高(>0.25)、Residual_energy中(-85~-70 dB)→ 存在中频冲击,疑似轴承早期磨损 - 簇 2(绿色):
IMF_1_max_freq>1800 Hz、IMF_3_std中 → 高频噪声突增,可能为传感器接触不良 - 簇 3(红色):
Residual_energy极低(<-95 dB)、IMF_5_kurtosis>8 → 残差能量耗尽,IMF_5 尖峰显著,对应严重故障阶段
这不是主观猜测。
label_interpreter.py脚本内置规则引擎:if (X[i, 0] < 0.15) and (X[i, 2] > -65): # IMF_3_std <0.15, Residual_energy >-65 label_name = "Normal" elif (X[i, 0] > 0.25) and (-85 < X[i, 2] < -70): label_name = "Bearing_Wear_Early" # ... 其余规则
5.2 用distance_heatmap_*.png定位异常时间窗
热力图横轴为时间序列索引,找到连续 5 个以上红色区块(距离 >1.2)的区间,例如sample_index: 120-125,对应原始时间戳2023-05-12 14:00:00至14:25:00。此时:
- 回查
data_EEMD1.csv中该时段IMF_1分量波形 → 发现周期性冲击 - 查
k-data-EEMD1.csv中IMF_1_max_freq列 → 该时段值跃升至 2100 Hz - 结论:在 14:00–14:25 发生轴承外圈缺陷,建议 48 小时内停机检测
5.3 生成《聚类分析报告》的自动化流水线
generate_report.py整合全部输出:
# 读取 metrics、labels、distance_array、centroid_trajectory report = { "summary": f"K={best_k} clusters identified on {dataset}", "metrics": {"silhouette": 0.623, "CH": 1248.7}, "cluster_distribution": [142, 98, 67, 53], "anomaly_windows": [(120,125), (288,292)], # 时间窗列表 "action_items": [ "Check bearing at timestamp 2023-05-12 14:00:00", "Verify sensor contact at 2023-05-12 19:30:00" ] } # 导出为 PDF(用 reportlab)和 Excel(含原始距离数据)运行python generate_report.py --dataset k-data-CEEMDAN.csv --k 4,10 秒生成带图表、结论、行动项的双格式报告。
6. 我再也不敢跳过这三步:从血泪翻车现场总结出的 KMeans 时序聚类铁律
6.1 铁律一:不做 EEMD/CEEMDAN 分解,就别碰工业时序聚类
去年调试一台齿轮箱振动数据,直接用原始信号跑 KMeans,silhouette=0.21,业务方说“这分的是噪声还是故障?”。后来用data_CEEMDAN.csv重跑,silhouette升至 0.68,簇 2 的IMF_2_skewness显著高于其他簇(p<0.001),对应齿轮断齿的冲击特征。EEMD/CEEMDAN 不是锦上添花,是把混沌信号掰开揉碎的手术刀——data_EEMDV2_9.csv里IMF_4分量信噪比比EEMD1.csv高 17%,直接让K=4的轮廓系数从 0.59 提到 0.63。所以,拿到新时序数据第一件事:用eemd_decompose.py(包内提供)跑一遍 CEEMDAN,再选k-data-*.csv中信噪比最高的那个。
6.2 铁律二:StandardScaler是时序特征的后悔药,MinMaxScaler是慢性毒药
曾用MinMaxScaler处理IMF_1_max_freq(0–2500 Hz)和Residual_energy(-120~-40 dB),KMeans质心全被IMF_1_max_freq拉偏。换成StandardScaler后,各特征对距离计算的贡献权重回归均衡(PCA 载荷矩阵显示前 3 主成分累计方差 87%)。更关键的是,StandardScaler对异常值鲁棒——IMF_5_kurtosis出现 12 个 >20 的尖峰,MinMaxScaler会把它压缩到 [0,1] 区间顶部,而StandardScaler仅将其拉回均值±3σ 范围,保留了异常模式的辨识度。
6.3 铁律三:验证必须用stability_score,不能只信单次silhouette
有一次用k-data-EEMD1.csv得到silhouette=0.65,兴奋地交付报告。客户复现时silhouette=0.42。查kmeans_stability.py输出:stability_score=0.61,远低于 0.75 阈值。根源是EEMD1分解的IMF_3分量在该批次数据中模态混叠严重,导致特征不稳定。立刻切换到k-data-CEEMDAN.csv,stability_score=0.89,silhouette两次运行误差 <0.02。单次 silhouette 是快照,stability_score 是录像——没看过录像,别交快照。
从那以后我每次跑 KMeans,都强制走一遍kmeans_stability.py,哪怕多等 3 分钟。它不保证结果完美,但能筛掉 80% 的伪阳性结论。希望帮到你。
本文还有配套的精品资源,点击获取