MCM520 ← 资料站首页 DBSCAN 密度聚类 · 深度手册 打开交互阅读器 →

DBSCAN 密度聚类 · 深度手册

分类:聚类分类 | 难度:★★☆ 进阶 | 编号:dbscan


📊 数据视角:它到底有多常用?

本资料站收录 343 篇 优秀范文,DBSCAN 共出现约 25 次,排名靠前。其中:

  • 美赛(MCM/ICM):18 次,主要用于异常检测和空间聚类;
  • 国赛(CUMCM):5 次,用于数据分组;
  • 电工杯 / 泰迪杯:约 2~3 次。

关键词覆盖:"DBSCAN"、"密度聚类"、"核心点"、"边界点"、"噪声"、"eps"、"MinPts" 等。

一句话:DBSCAN 不需要预先指定簇数,而是根据点的密度自动发现任意形状的簇——还能顺带把离群点识别出来,是 K-Means 的天然互补。


一、这是什么(一句话用途)

基于密度的聚类(任意形状簇 / 异常检测 / 无需预设簇数)


二、核心思想

DBSCAN(Density-Based Spatial Clustering of Applications with Noise)的核心直觉很简单:好的簇应该是高密度区域,被低密度区域隔开。它把数据点分为三类:

类型 定义 含义
核心点 在 ε 邻域内至少有 MinPts 个点 簇的内部点
边界点 在核心点的 ε 邻域内,但自身不满足核心点条件 簇的边缘
噪声点 既不是核心点也不是边界点 离群点

关键优势:

  • 自动发现任意形状的簇(不限于球形)
  • 不需要预先指定簇数 k
  • 能识别并标记噪声点
  • 对数据中的离群点鲁棒

三、数学原理与推导

3.1 基本定义

给定数据集 DD,两个参数 (ε,MinPts)(\varepsilon, \text{MinPts}):

  • ε-邻域:Nε(p)={q∈D∣dist(p,q)≤ε}N_\varepsilon(p) = \{q \in D \mid \text{dist}(p, q) \le \varepsilon\}
  • 核心点:∣Nε(p)∣≥MinPts|N_\varepsilon(p)| \ge \text{MinPts}
  • 直接密度可达:若 pp 是核心点且 q∈Nε(p)q \in N_\varepsilon(p),则 p→qp \to q
  • 密度可达:存在点链 p1,p2,…,pnp_1, p_2, \ldots, p_n,p1=p,pn=qp_1=p, p_n=q,且 pi+1p_{i+1} 直接密度可达于 pip_i
  • 密度相连:存在点 oo 使得 pp 和 qq 都密度可达于 oo

簇:满足最大密度相连性的点集合。

3.2 算法流程

输入: 数据集 D, 半径 ε, 最小点数 MinPts
输出: 簇标签

1. 初始化所有点为"未访问"
2. 对每个未访问点 p:
   a. 标记 p 为已访问
   b. 找到 p 的 ε-邻域 Nε(p)
   c. 如果 |Nε(p)| < MinPts: 
        标记 p 为噪声(暂时)
   d. 否则:
        创建新簇 C
        从 Nε(p) 中扩展簇 C:
          对 Nε(p) 中每个点 q:
            如果 q 未访问:
              标记 q 为已访问
              找到 q 的 ε-邻域
              如果 |Nε(q)| >= MinPts:
                将 Nε(q) 加入扩展队列
            如果 q 不属于任何簇:
              将 q 加入簇 C
3. 返回所有簇和噪声点

四、建模 / 求解步骤

  1. 数据标准化
  2. 选择距离度量(通常欧氏)
  3. 确定 ε 和 MinPts(见下文)
  4. 运行 DBSCAN 聚类
  5. 评估聚类质量 + 分析噪声点

五、参数选择指南

5.1 K-距离图法(选 ε)

对每个点计算到第 K 个最近邻居的距离(K≈MinPts),排序后画 K-距离图。曲线上的"拐点"对应的距离值即为合适的 ε。

5.2 MinPts 经验规则

MinPts≥维度数+1\text{MinPts} \ge \text{维度数} + 1

一般取 MinPts=4∼2d\text{MinPts} = 4 \sim 2d(d 为特征维度)。

5.3 交叉验证

在 ε 的候选范围内搜索,使轮廓系数(对密度簇)或 Calinski-Harabasz 指数最大。


六、Python 实现示例

6.1 从零手写 DBSCAN

import numpy as np

class DBSCAN:
    def __init__(self, eps=0.5, min_samples=5):
        self.eps = eps
        self.min_samples = min_samples
    
    def _distance(self, x1, x2):
        return np.sqrt(np.sum((x1 - x2)**2))
    
    def _get_neighbors(self, point_idx, X):
        neighbors = []
        for i, x in enumerate(X):
            if self._distance(X[point_idx], x) <= self.eps:
                neighbors.append(i)
        return neighbors
    
    def fit(self, X):
        n = len(X)
        labels = np.full(n, -1, dtype=int)  # -1 表示噪声
        visited = np.zeros(n, dtype=bool)
        
        for i in range(n):
            if visited[i]:
                continue
            visited[i] = True
            neighbors = self._get_neighbors(i, X)
            
            if len(neighbors) < self.min_samples:
                labels[i] = -1  # 噪声点
            else:
                # 新建簇
                cluster_id = np.max(labels) + 1 if -1 in labels else 0
                labels[i] = cluster_id
                
                # 扩展簇
                seed_set = neighbors[:]
                j = 0
                while j < len(seed_set):
                    q = seed_set[j]
                    if not visited[q]:
                        visited[q] = True
                        q_neighbors = self._get_neighbors(q, X)
                        if len(q_neighbors) >= self.min_samples:
                            seed_set.extend([x for x in q_neighbors if x not in seed_set])
                    
                    if labels[q] == -1:
                        labels[q] = cluster_id  # 边界点分配簇
                    
                    j += 1
        
        self.labels_ = labels
        return self

# 测试
np.random.seed(42)
# 三簇数据 + 噪声
X = np.vstack([
    np.random.randn(50, 2) * 0.5 + [0, 0],
    np.random.randn(50, 2) * 0.5 + [5, 5],
    np.random.randn(50, 2) * 0.3 + [10, 0],
    np.random.randn(10, 2) * 0.2 + [2.5, 7.5],  # 噪声
])
model = DBSCAN(eps=1.0, min_samples=5).fit(X)
print(f"发现 {len(set(model.labels_)) - (1 if -1 in model.labels_ else 0)} 个簇")
print(f"噪声点数: {np.sum(model.labels_ == -1)}")

6.2 使用 sklearn

import numpy as np
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
import matplotlib.pyplot as plt

np.random.seed(42)
# 模拟不规则形状的簇(螺旋形)
theta = np.linspace(0, 4*np.pi, 200)
X1 = np.column_stack([theta*np.cos(theta), theta*np.sin(theta)]) + np.random.randn(200, 2)*0.3
X2 = np.column_stack([theta*np.cos(theta)+5, theta*np.sin(theta)+5]) + np.random.randn(200, 2)*0.3
X = np.vstack([X1, X2])

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 用 K-距离图选 eps
k = 5
distances = np.sort(np.linalg.norm(X_scaled[:, None] - X_scaled[None, :], axis=2)[:, k])
plt.plot(distances)
plt.xlabel('Point index'); plt.ylabel(f'{k}-th nearest distance')
plt.title('K-Distance Graph for DBSCAN eps selection')
plt.tight_layout()
plt.show()

# 聚类
dbscan = DBSCAN(eps=0.8, min_samples=5)
labels = dbscan.fit_predict(X_scaled)
n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
n_noise = list(labels).count(-1)
print(f'Clusters: {n_clusters}, Noise: {n_noise}')

七、MATLAB 实现示例

%% DBSCAN 聚类(MATLAB/Octave)
rng(42);
% 生成数据
X1 = randn(50,2)*0.5 + [0,0];
X2 = randn(50,2)*0.5 + [5,5];
X3 = randn(50,2)*0.3 + [10,0];
X = [X1; X2; X3];
% 添加噪声
X = [X; randn(10,2)*0.2 + [2.5, 7.5]];

% DBSCAN
eps = 1.0; minPts = 5;
n = size(X,1);
labels = -ones(n,1);
visited = false(n,1);
clusterId = 0;

for i = 1:n
    if visited(i), continue; end
    visited(i) = true;
    % 找邻居
    dists = sqrt(sum((X - X(i,:)).^2, 2));
    neighbors = find(dists <= eps);
    
    if length(neighbors) < minPts
        labels(i) = -1;  % 噪声
    else
        clusterId = clusterId + 1;
        labels(i) = clusterId;
        seedSet = neighbors';
        j = 1;
        while j <= length(seedSet)
            q = seedSet(j);
            if ~visited(q)
                visited(q) = true;
                dists_q = sqrt(sum((X - X(q,:)).^2, 2));
                neighbors_q = find(dists_q <= eps);
                if length(neighbors_q) >= minPts
                    seedSet = [seedSet; neighbors_q'];
                end
            end
            if labels(q) == -1
                labels(q) = clusterId;  % 边界点
            end
            j = j + 1;
        end
    end
end

fprintf('发现 %d 个簇, 噪声点 %d 个\n', max(labels(labels>0)), sum(labels==-1));

八、常见问题与改进

问题 解决方案
高维数据效果差(维度灾难) 先用 PCA 降维
簇密度差异大时效果不佳 用 OPTICS 算法(自适应 ε)
参数选择困难 用 K-距离图 + 交叉验证
大数据集慢(O(n²)) 用球树/KD-tree 加速近邻查询

九、K-Means vs DBSCAN 对比

特性 K-Means DBSCAN
需要预设簇数 ✅ 是 ❌ 否
任意形状簇 ❌ 只适合球形 ✅ 可以
噪声处理 ❌ 强制分配 ✅ 自动识别
密度不均 ❌ 效果差 ✅ 鲁棒
计算复杂度 O(nkt) O(n²)(可优化到 O(nlogn))
参数 只需 k 需 ε 和 MinPts

十、论文写作技巧

1. 动机怎么写

由于数据分布具有任意形状且存在噪声点,K-Means 等传统聚类算法难以适用。本文采用 DBSCAN 密度聚类算法,该算法能够自动发现簇的数量和形状,同时识别噪声点,更适合本问题的数据特征。

2. 结果怎么写

必须包含:

  • 聚类散点图:不同颜色标注各簇,黑色标注噪声
  • 参数选择依据:K-距离图 + 参数说明
  • 噪声分析:噪声点占比及业务含义

十一、相关手册(延伸阅读)


完整算例(数字演示,照着算一遍)

场景:12 个点 2 维,eps=2, MinPts=3。

过程:核心点(邻域内点数≥3)彼此密度可达则连成簇;边界点归入相邻核心点所在簇;无足够邻域的点标为噪声。

结论:得到 2 个簇 + 1 个噪声点,无需预设簇数 K,能发现任意形状簇。

十二、本手册导航


本手册由「算法深度手册生成器」自动产出。