当前位置: 首页 > news >正文

深入浅出DBSCAN:基于密度的聚类算法详解与Python实战

文章目录

  • 引言
  • 一、DBSCAN是什么?
    • 1.1 与K-Means的本质区别
  • 二、核心概念:理解DBSCAN的基石
    • 2.1 邻域 (ε-Neighborhood)
    • 2.2 核心点 (Core Point)
    • 2.3 边界点 (Border Point)
    • 2.4 噪声点 (Noise Point)
    • 2.5 密度可达 (Density-Reachable) 和 密度相连 (Density-Connected)
  • 三、DBSCAN算法流程
  • 四、参数选择:ε 和 MinPts的深度剖析
    • 4.1 MinPts:最小样本数
    • 4.2 ε (epsilon):邻域半径
      • **k-距离图的绘制与解读**
      • **Python实现k-距离图**
    • 4.3 参数调优策略
  • 五、Python实战:使用sklearn实现DBSCAN
  • 六、DBSCAN的优势与局限性
    • 优势 (Pros)
    • 局限性 (Cons)
  • 七、何时使用DBSCAN?
  • 八、DBSCAN的变种与扩展
    • 8.1 OPTICS (Ordering Points To Identify the Clustering Structure)
    • 8.2 HDBSCAN (Hierarchical DBSCAN)
    • 8.3 LOF (Local Outlier Factor)
    • 8.2 HDBSCAN (Hierarchical DBSCAN)
    • 8.3 LOF (Local Outlier Factor)

引言

在机器学习的聚类算法中,K-Means因其简单高效而广受欢迎。然而,它对球形簇、均匀分布和预先指定簇数量的要求,使其在处理复杂形状或包含噪声的数据时显得力不从心。今天,我们将深入探讨一种强大的聚类算法——DBSCAN (Density-Based Spatial Clustering of Applications with Noise),它不仅能够发现任意形状的簇,还能有效识别噪声点。


一、DBSCAN是什么?

DBSCAN,全称基于密度的空间聚类算法,是一种基于样本分布密度的聚类方法。其核心思想是:高密度区域相连的样本点构成簇,而低密度区域的点则被视为噪声或边界点

与K-Means不同,DBSCAN不需要预先指定簇的数量,并且能够识别出离群点(Outliers),这使得它在异常检测、地理空间数据分析、图像分割等领域大放异彩。

1.1 与K-Means的本质区别

特性K-MeansDBSCAN
簇形状偏好球形、凸形簇能发现任意形状的簇
簇数量必须预先指定 k自动发现簇的数量
噪声处理将所有点强制分配到簇明确识别并标记噪声点
初始化敏感对初始质心敏感结果确定(无随机性)
距离度量通常使用欧氏距离可使用任意距离度量(如曼哈顿、余弦)

关键洞察:DBSCAN将聚类问题从“分配点到中心”转变为“连接高密度区域”,这是范式上的根本转变。


二、核心概念:理解DBSCAN的基石

要深刻理解DBSCAN,必须掌握以下几个关键概念。我们以一个二维数据集为例进行图解说明。

2.1 邻域 (ε-Neighborhood)

  • 以样本点 p 为中心,半径为 ε (epsilon) 的圆形区域。
  • 在该区域内包含的其他样本点的数量,称为 p邻域密度
  • 数学定义N_ε(p) = {q ∈ D | dist(p, q) ≤ ε},其中 D 是数据集,dist 是距离函数。

2.2 核心点 (Core Point)

  • 如果一个点 pε 邻域内包含至少 MinPts 个样本点(包括 p 自身),则 p 被称为核心点
  • MinPts 是用户设定的最小样本数阈值。
  • 重要性:核心点是簇的“种子”和“骨架”,簇的生长始于核心点。

2.3 边界点 (Border Point)

  • 如果一个点 p 本身不是核心点,但它位于某个核心点的 ε 邻域内,则 p 被称为边界点
  • 边界点属于某个簇,但不参与簇的扩展。

2.4 噪声点 (Noise Point)

  • 既不是核心点也不是边界点的点,被称为噪声点或离群点。
  • 在DBSCAN中,噪声点被赋予标签 -1

2.5 密度可达 (Density-Reachable) 和 密度相连 (Density-Connected)

这是DBSCAN理论中最精妙的部分:

  • 直接密度可达 (Directly Density-Reachable)

    • q 从点 p 直接密度可达,如果 p 是核心点,且 q ∈ N_ε(p)
    • 注意:直接密度可达是非对称的。如果 qp 的邻域内且 p 是核心点,则 qp 直接密度可达。但反过来不一定成立(除非 q 也是核心点)。
  • 密度可达 (Density-Reachable)

    • q 从点 p 密度可达,如果存在一个点链 p₁, p₂, ..., pₙ,其中 p₁ = ppₙ = q,且对于 1 ≤ i ≤ n-1pᵢ₊₁pᵢ 直接密度可达。
    • 注意:密度可达也是非对称的。
  • 密度相连 (Density-Connected)

    • p 和点 q密度相连的,如果存在一个点 o,使得 pq 都从 o 密度可达。
    • 关键点:密度相连是对称关系。
    • 聚类依据:DBSCAN将密度相连的所有点划分为同一个簇。这解决了直接密度可达的非对称性问题,确保了簇内点的连通性。

图解示例

o (核心点) ---- o (核心点) ---- o (核心点)
|               |               |
o (边界点)      o (边界点)      o (边界点)
|
o (边界点)x (噪声点)     x (噪声点)

在这个示意图中,三个核心点通过直接密度可达连接,形成一个链。所有边界点都从某个核心点密度可达。所有点(核心点和边界点)都从最左边的核心点密度可达,因此它们是密度相连的,属于同一个簇。x 表示噪声点。


三、DBSCAN算法流程

DBSCAN的算法步骤简洁而高效,以下是伪代码和详细解释:

def DBSCAN(D, eps, MinPts):clusters = []  # 存储所有簇visited = set()  # 记录已访问的点noise = set()    # 记录噪声点for point in D:if point in visited:continuevisited.add(point)# 找到point的ε邻域neighbors = regionQuery(point, eps)# 判断是否为核心点if len(neighbors) < MinPts:noise.add(point)  # 暂时标记为噪声else:# 创建新簇cluster = set()expandCluster(point, neighbors, cluster, eps, MinPts, visited, D)clusters.append(cluster)return clusters, noisedef expandCluster(point, neighbors, cluster, eps, MinPts, visited, D):"""扩展簇的核心函数"""cluster.add(point)  # 将核心点加入簇i = 0while i < len(neighbors):neighbor = neighbors[i]if neighbor not in visited:visited.add(neighbor)# 查询邻居的邻域new_neighbors = regionQuery(neighbor, eps)# 如果邻居也是核心点,则将其邻域加入待处理队列if len(new_neighbors) >= MinPts:neighbors.extend([n for n in new_neighbors if n not in neighbors])# 如果该点尚未被归为噪声,则将其加入当前簇if neighbor not in [c for cluster in clusters for c in cluster] and neighbor not in noise:cluster.add(neighbor)i += 1

算法关键步骤解析

  1. 初始化:选择参数 εMinPts,标记所有点为“未访问”。
  2. 遍历数据:随机选择一个未访问的点 p
  3. 确定邻域regionQuery(p, ε) 函数计算 pε 邻域内所有点。这一步是计算瓶颈,通常使用空间索引(如KD-Tree、R-Tree)优化。
  4. 判断核心点:
    • 如果 p 的邻域内点数 < MinPts,则暂时将 p 标记为“噪声”(后续可能被重新分类为边界点)。
    • 如果 p 的邻域内点数 >= MinPts,则 p 是核心点,创建一个新簇 C,并将 p 及其邻域内所有点加入 C
  5. 扩展簇 (expandCluster):这是算法的核心。
    • 将当前核心点 p 加入簇 C
    • 遍历 p 的所有邻居。
    • 对于每个邻居q
      • 如果 q 未被访问,则访问它,并查询其邻域。
      • 如果 q 是核心点,则将其邻域内的所有点加入待处理的邻居列表(实现广度优先搜索)。
      • 如果 q 尚未被分配到任何簇(且不是噪声),则将其加入当前簇 C
    • 重复此过程,直到没有新的点可以加入簇 C
  6. 继续遍历:返回主循环,选择下一个未访问的点。
  7. 最终分类:将所有仍标记为“噪声”的点正式确定为噪声点。

时间复杂度

  • 最坏情况:O(n²),当没有空间索引时,每次 regionQuery 都需要扫描所有点。
  • 使用空间索引(如KD-Tree):平均 O(n log n)

四、参数选择:ε 和 MinPts的深度剖析

参数选择对DBSCAN的结果至关重要,甚至可以说是成败的关键。

4.1 MinPts:最小样本数

  • 经验法则MinPts >= D + 1,其中 D 是数据的维度。这确保了在 D 维空间中,邻域内至少有 D+1 个点才能形成一个“稠密”的区域。
  • 推荐值:
    • 2D数据:MinPts = 4 是一个稳健的起点。
    • 一般建议:MinPts2*维度 左右尝试。
  • 影响:
    • MinPts 过小:容易将噪声点误判为核心点,导致大量小簇或“链条效应”(chaining effect)。
    • MinPts 过大:可能无法形成任何核心点,导致大部分点被标记为噪声,或只能发现非常稠密的簇。

4.2 ε (epsilon):邻域半径

选择合适的 ε 更具挑战性。最科学的方法是k-距离图 (k-distance graph)

k-距离图的绘制与解读

  1. 计算k-距离
    • 对于数据集中的每个点 p,计算它到其第 k 个最近邻的距离。这里的 k 通常取 MinPts - 1
    • 例如,如果 MinPts = 4,则计算每个点到其第3近邻的距离。
  2. 排序与绘图
    • 将所有点的k-距离从大到小排序。
    • 绘制排序后的k-距离曲线(x轴为点的索引,y轴为k-距离)。
  3. 寻找“肘部” (Elbow)
    • 观察曲线,寻找一个明显的“肘部”或“拐点”。
    • 该点之前的距离变化剧烈,之后趋于平缓。
    • 肘部对应的距离值就是 ε 的理想候选值

为什么有效?

  • 在稠密区域,点的k-距离较小且变化不大。
  • 在稀疏区域或簇边界,点的k-距离会突然增大。
  • “肘部”标志着从“内部点”到“边界或噪声点”的过渡,因此该距离能很好地区分簇内和簇外。

Python实现k-距离图

from sklearn.neighbors import NearestNeighbors
import numpy as npdef plot_k_distance_graph(X, k):"""绘制k-距离图,辅助选择eps"""# 找出每个点的k个最近邻nbrs = NearestNeighbors(n_neighbors=k, algorithm='auto').fit(X)distances, indices = nbrs.kneighbors(X)# 取第k个最近邻的距离(即k-距离)k_distances = distances[:, k-1]k_distances_sorted = np.sort(k_distances)[::-1]  # 从大到小排序plt.figure(figsize=(8, 5))plt.plot(range(1, len(k_distances_sorted) + 1), k_distances_sorted)plt.xlabel('按k-距离降序排列的点的序号')plt.ylabel(f'到第{k}个最近邻的距离')plt.title(f'k-距离图 (k={k})')plt.grid(True, alpha=0.3)plt.show()return k_distances_sorted# 使用示例
k = 4  # MinPts - 1 = 5 - 1 = 4
k_distances = plot_k_distance_graph(X_moons_scaled, k)
# 观察图形,选择肘部对应的距离作为eps

4.3 参数调优策略

  1. 固定 MinPts,调整 ε
    • 先根据维度设定一个合理的 MinPts(如4或5)。
    • 绘制k-距离图,选择 ε
    • 微调 ε 并观察聚类结果。
  2. 网格搜索 (Grid Search)
    • 在合理的范围内对 εMinPts 进行组合尝试。
    • 使用聚类评估指标(如轮廓系数 Silhouette Score)选择最优参数。
    • 注意:轮廓系数对噪声点敏感,有时DBSCAN的最优解可能对应较低的轮廓系数。
  3. 领域知识
    • 结合数据的实际意义。例如,在地理数据中,ε 可以代表一个合理的物理距离(如500米)。

五、Python实战:使用sklearn实现DBSCAN

让我们通过一个实际例子来演示DBSCAN的强大功能。我们将使用两个经典数据集:月牙形和同心圆。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
from sklearn.datasets import make_moons, make_circles
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import NearestNeighbors
import seaborn as sns
from sklearn.metrics import silhouette_score# 设置中文字体和样式
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'FangSong']  # 解决中文显示问题
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示为方块的问题
sns.set_style("whitegrid")# 生成示例数据集
X_moons, _ = make_moons(n_samples=300, noise=0.08, random_state=42)
X_circles, _ = make_circles(n_samples=300, noise=0.05, factor=0.5, random_state=42)# 标准化数据(DBSCAN对尺度敏感)
X_moons_scaled = StandardScaler().fit_transform(X_moons)
X_circles_scaled = StandardScaler().fit_transform(X_circles)# --- 参数选择:使用k-距离图 ---
k = 4  # MinPts - 1, 假设我们计划使用 MinPts=5def plot_k_distance_and_dbscan(X, dataset_name, k, eps_candidates, min_samples):"""绘制k-距离图和不同eps下的DBSCAN结果对比"""# 绘制k-距离图nbrs = NearestNeighbors(n_neighbors=k).fit(X)distances, _ = nbrs.kneighbors(X)k_distances = distances[:, k-1]k_distances_sorted = np.sort(k_distances)[::-1]fig, axes = plt.subplots(2, len(eps_candidates) + 1, figsize=(20, 10))fig.suptitle(f'{dataset_name}数据集: k-距离图与DBSCAN聚类结果对比', fontsize=16)# k-距离图axes[0, 0].plot(range(1, len(k_distances_sorted) + 1), k_distances_sorted, 'b-', alpha=0.7)axes[0, 0].set_xlabel('点的序号(按k-距离降序)')axes[0, 0].set_ylabel(f'到第{k}个最近邻的距离')axes[0, 0].set_title('k-距离图')axes[0, 0].grid(True, alpha=0.3)# 标记候选epsfor eps in eps_candidates:axes[0, 0].axhline(y=eps, color='r', linestyle='--', alpha=0.7)# 不同eps下的DBSCAN结果colors = sns.color_palette("husl", 10)  # 预定义颜色for idx, eps in enumerate(eps_candidates):dbscan = DBSCAN(eps=eps, min_samples=min_samples)labels = dbscan.fit_predict(X)n_clusters = len(set(labels)) - (1 if -1 in labels else 0)n_noise = list(labels).count(-1)# 计算轮廓系数,忽略噪声点if n_clusters > 1:sil_score = silhouette_score(X[labels != -1], labels[labels != -1]) if n_noise < len(labels) else silhouette_score(X, labels)else:sil_score = -1  # 单个簇或全是噪声# 绘制聚类结果ax = axes[1, idx + 1] if len(eps_candidates) > 1 else axes[1]ax.scatter(X[labels == -1, 0], X[labels == -1, 1], c='gray', marker='x', s=30, label='噪声点', alpha=0.8, linewidth=1.5)for i in range(n_clusters):ax.scatter(X[labels == i, 0], X[labels == i, 1], c=[colors[i]], label=f'簇 {i}', s=30, alpha=0.8)ax.set_title(f'eps={eps}, 簇数={n_clusters}, 噪声={n_noise}, 轮廓系数={sil_score:.3f}')ax.set_xlabel('特征 1')ax.set_ylabel('特征 2')ax.legend()plt.tight_layout()plt.show()# 定义候选eps值(根据k-距离图观察)
eps_candidates_moons = [0.2, 0.3, 0.4, 0.5]
eps_candidates_circles = [0.2, 0.3, 0.4, 0.5]
min_samples = 5# 分析月牙形数据
plot_k_distance_and_dbscan(X_moons_scaled, "月牙形", k, eps_candidates_moons, min_samples)# 分析同心圆数据
plot_k_distance_and_dbscan(X_circles_scaled, "同心圆", k, eps_candidates_circles, min_samples)

输出分析

  • 月牙形数据eps=0.30.4 通常能得到最佳效果,清晰地分离出两个簇,噪声点极少。
  • 同心圆数据eps=0.3 通常表现最好,能正确识别内外两个圆环。

六、DBSCAN的优势与局限性

优势 (Pros)

  • 无需指定簇数量:自动发现簇的数量,非常适合探索性数据分析。
  • 能发现任意形状的簇:对非凸、不规则形状的簇效果极佳,这是其最大优势。
  • 对噪声和离群点鲁棒:能明确识别并标记噪声点,无需预处理。
  • 结果确定:算法没有随机初始化,相同参数下结果可复现。
  • 参数相对较少:主要参数只有 εMinPts,易于理解。

局限性 (Cons)

  • 对参数敏感εMinPts 的选择对结果影响巨大,选择不当可能导致完全错误的结果。
  • 高维数据效果差:“维度灾难”使得距离度量在高维空间中变得不稳定,所有点的距离趋于相似,密度概念失效。
  • 簇密度差异大时效果不佳:如果数据集中不同簇的密度差异很大(如一个非常稠密的簇和一个非常稀疏的簇),很难找到一个 ε 值同时适用于所有簇。
  • 时间复杂度:对于大规模数据集,计算所有点对的距离可能较慢,尽管有空间索引优化。
  • 边界点归属模糊:一个边界点可能属于多个核心点的邻域,但DBSCAN会将其分配给“先发现”的簇,这有时会导致边界划分不自然。

七、何时使用DBSCAN?

DBSCAN是以下场景的理想选择:

  • 数据中可能存在噪声或离群点。
  • 簇的形状是非凸的、不规则的(如月牙形、环形、螺旋形)。
  • 事先不知道簇的数量。
  • 数据维度适中(建议低于10维)。
  • 需要进行异常检测(噪声点即为异常点)。
  • 数据分布相对均匀,不同簇的密度差异不大。

替代方案考虑

  • 高维数据:考虑使用谱聚类(Spectral Clustering)或t-SNE/UMAP降维后再聚类。
  • 密度差异大:考虑使用OPTICS(DBSCAN的扩展)或Mean-Shift。
  • 需要软聚类:考虑使用高斯混合模型(GMM)。

八、DBSCAN的变种与扩展

DBSCAN的成功催生了许多改进版本:

8.1 OPTICS (Ordering Points To Identify the Clustering Structure)

  • 核心思想:不直接聚类,而是生成一个“可达性图”(Reachability Plot),该图揭示了数据的聚类结构。
  • 优势:可以发现不同密度的簇,对 ε 的选择不敏感(使用一个较大的 ε 即可)。
  • 缺点:更复杂,需要额外的步骤从可达性图中提取簇。

8.2 HDBSCAN (Hierarchical DBSCAN)

  • 核心思想:构建一个层次化的聚类树,然后从中提取最稳定的簇。
  • 优势:自动选择最优簇数量,对参数更鲁棒,能处理密度变化的数据。
  • 应用:在文本聚类、客户分群中表现优异。

8.3 LOF (Local Outlier Factor)

  • 虽然不是聚类算法,但与DBSCAN思想同源。

  • 核心思想:计算每个点的局部离群因子,值越大越可能是离群点。
    re)

  • 核心思想:不直接聚类,而是生成一个“可达性图”(Reachability Plot),该图揭示了数据的聚类结构。

  • 优势:可以发现不同密度的簇,对 ε 的选择不敏感(使用一个较大的 ε 即可)。

  • 缺点:更复杂,需要额外的步骤从可达性图中提取簇。

8.2 HDBSCAN (Hierarchical DBSCAN)

  • 核心思想:构建一个层次化的聚类树,然后从中提取最稳定的簇。
  • 优势:自动选择最优簇数量,对参数更鲁棒,能处理密度变化的数据。
  • 应用:在文本聚类、客户分群中表现优异。

8.3 LOF (Local Outlier Factor)

  • 虽然不是聚类算法,但与DBSCAN思想同源。
  • 核心思想:计算每个点的局部离群因子,值越大越可能是离群点。
  • 应用:专门用于异常检测。
http://www.xdnf.cn/news/1275103.html

相关文章:

  • redis集群-本地环境
  • AAAI 2025丨具身智能+多模态感知如何精准锁定目标
  • BGP笔记整理
  • CST MATLAB 联合仿真超材料开口谐振环单元
  • PWM波的频谱分析及matlab 验证[电路原理]
  • 企业高性能web服务器——Nginx
  • PySpark
  • 【redis初阶】------List 列表类型
  • Mysql 8.0 新特性
  • drippingblues靶机通关练习笔记
  • 搭建本地 Git 服务器
  • nginx-主配置文件
  • Flask多进程数据库访问问题详解
  • Words or Vision Do Vision-Language Models Have Blind Faith in Text
  • Baumer高防护相机如何通过YoloV8深度学习模型实现道路坑洼的检测识别(C#代码UI界面版)
  • 基于FFmpeg的B站视频下载处理
  • 配置timer控制 IO的输出(STC8)
  • 浏览器CEFSharp88+X86+win7 之js交互开启(五)
  • 【LeetCode】102 - 二叉树的层序遍历
  • MySQL 处理重复数据详细说明
  • DBAPI 实现不同角色控制查看表的不同列
  • SQL约束:数据完整性的守护者
  • 【面试场景题】异地多活改造方案
  • 实现两个开发板的串口通讯(基于STC8实现)
  • Oracle lgwr触发条件
  • c语言常见错误
  • 深入解析微服务分布式事务的原理与优化实践
  • 【代码随想录day 16】 力扣 513.找树左下角的值
  • Linux 路由子系统深度分析:框架、实现与代码路径
  • MariaDB 数据库管理