别再只用K-Means了!用Scipy的linkage函数玩转层次聚类(Python代码实战)

张开发
2026/4/16 17:04:50 15 分钟阅读

分享文章

别再只用K-Means了!用Scipy的linkage函数玩转层次聚类(Python代码实战)
层次聚类实战用Scipy的linkage函数替代K-Means的五大场景当你第20次手动调整K-Means的n_clusters参数时有没有想过这样一个问题为什么我们要像算命先生一样猜测数据应该分成几类这就是层次聚类Hierarchical Clustering最迷人的地方——它不需要预先指定聚类数量而是像剥洋葱一样层层揭示数据的内在结构。今天我们就来解锁Scipy中那个被严重低估的linkage函数看看如何用它解决K-Means搞不定的实际问题。1. 为什么层次聚类值得你多花3分钟学习每次看到数据科学新人清一色地使用K-Means时我都想给他们展示这个对比实验用相同的数据集分别运行K-Means和层次聚类结果差异常常令人惊讶。层次聚类通过构建树状图dendrogram保留了完整的聚类过程这带来三个独特优势无需预设K值通过切割树状图获得任意数量的聚类抗噪声能力不像K-Means那样容易被离群点带偏中心位置可视化解释性树状图能直观展示数据层次关系实际案例某电商平台的用户行为分析中使用K-Means需要反复测试K5到K15的各种分组而层次聚类直接通过树状图发现8个自然分组节省了60%的调参时间。下表对比了两种算法的核心差异特性K-Means层次聚类聚类形状仅适合球形簇适应任意形状簇计算复杂度O(n)O(n³)结果稳定性受初始中心影响大确定性算法结果唯一最佳适用场景大数据量快速聚类中小数据集的精细分析# 快速体验层次聚类与K-Means的差异 from sklearn.cluster import KMeans from scipy.cluster.hierarchy import linkage, dendrogram import matplotlib.pyplot as plt import numpy as np # 生成测试数据 np.random.seed(42) data np.concatenate([ np.random.normal(loc0, scale0.5, size(50, 2)), np.random.normal(loc3, scale1, size(50, 2)) ]) # K-Means聚类 kmeans KMeans(n_clusters2).fit(data) plt.scatter(data[:,0], data[:,1], ckmeans.labels_) plt.title(K-Means聚类结果) # 层次聚类 plt.figure() Z linkage(data, ward) dendrogram(Z) plt.title(层次聚类树状图)2. linkage函数的六种武器如何选择method参数Scipy的linkage函数提供了method参数就像瑞士军刀的不同工具每种方法计算类间距离的策略截然不同。理解这些差异是避免垃圾聚类的关键2.1 Ward法方差最小化的优雅方案Ward方法methodward是我最常推荐的选择它通过最小化合并后的类内方差来决定聚类顺序。想象两个泡泡合并时选择让新泡泡最紧凑的组合# Ward法最佳实践 Z_ward linkage(data, ward) plt.figure() dendrogram(Z_ward) plt.title(Ward方法树状图)适用场景数据分布接近球形簇时效果最佳坑点警示必须使用欧式距离metriceuclidean2.2 单连接与全连接两极之间的选择单连接single和全连接complete像是聚类的两个极端性格single-linkage像个浪漫主义者只要两类中有任意两点相近就合并Z_single linkage(data, single)complete-linkage则像保守派要求两类所有点都足够接近才合并Z_complete linkage(data, complete)实际项目中我发现这两种方法特别适合以下情况单连接适合发现细长形、蜿蜒的簇结构如地理路径分析全连接对噪声更鲁棒适合质量参差不齐的数据2.3 平均连接的平衡之道average方法取两类之间所有点距离的平均值是前两种方法的折中方案。在文本聚类项目中我常用它来处理TF-IDF向量from sklearn.feature_extraction.text import TfidfVectorizer docs [机器学习 深度学习 神经网络, Python 编程 数据分析, 神经网络 自然语言处理, Java 编程 软件开发] vectorizer TfidfVectorizer() X vectorizer.fit_transform(docs) Z_avg linkage(X.toarray(), average, metriccosine)3. 从理论到实战完整项目流水线让我们通过一个真实案例——电商用户分群看看层次聚类如何从数据预处理到结果解读的全流程应用。3.1 数据准备与距离矩阵好的距离度量是层次聚类的基石。对于混合型数据数值分类我推荐使用gower距离import pandas as pd from scipy.spatial.distance import pdist # 模拟电商用户数据 users pd.DataFrame({ age: [25, 45, 30, 35, 28], spending: [500, 2000, 800, 1200, 600], favorite_category: [electronics, fashion, fashion, books, electronics] }) # 自定义距离计算 def mixed_metric(u, v): # 数值特征用欧式距离 num_dist np.sqrt((u[0]-v[0])**2 (u[1]-v[1])**2) # 分类特征用简单匹配 cat_dist 0 if u[2]v[2] else 1 return 0.7*num_dist 0.3*cat_dist # 加权组合 dist_matrix pdist(users.values, metricmixed_metric)3.2 聚类执行与树状图解读生成树状图后如何确定最佳切割高度这里有个实用技巧Z linkage(dist_matrix, ward) # 绘制带有颜色标记的树状图 plt.figure(figsize(10,5)) dendrogram(Z, truncate_modelastp, p12, show_leaf_countsTrue, leaf_rotation90) plt.axhline(y3.5, colorr, linestyle--) # 尝试切割线通过观察树状图纵轴距离的突变点可以找到自然的切割位置。上图中红色虚线就是可能的合理分割。3.3 结果提取与业务映射使用fcluster提取聚类标签并与原始数据关联from scipy.cluster.hierarchy import fcluster clusters fcluster(Z, t3.5, criteriondistance) users[cluster] clusters # 分析各簇特征 cluster_profile users.groupby(cluster).agg({ age: mean, spending: [mean, count], favorite_category: lambda x: x.mode()[0] })4. 性能优化与高级技巧当数据量超过5000条时层次聚类会变得缓慢。这时可以采用这些优化策略4.1 采样与批处理技术# 分层抽样保持分布 sample_idx np.random.choice(len(data), size2000, replaceFalse) sample_data data[sample_idx] # 先在小样本上确定最佳切割高度 Z_sample linkage(sample_data, ward) optimal_height find_elbow(Z_sample[:,2]) # 自定义肘部法则函数 # 全量数据聚类 full_Z linkage(data, ward) clusters fcluster(full_Z, toptimal_height, criteriondistance)4.2 并行计算与近似算法对于超大规模数据可以考虑这些替代方案FastClusterC实现的加速版本import fastcluster Z fastcluster.linkage(data, methodward)近似算法BIRCH、CURE等专门针对大数据的层次聚类变种4.3 结果稳定性评估通过bootstrap评估聚类稳定性from sklearn.utils import resample n_iterations 10 cluster_results [] for _ in range(n_iterations): sample_data resample(data, replaceTrue) Z linkage(sample_data, ward) clusters fcluster(Z, t3.5, criteriondistance) cluster_results.append(clusters) # 计算相似度矩阵 similarity np.zeros((len(data), len(data))) for clusters in cluster_results: for i in range(len(data)): for j in range(i1, len(data)): similarity[i,j] (clusters[i] clusters[j])5. 常见陷阱与解决方案在我辅导过的数据团队中这些错误出现的频率最高5.1 距离度量与方法不匹配典型错误# 错误示范Ward方法使用非欧式距离 Z linkage(data, ward, metriccosine) # 可能产生误导性结果正确做法# 先用欧式距离标准化数据 from sklearn.preprocessing import Normalizer norm_data Normalizer().fit_transform(data) Z linkage(norm_data, ward)5.2 忽略数据尺度差异不同特征的量纲差异会扭曲距离计算。解决方案from sklearn.preprocessing import StandardScaler scaler StandardScaler() scaled_data scaler.fit_transform(data) Z linkage(scaled_data, ward)5.3 树状图解读误区新手常犯的错误是机械地选择等距切割而忽略了这些原则寻找垂直距离突变明显的区域结合业务需求确定分组粒度用轮廓系数辅助验证from sklearn.metrics import silhouette_score score silhouette_score(data, clusters)最近在一个金融风控项目中团队最初使用K-Means将用户分成5组但模型效果不佳。改用层次聚类后通过树状图发现了7个自然分组其中两个特殊的小群体占总用户3%后来被证实是欺诈风险最高的群体——这正是K-Means容易忽略的小簇问题。

更多文章