3. DBSCAN 算法流程详解:步骤与伪代码
好,咱们直接切入正题。DBSCAN 这个名字听起来唬人,但它的核心逻辑非常直观。说白了,它就是一个“拉帮结派”的算法——谁跟谁挨得近,谁就是一家人;离群太远的,自己一边玩去。
我刚开始接触这个算法的时候,也觉得参数选择有点玄学。但后来在项目中用它处理分子动力学模拟的构象数据,嗯,真香。它的好处是不用预先指定要分多少类,这对我们探索未知构象空间来说太重要了。
咱们先把 DBSCAN 的整个流程拆开来看,我习惯把它分成四个阶段。先上一张流程图,你心里有个谱。
3.1 核心概念回顾
在聊流程之前,有两个参数你得刻在脑子里:Eps (邻域半径) 和 MinPts (密度阈值)。这是算法的命门。
我按自己的理解给你梳理一下这三个基本角色:
- 核心点 (Core Point):在半径为 Eps 的圈圈里,包含的点数 ≥ MinPts。
- 边界点 (Border Point):在某个核心点的圈圈里,但它自己圈里的点数 < MinPts。
- 噪音点 (Noise Point):既不是核心点,也不在任何一个核心点的圈圈里。这就是我们常说的离群值。
3.2 第一步:标记核心点
算法一开始,它会遍历你给的所有数据点。对每一个点,计算它到周围所有点的距离,然后数一数在 Eps 半径内到底有几个。
- 如果邻居数 ≥ MinPts,好,这个点被标记为核心点。
- 否则,暂时标记为候选边界点或噪音点。
这个过程说白了就是一次全局扫描。我刚入行时总觉得这步简单,但后来在百万级构象的数据集上跑,才发现计算距离矩阵非常耗时。所以,距离度量方式很重要。对于构象,RMSD 是最常用的。但你要知道,计算开销是 O(n²) 的,数据量大了必须上索引结构,比如 KD-tree 或 Ball tree。
3.3 第二步:基于密度扩展生成簇
这一块是算法的精髓。它定义了三个递进的关系,你感受一下:
- 密度直达:一个核心点 P,它的圈圈里有另一个点 Q,就说 Q 从 P 是密度直达的。注意,这关系是单向的。
- 密度可达:有一条由核心点连成的链,可以一头走到另一头。比如 P 到 Q 直达,Q 到 R 直达……那么 P 到 R 就是密度可达。
- 密度相连:如果存在一个核心点 O,使得 P 和 Q 都从 O 密度可达,那么 P 和 Q 就是密度相连的。这在处理构象合并时特别关键,两个看似不直接重叠的局域构象,可能通过中介态连在一起。
算法会随机选一个未处理的核心点开始,沿着密度可达的路径一路“生长”,把所有密度相连的点都拉进同一个簇。直到这个簇无法再扩张,才去选下一个核心点。
3.4 第三步与第四步:迭代与输出
重复第二步,直到所有核心点都被访问过。最后,那些始终没被任何簇“收留”的点,就被贴上噪音点的标签。在构象分析中,这些通常对应于热力学上不太稳定的高能瞬态构象。
输出结果很清爽:每个原始数据点都有一个簇标签。比如 -1 表示噪音,0 表示第一个簇,1 表示第二个簇,等等。
3.5 伪代码实现
光说不练假把式。这是我手写的伪代码风格,你直接照着这个思路去改写你的 Python 或者 C++ 代码,基本不会跑偏。
算法:DBSCAN(D, Eps, MinPts)
输入:数据点集 D,邻域半径 Eps,密度阈值 MinPts
输出:每个点的簇标签 label[]
1. 初始化所有点的 label = UNCLASSIFIED
2. 初始化当前簇编号 clusterId = 0
3. 对 D 中的每个点 P:
4. 如果 label[P] != UNCLASSIFIED,则跳过
5. 查找 P 的 Eps 邻域内的所有点 neighbors = regionQuery(P, Eps)
6. 如果 |neighbors| < MinPts:
7. 标记 label[P] = NOISE
8. 否则:
9. clusterId++
10. 标记 label[P] = clusterId
11. 创建一个种子集合 seeds = neighbors
12. 对 seeds 中的每个点 Q:
13. 如果 label[Q] == NOISE:
14. 标记 label[Q] = clusterId
15. 如果 label[Q] != UNCLASSIFIED,跳过
16. 标记 label[Q] = clusterId
17. 查找 Q 的 Eps 邻域 neighborsQ = regionQuery(Q, Eps)
18. 如果 |neighborsQ| >= MinPts:
19. seeds = seeds ∪ neighborsQ
20. 结束循环
regionQuery(P, Eps):
21. 返回所有与 P 距离小于等于 Eps 的点索引
注意第14行,我曾经漏掉这一步,结果边界点被重复标记,导致后面的簇扩张乱了套。这是一个经典的坑,填上就好了。
3.6 几个关键点你心里要有数
趁热打铁,我把容易出问题的地方列出来:
| 环节 | 常见问题 | 我的处理建议 |
|---|---|---|
| 参数初始化 | Eps 和 MinPts 选不好,聚类效果天差地别 | 先做 k-dist 图找拐点,我常用这个办法 |
| 距离计算 | 全量 O(n²) 计算在大数据集上跑不动 | 上 KD-tree 或 Ball tree 索引 |
| 噪音点处理 | 构象分析中噪音点往往有物理意义,不能直接丢弃 | 建议留出来做后续分析,或者和专家讨论 |
| 边界点归属 | 同一边界点可能被两个核心点争夺 | DBSCAN 的处理是分配给先到的一个 |
核心总结:DBSCAN 的全流程其实就两件事——找核心点和串密度链。不管数据集多大,这个骨架不变。你先把这两块写稳了,剩下的都是锦上添花。