第 51 课:DBSCAN 密度聚类
1. 学习目标
通过本课学习,你将能够:
- 理解基于密度的聚类思想及其与K-means等算法的区别。
- 掌握DBSCAN算法的核心概念(核心点、边界点、噪声)及其工作原理。
- 掌握DBSCAN的两个关键参数
eps和min_samples的作用与调优思路。 - 使用Scikit-learn库实现DBSCAN聚类,并对结果进行可视化分析。
- 应用DBSCAN处理具有非球形分布或包含噪声的真实数据集。
2. 核心概念
想象一下,你站在一个城市的夜景图前。城市中的灯火不是均匀分布的,而是聚集在一块块区域(居民区、商业区),这些区域之间由较暗的路段相连。DBSCAN(Density-Based Spatial Clustering of Applications with Noise)算法的工作原理,就如同在寻找这些“灯火通明”的区域。
与K-means的区别:
- K-means 像“切蛋糕”,需要预先指定切几块(簇数),并假设每块都是圆润的(球状分布)。
- DBSCAN 则像“找居民区”,它根据点的密度来划分,不需要预先指定簇的数量,能发现任意形状的簇(如环形、月牙形),并能自然地标记出不属于任何簇的“噪声点”。
DBSCAN的三个关键身份:
- 核心点 (Core Point):在以其为中心、半径为
eps的圆内,至少包含min_samples个点(包括它自己)。可以理解为“繁华的市中心”。 - 边界点 (Border Point):不是核心点,但它落在某个核心点的
eps邻域内。可以理解为“市中心周边的卫星城”。 - 噪声点 (Noise Point):既不是核心点,也不是任何核心点的边界点。可以理解为“荒郊野外的孤立建筑”。
算法流程(通俗版):
- 随机选择一个未访问的点。
- 以其为中心画一个半径为
eps的圆。 - 如果圈内点数 <
min_samples,暂时标记为“噪声”(后面可能被划入其他簇)。 - 如果圈内点数 ≥
min_samples,恭喜你找到了一个新簇!将这个点标记为核心点,并把圈内所有点都划入这个簇。 - 对于圈内刚划入的每一个点,重复第2步,将它们的密度可达邻居也吸纳进来。这个过程就像“一传十,十传百”的病毒式传播。
- 当一个簇无法再扩展时,返回第1步寻找下一个未访问的点。
3. 代码示例
下面的代码演示了如何使用DBSCAN对半月形和环形数据进行聚类,这是K-means束手无策的场景。
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
# 1. 生成具有挑战性的模拟数据:两个月牙形
X, y = datasets.make_moons(n_samples=300, noise=0.05, random_state=42)
# 2. 数据标准化(对于基于距离的算法很重要)
X = StandardScaler().fit_transform(X)
# 3. 创建并训练DBSCAN模型
# 参数说明:eps=0.3 定义了邻域半径, min_samples=5 定义了核心点的最小邻居数
dbscan = DBSCAN(eps=0.3, min_samples=5)
clusters = dbscan.fit_predict(X)
# 4. 可视化结果
unique_labels = set(clusters)
# 为噪声点(标签-1)和每个簇创建颜色
colors = [plt.cm.Spectral(each) for each in np.linspace(0, 1, len(unique_labels))]
plt.figure(figsize=(10, 6))
for k, col in zip(unique_labels, colors):
if k == -1:
# 黑色用于噪声点
col = [0, 0, 0, 1]
class_member_mask = (clusters == k)
xy = X[class_member_mask]
# 核心点用大圆圈表示,边界点用小点表示
core_samples_mask = np.zeros_like(clusters, dtype=bool)
core_samples_mask[dbscan.core_sample_indices_] = True
is_core = core_samples_mask[class_member_mask]
plt.scatter(xy[is_core, 0], xy[is_core, 1],
c=[col], marker='o', s=100, edgecolors='k', label=f'Cluster {k}' if k != -1 else 'Noise')
plt.scatter(xy[~is_core, 0], xy[~is_core, 1],
c=[col], marker='.', s=50, edgecolors='k')
plt.title('DBSCAN Clustering on Moon-shaped Data')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
# 打印聚类信息
n_clusters_ = len(set(clusters)) - (1 if -1 in clusters else 0)
n_noise_ = list(clusters).count(-1)
print(f"Estimated number of clusters: {n_clusters_}")
print(f"Estimated number of noise points: {n_noise_}")
运行上述代码,你将看到:DBSCAN完美地识别出了两个分离的月牙形簇,并用不同颜色标记出来。图中大的圆圈是核心点,小的点是边界点(如果有的话),未显示的黑色点是噪声点。
4. 实践练习
练习 1(基础):比较DBSCAN与K-means
使用 sklearn.datasets.make_circles 生成一个环形数据集(n_samples=500, factor=0.5, noise=0.05)。分别用K-means(n_clusters=2)和DBSCAN(尝试设置合适的eps和min_samples)进行聚类,并将结果子图并列展示。观察并总结它们的差异。
练习 2(进阶):参数敏感性探索
使用练习1中的环形数据。固定 min_samples=5,尝试不同的 eps 值(如0.1, 0.2, 0.5)。将不同eps下的聚类结果可视化,并描述eps值的变化如何影响最终形成的簇的数量和形状。
练习 3(应用):分析真实数据
加载sklearn中的鸢尾花数据集 (load_iris())。由于DBSCAN对尺度敏感,请先对数据进行标准化。使用DBSCAN对其进行聚类(你需要尝试找到合适的 eps 和 min_samples)。将结果与真实的鸢尾花标签进行对比,观察DBSCAN是否能将不同种类的花区分开,并注意它识别出的噪声点数量。
5. 常见错误
-
参数设置不当:这是最常见的问题。
eps设置过小:会导致大量点被标记为噪声,簇被分割得过细。eps设置过大:会将大部分点合并到一个簇中,甚至把噪声点也包括进来。min_samples设置过大:同样会导致更多点成为噪声,簇难以形成。- 解决方法:使用k-距离图来辅助确定
eps。绘制所有点到其第k近邻的距离图(k通常取min_samples-1),图中的“拐点”对应的纵坐标值是一个较好的eps选择。
-
忽略数据标准化:DBSCAN基于欧氏距离计算邻域,如果特征的尺度差异很大(如一个特征范围是0-1,另一个是0-10000),距离计算会被大尺度特征主导,导致聚类结果不合理。务必在聚类前进行标准化。
-
误解噪声点的含义:DBSCAN识别出的噪声点不一定就是“坏数据”。在业务场景中,这些点可能代表了异常用户、欺诈交易或产品分类中的新类别,其分析价值可能比簇本身更高。
6. 小结
- DBSCAN 是一种基于密度的聚类算法,无需预设簇数,能发现任意形状的簇并识别噪声。
- 其核心思想是:通过足够高密度的区域来扩展簇。
- 两个核心参数:
eps(邻域半径)和min_samples(成为核心点所需的最小邻居数),共同决定了“密度”的定义。它们对结果影响巨大,需要仔细调优。 - 优点:对噪声鲁棒、能处理非球形簇、不需要指定K值。
- 缺点:对参数敏感、当簇间密度差异很大时效果不佳、在高维数据中可能因“维度灾难”而性能下降。
- 适用场景:地理空间数据聚类、图像分割、异常检测、任何预期中簇形状不规则且存在噪声的数据集。
在接下来的课程中,我们将学习如何客观地评估DBSCAN(及其他聚类算法)的结果好坏,而不仅仅依赖于可视化。