十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

DBSCAN三维数据聚类实战:从参数调优到结果评估

DBSCAN三维数据聚类实战:从参数调优到结果评估 DBSCAN这个算法我是真喜欢。K-Means用多了总有点心虚数据长成月牙形或者螺旋形中间再飘着几个离群点K-Means直接就抓瞎了你不但得提前告诉它分几类拿噪声点还没一点办法。DBSCAN就不一样它靠密度把样本连成团不用预设簇数还能自动把孤零零的点标成噪声。今天这个案例就是我用三维正态分布随机数生成三个簇然后从造数据、调参数、跑通代码到看效果把DBSCAN整个流程顺一遍。文章里每一步都会贴代码边贴边解释适合刚接触机器学习想找个练手案例的人也适合已经在用K-Means、想换个聚类思路的同行参考。1. 为什么拿三维正态分布数据来跑DBSCAN1.1 测试数据的设计思路里有讲究学聚类算法最忌讳的是拿现成的公开数据集直接往上糊。不是说公开数据集不好而是你根本不了解里面埋着什么坑多少噪声点、簇分布什么形状、有没有重叠全是一笔糊涂账。聚类结果一塌糊涂你根本没法判断是算法不行还是数据本来的问题。自己生成数据就不一样了簇数多少、中心在哪、方差多大、重叠程度如何全都由你掌控调参的时候心里非常踏实。这次我特意选了三维数据而不是最常见的二维。原因有两个。第一二维数据太好看了散点图一画出来基本都能看穿很难体会到聚类算法的真实困境三维数据在可视化时需要进行投影信息会丢失你会明显感受到数据明明分得好好的换个角度看就糊成一团的困惑这种困惑在实际项目中非常常见。第二三维数据已经能展现高维数据的一些基本矛盾但又不至于像10维数据那样连可视化都做不了作为学习案例刚好卡在难度和可解释性的平衡点上。1.2 三个簇的剧本设计我设计三簇数据时特意让它们各有性格不是随便生成三个球就完事。第一个簇样本量120协方差矩阵取diag(0.5, 0.5, 0.5)围绕原点附近聚集密度高、形状规整。第二个簇样本量200协方差取diag(1.5, 1.5, 1.5)中心在(5, 5, 5)样本多但方差大所以分布范围广、密度低。第三个簇样本量只有80中心在(8, 2, 1)协方差是diag(1.0, 0.3, 0.3)也就是说在x方向上铺得比较开在y和z方向压得很扁。这样设计有三个用意。一是密度差异明显第一个簇和第三个簇密度高第二个簇密度低DBSCAN正好是吃密度这套逻辑的密度差异会让参数选择变得更有意思。二是总样本400个三簇数量也不一样考验算法在样本不均衡时还能不能识别出少数簇。第三个簇只有80个样本如果eps和min_samples设置不恰当它非常容易被当成噪声吞掉。三是簇间有一定间隔但又不完全远离标准化之后簇间距大约在2到3之间这种尺度关系给eps的调节留出了操作空间。1.3 工具链准备这个案例只需要三个库NumPy生成数据scikit-learn跑聚类和评估Matplotlib做三维可视化。版本方面没有太多讲究我当时用的是NumPy 1.24、scikit-learn 1.3、Matplotlib 3.7如果你用的版本稍新或稍旧也完全不影响这几个API都很稳定。import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from sklearn.cluster import DBSCAN from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler from sklearn.metrics import adjusted_rand_score先说清楚scikit-learn的DBSCAN在计算距离矩阵时用的是空间索引做加速时间复杂度接近O(n log n)400个样本的规模属于秒级出结果连性能焦虑都不需要有。2. DBSCAN的核心机制参数搞懂才能调明白2.1 核心点、边界点、噪声点密度聚类的三个角色想调好DBSCAN先得理解它的三个角色划分。围绕着每一个点画一个半径是eps的圆圈如果这个圆里至少包含min_samples个点包括它自己那这个点就是核心点。边界点的意思是它自身不满足核心点的条件但它落在某个核心点的eps半径内。剩下的就是噪声点既不是核心点也不在任何一个核心点的范围内。算法做的事情就是任选一个核心点把它eps半径内所有能连起来的点全部归入同一个簇。这里的连起来不是指直接相邻而是通过核心点一环套一环地传递。打个比喻就像一群人在广场上站着只要人和人之间的距离不超过eps就能手拉手连成网络哪怕队伍扯得很长、绕来绕去都没关系。这就是密度聚类的本质它不关心形状只关心密度是否连续。2.2 eps和min_samples的真实含义以及它们怎么互相制约参数理解的关键在于两个字——制约。eps是邻域半径决定了一个点能看到多远min_samples是密度阈值决定了一个点周围至少要攒够多少人它才配当核心点。两者完全不是独立关系eps固定时min_samples调大核心点变少簇会碎掉噪声点变多min_samples调小核心点变多簇容易合并。min_samples固定时eps调大等于放宽了核心点条件簇容易合并eps调小簇变碎噪声变多。关于min_samples怎么选圈子里有个常用经验数据是二维的话取4到5维度更高时取2倍特征维度以上。这个案例里我用的min_samples10就是2乘以维度3再加余量得到的。说实话这个参数比eps好选得多你只要别取得太小比如二维数据里取2那就会产生大量核心点聚类结果几乎被参数牵着鼻子走非常不稳定。2.3 用k-distance图来选定eps别靠瞎猜eps才是真正让无数新手头疼的参数。很多教程直接说根据经验调要我说这就是不负责任。靠谱的做法是先做一张k-distance图。原理其实非常朴素把每个样本点到它的第k个最近邻的距离算出来k就取min_samples然后把这些距离从大到小排序画成一条曲线。曲线会呈现明显的肘部形状肘部对应的距离就是eps的推荐值。背后逻辑也好理解如果某个点周围的邻居距离普遍较小说明它处在密度高的区域距离特别大的那几个点自然就是离群点或者稀疏区域的点。排序之后曲线会在密度较大的主体区域和稀疏尾部之间出现折点。这个折点就是区分簇内点和孤立点的分界线。代码长这样from sklearn.neighbors import NearestNeighbors nn NearestNeighbors(n_neighbors10) nn.fit(X_scaled) distances, indices nn.kneighbors(X_scaled) distances np.sort(distances[:, -1]) plt.figure(figsize(8, 5)) plt.plot(distances) plt.xlabel(样本点排序) plt.ylabel(第10近邻距离) plt.title(k-distance 曲线) plt.grid(True) plt.show()我在标准化之后跑了这段代码实测出来的曲线在0.4到0.6之间出现明显拐点所以后面聚类我选了eps0.5。有人可能会问为什么不直接取最小值因为取太小会把真实点也当噪声踢掉取稍大一点在拐点附近反而更稳健。k-distance图给的是一个范围不是精确值这是很多人第一次看完图就踩坑的地方。3. 完整代码实现从造数据到出图一次跑通3.1 造数据np.random.multivariate_normal怎么用现在开始上代码。生成三维正态分布随机数用np.random.multivariate_normal这个函数是多元正态分布的直接实现传入均值向量和协方差矩阵就能返回对应形状的样本矩阵。均值向量控制簇中心位置协方差矩阵控制各维度的方差和相关性。np.random.seed(42) centers [ np.array([0, 0, 0]), np.array([5, 5, 5]), np.array([8, 2, 1]) ] covs [ np.diag([0.5, 0.5, 0.5]), np.diag([1.5, 1.5, 1.5]), np.diag([1.0, 0.3, 0.3]) ] n_samples [120, 200, 80] X [] y_true [] for i, (center, cov, n) in enumerate(zip(centers, covs, n_samples)): X.append(np.random.multivariate_normal(center, cov, n)) y_true.append(np.full(n, i)) X np.vstack(X) y_true np.hstack(y_true)这里有个细节np.diag([0.5, 0.5, 0.5])生成的是一个3乘3对角矩阵对角线上的值就是每个维度的方差。方差0.5意味着标准差约0.707也就是说第一个簇的点在三个方向上的散布范围大概在正负1.4左右。第三个簇的协方差是diag(1.0, 0.3, 0.3)x方向方差1.0y和z方向都是0.3看起来就是一块扁平的饼。我把true label也保存下来了变量名叫y_true。你可能要问聚类不是无监督学习吗为什么要保留真值这个真值不是拿来给算法训练用的而是接下来评估聚类效果用的。算法看不见y_true但我自己知道每一条数据来自哪个簇这样才能算出ARI分数客观地判断聚类效果到底好不好。3.2 标准化这一步别省数据生成好以后先别急着直接丢进DBSCAN要先把数据标准化。这一步太多人忽略但它直接决定了聚类的成败。为什么必须标准化因为DBSCAN的核心是欧几里得距离如果不同特征的量纲差异很大比如x轴范围是0到100y轴范围是0到1距离计算时x轴会完全主导y轴的信息基本被淹没。我这组数据虽然是手工生成的三个维度看起来量级接近但它们的均值和方差其实差别不小。标准化后每个特征被缩放到均值0、方差1各维度在距离计算中的权重才是公平的。scaler StandardScaler() X_scaled scaler.fit_transform(X)就这么一行效果立竿见影。标准化之后三个簇的间距依然清晰但簇内的散布范围被统一到了相近的量级这给eps的选择提供了很大便利。不信你可以跑一下没标准化的版本同样的eps聚类结果会比标准化版本糟糕不少。3.3 DBSCAN聚类与三维可视化接下来是重头戏跑聚类加画图。db DBSCAN(eps0.5, min_samples10) y_db db.fit_predict(X_scaled) fig plt.figure(figsize(10, 7)) ax fig.add_subplot(111, projection3d) scatter ax.scatter(X[:, 0], X[:, 1], X[:, 2], cy_db, cmapplasma, alpha0.8, s30) ax.set_xlabel(X) ax.set_ylabel(Y) ax.set_zlabel(Z) plt.colorbar(scatter) plt.title(DBSCAN 聚类结果 (eps0.5, min_samples10)) plt.show()跑完之后你会发现y_db里大部分数据点的标签是0、1、2对应三个簇但会有零星几个标签是-1的点那就是被算法识别为噪声的点。第一次跑能看到这种效果是最好的既看到了聚类的正常表现也直观感受到了什么是噪声点。关于三维可视化我强烈建议你跑出图之后用鼠标拖拽旋转一下视角。静态图只能给一个角度的印象数据是立体的不同角度看到的聚类结构完全不同特别是第三个扁平的簇你从某个侧面看几乎和另外某个簇叠在一起转个角度却发现它们间距明显。这种体验对理解高维数据为什么要降维、为什么不能只看一个投影非常有帮助。颜色的设置上我推荐用plasma或者viridis色图这两种对色盲比较友好而且颜色区分度很高。CMA的tab20那种多色图虽然也很常用但在三维散点上相邻颜色容易混淆我不太推荐。3.4 用ARI评估聚类效果顺带和K-Means比一把光看图不够客观还得用数字说话。这里我用adjusted_rand_score调整兰德指数的值域是-1到11表示聚类结果和真实标签完全相同0表示随机猜的水平。它最大的优点是即便两个标签的编号不同比如算法把簇0叫成簇2、把簇2叫成簇0分数依然能正确计算不会因为编号对不上而误判。ari_dbscan adjusted_rand_score(y_true, y_db) print(fDBSCAN ARI: {ari_dbscan:.4f})同等条件下我再用K-Means跑一遍对比km KMeans(n_clusters3, random_state42) y_km km.fit_predict(X_scaled) ari_kmeans adjusted_rand_score(y_true, y_km) print(fK-Means ARI: {ari_kmeans:.4f})我在这个数据的构造下实测两者的ARI分数可能非常接近甚至K-Means略好一点点这是正常的。因为我的数据是三个高斯分布生成的球形簇本身就是K-Means最擅长的场景而DBSCAN在这种情况下优势不明显它擅长的是不规则形状和带强噪声的数据这一点后面再展开。你可能会问既然这组数据上K-Means并不差那为什么还要学DBSCAN问得好。因为这组数据的价值在于讲解密度聚类的完整流程和参数机制如果你能把DBSCAN调出和K-Means相当的效果说明你真正理解了参数的含义。至于体现DBSCAN的碾压优势需要在月牙形、同心圆这种非凸形状数据上做实验后面扩展部分我会提到。4. 实操中踩过的坑与排查技巧4.1 eps与min_samples参数敏感的实测记录说到参数我必须把实操中真实的调试记录拿出来讲。我在这个案例上反复跑了很多遍记录了几个典型结果。参数配置聚类结果问题分析eps0.3, min_samples10细分出6到7个簇噪声点比例超过20%eps太小簇内密度不够原本完整的簇被切成碎片eps0.5, min_samples10聚类成3个簇噪声点约10个左右参数合理与真实结构吻合eps0.8, min_samples10第二簇和第三簇开始粘连簇数变2eps偏大把稀疏区域的边缘点也连了进来eps1.5, min_samples10全部样本聚成1个簇噪声为0邻域半径过大密度连通整个空间eps0.5, min_samples3几乎全部聚成1簇噪声极少min_samples太小核心点条件过于宽松eps0.5, min_samples30噪声点暴涨第三簇几乎全部丢失min_samples太大小簇样本量不足以形成核心这张表我建议你保存下来。调参的时候如果发现聚类结果接近表里某一行的特征基本能倒推回去判断是调大还是调小对应参数。这里想多说一句很多人看DBSCAN文档时有一个误解以为eps和min_samples有一个标准答案。其实它们严重依赖数据本身的密度和尺度换一组数据就得重新调。你在这个案例上学到的不是某个固定的参数值而是调参的方法论。4.2 三维可视化里的判断误区三维散点图看似直观其实藏着不少坑。第一视角不同会带来完全不同的判断。我拿第三个簇举例它x方向方差大、y和z方向方差小你从x轴的方向看过去它就是一个薄片状的结构几乎和第二个簇投影重叠如果这时候你截图给别人看对方很可能认为两个簇混在一起。但实际上从另一个角度看它们分得很开。所以做三维数据探索时一定要拖拽旋转不要只看一个静态角度。第二颜色图例的比例问题。Matplotlib里用cy_db做颜色映射时-1这个噪声点和簇标签0、1、2在颜色映射中的相对位置可能会让人误判。建议在图上把噪声点单独处理比如把噪声点标成灰色或黑色再把正常簇用彩色区分colors np.array([#7f7f7f, #1f77b4, #ff7f0e, #2ca02c]) ax.scatter(X[:, 0], X[:, 1], X[:, 2], c[colors[label 1] for label in y_db], alpha0.8, s30)这里y_db中-1的索引会被映射到colors[0]即灰色0、1、2分别映射到蓝橙绿视觉层次会清晰很多。第三三维图里点与点前后遮挡很严重密度高的簇的中心部分可能完全被外围点挡住让人低估簇内实际样本数。这种情况下可以调整alpha透明度到0.6到0.8之间或者把点的大小降到s20能稍微缓解遮挡问题。4.3 高维数据里DBSCAN的局限与应对这个案例虽然是三维但DBSCAN应用在高维场景时有一系列问题提前了解能帮你少走弯路。首先是距离度量问题。欧几里得距离在高维空间里会变得不那么可靠因为当维度升高时所有点之间的距离趋向于相近最近邻的概念被稀释密度聚类的根基就动摇了。其次eps对高维数据极其敏感维度一高可调范围可能非常窄差0.01就能让聚类结果从合适变成全部噪声。第三个问题是复杂度虽然sklearn的实现有空间索引加速但样本量一旦到十万级别距离矩阵的计算开销仍然不可忽视。如果真遇到高维数据我的应对思路是先用PCA、t-SNE或UMAP把维度降到2到3维再跑DBSCAN做可视化聚类。降维不只是为了可视化更是让DBSCAN的距离计算回归到一个相对可信的度量空间。当然降维本身会丢失信息这个代价要心里有数。5. 这个案例的后续扩展玩法5.1 试试换协方差矩阵制造更多重叠基础的三个簇你已经会造了接下来可以自己动手改参数玩花样。把协方差矩阵改成带非对角元素的矩阵比如np.array([[1.0, 0.8, 0.0], [0.8, 1.0, 0.0], [0.0, 0.0, 0.5]])数据就会在x和y方向上呈现出正相关的倾斜分布。这样的数据更接近真实业务场景因为真实数据的特征之间极少完全独立。你还可以故意把两个簇的中心拉近让它们在边界处部分重叠。这种场景下DBSCAN的表现会非常有意思它可能会把两个簇合并成一个也可能因为密度差异把它们分开具体结果取决于重叠区域里的密度形态。通过这种实验你会发现DBSCAN对密度连续的敏感度远超对几何距离的敏感度这是它区别于K-Means最核心的地方。5.2 跟其他密度聚类算法做对比DBSCAN虽然经典但并非密度聚类的终点。HDBSCAN是在DBSCAN基础上改进的算法它把eps这个参数变成了一个自适应的层级阈值你不再需要手动调邻域半径只需要给一个最小簇大小。对于调参困难户来说HDBSCAN的体验会友好很多。OPTICS则引入了可达距离的概念能在不同的密度层级上同时识别簇结构对于一个数据集中同时存在高密度簇和低密度簇的情况它的适应性更好。拿本案例的数据来跑HDBSCAN你会发现它基本上不需要调参就能得到差不多的聚类效果。这不是说DBSCAN被淘汰了而是说在不同场景下适合的算法不同。如果数据密度均匀、你需要干净快速的参数控制DBSCAN足够好如果数据密度变化极大、你又没有精力反复调参HDBSCAN才是更优解。我个人的建议是把DBSCAN作为第一把刀先快速看结果遇到密度不均的情况再换HDBSCAN上场。最后再说一点我个人实操中的体会。做聚类项目时我几乎不去追求ARI、轮廓系数这些指标达到0.99那往往是数据太理想。现实中的聚类问题噪声点比例、簇形态、可解释性往往比分数更重要。像这次案例里选中一个合理的eps后哪怕噪声点数量从8个变成15个只要簇结构稳定、业务上说得通它就是可用的。DBSCAN的魅力恰恰在这一点——它给你保留了对噪声的显式判断权这一点是K-Means体系给不了的。自己动手把代码跑一遍再改几个参数看看结果变化你对密度聚类的理解会比翻十篇教程都管用。
返回列表