基于密度的聚类方法(DBSCAN)

在数据分析中,K-means 聚类是一种非常常用的聚类方法。它的核心思想是:将相似的数据点归为同一簇,并为每个簇计算一个质心,然后把每个点分配到距离最近的质心所属簇中。

这种方法简单高效,但也存在一些明显的局限性:

  • 对噪声和异常点敏感:极端值会影响质心的位置,从而扭曲聚类结果。
  • 需要预先指定簇的数量:在实际场景中,簇的数量往往未知,而错误的预设可能导致聚类不准确。
  • 对复杂数据分布适应性差:对于非球形或不规则形状的簇,K-means 很难得到理想的划分。

如下图所示,我们可以看到 K-means 在某些复杂数据分布下的聚类效果并不理想。

为了克服这些问题,DBSCAN(Density-Based Spatial Clustering of Applications with Noise)应运而生。它是一种基于密度的聚类方法,相比 K-means 有几个明显优势:

  1. 无需预先指定簇的数量,能够自动发现簇的数量。
  2. 能够识别任意形状的簇,不局限于球形。
  3. 自动识别噪声点,避免异常点干扰聚类结果。

这里的密度指的是单位区域内点的多少,简单来说,DBSCAN 就是找点多的地方,把它们聚成簇,把点少的地方当作噪声

凭借这些特点,DBSCAN 在地理信息分析、图像处理、数据挖掘等领域得到了广泛应用。

其他链接:
基于质心的聚类方法:https://mengbaoliang.cn/archives/93699/
基于分布的聚类方法:https://mengbaoliang.cn/archives/tag/gshh/

1. 算法原理

在理解 DBSCAN 聚类原理之前,先要明确一点,在聚类的过程中,DBSCAN 会把所有数据点分成三类:

  • 核心点:在半径 epsilon 范围内至少有 min_samples 个点(算上自己)
  • 边界点:自己周围点数不够 min_samples,但落在某个核心点的 epsilon 范围内
  • 噪声点:既不是核心点,也不在任何核心点的 epsilon 范围内

ABCDEFGHIJ
(1,1)(1,2)(2,1)(2,2)(0.5,2.5)(8,8)(8,9)(9,8)(7.5,9.5)(5,5)

假设:epsilon = 1.5、min_samples = 3

接下来,我们看看具体的聚类过程。计算每个数据点到其他数据点的距离:

ABCDEFGHIJ
A0.0001.0001.0001.4141.5819.89910.63010.63010.7005.657
B1.0000.0001.4141.0000.7079.2209.89910.0009.9255.000
C1.0001.4140.0001.0002.1219.22010.0009.89910.1245.000
D1.4141.0001.0000.0001.5818.4859.2209.2209.3014.243
E1.5810.7072.1211.5810.0009.3019.92510.1249.8995.148
F9.8999.2209.2208.4859.3010.0001.0001.0001.5814.243
G10.6309.89910.0009.2209.9251.0000.0001.4140.7075.000
H10.63010.0009.8999.22010.1241.0001.4140.0002.1215.000
I10.7009.92510.1249.3019.8991.5810.7072.1210.0005.148
J5.6575.0005.0004.2435.1484.2435.0005.0005.1480.000

统计每个数据点的邻域

数据点邻域点集合邻域点数量数据点类型
A(1,1)A, B, C, D4核心点
B(1,2)A, B, C, D, E5核心点
C(2,1)A, B, C, D4核心点
D(2,2)A, B, C, D4核心点
E(0.5,2.5)B, E2边界点
F(8,8)F, G, H3核心点
G(8,9)F, G, H, I4核心点
H(9,8)F, G, H3核心点
I(7.5,9.5)G, I2边界点
J(5,5)J1噪声点

开始聚类:

第一步:外层循环遍历数据点

  • 访问 A:A 未访问且为核心点 → 创建簇 C1
  • 将 A 的邻域 {A, B, C, D} 全部加入簇
  • 核心邻居 B、C、D 入队列等待扩展 → 队列状态 [B, C, D]

第二步:内层队列扩展簇 C1

  • 出队 B:邻域 {B, E, A, D, C}
    • E 不是核心点,但在 B 的邻域内 → 加入簇作为边界点,不入队
    • 队列更新为 [C, D]
  • 出队 C:邻域 {C, A, D, B} 已全部在簇中 → 无新增,队列 [D]
  • 出队 D:邻域 {D, B, C, A} 已全部在簇中 → 队列清空,C1 扩展完成

第三步:外层循环继续遍历

  • 遇到 B、C、D、E 已访问 → 跳过
  • 下一个未访问核心点 F → 创建簇 C2
  • 将 F 的邻域 {F, G, H} 加入簇
  • 核心邻居 G、H 入队列 → 队列状态 [G, H]

第四步:内层队列扩展簇 C2

  • 出队 G:邻域 {G, I, F, H}
    • I 不是核心点,但在 G 的邻域内 → 加入簇作为边界点,不入队
    • 队列更新为 [H]
  • 出队 H:邻域 {H, F, G} 已全部在簇中 → 队列清空,C2 扩展完成

第五步:外层循环继续遍历剩余数据点

  • I 已访问 → 跳过
  • J 未访问且邻域点数 < min_samples,且不在任何核心点邻域 → 判定为噪声点

第六步:最终聚类结果

  • 簇 C1:核心点 A、B、C、D;边界点 E
  • 簇 C2:核心点 F、G、H;边界点 I
  • 噪声点:J

通过对 DBSCAN 聚类过程的了解,我们可以发现,簇的形成和扩展是依靠核心点推动的,而边界点会被吸收进簇,噪声点则不属于任何簇。

简单总结下,在 DBSCAN 里,簇是长出来的,而不是一开始就分好的。整个聚类的过程可以想成:

  1. 找到一个核心人物(邻居够多的点)。
  2. 这个核心人物会把自己周围(epsilon 范围内)的邻居都拉进来。
  3. 新进来的点如果自己也是“核心”,就继续往外扩 → 簇就这样不断生长。
  4. 如果一个点既不是核心,也没有核心愿意拉它,就成了噪声

另外,我们也发现 DBSCAN 聚类过程中,寻找邻域需要大量的计算。例如:要对 1000 条数据进行聚类,则会进行 100 万次的距离计算,如果数据维度较高,则会需要更多的计算时间。所以,DBSCAN 的聚类优化,主要是通过对邻域计算的优化来提升聚类效率。常见的是,使用 KD TREE 或者 BALL TREE,KD Tree 适合低维数据(通常 20 维),BALL tree 适合较高维数据(20 维以上)。

2. 样本归簇

在很多聚类算法中,例如 K-Means 和 高斯混合模型(GMM),训练结束后通常会提供 predict 接口。当新样本到来时,无需依赖原始数据的完整分布,只需基于模型训练得到的簇的数学描述,即可直接判定新样本的簇归属。例如:

  • K-Means:训练后会得到各簇的质心,新样本的归属可通过计算其与各质心的距离确定
  • 高斯混合模型(GMM):训练后会输出每个高斯子分布的均值、方差、混合系数,新样本的归属可通过计算其在各子分布下的概率密度,选择概率最大的簇作为结果

但 DBSCAN 与二者存在本质区别:DBSCAN 的核心逻辑是基于密度划分簇,它仅对已有训练数据点完成标记,例如:哪些点属于某一簇、哪些点是噪声点,并不会生成质心或者概率分布这类可描述簇形态的数学参数。

因此,DBSCAN 本质上是一种非参数化、完全依赖原始数据密度分布的聚类算法:由于缺乏对簇的通用数学描述,它无法像 K-Means 或 GMM 那样,直接通过 predict 接口判定新样本的簇归属(若要处理新样本,需将其重新放入原始数据集,结合密度规则重新计算)。

3. 设置参数

DBSCAN 聚类需要两个非常关键的超参数:epsilon(邻域半径)min_samples(核心点的最少邻居数)
这两个参数的选择会直接影响聚类效果。虽然没有一个固定的“最佳公式”,但有一些直观的经验方法可以帮助我们更合理地设置。

在实践中,通常建议 先确定 min_samples,再选择合适的 epsilon

3.1 min_samples

min_samples 最常用的参考公式为:

min_samples ≈ 数据维度 + 1

这样设置的直观意义是:在 d 维空间中,核心点的邻域至少需要 d+1 个点,才能形成一个稳定的几何结构,从而保证簇的密度足够高。举几个例子:

  • 1 维空间:1 维空间是直线,要形成密集段,一个点至少需要 1 个相邻点(加上自身共 2 个)
  • 2 维空间:2 维空间是平面,要形成密集区域,至少需要 3 个点才能构成最小三角形单元
  • d 维空间:需要 d+1 个点才能支撑起 d 维空间的最小密集结构,避免因点数量过少导致误判密集区域

注意:这个公式并不是硬性规定,只是一种基于几何直觉的经验参考。实际中,还需要结合数据分布进行调整。

注意:这只是一个 “入门级参考标准”,并非绝对准则。实际应用中,若数据噪声较多,可适当增大 min_samples(如取 d+2、d+3),减少噪声点被误判为核心点的概率;若数据簇本身较稀疏,可适当减小 min_samples,避免漏判核心点。

3.2 epsilon

当 min_samples 确定后,epsilon 的作用就是划定邻域范围,确保核心点的邻域内,能稳定包含至少 min_samples 个样本。此时最有效的方法是K 距离图法,其中 “K 值” 需与已确定的 min_samples 保持一致,即 K=min_samples。

下面举个例子,假设有 5 个数据点,k = 3(min_samples = 3)

A(1,1), B(2,2), C(2,0), D(5,5), E(8,8)

计算每个点到其他点的距离:

ABCDE
A01.411.415.669.90
B1.4102.004.248.49
C1.412.0005.8310.00
D5.664.245.8304.24
E9.908.4910.004.240

对每个点,把距离排序,取第 3 个最近邻距离(k = 3)

升序距离(不含自己)第 3 近邻距离
A1.41, 1.41, 5.66, 9.905.66
B1.41, 2.00, 4.24, 8.494.24
C1.41, 2.00, 5.83, 10.005.83
D4.24, 4.24, 5.66, 5.835.66
E4.24, 8.49, 9.90, 10.009.90

排序第 3 近邻距离:4.24、5.66、5.66、5.83、9.90,并绘制 K 距离图:

在上图中:

  • K 距离较小的点,说明它周围邻居密集
  • K 距离较大的点,说明点周围邻居稀疏
  • 密集区域和稀疏区域之间的点称作拐点

拐点左侧是高密度区域,右侧是低密度区域,因此 epsilon 应取拐点附近的区间,避免将低密度样本误判为核心点。上图这种情况,我们可以大概在 [5.66, 5.83] 之间选择一个作为 epsilon 的值,这样能让 DBSCAN 准确识别核心点和噪声点,得到合理的聚类结果。

注意:即使初步选择的 epsilon 值参考了 K 距离图,聚类效果可能仍存在不足,此时可以通过微调 epsilonmin_samples,以获得更合理的簇划分。

4. 接口使用

import numpy as np
from sklearn.cluster import DBSCAN
import pandas as pd


def demo():
    points = np.array([[1, 1], [1, 2], [2, 1], [2, 2], [0.5, 2.5], [8, 8], [8, 9], [9, 8], [7.5, 9.5], [5, 5]])
    columns = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']

    # algorithm 邻域搜索算法,可选值:auto、ball_tree、kd_tree、brute
    # metric 样本距离计算方法,例如:euclidean、cosine、minkowski、manhattan
    dbscan = DBSCAN(eps=1.5, min_samples=3, metric='euclidean', algorithm='brute')
    result = dbscan.fit_predict(points)

    # 聚类结果
    # [ 0  0  0  0  0  1  1  1  1 -1]
    # 0 表示第一个簇
    # 1 表示第二个簇
    # -1 表示噪声点
    print(result)

    # 核心样本索引:[0 1 2 3 5 6 7]
    print(dbscan.core_sample_indices_)
    # 具体核心样本:[[1. 1.] [1. 2.] [2. 1.] [2. 2.] [8. 8.] [8. 9.] [9. 8.]]
    print(dbscan.components_)


if __name__ == '__main__':
    demo()
未经允许不得转载:一亩三分地 » 基于密度的聚类方法(DBSCAN)
评论 (0)

9 + 2 =