机器学习入门:DBSCAN 聚类
机器学习入门:DBSCAN 聚类
前言:本文是“机器学习入门”系列的第九站。上一站我们学习了 K-Means 聚类,它通过 K 个质心将数据划分成球形簇,但需要预先指定 K 值,且只能发现凸形簇。本篇我们将学习另一种完全不同的聚类算法——DBSCAN。它不需要指定簇的数量,能发现任意形状的簇,还能自动识别噪声点。其核心思想是:将密度足够高的区域划分为簇,低密度区域的点则标记为噪声。
目录
- 一、认识 DBSCAN 聚类
- 二、DBSCAN 的核心原理
- 三、DBSCAN vs K-Means
- 四、DBSCAN 的优缺点
- 五、典型应用场景
- 六、核心 API 速查
- 七、实战案例:啤酒聚类分析
- 八、总结
一、认识 DBSCAN 聚类
1.1 什么是 DBSCAN?
DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种基于密度的无监督聚类算法。它的核心特点是能根据数据的密度分布自动发现任意形状的簇,同时还能识别出数据中的噪声点。
通俗理解:想象你在地图上标注了一堆地点。DBSCAN 要做的事情是:找到那些“邻居特别多”的核心区域,从这些区域向外扩散,把所有能连通的密集区域圈成一个簇。而那些孤零零的、周围没什么邻居的点,就被标记为“噪声”。
DBSCAN 用于聚类。它不依赖标签,仅凭数据本身的密度分布进行分组。
1.2 为什么要用 DBSCAN?
K-Means 有两个明显的局限:
- 必须预先指定 K 值:在无监督学习中,我们往往不知道数据应该分成几类。
- 只能发现凸形簇:对于环形、S 形等非凸形状的数据,K-Means 效果很差。
DBSCAN 正是为了解决这两个问题而生的。
二、DBSCAN 的核心原理
2.1 两个核心参数
DBSCAN 通过两个参数来定义“密度”:
| 参数 | 含义 | 说明 |
|---|---|---|
| ε(eps) | 邻域半径 | 判断“邻居”的距离阈值。两个样本距离小于 ε 时,认为是邻居 |
| min_samples(MinPts) | 最小样本数 | 判断“核心点”的密度阈值。某点 ε 邻域内至少有 min_samples 个样本时,该点才被视为核心点 |
2.2 三类样本点
基于这两个参数,DBSCAN 将样本点分为三类:
| 类型 | 定义 |
|---|---|
| 核心点(Core Point) | ε 邻域内样本数 ≥ min_samples 的点 |
| 边界点(Border Point) | ε 邻域内样本数 < min_samples,但落在某个核心点的 ε 邻域内的点 |
| 噪声点(Noise Point) | 既不是核心点也不是边界点的点(标签为 -1) |
2.3 簇的定义与连接
DBSCAN 的簇由密度相连的核心点及其边界点组成:
- 直接密度可达:点 q 在核心点 p 的 ε 邻域内,则 q 从 p 直接密度可达
- 密度可达:通过一串核心点,从 p 可以“接力”到达 q,则 q 从 p 密度可达
- 密度相连:存在点 o,使得 p 和 q 都从 o 密度可达,则 p 和 q 密度相连
一个簇就是所有密度相连的点的最大集合。
通俗理解:核心点就像“感染源”,它会把 ε 邻域内的所有点拉入同一个簇。如果这个簇里有其他核心点,它们又会继续向外“感染”更多的点。直到遇到“边界点”或“噪声点”,传播才停止。
2.4 算法流程
- 随机选择一个未访问的数据点
- 检查该点的 ε 邻域:
- 如果邻域内点数 ≥ min_samples,标记为核心点,邻域内所有点加入同一簇
- 否则标记为噪声(但之后如果被核心点纳入邻域,可能转为边界点)
- 对簇内的核心点,递归扩展其邻域,将更多点加入簇
- 重复 1-3 步,直到所有点都被访问
三、DBSCAN vs K-Means
| 对比维度 | K-Means | DBSCAN |
|---|---|---|
| 是否需指定 K | 是,必须预先指定 | 否,自动发现簇 |
| 簇的形状 | 只能发现凸形(球形)簇 | 可发现任意形状的簇 |
| 噪声处理 | 所有点都被分配,无噪声概念 | 自动识别噪声点(标签 -1) |
| 参数依赖 | 主要依赖 K 值 | 依赖 eps 和 min_samples,较敏感 |
| 密度差异 | 对密度差异不敏感 | 对密度差异大的数据表现不佳 |
四、DBSCAN 的优缺点
4.1 优点
- 无需预设簇数量:自动根据密度划分簇
- 支持任意形状的簇:可识别环形、S 形、不规则形状的簇
- 能识别噪声点:天然支持离群点检测
- 对数据顺序不敏感:聚类结果稳定
4.2 缺点
- 对参数 eps 和 min_samples 敏感:参数选择不当会导致聚类结果差异极大
- 不适合密度差异大的数据:同一套参数无法同时适配高密度和低密度区域
- 高维数据效果差:高维空间中“距离”概念失效,需先降维
- 大规模数据效率较低:传统实现时间复杂度为 O(n²)
五、典型应用场景
- 图像分割:将图片中的不同区域(如森林、湖泊)分割成不同的簇
- 社交网络分析:识别不同的用户群体或社区
- 异常检测:远离所有簇的样本点可能是异常点
- 地理空间数据:GPS 轨迹聚类、犯罪热点分析
- 文本挖掘:将相似文档归为一组
六、核心 API 速查
6.1 导包方式
fromsklearn.clusterimportDBSCAN6.2 核心参数详解
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
eps | float | 0.5 | 邻域半径。最重要的参数,决定邻居的距离阈值。eps 过大会合并不同的簇,过小会拆分同一个簇 |
min_samples | int | 5 | 核心点密度阈值。eps 一定时,min_samples 过大会导致核心点过少,噪声增多;过小会产生大量核心点,簇数减少 |
metric | str | 'euclidean' | 距离度量方式。常用'euclidean'(欧氏距离)、'manhattan'(曼哈顿距离) |
algorithm | str | 'auto' | 近邻搜索算法。'auto'自动选择,'kd_tree'、'ball_tree'、'brute' |
leaf_size | int | 30 | KD树或球树的叶子节点大小,影响建树和查询速度 |
6.3 常用属性
| 属性名 | 说明 |
|---|---|
core_sample_indices_ | 核心点的索引 |
components_ | 所有核心点的坐标 |
labels_ | 每个样本的簇标签,-1 表示噪声点 |
6.4 常用方法
| 方法名 | 说明 |
|---|---|
fit(X) | 训练模型 |
fit_predict(X) | 训练并返回每个样本的簇标签(噪声点为 -1) |
七、实战案例:啤酒聚类分析
7.1 案例背景
在 K-Means 篇中,我们将 20 种啤酒按热量、钠含量、酒精浓度、价格聚成了 4 类。本篇尝试用 DBSCAN 对同样的数据进行分析,对比两种算法的聚类结果。
7.2 数据说明
| 字段 | 含义 | 单位 |
|---|---|---|
calories | 热量 | 卡路里 |
sodium | 钠含量 | 毫克 |
alcohol | 酒精浓度 | 体积百分比 |
cost | 价格 | 美元 |
数据示例:
| 品牌 | 热量 | 钠含量 | 酒精浓度 | 价格 |
|---|---|---|---|---|
| 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 |
7.3 完整代码
importpandasaspdimportnumpyasnpfromsklearn.clusterimportDBSCANfromsklearn.preprocessingimportMinMaxScalerfromsklearn.metricsimportsilhouette_scoreimportmatplotlib.pyplotasplt# ===================设置中文字体=========================plt.rcParams['font.sans-serif']=['SimHei']plt.rcParams['axes.unicode_minus']=False# ===================读取数据=========================beer=pd.read_table("data.txt",sep=' ',encoding='utf-8',engine='python')X=beer[['calories','sodium','alcohol','cost']]# ===================01标准化=========================scaler=MinMaxScaler()X_scaled=scaler.fit_transform(X)# ===================网格搜索=========================eps_values=np.arange(0.1,1.0,0.1)# 邻域半径候选值min_samples_values=range(2,7)# 最小样本数候选值results=[]print("===== 网格搜索 =====")forepsineps_values:formin_samplesinmin_samples_values:labels=DBSCAN(eps=eps,min_samples=min_samples).fit_predict(X_scaled)n_clusters=len(set(labels))-(1if-1inlabelselse0)# 簇数(排除噪声)n_noise=list(labels).count(-1)# 噪声点数ifn_clusters>=2:# 至少2个簇才能计算轮廓系数score=silhouette_score(X_scaled,labels)results.append({'eps':eps,'min_samples':min_samples,'n_clusters':n_clusters,'n_noise':n_noise,'score':score})print(f"eps={eps:.1f}, min_samples={min_samples}: 簇数={n_clusters}, 噪声={n_noise}, 轮廓系数={score:.4f}")# ===================最优参数=========================ifresults:best=max(results,key=lambdax:x['score'])# 选轮廓系数最大的print(f"\n最优参数: eps={best['eps']:.1f}, min_samples={best['min_samples']}")print(f"最优轮廓系数:{best['score']:.4f}")best_labels=DBSCAN(eps=best['eps'],min_samples=best['min_samples']).fit_predict(X_scaled)beer['cluster']=best_labelsprint(f"\n簇数:{best['n_clusters']}")print(f"噪声点:{best['n_noise']}")print("\n各簇包含的品牌:")forcidinsorted(beer['cluster'].unique()):brands=beer[beer['cluster']==cid]['name'].tolist()ifcid==-1:print(f"噪声点:{', '.join(brands)}")else:print(f"簇{cid}({len(brands)}种):{', '.join(brands)}")else:print("\n未找到有效的聚类组合")===== 网格搜索 ===== eps=0.2, min_samples=2: 簇数=3, 噪声=12, 轮廓系数=0.0281 eps=0.3, min_samples=2: 簇数=3, 噪声=6, 轮廓系数=0.2266 eps=0.3, min_samples=3: 簇数=3, 噪声=6, 轮廓系数=0.2266 eps=0.3, min_samples=4: 簇数=2, 噪声=9, 轮廓系数=0.1283 eps=0.4, min_samples=2: 簇数=2, 噪声=2, 轮廓系数=0.3162 eps=0.4, min_samples=3: 簇数=2, 噪声=2, 轮廓系数=0.3162 eps=0.4, min_samples=4: 簇数=2, 噪声=2, 轮廓系数=0.3162 eps=0.5, min_samples=2: 簇数=2, 噪声=1, 轮廓系数=0.3435 eps=0.5, min_samples=3: 簇数=2, 噪声=1, 轮廓系数=0.3435 eps=0.5, min_samples=4: 簇数=2, 噪声=1, 轮廓系数=0.3435 最优参数: eps=0.5, min_samples=2 最优轮廓系数: 0.3435 簇数: 2 噪声点: 1 各簇包含的品牌: 噪声点: Lowenbrau 簇 0(15 种): Budweiser, Schlitz, Old_Milwaukee, Augsberger, Srohs_Bohemian_Style, Miller_Lite, Budweiser_Light, Coors, Coors_Light, Michelob_Light, Pabst_Extra_Light, Hamms, Heilemans_Old_Style, Olympia_Goled_Light, Schlitz_Light 簇 1(4 种): Kronenbourg, Heineken, Becks, Kirin7.4 关键步骤说明
| 步骤 | 说明 |
|---|---|
| 数据标准化 | DBSCAN 基于距离计算,特征尺度不同会影响结果,需先标准化 |
| 参数探索 | 尝试不同的 eps 值,观察簇数和噪声点的变化,选择合适参数 |
| DBSCAN 聚类 | 使用最优 eps 和 min_samples 进行聚类 |
八、总结
核心知识点速查
| 知识点 | 关键概念 |
|---|---|
| DBSCAN | 基于密度的聚类,发现任意形状的簇 |
| 核心点 | ε 邻域内样本数 ≥ min_samples |
| 边界点 | 邻域内样本不足,但落在核心点的邻域内 |
| 噪声点 | 既非核心点也非边界点(标签 -1) |
| eps | 邻域半径,最关键参数 |
| min_samples | 核心点的密度阈值 |
核心 API 一览
| 用途 | 对应模块 / 方法 |
|---|---|
| 模型 | sklearn.cluster.DBSCAN |
| 训练并返回标签 | fit_predict(X) |
| 核心点索引 | core_sample_indices_ |
| 簇标签(-1 为噪声) | labels_ |
| 轮廓系数 | sklearn.metrics.silhouette_score |
注意事项
| 要点 | 说明 |
|---|---|
| 特征必须标准化 | DBSCAN 基于距离,特征尺度不同会严重影响结果 |
| eps 选择 | 过小:簇被拆分,噪声增多;过大:簇被合并 |
| min_samples 选择 | 通常设为维度 + 1(如二维数据设 3) |
| 密度差异大 | 同一套参数无法适配高密度和低密度区域 |
系列直达
- 上篇:机器学习入门:K-Means 聚类
- 本篇:机器学习入门:DBSCAN 聚类(本文)
- 下篇:敬请期待