多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Python实现KMeans聚类算法:源码解析与数据集实战指南

Python实现KMeans聚类算法:源码解析与数据集实战指南 简介这份资源面向机器学习初学者与数据挖掘实践者提供一套可直接运行的KMeans聚类算法Python实现方案帮助读者理解从数据预处理、核心算法执行到结果可视化的完整聚类分析流程。压缩包共246个文件约35.02MB其中141个CSV数据集文件用于承载多组实验数据16个Python程序文件构成算法主体43个PNG与2个JPG图表文件呈现聚类结果另有若干备份与说明文件目录结构清晰、便于按模块检索。已有62人学习下载适合作为课程作业、算法练习或聚类研究的参考范例。读者可借此掌握KMeans的编码实现思路、数据组织方式与结果呈现方法并基于现成数据集快速复现实验、对比不同数据下的聚类效果为后续调参与扩展打下基础。1. 从一堆散点到 KMeansPython 聚类源码与数据集到底该怎么落地手上有一份客户行为表、一批传感器读数或者几千条文本向量老板让你「分个群看看」。这时候 KMeans 往往是第一个被想起来的算法不用标签、调参少、跑得快。但真到写代码问题立刻冒出来——数据要不要标准化、K 怎么定、初始点怎么选、结果怎么评估、数据集从哪来。标题里的「Python 实现 KMeans 聚类算法源码及数据集」讲的正是这条从原始数据到可用分群的完整链路。它适合两类人刚学完 python 语法、想找一个能跑通的项目练手的入门者以及手上有真实业务数据、需要快速拿到基线分群结果的工程师。下面按「原理选型 → 源码实现 → 数据集处理 → 避坑 → 进阶验证」的顺序把每一步都落到能复现的代码和参数上。2. KMeans 的数学直觉与选型边界为什么它仍是聚类基线2.1 目标函数与迭代逻辑KMeans 要解决的问题可以用一句话概括把 n 个样本划分到 K 个簇里让每个样本到它所属簇中心的距离平方和最小。这个量叫 SSESum of Squared Errors也叫簇内平方和SSE Σ(k1→K) Σ(x∈Ck) ||x − μk||²其中 μk 是第 k 个簇的均值向量。算法本身是个交替优化过程先固定中心把每个点分配给最近的中心再固定分配把每个中心更新为簇内点的均值。这两步反复执行SSE 单调不增因此一定收敛——但收敛到的是局部最优不是全局最优。这一点决定了后面所有关于初始化的讨论。理解这个目标函数很关键因为它直接解释了 KMeans 的三个隐含假设簇是凸的、各簇大小大致均衡、各维度方差接近。数据一旦违反这些假设比如出现环形簇、密度差异极大的簇KMeans 就会给出反直觉的结果。这不是代码写错了是模型本身的边界。2.2 和 DBSCAN、层次聚类的选型对比热搜里 dbscan 聚类算法 经常和 KMeans 一起出现很多人纠结选哪个。我的经验是按数据形态和数据量两个维度判断维度KMeansDBSCAN层次聚类是否需要预设簇数需要不需要不需要能识别任意形状簇否是部分对噪声点处理全部分配标记为噪声全部分配时间复杂度O(n·K·d·迭代)O(n log n) 左右O(n²) 以上适合数据量十万级以上万级千级以内选型结论很直接数据量大、簇形状接近球形、需要快速出结果用 KMeans数据里有明显噪声、簇形状不规则用 DBSCAN数据量小、想要看聚类树状结构用层次聚类。实际项目里我一般先用 KMeans 跑一版基线看轮廓系数和业务解释性再决定要不要换模型。2.3 距离度量与标准化的必要性KMeans 默认用欧氏距离。欧氏距离对量纲极其敏感如果一个特征是「年收入」单位元数值几万到几十万另一个是「年龄」单位岁数值 18 到 65那么距离几乎完全由收入决定年龄这个维度等于白给。所以标准化不是可选项是必做项。常见做法是 Z-Score 标准化减均值除标准差或 Min-Max 归一化。我一般用 Z-Score因为它对异常值的鲁棒性略好且保留了分布形状。代码上就是 sklearn 的 StandardScaler。注意标准化参数必须只在训练集上 fit再 transform 到全部数据否则会引入数据泄漏——虽然聚类没有严格意义上的标签泄漏但评估时如果用全量数据 fit 标准化器轮廓系数会偏乐观。提示如果特征里有类别型变量先做独热编码再标准化如果特征维度超过 50建议先做 PCA 降维否则距离计算会被大量弱相关维度稀释。3. 用 Python 从零实现 KMeans源码逐段拆解与 sklearn 对照3.1 纯 NumPy 版本核心循环只有 40 行先给一份不依赖 sklearn 的实现目的是让你看清每一步在做什么。这份代码可以直接复制运行import numpy as np def kmeans_numpy(X, k, max_iter300, tol1e-4, random_state42): X: (n_samples, n_features) 已标准化的数据 k: 簇数量 max_iter: 最大迭代次数 tol: 中心点变化小于该阈值时提前停止 rng np.random.RandomState(random_state) n_samples, n_features X.shape # 1. 随机选 k 个样本作为初始中心 idx rng.choice(n_samples, k, replaceFalse) centers X[idx].copy() for i in range(max_iter): # 2. 分配步计算每个点到各中心的距离取最近 # 用 (a-b)^2 a^2 b^2 - 2ab 展开避免显式双重循环 dists np.sum(X**2, axis1, keepdimsTrue) \ - 2 * X centers.T \ np.sum(centers**2, axis1) labels np.argmin(dists, axis1) # 3. 更新步重新计算每个簇的均值 new_centers np.array([ X[labels j].mean(axis0) if np.any(labels j) else centers[j] # 空簇保持原中心 for j in range(k) ]) # 4. 收敛判断中心移动量小于 tol 就停 shift np.sum((new_centers - centers) ** 2) centers new_centers if shift tol: print(f在第 {i1} 次迭代收敛) break return labels, centers逻辑说明分配步用矩阵运算一次性算出所有点到所有中心的距离比双重 for 循环快一到两个数量级。更新步里对空簇做了保护——如果某个中心没有分到任何点保持原位而不是变成 NaN这是很多人手写时翻车的地方。收敛判断用的是中心位移平方和tol 设 1e-4 是经验值数据标准化后这个量级比较合适。参数说明max_iter 默认 300 足够实际数据通常 20 到 50 次就收敛random_state 固定后结果可复现k 需要外部指定下一章讲怎么定。3.2 sklearn 版本生产环境该用的写法手写版用来理解原理生产环境直接用 sklearn.cluster.KMeans它内置了 k-means 初始化和多轮重启from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler from sklearn.metrics import silhouette_score import numpy as np # 假设 X_raw 是原始特征矩阵 scaler StandardScaler() X scaler.fit_transform(X_raw) km KMeans( n_clusters4, # 簇数需调 initk-means, # 初始中心选择策略 n_init10, # 用不同随机种子跑 10 次取 SSE 最小的 max_iter300, tol1e-4, random_state42 ) labels km.fit_predict(X) print(SSE:, km.inertia_) print(轮廓系数:, silhouette_score(X, labels))逻辑说明n_init10 是 sklearn 较新版本的默认值含义是用 10 组不同初始中心各跑一遍保留 SSE 最小的结果。这直接缓解了 KMeans 对初始点敏感的问题。initk-means 让初始中心尽量分散比纯随机好很多。km.inertia_ 就是 SSE用来做肘部法则。参数说明n_clusters 是唯一必须人工决定的参数n_init 越大越稳但越慢数据量大时设 5 到 10 即可max_iter 和 tol 一般不用动。3.3 k-means 初始化为什么能减少翻车纯随机初始化有个经典失败模式两个初始中心落在同一个真实簇里导致这个簇被劈成两半另一个真实簇被合并。k-means 的做法是第一个中心随机选之后每个中心以正比于「到已有中心最近距离的平方」的概率被选中。距离现有中心越远的点越可能成为下一个中心。这样初始中心天然分散SSE 最终结果通常比随机初始化低 10% 到 30%。代价是多了一次距离计算但相对于整体迭代开销可以忽略。所以除非你有特殊理由init 永远用 k-means。注意即使有 k-means也不能保证全局最优。如果业务对分群稳定性要求极高把 n_init 调到 20 以上并固定 random_state保证每次跑出来的标签一致。4. 数据集从哪来、怎么处理内置数据与自定义 CSV 两条路4.1 用 sklearn 内置数据集快速验证刚上手时不要急着找真实数据sklearn 自带几个适合聚类的数据集几行代码就能加载from sklearn.datasets import load_iris, make_blobs from sklearn.preprocessing import StandardScaler # 路线一真实小数据集 iris150 条 4 维 iris load_iris() X_iris StandardScaler().fit_transform(iris.data) # 路线二合成数据可控簇数和分布 X_syn, y_true make_blobs( n_samples1000, centers4, n_features2, cluster_std1.0, random_state42 ) X_syn StandardScaler().fit_transform(X_syn)逻辑说明iris 适合验证流程是否跑通但它只有 3 类且特征区分度高聚类太容易不适合评估算法能力。make_blobs 可以指定 centers真实簇数、cluster_std簇的松散程度用来测试 KMeans 在不同难度下的表现。把 cluster_std 从 1.0 调到 3.0你会看到轮廓系数明显下降这就是数据难度对聚类的影响。参数说明n_samples 控制数据量centers 是真实簇数用来和预测的 K 对比cluster_std 越大簇越重叠。4.2 加载自定义 CSV 与缺失值处理真实项目里数据基本是 CSV 或数据库导出的表格。加载和清洗的典型流程import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler from sklearn.impute import SimpleImputer df pd.read_csv(customer_data.csv) # 1. 只保留数值型特征用于聚类 num_cols df.select_dtypes(include[np.number]).columns.tolist() X_raw df[num_cols].values # 2. 缺失值填充中位数比均值更抗异常值 imputer SimpleImputer(strategymedian) X_filled imputer.fit_transform(X_raw) # 3. 标准化 X StandardScaler().fit_transform(X_filled) print(处理后形状:, X.shape) print(各维度均值(应接近0):, np.round(X.mean(axis0), 3))逻辑说明select_dtypes 自动筛掉文本列避免后续报错。缺失值用中位数填充因为很多业务指标收入、消费额是右偏分布均值会被极端值拉偏。标准化后检查各维度均值是否接近 0、标准差是否接近 1这是验证预处理是否正确的最快方式。参数说明strategy 可选 mean、median、most_frequent如果缺失比例超过 30%建议直接删列而不是填充。4.3 高维数据的降维预处理当特征维度超过 50距离计算会变得没有区分度——所有点对之间的距离都差不多这叫维度灾难。热搜里提到的各类高光谱数据集、图像数据集就属于这种场景。常见做法是先 PCA 降到 10 到 20 维from sklearn.decomposition import PCA pca PCA(n_components0.9) # 保留 90% 方差 X_pca pca.fit_transform(X) print(降维后维度:, X_pca.shape[1]) print(累计方差贡献:, pca.explained_variance_ratio_.sum())逻辑说明n_components 设成 0.9 表示自动选择能解释 90% 方差的最少主成分数比手动指定维度更省心。降维后再跑 KMeans速度和稳定性都会提升。注意 PCA 对量纲敏感必须在标准化之后做。参数说明n_components 可以是整数指定维度或 0 到 1 的小数指定方差比例如果数据稀疏改用 TruncatedSVD。5. 避坑与排查KMeans 实战中最容易翻车的 5 个点5.1 现象每次运行结果都不一样原因没有固定 random_state且 n_init 较小时不同初始中心导致收敛到不同局部最优。解决设置 random_state42任意固定值并把 n_init 提到 10 以上。如果业务要求标签跨批次可比还需要保存训练好的中心点新数据用最近中心分配而不是重新 fit。5.2 现象某个簇只有一两个点或者空簇原因K 设得过大或者数据里存在远离主体的异常点异常点自己成了一个簇。解决先做异常值检测比如 3 倍标准差或 IQR 方法剔除极端点再用肘部法则重新评估 K。sklearn 遇到空簇会自动处理但手写代码必须自己加保护否则会得到 NaN 中心。5.3 现象轮廓系数很高但业务上没法解释原因轮廓系数衡量的是几何分离度不代表分群有业务含义。高维数据里经常出现「数学上漂亮、业务上无用」的簇。解决把每个簇的中心点反标准化回原始量纲看各维度均值差异。如果簇之间的差异集中在某个无关紧要的维度上说明特征选择有问题需要引入业务先验筛选特征。5.4 现象数据量到百万级后跑得极慢原因标准 KMeans 每轮迭代要算 n×K 个距离n 大时内存和时间都吃不消。解决改用 MiniBatchKMeans每次只用一小批样本更新中心速度提升 10 倍以上精度损失通常在可接受范围。batch_size 设 1000 到 10000 之间数据量越大可以设越大。from sklearn.cluster import MiniBatchKMeans mbk MiniBatchKMeans( n_clusters8, batch_size4096, n_init5, random_state42 ) labels mbk.fit_predict(X)5.5 现象标准化后结果反而变差了原因某些特征本身量纲一致且业务上重要性不同盲目标准化抹掉了重要性差异。解决不要无脑标准化。先看各特征量纲是否一致如果一致比如都是 0 到 1 的评分可以跳过标准化。如果确实需要标准化但想保留重要性差异给关键特征乘一个权重系数再送进模型。6. 定 K 与验证肘部法则、轮廓系数和业务校验三件套6.1 肘部法则的代码实现与读数技巧肘部法则的思路是K 增大时 SSE 必然下降但下降速度会在某个点明显变缓那个拐点就是候选 K。import matplotlib.pyplot as plt from sklearn.cluster import KMeans sse [] k_range range(2, 11) for k in k_range: km KMeans(n_clustersk, n_init10, random_state42) km.fit(X) sse.append(km.inertia_) plt.plot(k_range, sse, markero) plt.xlabel(K) plt.ylabel(SSE) plt.title(Elbow Method) plt.show()逻辑说明inertia_ 就是 SSE。实际看图时拐点往往不锐利这时候不要死磕「最尖的那个点」而是把拐点附近的 2 到 3 个 K 都跑一遍结合轮廓系数和业务解释性一起判断。参数说明k_range 一般从 2 开始到 10 或 sqrt(n/2) 为止n_init 固定 10 保证可比性。6.2 轮廓系数的计算与阈值判断轮廓系数衡量每个点与自身簇的紧密度和与最近邻簇的分离度取值 -1 到 1越大越好。from sklearn.metrics import silhouette_score for k in range(2, 8): km KMeans(n_clustersk, n_init10, random_state42) labels km.fit_predict(X) score silhouette_score(X, labels) print(fK{k}, 轮廓系数{score:.4f})经验阈值0.5 以上说明分群结构清晰0.3 到 0.5 属于可接受需要业务判断低于 0.25 说明数据本身没有明显簇结构强行聚类意义不大。注意轮廓系数在簇大小极不均衡时会偏低这时候要结合其他指标。6.3 用业务指标做最终校验数学指标只能筛掉明显不合理的 K最终决定要靠业务。我一般会做一张簇画像表把每个簇的中心点反标准化列出各特征均值再算每个簇的样本占比。如果某个簇占比不到 5%或者两个簇的画像几乎一样就说明 K 偏大。反过来如果某个簇内部业务指标方差极大说明 K 偏小需要拆分。centers_original scaler.inverse_transform(km.cluster_centers_) profile pd.DataFrame(centers_original, columnsnum_cols) profile[样本数] pd.Series(labels).value_counts().sort_index().values profile[占比] (profile[样本数] / len(labels)).round(3) print(profile)这张表拿给业务方看比任何系数都有说服力。我踩过的坑是曾经用轮廓系数选了一个 K数学上最优但业务方一看画像说「这两个群在我们眼里是同一类客户」最后只能重来。所以定 K 这件事数学给候选业务拍板。希望帮到你。本文还有配套的精品资源点击获取
返回列表