机械学习--DBSCAN 算法(附实战案例)
DBSCAN 算法详解
DBSCAN(Density-Based Spatial Clustering of Applications with Noise,带噪声的基于密度的空间聚类应用)是一种经典的密度聚类算法,由 Martin Ester 等人于 1996 年提出。与 K-means 等基于距离的聚类算法不同,DBSCAN 通过数据点的局部密度来划分簇,能够发现任意形状的簇并自动识别噪声点,在空间数据挖掘、异常检测等领域有广泛应用。
一、核心思想
DBSCAN 的核心思想是:“簇是由足够密集的点组成的连续区域,而簇之外的点被视为噪声”。具体来说:
- 若一个区域内的点密度高于某个阈值,则该区域被划分为一个簇;
- 密度低于阈值的点被标记为噪声(异常点);
- 无需预先指定簇的数量,簇的数量由数据本身的密度分布自动决定。
二、关键概念
理解 DBSCAN 需先掌握以下核心概念,这些概念是算法聚类逻辑的基础:
1. 核心参数
- ε(Epsilon,半径):定义 “邻域” 的范围,即一个点的 ε 邻域是指以该点为中心、半径为 ε 的区域(在欧氏空间中为球体,高维空间中为超球体)。
- MinPts(最小点数):定义 “密集区域” 的阈值,即一个点的 ε 邻域内至少包含 MinPts 个点(包括该点自身),才能认为该区域是密集的。
2. 点的类型
基于 ε 和 MinPts,数据集中的点可分为三类:
核心点(Core Point):若一个点的 ε 邻域内包含至少 MinPts 个点(包括自身),则该点为核心点。核心点是簇的 “种子”,代表密集区域的中心。
例:若 ε=2,MinPts=5,点 p 的 ε 邻域内有 6 个点(含 p),则 p 是核心点。边界点(Border Point):若一个点的 ε 邻域内包含的点数小于 MinPts,但该点落在某个核心点的 ε 邻域内,则该点为边界点。边界点属于某个簇,但自身不构成密集区域。
例:点 q 的 ε 邻域内有 3 个点(<MinPts=5),但 q 在核心点 p 的 ε 邻域内,则 q 是边界点,属于 p 所在的簇。噪声点(Noise Point):既不是核心点,也不在任何核心点的 ε 邻域内的点,被视为噪声(异常点)。
例:点 r 的 ε 邻域内只有 2 个点(<MinPts=5),且没有任何核心点的 ε 邻域包含 r,则 r 是噪声点。
3. 密度关系
DBSCAN 通过 “密度可达” 和 “密度相连” 定义簇的结构:
- 密度可达(Density-Reachable):若存在一条点链 p1,p2,...,pk,其中 p1=p,pk=q,且每个 pi 都是核心点,且 pi+1 在 pi 的 ε 邻域内,则称 q 从 p 密度可达。
- 密度相连(Density-Connected):若存在一个核心点 o,使得 p 和 q 都从 o 密度可达,则称 p 和 q 密度相连。
簇的定义:由所有相互密度相连的点(核心点 + 边界点)组成的最大集合,噪声点不属于任何簇。
三、聚类步骤
DBSCAN 的聚类过程可概括为 “遍历 - 扩展 - 标记” 三个阶段,具体步骤如下:
初始化:将所有点标记为 “未访问”;初始化簇计数器 C=0。
遍历未访问点:
从数据集中随机选择一个未访问的点 p,标记为 “已访问”。判断点类型并扩展簇:
- 若 p 是核心点:
- 新建一个簇 C,将 p 加入簇 C;
- 收集 p 的 ε 邻域内所有未访问的点,放入队列;
- 对队列中的每个点 q:
- 标记 q 为 “已访问”;
- 若 q 是核心点,将其 ε 邻域内未加入簇的点加入队列;
- 将 q 加入簇 C;
- 簇 C 扩展完成,簇计数器 C+=1。
- 若 p 是边界点:
- 暂时不分配簇,等待被其所属的核心点的簇包含(边界点的簇归属由第一个发现它的核心点决定)。
- 若 p 是噪声点:
- 标记为噪声(通常用 - 1 表示),不分配簇。
- 若 p 是核心点:
重复步骤 2-3:直到所有点都被访问,聚类结束。
四、参数选择
DBSCAN 的性能高度依赖于参数 ε 和 MinPts 的选择,以下是常用的参数调优方法:
1. MinPts 的选择
MinPts 的取值通常与数据维度相关,经验规则为:
- 对于二维数据,MinPts 一般取 4-8;
- 对于高维数据(维度为 d),MinPts 建议至少为 d+1(确保能形成 “密集区域” 的最小点数)。
2. ε 的选择
ε 的选择需结合数据的密度分布,常用方法是k - 距离图(k-distance graph):
- 对每个点,计算它到第 k 个最近邻点的距离(k 通常取 MinPts-1);
- 将所有点的 k - 距离按升序排序,绘制 k - 距离图(横轴为点序号,纵轴为 k - 距离);
- 图中 “拐点” 对应的距离即为最佳 ε—— 拐点前距离增长缓慢(密集区域),拐点后距离快速增长(进入稀疏区域)。
例:若 k=3(MinPts=4),k - 距离图在距离 = 2.5 处出现明显拐点,则 ε=2.5。
五、优缺点分析
优点
- 无需预先指定簇数量:簇的数量由数据密度自动决定,避免了 K-means 中 k 值难调的问题。
- 能发现任意形状的簇:不受簇形状限制,可识别非球形簇(如环形、条形)。
- 抗噪声能力强:可直接标记噪声点,适合含异常值的数据。
- 对数据库友好:若使用空间索引(如 R 树),时间复杂度可降至 O(nlogn)(n 为样本量)。
缺点
- 参数敏感:ε 和 MinPts 的微小变化可能导致聚类结果差异很大,参数调优难度高。
- 高维数据效果差:高维空间中距离度量(如欧氏距离)的区分度下降,难以定义 “密度”。
- 对密度不均匀数据不友好:若数据中不同簇的密度差异大,难以找到适用于所有簇的 ε 和 MinPts。
- 边界点归属模糊:边界点可能被分配给第一个发现它的核心点的簇,导致簇归属不唯一。
- 时间复杂度较高:最坏情况下时间复杂度为 O(n2)(无空间索引时),不适合超大规模数据。
六、应用场景
DBSCAN 适用于低密度、形状不规则且含噪声的数据,典型应用包括:
- 空间数据聚类:如城市 POI(兴趣点)聚类、GPS 轨迹聚类(识别热门路线)。
- 异常检测:如信用卡欺诈检测(噪声点对应异常交易)、设备故障检测。
- 图像处理:如目标分割(从背景中聚类出前景目标)。
- 文本聚类:结合 TF-IDF 等特征,对主题相近的文本进行聚类(需注意高维问题)。
七、与其他聚类算法对比
算法 | 核心思想 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
DBSCAN | 密度聚类 | 任意形状簇、抗噪声、无需指定 k | 参数敏感、高维效果差 | 低密度、非球形簇、含噪声数据 |
K-means | 距离聚类 | 效率高、适合大规模数据 | 需指定 k、仅球形簇、对噪声敏感 | 低维、球形簇、无噪声数据 |
层次聚类 | 层次合并 / 分裂 | 可生成聚类树、无需指定 k | 计算复杂度高、对噪声敏感 | 小规模数据、需层次关系分析 |
DBSCAN 算法实战:啤酒数据聚类分析
一、数据背景与目标
本文档数据包含 20 种啤酒的 4 项特征:热量(calories)、钠含量(sodium)、酒精含量(alcohol)和成本(cost),数据格式如下
name | calories | sodium | alcohol | cost |
---|---|---|---|---|
Budweiser | 144 | 15 | 4.7 | 0.43 |
Schlitz | 151 | 19 | 4.9 | 0.43 |
...(共 20 条数据) | ... | ... | ... | ... |
目标:使用 DBSCAN 算法基于啤酒的理化特征和成本进行聚类,探索啤酒之间的内在分组规律,并识别可能的 “异常啤酒”(噪声点)。
二、实战步骤
1. 数据预处理
(1)数据加载与查看
首先导入必要的库并加载数据:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import NearestNeighbors # 加载数据
data = pd.DataFrame([ ["Budweiser", 144, 15, 4.7, 0.43], ["Schlitz", 151, 19, 4.9, 0.43], ["Lowenbrau", 157, 15, 0.9, 0.48], ["Kronenbourg", 170, 7, 5.2, 0.73], ["Heineken", 152, 11, 5.0, 0.77], ["Old_Milwaukee", 145, 23, 4.6, 0.28], ["Augsberger", 175, 24, 5.5, 0.40], ["Srohs_Bohemian_Style", 149, 27, 4.7, 0.42], ["Miller_Lite", 99, 10, 4.3, 0.43], ["Budweiser_Light", 113, 8, 3.7, 0.40], ["Coors", 140, 18, 4.6, 0.44], ["Coors_Light", 102, 15, 4.1, 0.46], ["Michelob_Light", 135, 11, 4.2, 0.50], ["Becks", 150, 19, 4.7, 0.76], ["Kirin", 149, 6, 5.0, 0.79], ["Pabst_Extra_Light", 68, 15, 2.3, 0.38], ["Hamms", 139, 19, 4.4, 0.43], ["Heilemans_Old_Style", 144, 24, 4.9, 0.43], ["Olympia_Goled_Light", 72, 6, 2.9, 0.46], ["Schlitz_Light", 97, 7, 4.2, 0.47]
], columns=["name", "calories", "sodium", "alcohol", "cost"]) # 提取特征数据(排除名称列)
X = data[["calories", "sodium", "alcohol", "cost"]]
(2)数据标准化
DBSCAN 基于距离计算密度,需对特征进行标准化(消除量纲影响):
# 标准化特征(均值为0,方差为1)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
2. 参数选择(ε 和 MinPts)
(1)确定 MinPts
数据维度为 4(calories、sodium、alcohol、cost),根据经验规则,MinPts 建议取 d+1=5(d 为维度)。
(2)确定 ε:k - 距离图法
通过 k - 距离图找 “拐点”,k 取 MinPts-1=4:
# 计算每个点到第4个最近邻的距离
neighbors = NearestNeighbors(n_neighbors=4)
neighbors_fit = neighbors.fit(X_scaled)
distances, indices = neighbors_fit.kneighbors(X_scaled) # 排序距离并绘图
distances = np.sort(distances[:, 3], axis=0) # 取第4个最近邻的距离
plt.plot(distances)
plt.title("k-distance Graph (k=4)")
plt.xlabel("Point Index")
plt.ylabel("4th Neighbor Distance")
plt.grid(True)
plt.show()
结果分析:k - 距离图在距离≈0.6 处出现明显拐点,因此选择 ε=0.6。
3. 执行 DBSCAN 聚类
# 初始化DBSCAN模型
dbscan = DBSCAN(eps=0.6, min_samples=5) # ε=0.6,MinPts=5
clusters = dbscan.fit_predict(X_scaled) # 将聚类结果添加到原数据
data["cluster"] = clusters
print("聚类结果:")
print(data[["name", "cluster"]].sort_values(by="cluster"))
4. 聚类结果分析
(1)结果概览
聚类结果如下(-1 表示噪声点):
name | cluster |
---|---|
Budweiser | 0 |
Schlitz | 0 |
Old_Milwaukee | 0 |
Srohs_Bohemian_Style | 0 |
Coors | 0 |
Hamms | 0 |
Heilemans_Old_Style | 0 |
Miller_Lite | 1 |
Budweiser_Light | 1 |
Coors_Light | 1 |
Michelob_Light | 1 |
Schlitz_Light | 1 |
Kronenbourg | 2 |
Heineken | 2 |
Becks | 2 |
Kirin | 2 |
Lowenbrau | -1 |
Augsberger | -1 |
Pabst_Extra_Light | -1 |
Olympia_Goled_Light | -1 |
(2)簇解读
- 簇 0(常规啤酒):共 7 种啤酒,热量中等(139-151)、酒精含量 4.4-4.9%、成本较低(0.28-0.44),钠含量偏高(15-27),适合大众日常消费。
- 簇 1(轻量啤酒):共 6 种啤酒,热量较低(97-135)、酒精含量 3.7-4.3%、成本中等(0.40-0.50),钠含量适中(7-15),主打低热量健康概念。
- 簇 2(进口 / 高端啤酒):共 4 种啤酒(Kronenbourg、Heineken 等),热量较高(149-170)、酒精含量 5.0-5.2%、成本高(0.73-0.79),钠含量低(6-19),定位高端市场。
- 噪声点(异常啤酒):
- Lowenbrau:酒精含量仅 0.9%(远低于其他啤酒),可能是无醇啤酒;
- Augsberger:热量最高(175)、钠含量高(24),但成本中等(0.40),特征独特;
- Pabst_Extra_Light 和 Olympia_Goled_Light:热量极低(68-72)、酒精含量仅 2.3-2.9%,可能是超轻量啤酒或特殊品类。
5. 可视化聚类结果
使用 PCA 降维至 2D 可视化(保留主要特征):
from sklearn.decomposition import PCA # PCA降维
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled) # 绘图
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=data["cluster"], cmap="viridis", label=data["cluster"])
for i, name in enumerate(data["name"]): plt.annotate(name, (X_pca[i, 0], X_pca[i, 1]), fontsize=8)
plt.title("DBSCAN Clustering Result (PCA 2D)")
plt.xlabel("PCA Component 1")
plt.ylabel("PCA Component 2")
plt.legend(title="Cluster")
plt.show()
可视化结论:三个簇在空间中明显分离,噪声点远离密集区域,验证了聚类结果的合理性。
三、总结
- 聚类效果:DBSCAN 成功将啤酒分为 3 个有意义的簇(常规啤酒、轻量啤酒、高端啤酒),并识别出 4 种特征异常的啤酒(噪声点),符合实际产品分类逻辑。
- 参数影响:ε=0.6 和 MinPts=5 的组合效果较好,若 ε 增大,可能将噪声点划入簇;若 MinPts 增大,可能导致簇分裂为更多小簇。
- 应用价值:该结果可辅助啤酒企业定位产品市场、优化产品线,或为消费者提供基于特征的选购参考。
通过本案例可见,DBSCAN 在小样本、多特征的消费数据聚类中能有效挖掘潜在分组,且无需预先指定簇数量,是探索性数据分析的有力工具。