ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

四种聚类算法源代码及示例代码:K-Means、DBSCAN、层次聚类、GMM 统一实现与调参指南

四种聚类算法源代码及示例代码:K-Means、DBSCAN、层次聚类、GMM 统一实现与调参指南 简介这份资源面向机器学习初学者与需要快速上手聚类分析的MATLAB用户提供FCM模糊C均值、K-means、Kmedoid等经典聚类算法的源代码与示例并附带FuzzyClusteringToolbox工具箱帮助读者理解无监督分类的数学原理与实现细节。压缩包共74个文件约2.05MB以40个m脚本为核心配合gif与png演示图、txt说明、htm与css页面及mat数据文件覆盖算法实现、数据预处理、结果可视化与性能评估等环节。资源中FCM允许样本以隶属度同时归属多类K-means通过迭代更新聚类中心Kmedoid则引入媒体点增强对非球形分布的适应性示例还涉及归一化、PCA降维与聚类有效性指标。已有4883人学习下载适合对照代码调试参数、比较不同算法在模糊边界与凸形数据上的表现差异。1. 四种聚类算法源代码及示例代码从一份能跑通的骨架说起很多人第一次拿到「四种聚类算法源代码及示例代码」这类资源时习惯直接复制粘贴到编辑器里点运行结果要么报错要么跑出来的图跟教程里完全不一样。问题不在代码本身而在于聚类这件事对数据分布、参数、距离度量极度敏感——同一份 K-Means 代码换一组没归一化的特征簇心能飘到你看不懂的地方。我写这篇的出发点很直接把 K-Means、层次聚类、DBSCAN、GMM 这四种最常被放在一起对比的算法用一套统一的接口串起来让你能在一份数据上横向跑完四种看清它们各自的脾气。适合已经会写 Python、但被「到底该选哪个聚类算法」卡住的同学也适合需要把聚类塞进业务流水线、想知道参数边界在哪的工程师。下面所有代码都是可复现的最小骨架不依赖任何私有包。2. 四种聚类算法的选型逻辑与统一数据接口2.1 为什么是这四种而不是别的聚类算法上百种但真正在工程里被反复拿出来对比的基本就是这四类代表基于划分的 K-Means、基于层次的 Agglomerative、基于密度的 DBSCAN、基于概率生成的 Gaussian Mixture。它们覆盖了四种完全不同的假设——K-Means 假设簇是凸的、大小相近层次聚类假设你关心的是「合并顺序」这棵树DBSCAN 假设簇是高密度区域被低密度隔开GMM 假设数据由若干个高斯分布混合生成。选型时先问自己三个问题簇形状是不是球形噪声点多不多要不要给出「这个点属于某簇的概率」球形且噪声少K-Means 最快形状任意且有噪声DBSCAN 更稳需要软分配GMM需要看层次结构Agglomerative。热搜里 dbscan聚类算法 出现频率很高但很多人忽略它的核心参数 eps 和 min_samples 是跟数据尺度强绑定的。同一份数据不做标准化eps 设 0.5 和设 5 得到的结果天差地别。这也是我把标准化放在所有算法之前的原因。2.2 统一数据接口一份数据喂四种算法为了让对比公平先写一个数据准备模块。用 sklearn 自带的 make_moons 造一组非线性可分的数据再加一点噪声这样四种算法的差异会非常明显。# data_prep.py import numpy as np from sklearn.datasets import make_moons from sklearn.preprocessing import StandardScaler def load_data(n_samples600, noise0.08, random_state42): # 双月数据非线性可分专门用来暴露 K-Means 的短板 X, y_true make_moons(n_samplesn_samples, noisenoise, random_staterandom_state) # 标准化DBSCAN 和 GMM 对尺度敏感必须做 X StandardScaler().fit_transform(X) return X, y_true if __name__ __main__: X, y load_data() print(shape:, X.shape, label dist:, np.bincount(y))这段代码做了两件事造数据、标准化。noise0.08控制月牙的松散程度调大到 0.15 以上时 DBSCAN 的 eps 需要同步放大否则会把整片区域判成噪声。StandardScaler把每个特征拉到均值 0、方差 1这一步不做后面 DBSCAN 的 eps 调参基本靠猜。random_state固定住保证你复现出来的图和本文一致。2.3 四种算法的统一封装下面把四种算法包成同一个调用签名输入 X输出 labels方便后面统一评估。# clusterers.py import numpy as np from sklearn.cluster import KMeans, AgglomerativeClustering, DBSCAN from sklearn.mixture import GaussianMixture def run_kmeans(X, n_clusters2, random_state42): # n_init 显式设 10避免新版 sklearn 默认值变化导致结果漂移 model KMeans(n_clustersn_clusters, n_init10, random_staterandom_state) return model.fit_predict(X), model def run_agglomerative(X, n_clusters2, linkageward): # linkage 可选 ward / complete / average / single model AgglomerativeClustering(n_clustersn_clusters, linkagelinkage) return model.fit_predict(X), model def run_dbscan(X, eps0.3, min_samples5): # eps 是邻域半径min_samples 是核心点的最小邻居数 model DBSCAN(epseps, min_samplesmin_samples) return model.fit_predict(X), model def run_gmm(X, n_components2, covariance_typefull, random_state42): # covariance_type 决定每个高斯分量的协方差形状 model GaussianMixture(n_componentsn_components, covariance_typecovariance_type, random_staterandom_state) labels model.fit_predict(X) return labels, model四个函数的参数含义需要说清楚。K-Means 的n_init10是跑 10 次不同初始化取最优不设的话新版 sklearn 会警告。Agglomerative 的linkage决定簇间距离怎么算ward 最小化簇内方差适合球形簇single 容易产生链状簇对双月数据反而可能有效。DBSCAN 的eps是半径min_samples是核心点门槛这两个参数必须一起调。GMM 的covariance_type里 full 最灵活但参数最多数据量小时容易过拟合可以退到 diag 或 spherical。2.4 一次跑完四种并可视化# compare.py import matplotlib.pyplot as plt from data_prep import load_data from clusterers import run_kmeans, run_agglomerative, run_dbscan, run_gmm X, y_true load_data() results { KMeans: run_kmeans(X, n_clusters2)[0], Agglomerative: run_agglomerative(X, n_clusters2, linkageward)[0], DBSCAN: run_dbscan(X, eps0.3, min_samples5)[0], GMM: run_gmm(X, n_components2)[0], } fig, axes plt.subplots(1, 5, figsize(22, 4)) axes[0].scatter(X[:, 0], X[:, 1], cy_true, s8) axes[0].set_title(Ground Truth) for ax, (name, labels) in zip(axes[1:], results.items()): ax.scatter(X[:, 0], X[:, 1], clabels, s8, cmapviridis) ax.set_title(name) plt.tight_layout() plt.savefig(compare.png, dpi120)跑完你会看到K-Means 把双月从中间切开因为它只会找凸簇Agglomerative 用 ward 也切得差不多DBSCAN 能把两个月牙分开但边缘一些点被标成 -1噪声GMM 因为 full 协方差能拟合出弯曲的边界效果接近 DBSCAN。这张图就是选型的第一手依据——如果你的数据长得像双月别用 K-Means。3. 参数怎么调四种算法的关键旋钮与评估指标3.1 K-Means 的 K 怎么定肘部法与轮廓系数K-Means 唯一必须人为指定的就是簇数 K。两种常用做法肘部法看 inertia 的拐点轮廓系数看簇的紧致与分离。# kmeans_tune.py import numpy as np from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score from data_prep import load_data X, _ load_data() inertias, sils [], [] K_range range(2, 9) for k in K_range: km KMeans(n_clustersk, n_init10, random_state42) labels km.fit_predict(X) inertias.append(km.inertia_) sils.append(silhouette_score(X, labels)) for k, i, s in zip(K_range, inertias, sils): print(fK{k} inertia{i:.2f} silhouette{s:.3f})inertia_是簇内平方和随 K 增大单调下降看的是下降速度突然变缓的那个点。silhouette_score取值 -1 到 1越接近 1 越好但它对凸簇友好对双月这种数据会给出偏低的分数别只信它。实际调参时两个指标一起看再结合业务上「簇数有没有意义」来定。3.2 DBSCAN 的 eps 与 min_samplesk-距离图DBSCAN 最让人头疼的就是 eps。一个可操作的流程是先画 k-距离图找拐点。# dbscan_tune.py import numpy as np from sklearn.neighbors import NearestNeighbors import matplotlib.pyplot as plt from data_prep import load_data X, _ load_data() k 5 # 与 min_samples 保持一致 nbrs NearestNeighbors(n_neighborsk).fit(X) distances, _ nbrs.kneighbors(X) distances np.sort(distances[:, k-1], axis0) plt.plot(distances) plt.xlabel(Points sorted by distance) plt.ylabel(f{k}-th nearest neighbor distance) plt.savefig(kdist.png, dpi120)图里曲线急剧上升的那个拐点对应的 y 值就是 eps 的合理起点。min_samples一般取特征维度加 1 起步二维数据取 4 到 6 都行数据噪声大就往上加。注意 DBSCAN 输出的 -1 是噪声标签评估时要先剔除否则 silhouette_score 会算错。3.3 GMM 的协方差类型与 BIC 选择GMM 的簇数可以用 BIC 或 AIC 来选值越小越好。# gmm_tune.py from sklearn.mixture import GaussianMixture from data_prep import load_data X, _ load_data() for cov in [full, diag, spherical]: for n in range(2, 7): gmm GaussianMixture(n_componentsn, covariance_typecov, random_state42) gmm.fit(X) print(fcov{cov:9s} n{n} BIC{gmm.bic(X):.1f} AIC{gmm.aic(X):.1f})full每个分量有独立完整协方差矩阵参数最多diag只保留对角适合特征独立spherical所有方向方差相同最省参数。数据量小于几千时优先试 diag能显著降低过拟合风险。BIC 比 AIC 惩罚更重倾向选简单模型。3.4 统一评估ARI 与 NMI有真实标签时用调整兰德指数 ARI 和标准化互信息 NMI两者都对标签置换不敏感。# evaluate.py from sklearn.metrics import adjusted_rand_score, normalized_mutual_info_score from data_prep import load_data from clusterers import run_kmeans, run_dbscan, run_gmm X, y_true load_data() for name, labels in [ (KMeans, run_kmeans(X, 2)[0]), (DBSCAN, run_dbscan(X, 0.3, 5)[0]), (GMM, run_gmm(X, 2)[0]), ]: mask labels ! -1 # 剔除噪声点 ari adjusted_rand_score(y_true[mask], labels[mask]) nmi normalized_mutual_info_score(y_true[mask], labels[mask]) print(f{name:8s} ARI{ari:.3f} NMI{nmi:.3f})ARI 取值约 -0.5 到 10 表示随机1 表示完全一致。NMI 取值 0 到 1。DBSCAN 一定要先 mask 掉 -1否则噪声点会被当成一个独立簇参与计算指标虚高。4. 避坑与排查四种聚类算法最常见的五个翻车现场4.1 不标准化直接跑 DBSCAN结果全是噪声现象DBSCAN 跑完 labels 里 -1 占了一大半或者所有点归成一个簇。原因某个特征量纲远大于其他特征距离计算被它主导eps 在原始尺度下要么太小要么太大。解决跑之前一律 StandardScaler 或 MinMaxScaler然后重新用 k-距离图定 eps。我自己的习惯是数据进任何聚类算法前先做一次尺度检查X.std(axis0)里最大值和最小值差两个数量级以上就必须标准化。4.2 K-Means 的 n_init 没设每次结果不一样现象同一份数据跑两次簇的编号和边界都变了。原因K-Means 对初始簇心敏感旧版 sklearn 默认 n_init10新版改成 auto 后行为可能变化。解决显式写n_init10或更高并固定random_state。如果数据量大可以用initk-means加速收敛。4.3 用轮廓系数评估 DBSCAN分数低得离谱现象DBSCAN 明明分得对silhouette_score 却只有 0.1。原因轮廓系数假设簇是凸的DBSCAN 能找出任意形状的簇指标本身不适用另外 -1 噪声点没剔除会拉低分数。解决DBSCAN 优先看 ARI/NMI有标签时无标签时看簇的密度连续性和业务合理性别硬套轮廓系数。4.4 GMM 在少量数据上 full 协方差直接过拟合现象GMM 把每个点都当成一个小高斯n_components 稍大就报奇异矩阵错误。原因full 协方差每个分量要估计 d*(d1)/2 个参数样本不够就估计不准。解决加reg_covar1e-6做正则或把 covariance_type 降到 diag。数据量少于特征数 10 倍时我一般直接用 diag。4.5 层次聚类 linkage 选 single簇被拉成一条链现象Agglomerative 用 single linkage最后所有点连成一串看不出簇结构。原因single 只取两簇最近点距离容易产生链式效应对噪声极敏感。解决球形簇用 ward噪声多用 complete 或 average。如果数据本身是长条形single 反而可能对但要配合可视化确认。5. 把四种算法塞进流水线一个可复用的自动选型脚本前面都是单点验证真正落地时你需要的是一段能自动跑完四种、给出推荐结果的脚本。核心思路先用 k-距离图估 DBSCAN 的 eps用 BIC 估 GMM 的簇数用肘部法估 K-Means 的 K然后统一评估按 ARI 或轮廓系数排序输出。# auto_select.py import numpy as np from sklearn.neighbors import NearestNeighbors from sklearn.metrics import silhouette_score, adjusted_rand_score from data_prep import load_data from clusterers import run_kmeans, run_agglomerative, run_dbscan, run_gmm def estimate_eps(X, k5): nbrs NearestNeighbors(n_neighborsk).fit(X) dist, _ nbrs.kneighbors(X) dist np.sort(dist[:, k-1]) # 取最大曲率点作为 eps 估计 diffs np.diff(dist) knee np.argmax(diffs) 1 return float(dist[knee]) def auto_select(X, y_trueNone): eps estimate_eps(X) candidates { KMeans: run_kmeans(X, n_clusters2)[0], Agglomerative: run_agglomerative(X, n_clusters2, linkageward)[0], DBSCAN: run_dbscan(X, epseps, min_samples5)[0], GMM: run_gmm(X, n_components2)[0], } report [] for name, labels in candidates.items(): mask labels ! -1 if len(set(labels[mask])) 2: report.append((name, -1.0, -1.0)) continue sil silhouette_score(X[mask], labels[mask]) ari adjusted_rand_score(y_true[mask], labels[mask]) if y_true is not None else None report.append((name, sil, ari)) report.sort(keylambda r: r[1], reverseTrue) return report, eps if __name__ __main__: X, y load_data() report, eps auto_select(X, y) print(festimated eps {eps:.3f}) for name, sil, ari in report: print(f{name:14s} silhouette{sil:.3f} ARI{ari if ari is None else round(ari,3)})这段脚本里estimate_eps用最大曲率近似 k-距离图的拐点比手画图快但只是起点最终 eps 还要在拐点附近手动试两三个值。auto_select把四种算法跑一遍按轮廓系数排序有真实标签时同时输出 ARI。注意 DBSCAN 的 -1 在评估前被 mask 掉避免噪声干扰。一个我踩过的坑自动选型脚本不要直接用在生产数据上做决策。它给的是候选排序不是最终答案。真实业务里簇的「意义」往往比指标更重要——轮廓系数最高的方案可能把用户分成两个毫无业务解释的群体。我的习惯是脚本跑完把前两名的标签导出来抽样看每个簇的特征均值确认业务上说得通再上线。最后说个验证技巧把四种算法的标签存成 CSV用同一套降维比如 PCA 降到二维画在一张图上对比。降维只用于可视化不参与聚类。这样你能直观看到 K-Means 的直线边界、DBSCAN 的噪声点、GMM 的椭圆边界。我一般会把这个对比图作为选型评审的第一页材料比任何指标都直观。希望帮到你。本文还有配套的精品资源点击获取
返回列表