聚类分析(KMeans/DBSCAN/层次聚类)

数据分析专题 · 无监督学习的数据分组

专题:Python数据分析系统学习 - 无监督学习篇

关键词:聚类, KMeans, DBSCAN, 层次聚类, 轮廓系数, 肘部法, 无监督学习, scikit-learn

目录:

一、聚类分析概述

聚类分析(Clustering Analysis)是无监督学习中最核心的任务之一。它的目标是将数据集中相似的对象划分到同一个组(簇)中,使得同一簇内的样本尽可能相似,不同簇之间的样本尽可能不同。与分类任务不同,聚类在训练时不需要样本的标签信息,完全依赖数据自身的内在结构进行分组。

聚类分析在数据科学领域有着广泛的应用:市场细分中将消费者按行为特征分组、图像处理中将像素点聚合成区域、生物信息学中挖掘基因表达模式、推荐系统中发现用户兴趣群组、异常检测中识别偏离正常模式的离群点。可以说,凡是需要"发现数据中的自然分组"的场景,聚类都是首选的探索工具。

核心思想:物以类聚,人以群分。聚类算法自动发现数据中隐藏的结构化分组信息,无需人工标注。

常见的聚类算法可以分为四大类:基于划分的聚类(如KMeans,通过迭代优化簇内距离)、基于密度的聚类(如DBSCAN,通过密度连通性发现任意形状的簇)、层次聚类(如Agglomerative,自底向上或自顶向下构建聚类树)、以及基于模型的聚类(如高斯混合模型GMM,假设数据服从概率分布)。

选择聚类算法时需要考虑数据的特性:簇的形状是否为凸形、数据是否包含噪声和离群点、簇的密度是否均匀、是否需要预先指定簇的数量、数据集规模有多大。这些因素直接影响不同算法在特定任务上的表现。下面我们将逐一深入剖析三种最主流、最具代表性的聚类算法。

二、KMeans聚类

2.1 算法原理

KMeans是应用最广泛的划分式聚类算法。它的目标是将n个样本划分到K个簇中,使得每个样本到其所属簇中心的距离平方和(即惯性 Inertia)最小。KMeans的核心假设是:簇是凸形的、各向同性的,且所有簇的方差大致相同。

其优化目标函数为:J = ∑₁⁓⁻⁶ ||xᵢ - μ₃(i)||²,其中μ₃是第k个簇的质心(Centroid),r(i)表示样本i所属的簇编号。这个优化问题在计算上是NP难的,因此KMeans采用贪心的迭代启发式策略来近似求解。

2.2 E-M迭代过程

KMeans算法的本质是期望最大化(Expectation-Maximization, E-M)框架的一个特例。整个迭代过程分为两个交替进行的步骤:

算法收敛的判定条件通常有三种:质心位置的变化小于阈值、簇分配不再发生变化、或者达到预设的最大迭代次数。由于KMeans对初始质心敏感,算法通常需要多次运行并选取最优结果。

# KMeans算法的E-M迭代过程手写实现 import numpy as np from sklearn.metrics import pairwise_distances_argmin def kmeans_em_manual(X, n_clusters, max_iter=300, tol=1e-4): """ 手动实现KMeans的E-M迭代过程 Parameters: ----------- X : array-like, shape (n_samples, n_features) n_clusters : int, 簇数量K max_iter : int, 最大迭代次数 tol : float, 收敛阈值 Returns: -------- centroids : 最终质心 labels : 每个样本的簇标签 inertia : 簇内平方和 """ # 随机初始化质心(从样本中选择K个) rng = np.random.RandomState(42) idx = rng.permutation(X.shape[0])[:n_clusters] centroids = X[idx].copy() for i in range(max_iter): # E步骤:分配每个样本到最近的质心 labels = pairwise_distances_argmin(X, centroids) # M步骤:重新计算质心 new_centroids = np.zeros_like(centroids) for k in range(n_clusters): mask = (labels == k) if np.any(mask): new_centroids[k] = X[mask].mean(axis=0) else: new_centroids[k] = centroids[k] # 检查是否收敛 shift = np.sqrt(((new_centroids - centroids) ** 2).sum(axis=1)).max() centroids = new_centroids if shift < tol: break # 计算惯性(簇内平方和) distances = np.zeros(X.shape[0]) for k in range(n_clusters): mask = (labels == k) if np.any(mask): distances[mask] = ((X[mask] - centroids[k]) ** 2).sum(axis=1) inertia = distances.sum() return centroids, labels, inertia

2.3 K值选择方法

KMeans需要预先指定K值,这在实际应用中往往是最困难的环节。以下介绍四种主流的K值选择方法:

肘部法(Elbow Method)

绘制不同K值对应的惯性(Inertia)曲线,选择曲线"拐点"对应的K值。拐点处表明继续增加K值带来的收益递减。

轮廓系数(Silhouette Score)

综合考虑簇内紧密度和簇间分离度,取值[-1, 1],越高越好。选择平均轮廓系数最大的K值。

Calinski-Harabasz指数

又称方差比准则,计算簇间离散度与簇内离散度的比值。该值越大,说明聚类效果越好,簇间分离越明显。

Davies-Bouldin指数

计算每个簇与其最相似簇之间的平均相似度。该值越小越好,最小值通常为0。DB指数对簇的形状更为敏感。

# 四种K值选择方法的完整实现 from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score import matplotlib.pyplot as plt def evaluate_k_values(X, k_range=range(2, 11)): """综合评价不同K值的聚类效果""" results = {'k': [], 'inertia': [], 'silhouette': [], 'calinski_harabasz': [], 'davies_bouldin': []} for k in k_range: kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) labels = kmeans.fit_predict(X) results['k'].append(k) results['inertia'].append(kmeans.inertia_) results['silhouette'].append( silhouette_score(X, labels)) results['calinski_harabasz'].append( calinski_harabasz_score(X, labels)) results['davies_bouldin'].append( davies_bouldin_score(X, labels)) return results # 可视化四种评估指标 results = evaluate_k_values(X_scaled) fig, axes = plt.subplots(2, 2, figsize=(12, 10)) axes[0, 0].plot(results['k'], results['inertia'], 'bo-') axes[0, 0].set_title('肘部法 (Inertia)') axes[0, 0].set_xlabel('K值') axes[0, 1].plot(results['k'], results['silhouette'], 'rs-') axes[0, 1].set_title('轮廓系数 (越高越好)') axes[0, 1].set_xlabel('K值') axes[1, 0].plot(results['k'], results['calinski_harabasz'], 'g^-') axes[1, 0].set_title('CH指数 (越高越好)') axes[1, 0].set_xlabel('K值') axes[1, 1].plot(results['k'], results['davies_bouldin'], 'mD-') axes[1, 1].set_title('DB指数 (越低越好)') axes[1, 1].set_xlabel('K值') plt.tight_layout() plt.show()

实践建议:实际项目中应综合多种指标选择K值。肘部法简单直观但有时拐点不明显(称为"平滑肘部"问题),此时应优先参考轮廓系数和CH指数。DB指数对簇的重叠程度更敏感,适合作为辅助验证。

2.4 k-means++ 初始化策略

传统的KMeans随机初始化质心可能导致收敛到局部最优解。k-means++是一种更为智能的初始化方法,其核心思想是:初始质心之间应该尽可能远。具体算法流程如下:

k-means++初始化可以显著提高聚类结果的稳定性和质量,是scikit-learn中KMeans的默认初始化方式(init='k-means++')。实际应用中,即使使用k-means++,也建议设置n_init参数(如n_init=10)多次运行取最优。

# k-means++初始化与随机初始化的对比 from sklearn.cluster import KMeans from sklearn.datasets import make_blobs # 生成数据集 X, _ = make_blobs(n_samples=1000, n_features=2, centers=5, cluster_std=1.5, random_state=42) # k-means++初始化 kmeans_pp = KMeans(n_clusters=5, init='k-means++', n_init=10, random_state=42) labels_pp = kmeans_pp.fit_predict(X) inertia_pp = kmeans_pp.inertia_ print(f"k-means++ 最终惯性: {inertia_pp:.2f}") # 随机初始化 kmeans_random = KMeans(n_clusters=5, init='random', n_init=1, random_state=42) labels_rand = kmeans_random.fit_predict(X) inertia_rand = kmeans_random.inertia_ print(f"随机初始化 最终惯性: {inertia_rand:.2f}") # 输出对比 print(f"惯性降低比例: {(1 - inertia_pp/inertia_rand)*100:.1f}%")

2.5 Mini-Batch K-Means

当数据集规模很大(百万级以上样本)时,标准KMeans的每次迭代都需要计算所有样本到所有质心的距离,计算开销巨大。Mini-Batch K-Means通过每次迭代随机采样一个小批次(mini-batch)来更新质心,显著降低了计算复杂度。

Mini-Batch K-Means的更新规则不同于标准KMeans:它使用学习率来控制历史信息和新批次的权重,公式为c = c * (1 - α) + x * α,其中α是该样本属于该簇的历史次数的倒数。与标准KMeans相比,Mini-Batch版本的收敛速度快1-2个数量级,但最终惯性通常略高(即质量略低)。

# Mini-Batch K-Means vs 标准KMeans 性能对比 from sklearn.cluster import MiniBatchKMeans, KMeans import time # 生成大数据集 X_large, _ = make_blobs(n_samples=100000, n_features=10, centers=20, random_state=42) # 标准KMeans start = time.time() kmeans = KMeans(n_clusters=20, random_state=42, n_init=3) kmeans.fit(X_large) time_standard = time.time() - start print(f"标准KMeans耗时: {time_standard:.2f}秒") # Mini-Batch K-Means start = time.time() mbkmeans = MiniBatchKMeans(n_clusters=20, random_state=42, batch_size=1024, n_init=3) mbkmeans.fit(X_large) time_minibatch = time.time() - start print(f"Mini-Batch KMeans耗时: {time_minibatch:.2f}秒") print(f"加速比: {time_standard/time_minibatch:.1f}x")

选择建议:当样本数小于10万时,标准KMeans足够快;样本数超过10万时,优先考慮Mini-Batch K-Means。对于流式数据或在线学习场景,Mini-Batch K-Means是唯一可行的选择。

三、DBSCAN密度聚类

3.1 算法思想与基本概念

DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种基于密度的聚类算法。与KMeans不同,它不需要预先指定簇的数量,能够发现任意形状的簇,并且可以自动识别噪声点。DBSCAN的核心思想是:聚类是由密度相连的最大样本集合构成的

DBSCAN定义了两个关键参数:

基于这两个参数,DBSCAN将数据集中的样本分为三类:

# DBSCAN基本使用与参数探索 from sklearn.cluster import DBSCAN from sklearn.datasets import make_moons import numpy as np # 生成非凸形状数据集 - 两个半圆形 X_moons, _ = make_moons(n_samples=500, noise=0.05, random_state=42) # DBSCAN聚类 dbscan = DBSCAN(eps=0.3, min_samples=5) labels = dbscan.fit_predict(X_moons) # 分析聚类结果 n_clusters = len(set(labels)) - (1 if -1 in labels else 0) n_noise = list(labels).count(-1) print(f"发现的簇数量: {n_clusters}") print(f"噪声点数量: {n_noise}") print(f"噪声比例: {n_noise/len(labels)*100:.1f}%") # 核心点、边界点和噪声点的数量统计 core_samples_mask = np.zeros_like(labels, dtype=bool) core_samples_mask[dbscan.core_sample_indices_] = True n_core = np.sum(core_samples_mask) n_border = len(labels) - n_core - n_noise print(f"核心点: {n_core}, 边界点: {n_border}, 噪声点: {n_noise}")

3.2 DBSCAN与KMeans的对比

DBSCAN和KMeans代表了两种截然不同的聚类范式。理解它们的区别是选择正确算法的关键:

对比维度KMeansDBSCAN
簇形状仅支持凸形(球形)簇支持任意形状簇(包括非凸、环形、S形)
簇数量需要预先指定K值由算法自动发现
噪声处理所有样本都被分配到一个簇自动识别并标记噪声点(标签-1)
参数K值(n_clusters)eps和minPts
密度均匀性适用于密度均匀的数据适用于密度变化大的数据
可重复性稳定(多次运行结果一致)确定性的(给定参数完全一致)
计算复杂度O(n·K·d·I)O(n²)(默认),使用KD-Tree可优化到O(nlogn)
高维数据尚可(但距离度量失效)较差(维度灾难影响密度定义)
# KMeans vs DBSCAN 在非凸数据上的对比 from sklearn.cluster import KMeans, DBSCAN from sklearn.datasets import make_circles, make_moons import matplotlib.pyplot as plt # 生成环形数据 X_circles, _ = make_circles(n_samples=500, factor=0.5, noise=0.05) fig, axes = plt.subplots(2, 2, figsize=(10, 10)) # KMeans在环形数据上的失败案例 kmeans = KMeans(n_clusters=2, random_state=42) labels_km = kmeans.fit_predict(X_circles) axes[0, 0].scatter(X_circles[:, 0], X_circles[:, 1], c=labels_km, cmap='viridis') axes[0, 0].set_title('KMeans在环形数据 - 失败') # DBSCAN在环形数据上的成功案例 dbscan = DBSCAN(eps=0.15, min_samples=5) labels_db = dbscan.fit_predict(X_circles) axes[0, 1].scatter(X_circles[:, 0], X_circles[:, 1], c=labels_db, cmap='viridis') axes[0, 1].set_title('DBSCAN在环形数据 - 成功') # KMeans在半月亮数据上的失败 X_moons, _ = make_moons(n_samples=500, noise=0.05) labels_km2 = KMeans(n_clusters=2, random_state=42).fit_predict(X_moons) axes[1, 0].scatter(X_moons[:, 0], X_moons[:, 1], c=labels_km2, cmap='viridis') axes[1, 0].set_title('KMeans在半月数据 - 失败') # DBSCAN在半月亮数据上的成功 labels_db2 = DBSCAN(eps=0.2, min_samples=5).fit_predict(X_moons) axes[1, 1].scatter(X_moons[:, 0], X_moons[:, 1], c=labels_db2, cmap='viridis') axes[1, 1].set_title('DBSCAN在半月数据 - 成功') plt.tight_layout() plt.show()

3.3 eps参数的确定方法

选择合理的eps是DBSCAN使用的关键。一个常用的启发式方法是绘制k距离图(k-distance graph):计算每个样本到其第k近邻的距离(k取minPts-1),将所有距离排序后绘制曲线。曲线中的"拐点"对应的距离通常是最合适的eps值。

# k距离图法选择eps参数 from sklearn.neighbors import NearestNeighbors import numpy as np import matplotlib.pyplot as plt def plot_k_distance(X, k=5): """绘制k距离图,用于选择DBSCAN的eps参数""" # 计算每个样本到第k近邻的距离 nbrs = NearestNeighbors(n_neighbors=k).fit(X) distances, _ = nbrs.kneighbors(X) # 取第k近的距离(从0开始索引,所以是k-1) k_dist = np.sort(distances[:, k-1]) plt.figure(figsize=(8, 5)) plt.plot(k_dist) plt.xlabel('样本点(按距离排序)') plt.ylabel(f'第{k}近邻距离') plt.title('k距离图 - 用于选择eps参数') plt.grid(True, alpha=0.3) plt.show() print("曲线拐点处对应的距离即为推荐的eps值") # 使用示例 # plot_k_distance(X_scaled, k=5)

DBSCAN局限性:当数据集的密度差异非常大时(即存在密度极其不均的簇),单一的eps参数难以同时捕获所有簇。此时可考虑使用OPTICS算法(DBSCAN的改进版本),它不需要显式指定eps。此外,DBSCAN在高维数据上表现不佳,因为在高维空间中距离度量失去区分度(维度灾难)。

四、层次聚类

4.1 凝聚式层次聚类

层次聚类(Hierarchical Clustering)通过构建一个层次化的聚类树(树状图Dendrogram)来组织数据分组关系。最常用的策略是凝聚式(Agglomerative)层次聚类:自底向上,开始时每个样本自成一簇,然后逐步合并距离最近的簇,直到所有样本合并为一个簇或达到预设的簇数量。

层次聚类的最大优势是:不需要预先指定K值,可以通过树状图直观地观察数据的层次结构,并灵活地选择切割位置来获得不同粒度的聚类结果。

4.2 Linkage策略

凝聚式层次聚类的核心是定义"簇间距离"的计算方式,即linkage准则。不同的linkage策略会导致截然不同的聚类结果:

# 层次聚类 - 不同linkage策略的对比 from sklearn.cluster import AgglomerativeClustering from sklearn.datasets import make_blobs import matplotlib.pyplot as plt # 生成具有层次结构的数据集 X_hier, _ = make_blobs(n_samples=300, n_features=2, centers=[[0,0],[3,3],[6,0],[9,3]], cluster_std=0.6, random_state=42) fig, axes = plt.subplots(2, 2, figsize=(12, 10)) linkage_methods = ['ward', 'complete', 'average', 'single'] for ax, method in zip(axes.ravel(), linkage_methods): model = AgglomerativeClustering( n_clusters=4, linkage=method) labels = model.fit_predict(X_hier) ax.scatter(X_hier[:, 0], X_hier[:, 1], c=labels, cmap='viridis', edgecolors='k', s=40) ax.set_title(f'Linkage: {method}') ax.set_xlabel('特征1') ax.set_ylabel('特征2') plt.tight_layout() plt.show()

4.3 Dendrogram树状图

树状图(Dendrogram)是层次聚类最具特色的可视化工具,它完整展示了一棵聚类树的合并过程。通过观察树状图,可以直观地判断数据的层次结构、选择合理的切割阈值、识别潜在的离群点。树状图的纵轴表示合并时的距离(或相异性),横轴表示样本或簇。

# 绘制Dendrogram树状图 from scipy.cluster.hierarchy import dendrogram, linkage import matplotlib.pyplot as plt # 计算链接矩阵 Z = linkage(X_hier, method='ward') # 绘制树状图 plt.figure(figsize=(12, 6)) plt.title('层次聚类树状图 (Dendrogram)') plt.xlabel('样本索引') plt.ylabel('距离 (Ward)') # 绘制树状图并用红线标记切割阈值 dn = dendrogram(Z, leaf_rotation=90, leaf_font_size=8) plt.axhline(y=3.0, color='r', linestyle='--', label='切割阈值 = 3.0') plt.legend() plt.tight_layout() plt.show() # 根据树状图确定的阈值切割得到4个簇 from scipy.cluster.hierarchy import fcluster clusters = fcluster(Z, t=3.0, criterion='distance') print(f"阈值3.0切割得到 {len(set(clusters))} 个簇")

树状图解读技巧:(1)纵轴距离越大,合并发生的层次越低(越不相似);(2)水平线的高度表示两个簇合并时的距离;(3)长的垂直线条表明该合并具有较强的结构支撑;(4)选择切割阈值时,寻找最长垂直线条未被水平线穿越的位置,这通常对应最自然的簇结构。

4.4 距离矩阵与计算复杂度

层次聚类需要计算样本间的距离矩阵,其空间复杂度为O(n²),这意味着当样本数超过数万时,层次聚类的计算开销会变得难以接受。这在很大程度上限制了层次聚类在大规模数据集上的应用。在scikit-learn中,当设置distance_threshold参数并配合ward linkage时,算法可以利用一些优化技巧来降低计算量。

# 距离矩阵可视化 from scipy.spatial.distance import pdist, squareform import seaborn as sns import matplotlib.pyplot as plt # 计算成对距离矩阵 dist_matrix = squareform(pdist(X_hier[:30])) plt.figure(figsize=(8, 6)) sns.heatmap(dist_matrix, cmap='viridis', square=True) plt.title('样本间距离矩阵热图(前30个样本)') plt.xlabel('样本索引') plt.ylabel('样本索引') plt.show() # 从距离矩阵直接构建层次聚类 from sklearn.cluster import AgglomerativeClustering model_dist = AgglomerativeClustering( n_clusters=None, distance_threshold=2.5, linkage='ward' ) labels_dist = model_dist.fit_predict(X_hier) print(f"距离阈值2.5切割得到 {len(set(labels_dist))} 个簇")

五、聚类评估指标

聚类评估是聚类分析中极具挑战性的环节——因为没有真实标签作为"标准答案"。评估指标分为两类:外部评估(需要真实标签,用于衡量聚类结果与真实划分的匹配程度)和内部评估(仅依赖数据本身的特征,衡量簇的分离度和紧密度)。

5.1 外部评估指标

NMI - 标准化互信息

标准化互信息(Normalized Mutual Information)衡量聚类标签与真实标签之间的信息共享程度。取值[0, 1],1表示完全一致,0表示不共享任何信息。NMI对簇的数量不敏感,适合比较不同K值下的聚类质量。

ARI - 调整兰德指数

调整兰德指数(Adjusted Rand Index)基于样本对的一致性来计算。它统计所有样本对中,聚类结果和真实标签在"是否属于同一簇"上保持一致的比例,并进行了随机调整(校正了随机一致性的期望值)。ARI的取值范围[-1, 1],1表示完全一致,0表示随机水平,负值表示比随机更差。

# 外部评估指标计算 from sklearn.metrics import normalized_mutual_info_score from sklearn.metrics import adjusted_rand_score from sklearn.metrics import homogeneity_score from sklearn.metrics import completeness_score from sklearn.metrics import v_measure_score def evaluate_external(labels_true, labels_pred): """计算所有外部聚类评估指标""" metrics = { 'NMI': normalized_mutual_info_score(labels_true, labels_pred), 'ARI': adjusted_rand_score(labels_true, labels_pred), '同质性 (Homogeneity)': homogeneity_score(labels_true, labels_pred), '完整性 (Completeness)': completeness_score(labels_true, labels_pred), 'V-Measure': v_measure_score(labels_true, labels_pred), } for name, value in metrics.items(): print(f"{name}: {value:.4f}") return metrics # 使用示例:评估KMeans在已知标签数据上的表现 from sklearn.datasets import load_iris from sklearn.cluster import KMeans iris = load_iris() X_iris, y_iris = iris.data, iris.target kmeans = KMeans(n_clusters=3, random_state=42, n_init=10) y_pred = kmeans.fit_predict(X_iris) print("=== KMeans在Iris数据集上的外部评估 ===") metrics = evaluate_external(y_iris, y_pred)

关于同质性、完整性与V-Measure:同质性要求每个簇只包含单一类别的样本;完整性要求同一类别的所有样本都被分到同一个簇中。V-Measure是同质性和完整性的调和平均数,可以理解为聚类结果的"综合质量"评分。

5.2 内部评估指标

内部评估指标不依赖于真实标签,是实际应用中最常用的评估方式:

Silhouette轮廓分析图

轮廓系数(Silhouette Coefficient)结合了簇内紧密度(a:样本到同簇其他样本的平均距离)和簇间分离度(b:样本到最近其他簇所有样本的平均距离)。每个样本的轮廓系数为(b - a) / max(a, b)。

轮廓分析图将每个簇的轮廓系数绘制在同一张图上,可以直观地识别簇的紧密度、宽度是否均衡、是否存在异常样本。如果一个簇的轮廓系数明显低于其他簇,或者出现负值样本,说明聚类质量不佳。

# Silhouette轮廓分析图 from sklearn.metrics import silhouette_samples, silhouette_score import matplotlib.pyplot as plt import numpy as np def plot_silhouette_analysis(X, labels): """绘制轮廓分析图""" n_clusters = len(set(labels)) - (1 if -1 in labels else 0) n_samples = len(X) # 计算每个样本的轮廓系数 silhouette_vals = silhouette_samples(X, labels) avg_score = np.mean(silhouette_vals) fig, ax = plt.subplots(figsize=(10, 6)) y_lower = 10 for i in range(n_clusters): # 获取第i个簇的样本 cluster_vals = silhouette_vals[labels == i] cluster_vals.sort() size = len(cluster_vals) y_upper = y_lower + size color = plt.cm.nipy_spectral(float(i) / n_clusters) ax.fill_betweenx(np.arange(y_lower, y_upper), 0, cluster_vals, facecolor=color, alpha=0.7) ax.text(-0.05, y_lower + 0.5 * size, f'簇 {i}') y_lower = y_upper + 10 # 平均轮廓系数线 ax.axvline(x=avg_score, color='red', linestyle='--') ax.set_xlabel('轮廓系数值') ax.set_ylabel('簇') ax.set_title(f'轮廓分析图 (平均轮廓系数 = {avg_score:.3f})') plt.show() return avg_score # 使用示例 kmeans_3 = KMeans(n_clusters=3, random_state=42, n_init=10) labels_3 = kmeans_3.fit_predict(X_scaled) score = plot_silhouette_analysis(X_scaled, labels_3) print(f"平均轮廓系数: {score:.4f}")

轮廓系数的经验阈值:大于0.7表示强结构(聚类效果优秀);0.5-0.7表示合理结构(可用但可优化);0.25-0.5表示弱结构(需要谨慎分析);小于0.25表示没有明显的结构(数据可能不适合聚类)。

六、实际应用场景

6.1 客户分群(Customer Segmentation)

客户分群是聚类分析最经典的应用场景。通过对用户的消费行为、活跃度、偏好等特征的聚类,企业可以将用户分为高价值客户、沉睡客户、流失风险客户等群组,从而制定差异化的营销策略。KMeans是客户分群中最常用的算法,配合PCA降维可以将高维行为数据可视化。

# 客户分群实战 - RFM模型+KMeans import pandas as pd from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler # RFM特征:Recency(最近消费时间), Frequency(消费频率), Monetary(消费金额) # 模拟客户数据 rfm_data = pd.DataFrame({ 'recency': [5, 30, 90, 2, 60, 120, 15, 45, 180, 7], 'frequency': [50, 12, 3, 80, 8, 1, 30, 15, 2, 40], 'monetary': [5000, 1200, 300, 8000, 600, 100, 3000, 1500, 200, 4000] }) # 标准化 scaler = StandardScaler() rfm_scaled = scaler.fit_transform(rfm_data) # 客户分群 kmeans_rfm = KMeans(n_clusters=3, random_state=42, n_init=10) rfm_data['segment'] = kmeans_rfm.fit_predict(rfm_scaled) # 分析各群特征 rfm_analysis = rfm_data.groupby('segment').agg({ 'recency': 'mean', 'frequency': 'mean', 'monetary': 'mean', 'segment': 'count' }).rename(columns={'segment': 'count'}) print("=== 客户分群结果分析 ===") print(rfm_analysis)

6.2 图像分割(Image Segmentation)

在图像处理中,聚类算法可以将像素点按其颜色值(RGB)或空间位置进行分组,从而实现图像分割。每个像素点被视为三维空间(R, G, B)中的一个点,聚类后同一簇的像素赋予相同的颜色值,从而达到分割或压缩图像的效果。

# 使用KMeans进行图像颜色量化(图像分割) from sklearn.cluster import KMeans import numpy as np from PIL import Image def compress_image_colors(image_path, n_colors=16): """使用KMeans对图像进行颜色量化""" # 加载图像并转换为RGB数组 img = Image.open(image_path).convert('RGB') img_array = np.array(img) h, w, c = img_array.shape # 重塑为像素列表 (H*W, 3) pixels = img_array.reshape(-1, 3) # KMeans聚类 kmeans = KMeans(n_clusters=n_colors, random_state=42, n_init=3) labels = kmeans.fit_predict(pixels) # 用质心颜色替换每个像素 compressed = kmeans.cluster_centers_[labels].reshape(h, w, 3) compressed = np.clip(compressed, 0, 255).astype(np.uint8) return Image.fromarray(compressed) # 使用示例(需替换为实际图像路径) # compressed_img = compress_image_colors('input.jpg', n_colors=8) # compressed_img.save('output_quantized.jpg') print("图像颜色量化完成: 原图颜色数 → 8种主色")

6.3 文档聚类与主题发现

在文本挖掘中,聚类可以自动发现文档的潜在主题结构。通常的流程是:首先使用TF-IDF或Word2Vec将文档转换为向量,然后应用聚类算法将相似文档分组。层次聚类特别适合文档聚类,因为树状图可以直观地展示主题的层次结构。

# 文档聚类 - 新闻主题发现 from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.cluster import KMeans # 模拟文档数据 documents = [ "华为发布新款Mate系列手机支持卫星通信", "苹果iPhone销量超预期股价上涨", "央行宣布降准释放流动性支持实体经济", "A股三大指数收涨新能源板块领涨", "气候变暖导致北极冰层加速融化", "新能源车企公布上月交付数据", "环保组织呼吁减少塑料使用保护海洋", "基金公司布局科技创新主题ETF", ] # TF-IDF向量化 vectorizer = TfidfVectorizer(token_pattern=r'\w+') X_tfidf = vectorizer.fit_transform(documents) # KMeans聚类发现主题 kmeans_doc = KMeans(n_clusters=3, random_state=42, n_init=10) doc_labels = kmeans_doc.fit_predict(X_tfidf) # 输出每个主题下的文档 for cluster_id in sorted(set(doc_labels)): print(f"\n主题簇 {cluster_id}:") idx = [i for i, l in enumerate(doc_labels) if l == cluster_id] for i in idx: print(f" - {documents[i]}")

6.4 异常检测(Anomaly Detection)

DBSCAN天然支持异常检测功能——被标记为-1(噪声点)的样本即被视为异常。这种方法特别适用于检测那些远离正常数据密集区域的离群点。与传统的统计方法(如Z-score、IQR)相比,DBSCAN的异常检测具有以下优势:

# DBSCAN异常检测 from sklearn.cluster import DBSCAN import numpy as np def detect_anomalies_dbscan(X, eps=0.5, min_samples=5): """使用DBSCAN进行异常检测""" model = DBSCAN(eps=eps, min_samples=min_samples) labels = model.fit_predict(X) n_anomalies = np.sum(labels == -1) anomaly_ratio = n_anomalies / len(labels) print(f"检测到 {n_anomalies} 个异常点") print(f"异常比例: {anomaly_ratio:.2%}") return labels # -1表示异常 # 生成带有异常点的数据 rng = np.random.RandomState(42) X_normal = rng.randn(200, 2) * 0.5 X_anomaly = rng.uniform(low=-3, high=3, size=(10, 2)) X_with_anomalies = np.vstack([X_normal, X_anomaly]) labels = detect_anomalies_dbscan(X_with_anomalies, eps=0.3, min_samples=5)

七、聚类方法对比总结

维度KMeansDBSCAN层次聚类
簇形状凸形/球形任意形状依赖linkage策略
需指定K值否(切割时需指定)
噪声处理不支持原生支持不支持
可重复性依赖初始化确定性的确定性的
参数数量少(K值)中等(eps, minPts)少(linkage, n_clusters)
可解释性中等很高(树状图)
计算复杂度O(n·K·d·I)O(n²) ~ O(nlogn)O(n³) ~ O(n²)
大规模数据优秀(含Mini-Batch)中等
高维数据一般一般
Python实现sklearn.cluster.KMeanssklearn.cluster.DBSCANsklearn.cluster.AgglomerativeClustering

算法选择速查:

  • 数据是球形簇、簇大小均匀、无噪声 → KMeans
  • 簇形状复杂(环形、S形)、需要自动识别噪声 → DBSCAN
  • 需要可视化层次结构、小数据集、不关心K值 → 层次聚类
  • 百万级样本 → Mini-Batch KMeans
  • 密度不均匀、eps难以选择 → OPTICS(DBSCAN的进阶版)
  • 需要概率输出的柔性聚类 → Gaussian Mixture Model (GMM)

完整实战流程

下面是一个端到端的聚类分析pipeline,展示了从数据预处理到模型评估的完整流程:

# 完整的聚类分析pipeline import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering from sklearn.metrics import silhouette_score class ClusteringPipeline: """聚类分析完整pipeline""" def __init__(self, data): self.data = data self.scaler = StandardScaler() self.scaled_data = self.scaler.fit_transform(data) self.results = {} def run_kmeans(self, n_clusters=range(2, 8)): """自动寻找最佳K值并运行KMeans""" best_k, best_score = 2, -1 for k in n_clusters: model = KMeans(n_clusters=k, random_state=42, n_init=10) labels = model.fit_predict(self.scaled_data) score = silhouette_score(self.scaled_data, labels) if score > best_score: best_k, best_score = k, score model = KMeans(n_clusters=best_k, random_state=42, n_init=10) self.results['kmeans'] = { 'labels': model.fit_predict(self.scaled_data), 'k': best_k, 'silhouette': best_score, 'model': model } print(f"KMeans: K={best_k}, 轮廓系数={best_score:.4f}") def run_dbscan(self, eps=0.5, min_samples=5): model = DBSCAN(eps=eps, min_samples=min_samples) labels = model.fit_predict(self.scaled_data) n_clusters = len(set(labels)) - (1 if -1 in labels else 0) n_noise = list(labels).count(-1) # 过滤噪声点后计算轮廓系数 mask = labels != -1 if np.sum(mask) > 1 and n_clusters > 1: score = silhouette_score(self.scaled_data[mask], labels[mask]) else: score = -1 self.results['dbscan'] = { 'labels': labels, 'n_clusters': n_clusters, 'n_noise': n_noise, 'silhouette': score, 'model': model } print(f"DBSCAN: 簇={n_clusters}, 噪声={n_noise}, 轮廓系数={score:.4f}") def run_hierarchical(self, n_clusters=3, linkage='ward'): model = AgglomerativeClustering( n_clusters=n_clusters, linkage=linkage) labels = model.fit_predict(self.scaled_data) score = silhouette_score(self.scaled_data, labels) self.results['hierarchical'] = { 'labels': labels, 'n_clusters': n_clusters, 'silhouette': score, 'model': model } print(f"层次聚类: K={n_clusters}, 轮廓系数={score:.4f}") def summary(self): """输出所有聚类结果的对比摘要""" print("\n=== 聚类结果对比 ===") for method, res in self.results.items(): print(f"{method}: 轮廓系数={res['silhouette']:.4f}") # 在Iris数据集上运行pipeline pipeline = ClusteringPipeline(iris.data) pipeline.run_kmeans(n_clusters=range(2, 8)) pipeline.run_dbscan(eps=0.5, min_samples=5) pipeline.run_hierarchical(n_clusters=3, linkage='ward') pipeline.summary()

学习建议:聚类分析是数据科学中最富探索性的技术之一。建议读者在UCI机器学习库或Kaggle上寻找无标签数据集进行练习,重点培养以下能力:(1)观察数据分布特征选择合适的聚类算法;(2)运用多种评估指标综合判断聚类质量;(3)将聚类结果与业务知识结合形成可落地的洞察。聚类分析的真正价值不在于算法的技术细节,而在于从数据中发现有意义的、可解释的结构化信息。