资讯动态

DBSCAN聚类算法实战:从原理、参数调优到避坑指南

发布时间:2026/10/15 1:22:58 来源:尧图企业网站定制
1. 从K-Means的软肋说起为什么还需要DBSCAN很多人学聚类第一个接触的就是K-Means。它简单、快、好理解基本上半天就能跑通一个Demo。但真正拿它去处理实际数据的时候你会发现一个很尴尬的事情K-Means有个硬性前提——你得提前告诉它分几类。这个K值选错了结果就全废了。更麻烦的是K-Means本质上是在找球形的、大小差不多的簇它用距离均值来迭代中心点遇到形状不规则的数据就彻底歇菜。我举个具体的场景你就明白了。假设你手头有一批地理位置数据想找出城市里人群聚集的热点区域。这些热点可能是沿着一条商业街延伸的长条形也可能是围绕某个交通枢纽的环形分布。你用K-Means去跑它会强行把这些形状切成几个圆饼结果就是同一个热点被拆到两个簇里或者两个不相干的热点被合并成一个。这不是调参能解决的问题是算法假设本身就不匹配。DBSCAN就是来解决这类问题的。它的全称是Density-Based Spatial Clustering of Applications with Noise翻译过来就是基于密度的带噪声空间聚类方法。名字听着长核心思想其实特别朴素如果一个点周围邻居足够多那它就和邻居属于同一个簇如果一个点周围冷冷清清那它可能就是噪声。这个思路带来的好处是直接的。第一你不需要提前指定簇的数量算法自己会判断能形成几个簇。第二它能发现任意形状的簇长条形、环形、S形都行只要这些区域的数据密度够高。第三它能识别噪声点——那些不属于任何簇的孤立数据这在异常检测场景里非常有用。当然DBSCAN也不是没有代价。它有两个参数需要调而且对参数比较敏感。参数选不好要么把所有点都归成一个簇要么把所有点都当成噪声。另外当数据维度很高的时候密度这个概念会变得不那么可靠效果会打折扣。但总体来说对于中小规模、维度不太高的数据集DBSCAN是一个非常值得掌握的聚类工具。这篇文章我会从DBSCAN的核心概念讲起把每个参数的含义和影响说透然后给出完整的实操步骤和代码最后重点分享我在实际使用中踩过的坑和调参经验。不管你是刚入门的数据分析新手还是已经用过K-Means想扩展工具箱的从业者应该都能从中拿到可以直接用的东西。2. 拆解DBSCAN的三个核心概念邻域、核心点与密度可达DBSCAN的逻辑链条其实很短但每个环节都需要理解到位否则调参就是瞎猜。我把它的核心概念拆成三个层次来讲邻域、点的分类、以及簇的形成规则。2.1 邻域与MinPts一个点的社交圈有多大DBSCAN判断密度的方式是看一个点周围有多大范围内有多少个邻居。这里涉及两个参数Epsε邻域半径。以某个点为中心画一个半径为Eps的圆这个圆内的所有点包括自己都算它的邻居。MinPts最小邻居数。一个点的邻域内至少要有这么多个点它才算核心点。你可以把Eps理解成社交距离MinPts理解成最少要认识几个人才算合群。Eps越大每个点的社交圈就越大越容易找到邻居MinPts越大对合群的要求就越高越多的点会被判定为不合群。这两个参数是耦合的。你不能单独说Eps设0.5就行因为Eps设0.5、MinPts设3和Eps设0.5、MinPts设10结果完全不同。一般来说MinPts的经验值在3到10之间对于二维数据常用4或5。Eps则需要根据数据的实际分布来定后面我会讲具体的确定方法。注意Eps和MinPts的取值跟数据的量纲直接相关。如果你的特征是年龄和收入年龄范围是0到100收入范围是0到100000那距离计算会被收入主导年龄几乎不起作用。所以做DBSCAN之前标准化是必须的。2.2 核心点、边界点与噪声点三种命运不同的数据根据邻域内的邻居数量DBSCAN把每个点分成三类核心点Core Point邻域内的点数含自身大于等于MinPts。这些点是簇的骨架它们的存在确认了这片区域是密集的。边界点Border Point自己不是核心点但落在某个核心点的邻域内。这些点属于某个簇但不具备扩展能力它们只是被核心点拉进来的。噪声点Noise Point既不是核心点也不在任何核心点的邻域内。这些点不属于任何簇被标记为-1。这个分类逻辑很直观核心点是有话语权的人边界点是跟着核心点混的人噪声点是谁也不认识的人。一个簇就是从一个核心点出发不断把密度可达的点拉进来形成的。2.3 密度可达与密度相连簇是怎么长出来的DBSCAN形成簇的过程用的是密度可达的概念。如果点A是核心点点B在A的邻域内那么B从A直接密度可达。如果B也是核心点C在B的邻域内那么C从A密度可达通过B中转。这个过程可以一直传递下去形成一个密度相连的集合。簇的定义就是一个最大的密度相连点集。换句话说从簇里任意一个核心点出发通过密度可达关系能到达的所有点都属于这个簇。这个机制带来的一个关键特性是DBSCAN不需要指定簇的数量。簇的数量和大小完全由数据的密度分布决定。你给一组数据它自己会判断哪些区域足够密集、能形成独立的簇哪些区域太稀疏、只能算噪声。我用一个生活化的类比来收尾这部分想象你在一个广场上找人组团。规则是——如果你周围三米内有至少四个人你就算团长可以拉人组队你拉进来的人如果也满足条件也可以继续拉人。最后形成的队伍就是簇而那些周围三米内不到四个人、也没被任何团长拉进来的人就是散客噪声。DBSCAN干的就是这件事只不过它用的是数学上的距离和计数。3. 参数怎么定Eps和MinPts的确定方法与调参逻辑DBSCAN最让人头疼的地方就是参数选择。K-Means只需要定一个KDBSCAN要定两个而且这两个参数互相影响。但好消息是参数选择有比较成熟的方法论不是纯靠试。3.1 k-距离图用数据自己告诉你Eps该设多少确定Eps最常用的方法是k-距离图k-distance graph。具体做法是对每个点计算它到第k个最近邻居的距离。这里的k通常取MinPts的值如果MinPts4就算每个点到第4近邻居的距离。把所有点的这个距离从小到大排序画成一条曲线。观察曲线的拐点肘部拐点对应的距离值就是比较合适的Eps。为什么这样做有效因为对于密集区域内的点它到第k个邻居的距离会比较小对于噪声点或稀疏区域的点这个距离会突然变大。在排序后的曲线上密集区域的点集中在左侧距离小稀疏区域的点集中在右侧距离大中间会有一个明显的跳跃。这个跳跃点就是Eps的合理取值。我用Python写一下这个过程的代码import numpy as np from sklearn.neighbors import NearestNeighbors import matplotlib.pyplot as plt def plot_k_distance(X, k4): 绘制k-距离图帮助确定Eps X: 标准化后的数据 k: 通常取MinPts的值 neighbors NearestNeighbors(n_neighborsk) neighbors.fit(X) distances, indices neighbors.kneighbors(X) # 取每个点到第k个邻居的距离 k_distances distances[:, k-1] k_distances np.sort(k_distances) plt.figure(figsize(10, 6)) plt.plot(range(len(k_distances)), k_distances) plt.xlabel(Points sorted by distance) plt.ylabel(f{k}-th nearest neighbor distance) plt.title(k-distance Graph) plt.grid(True) plt.show() return k_distances跑完这个图之后你需要用眼睛找拐点。拐点之前曲线比较平缓拐点之后曲线突然变陡。那个拐弯的位置对应的y值就是Eps的候选值。实操心得k-距离图的拐点有时候不明显尤其是数据密度分布比较连续的时候。这时候不要死磕一个精确值可以在拐点附近取几个值分别跑一遍看聚类结果哪个更符合业务直觉。我一般会取拐点值上下浮动20%的范围试三四个值。3.2 MinPts的取值从经验法则到数据维度MinPts的取值相对Eps来说更有章可循一些。有几个常用的经验法则MinPts 维度 1这是最低要求低于这个值密度估计没有意义。MinPts 2 * 维度这是一个比较稳健的默认值对于二维数据就是4三维就是6。MinPts 4对于二维数据这是最常用的默认值很多文献和教程都推荐这个。但经验法则只是起点。MinPts的实际影响是值越大核心点的判定越严格簇会更紧凑噪声点会更多值越小核心点越多簇会更大更松散噪声点更少。如果你的数据噪声本来就多可以适当提高MinPts让算法更挑剔一些。如果你希望尽量少标记噪声可以降低MinPts但代价是可能把一些本该独立的点合并到簇里。3.3 参数组合的网格搜索一个可复现的调参流程当你对数据不太熟悉的时候最稳妥的方式是做一个小范围的网格搜索。下面这个流程我用了很多次比较靠谱from sklearn.cluster import DBSCAN from sklearn.metrics import silhouette_score import numpy as np def grid_search_dbscan(X, eps_range, minpts_range): 对Eps和MinPts做网格搜索 返回每个参数组合的聚类数量和轮廓系数 results [] for eps in eps_range: for minpts in minpts_range: db DBSCAN(epseps, min_samplesminpts) labels db.fit_predict(X) n_clusters len(set(labels)) - (1 if -1 in labels else 0) n_noise list(labels).count(-1) # 轮廓系数需要至少两个簇 if n_clusters 2: # 排除噪声点后计算轮廓系数 mask labels ! -1 if mask.sum() 1: sil silhouette_score(X[mask], labels[mask]) else: sil -1 else: sil -1 results.append({ eps: eps, minpts: minpts, n_clusters: n_clusters, n_noise: n_noise, silhouette: sil }) return results跑完网格搜索之后不要只看轮廓系数最高的那个。轮廓系数在DBSCAN里不是万能的因为它对噪声点的处理方式会影响计算。我一般会综合看三个指标簇的数量是否符合业务预期、噪声比例是否在可接受范围通常5%到20%之间比较合理、轮廓系数是否过得去。注意如果噪声比例超过30%说明Eps可能太小或者MinPts太大大量点被排斥在簇外。如果噪声比例低于2%说明参数太宽松可能把噪声也拉进簇里了。这两种情况都需要调整。4. 完整实操从原始数据到聚类结果的端到端流程光讲概念不够我拿一个模拟数据集把整个流程走一遍。这个数据集是我自己生成的包含三个不同形状的簇和一部分噪声用来展示DBSCAN处理非球形簇的能力。4.1 数据准备与标准化为什么这一步不能省import numpy as np import matplotlib.pyplot as plt from sklearn.preprocessing import StandardScaler from sklearn.cluster import DBSCAN # 生成模拟数据两个半月形簇 一个圆形簇 噪声 np.random.seed(42) # 半月形簇1 theta1 np.linspace(0, np.pi, 100) x1 np.cos(theta1) * 2 np.random.normal(0, 0.15, 100) y1 np.sin(theta1) * 2 np.random.normal(0, 0.15, 100) # 半月形簇2 theta2 np.linspace(np.pi, 2*np.pi, 100) x2 np.cos(theta2) * 2 np.random.normal(0, 0.15, 100) y2 np.sin(theta2) * 2 np.random.normal(0, 0.15, 100) # 圆形簇 theta3 np.random.uniform(0, 2*np.pi, 80) r3 np.random.uniform(0, 1, 80) x3 r3 * np.cos(theta3) 5 np.random.normal(0, 0.1, 80) y3 r3 * np.sin(theta3) np.random.normal(0, 0.1, 80) # 噪声点 x_noise np.random.uniform(-4, 8, 30) y_noise np.random.uniform(-4, 4, 30) # 合并 X np.vstack([np.column_stack([x1, y1]), np.column_stack([x2, y2]), np.column_stack([x3, y3]), np.column_stack([x_noise, y_noise])]) # 标准化 scaler StandardScaler() X_scaled scaler.fit_transform(X) print(f数据形状: {X_scaled.shape}) print(f原始数据范围: x[{X[:,0].min():.2f}, {X[:,0].max():.2f}], y[{X[:,1].min():.2f}, {X[:,1].max():.2f}])标准化这一步很多人会忽略觉得我的数据本来就是数值直接跑不就行了。但DBSCAN是基于距离的算法如果两个特征的量纲差距很大距离计算就会被大量纲特征主导。比如一个特征是0到1的比例值另一个是0到10000的金额那Eps设0.5的时候金额特征几乎不参与距离计算。标准化之后每个特征的均值为0、方差为1距离计算才公平。4.2 用k-距离图确定Eps的实操过程from sklearn.neighbors import NearestNeighbors # 用MinPts4来确定Eps k 4 neighbors NearestNeighbors(n_neighborsk) neighbors.fit(X_scaled) distances, indices neighbors.kneighbors(X_scaled) k_distances np.sort(distances[:, k-1]) # 找拐点计算二阶差分取最大值位置 diff1 np.diff(k_distances) diff2 np.diff(diff1) elbow_idx np.argmax(diff2) 1 eps_candidate k_distances[elbow_idx] print(f拐点位置索引: {elbow_idx}) print(f建议的Eps值: {eps_candidate:.4f}) # 画图 plt.figure(figsize(10, 5)) plt.plot(k_distances, labelk-distance) plt.axvline(xelbow_idx, colorr, linestyle--, labelfElbow (eps{eps_candidate:.3f})) plt.xlabel(Points sorted by distance) plt.ylabel(4th nearest neighbor distance) plt.legend() plt.title(k-distance Graph for Eps Selection) plt.grid(True) plt.show()这段代码跑出来的拐点值就是Eps的初始候选。但正如我前面说的不要迷信这一个值。我会在拐点值附近取三个值分别跑一遍对比结果。4.3 跑通DBSCAN并解读聚类结果# 用候选Eps跑DBSCAN eps_values [eps_candidate * 0.8, eps_candidate, eps_candidate * 1.2] minpts 4 fig, axes plt.subplots(1, 3, figsize(18, 5)) for ax, eps in zip(axes, eps_values): db DBSCAN(epseps, min_samplesminpts) labels db.fit_predict(X_scaled) n_clusters len(set(labels)) - (1 if -1 in labels else 0) n_noise list(labels).count(-1) # 绘制 unique_labels set(labels) colors plt.cm.Spectral(np.linspace(0, 1, len(unique_labels))) for label, color in zip(unique_labels, colors): if label -1: color black marker x size 30 else: marker o size 50 mask labels label ax.scatter(X_scaled[mask, 0], X_scaled[mask, 1], c[color], markermarker, ssize, alpha0.7) ax.set_title(fEps{eps:.3f}, MinPts{minpts}\nClusters{n_clusters}, Noise{n_noise}) ax.set_xlabel(Feature 1 (scaled)) ax.set_ylabel(Feature 2 (scaled)) plt.tight_layout() plt.show()跑完这个对比图你会直观地看到Eps变化对结果的影响。Eps偏小的时候簇会被拆得很碎噪声点很多Eps偏大的时候簇会合并噪声减少但可能把不该合并的点也拉进来了。4.4 结果评估不只看轮廓系数DBSCAN的结果评估比K-Means要复杂一些因为多了噪声这个维度。我通常从以下几个角度综合判断评估维度合理范围异常信号簇数量符合业务预期只有1个簇或簇数量过多噪声比例5%-20%超过30%或低于2%轮廓系数0.3以上低于0.2簇大小分布相对均匀某个簇特别大或特别小可视化形状边界清晰簇之间重叠严重轮廓系数的计算要注意排除噪声点因为噪声点的标签是-1直接算会拉低分数。另外如果簇的形状是非凸的轮廓系数本身就不太适用这时候可视化检查比数值指标更可靠。from sklearn.metrics import silhouette_score, calinski_harabasz_score # 用最终选定的参数 best_eps eps_candidate db_final DBSCAN(epsbest_eps, min_samples4) labels_final db_final.fit_predict(X_scaled) # 排除噪声后计算指标 mask labels_final ! -1 if len(set(labels_final[mask])) 2: sil silhouette_score(X_scaled[mask], labels_final[mask]) ch calinski_harabasz_score(X_scaled[mask], labels_final[mask]) print(f轮廓系数: {sil:.4f}) print(fCalinski-Harabasz指数: {ch:.2f}) print(f簇数量: {len(set(labels_final)) - (1 if -1 in labels_final else 0)}) print(f噪声点数量: {list(labels_final).count(-1)}) print(f噪声比例: {list(labels_final).count(-1) / len(labels_final) * 100:.1f}%)5. 踩坑实录DBSCAN实际使用中最容易翻车的五个地方这一部分是我最想写的。DBSCAN的原理不复杂代码也就几行但实际用起来坑不少。下面这五个问题我几乎每次用DBSCAN都会遇到至少一两个。5.1 高维数据下的密度失效维度灾难的真实影响DBSCAN在二维、三维数据上表现很好但维度一高问题就来了。在高维空间里所有点之间的距离都会趋于相近密度这个概念就失去了区分度。你设一个Eps可能所有点都在邻域内也可能所有点都不在很难找到一个合适的中间值。我试过一个20维的数据集用k-距离图找Eps曲线几乎是平的根本没有明显的拐点。最后跑出来的结果要么是一个大簇要么全是噪声。应对这个问题的办法有两个一是先做降维PCA或t-SNE把维度降到10以下再跑DBSCAN二是换用其他对高维更友好的聚类方法。如果非要在高维用DBSCAN可以尝试用余弦距离代替欧氏距离有时候会有改善但效果因数据而异。5.2 Eps的微小变化导致结果剧变参数敏感性的应对DBSCAN对Eps的敏感程度有时候会超出你的预期。我遇到过Eps从0.45调到0.50簇的数量从7个变成3个的情况。这种敏感性在数据密度分布不均匀的时候特别明显。应对策略是不要只跑一个Eps值而是跑一组值观察结果的稳定性。如果某个Eps附近的结果比较稳定簇数量和噪声比例变化不大那这个值就比较可靠。如果结果剧烈波动说明数据本身的密度结构不清晰可能需要重新审视数据或者换方法。# 参数敏感性分析 eps_test np.arange(0.2, 1.0, 0.05) results [] for eps in eps_test: db DBSCAN(epseps, min_samples4) labels db.fit_predict(X_scaled) n_clusters len(set(labels)) - (1 if -1 in labels else 0) n_noise list(labels).count(-1) results.append((eps, n_clusters, n_noise)) # 打印结果表 print(f{Eps:8} {Clusters:10} {Noise:8}) print(- * 26) for eps, nc, nn in results: print(f{eps:8.2f} {nc:10} {nn:8})5.3 数据量纲不统一导致的距离失真这个问题我单独拿出来说因为太常见了。很多人拿到数据直接丢进DBSCAN忘了标准化结果跑出来的簇完全不是那么回事。我举个例子假设你在做一个客户分群特征包括年龄18到80和年消费额1000到500000。如果不标准化距离计算几乎完全由消费额决定年龄的影响可以忽略不计。你设Eps5000那年龄相差50岁和相差5岁在距离上几乎没有区别。标准化之后两个特征才真正平等地参与聚类。实操心得标准化不是万能的。如果某些特征你希望它权重更大可以手动加权。比如在距离计算前给某个特征乘以一个系数。但这是进阶操作新手先把StandardScaler用上就行。5.4 噪声比例过高或过低时的排查思路噪声比例是DBSCAN结果是否合理的一个重要信号。我总结了一个排查流程噪声比例过高30%首先检查Eps是不是太小了尝试增大Eps检查MinPts是不是太大了尝试减小MinPts检查数据是否做了标准化如果以上都试了还是高可能是数据本身就很稀疏DBSCAN不适合噪声比例过低2%Eps可能太大了尝试减小MinPts可能太小了尝试增大检查是否有大量重复点或近似重复点这些点会人为地增加局部密度噪声比例在合理范围但簇的数量不对如果簇太少可能是Eps太大导致不同簇合并了如果簇太多可能是Eps太小导致同一个簇被拆分了结合业务知识判断这些簇在业务上说得通吗5.5 大数据量下的性能瓶颈与优化DBSCAN的时间复杂度在最坏情况下是O(n²)因为要计算每对点之间的距离。数据量到几万条的时候跑一次可能要几分钟甚至更久。到几十万条的时候基本就跑不动了。优化思路有几个使用空间索引sklearn的DBSCAN默认会用KD-Tree或Ball-Tree来加速邻居查询但这只在低维数据上有效。维度超过20左右树结构的效率会急剧下降。降采样如果数据量太大可以先随机采样一部分跑DBSCAN确定合适的参数再对全量数据用确定好的参数跑一次。或者直接用采样结果作为参考。使用更高效的实现有一些专门优化的DBSCAN实现比如基于网格的方法在大数据量下比原始实现快很多。但这类实现通常需要额外安装库而且参数调优的方式可能略有不同。换用HDBSCANHDBSCAN是DBSCAN的改进版本不需要手动指定Eps对参数不那么敏感而且有更高效的实现。如果DBSCAN调参让你头疼可以试试HDBSCAN。不过这是另一个话题了这里不展开。6. 和K-Means的对比什么时候该用DBSCAN什么时候不该既然DBSCAN这么好是不是可以完全替代K-Means当然不是。两种算法各有适用场景选错了就是给自己找麻烦。6.1 一张表看清两种算法的本质差异对比维度K-MeansDBSCAN需要指定簇数量是否簇的形状球形/凸形任意形状噪声处理所有点都归入某个簇可以识别噪声参数数量1个K2个Eps, MinPts参数敏感性对K敏感对Eps和MinPts都敏感大数据性能较好可用MiniBatch较差O(n²)最坏高维数据一般较差结果可复现性受初始化影响确定性结果密度不均匀数据效果差效果较好从这张表可以看出来DBSCAN的优势在于不需要指定簇数量、能处理任意形状、能识别噪声。劣势在于参数敏感、大数据性能差、高维效果不好。6.2 选型决策从数据特征出发的判断逻辑我一般用下面这个逻辑来判断先用K-Means的情况数据量很大几十万以上对性能有要求数据维度很高几十维以上你大致知道簇的数量或者业务上需要固定数量的分群簇的形状大致是球形的先用DBSCAN的情况数据量中等几千到几万维度不高10维以下不知道簇的数量需要算法自己发现数据中有噪声需要识别出来簇的形状不规则两个都试试的情况数据量中等、维度中等对结果没有明确的预期有时间做对比实验我自己的习惯是拿到一个新数据集先跑一遍K-Means看看大致分布再跑一遍DBSCAN看看密度结构。两个结果对比着看往往能发现一些单独用某一种算法看不到的东西。6.3 一个容易忽略的点DBSCAN的结果是确定性的这一点值得单独提一下。K-Means的结果受初始中心点的影响每次跑可能不一样需要设随机种子或者跑多次取最优。DBSCAN不一样只要参数固定、数据顺序固定结果是完全确定的。它没有随机初始化的过程核心点的判定和簇的扩展都是确定性的。这个特性在某些场景下很有价值。比如你需要向业务方解释聚类结果确定性的结果更容易复现和沟通。又比如你在做A/B测试需要保证每次跑出来的分群是一致的DBSCAN的确定性就很有优势。但要注意如果数据点的顺序变了DBSCAN的结果可能会有细微差别。因为边界点的归属可能依赖于处理顺序。不过这种差别通常很小不影响整体结构。7. 几个可以直接抄作业的实战技巧最后这部分我分享几个在实际项目中总结出来的小技巧都是那种知道了能省不少时间的东西。7.1 用PCA可视化辅助参数选择虽然DBSCAN不适合在高维数据上直接跑但你可以用PCA把数据降到二维画散点图用肉眼观察密度分布。这个图能帮你直观地判断Eps大概该设多大。如果二维图上两个簇之间的间隙大概是0.5个标准化单位那Eps设0.3到0.4左右可能比较合适。from sklearn.decomposition import PCA # 降维到二维用于可视化 pca PCA(n_components2) X_pca pca.fit_transform(X_scaled) plt.figure(figsize(8, 6)) plt.scatter(X_pca[:, 0], X_pca[:, 1], alpha0.6) plt.title(PCA Visualization of Data Distribution) plt.xlabel(PC1) plt.ylabel(PC2) plt.grid(True) plt.show() print(f解释方差比: {pca.explained_variance_ratio_})7.2 对结果做稳定性检验跑完DBSCAN之后我习惯做一个简单的稳定性检验对数据做多次bootstrap采样每次跑DBSCAN看簇的数量和大小分布是否稳定。如果不同采样下结果差异很大说明聚类结构本身不稳定需要谨慎解读。def stability_check(X, eps, minpts, n_iterations10): 通过bootstrap采样检验聚类稳定性 n_samples len(X) cluster_counts [] noise_ratios [] for i in range(n_iterations): # 有放回采样 idx np.random.choice(n_samples, n_samples, replaceTrue) X_boot X[idx] db DBSCAN(epseps, min_samplesminpts) labels db.fit_predict(X_boot) n_clusters len(set(labels)) - (1 if -1 in labels else 0) n_noise list(labels).count(-1) cluster_counts.append(n_clusters) noise_ratios.append(n_noise / n_samples) print(f簇数量: 均值{np.mean(cluster_counts):.1f}, 标准差{np.std(cluster_counts):.2f}) print(f噪声比例: 均值{np.mean(noise_ratios):.3f}, 标准差{np.std(noise_ratios):.3f}) return cluster_counts, noise_ratios如果簇数量的标准差超过1或者噪声比例的标准差超过0.05就说明结果不太稳定需要重新考虑参数或者数据质量。7.3 处理类别特征时的距离计算DBSCAN默认用欧氏距离但如果你有类别特征欧氏距离就不太合适了。比如城市这个特征北京和上海的距离该怎么算直接编码成0和1距离就是1但这没有实际意义。处理类别特征有两种常见做法一是用独热编码One-Hot Encoding把类别特征展开成多个0/1特征然后用欧氏距离。但这样会增加维度可能加剧维度灾难。二是用自定义的距离函数比如Gower距离它能同时处理数值和类别特征。sklearn的DBSCAN支持传入自定义的metric参数但需要自己实现距离函数。from sklearn.metrics.pairwise import pairwise_distances def gower_distance(X): 简化的Gower距离实现 假设前n_num列是数值特征后面是类别特征 n_samples X.shape[0] dist_matrix np.zeros((n_samples, n_samples)) # 这里需要根据实际数据类型分别处理 # 数值特征归一化后的绝对差 # 类别特征0或1 # 具体实现略根据数据结构调整 return dist_matrix # 使用预计算的距离矩阵 # db DBSCAN(eps0.3, min_samples4, metricprecomputed) # labels db.fit_predict(distance_matrix)这个技巧稍微进阶一些但如果你处理的是混合类型的数据迟早会遇到这个问题。7.4 把DBSCAN用在异常检测上DBSCAN的噪声标签-1本身就是一个异常检测的结果。那些被标记为噪声的点就是密度意义上的异常点。这个特性在一些场景下比专门的异常检测算法还好用因为它不需要训练、不需要假设数据分布、直接给出结果。我试过用DBSCAN做日志异常检测把日志事件向量化之后跑DBSCAN噪声点对应的就是异常日志。效果比一些需要调参的异常检测方法还稳定。当然前提是异常确实是密度低的如果异常是密度高的比如攻击流量集中爆发那DBSCAN就不适用了。提示用DBSCAN做异常检测的时候MinPts可以设得稍微大一点比如6到10这样核心点的判定更严格更多的边缘点会被标记为噪声异常检测的召回率会更高。但精确率可能会下降需要根据业务需求权衡。7.5 保存和复用聚类模型DBSCAN本身不是一个模型——它没有像K-Means那样的中心点可以保存。每次对新数据做预测都需要把新数据和原始数据放在一起重新跑。这在某些场景下不太方便。如果你需要对新数据做快速预测可以考虑用DBSCAN的结果训练一个分类器比如随机森林把簇标签作为目标变量。这样新数据来了之后用分类器预测它属于哪个簇。当然这个方法的前提是簇的结构比较稳定新数据的分布和训练数据一致。from sklearn.ensemble import RandomForestClassifier # 用DBSCAN结果训练分类器 mask labels_final ! -1 # 排除噪声 rf RandomForestClassifier(n_estimators100, random_state42) rf.fit(X_scaled[mask], labels_final[mask]) # 对新数据预测 # new_labels rf.predict(X_new_scaled)这个技巧在需要实时预测的场景下比较实用但要注意定期用新数据重新跑DBSCAN更新分类器否则模型会随着数据分布的变化而失效。写到这里DBSCAN的核心内容基本覆盖了。从原理到参数、从实操到避坑、从对比到技巧该说的都说了。如果你之前只用过K-Means我建议你找一个手头的数据集按照第4节的流程完整跑一遍。跑完之后你对聚类的理解会上一个台阶——原来聚类不只是分堆还可以发现形状、识别噪声、自动确定数量。这些能力在实际项目中能帮你解决很多K-Means搞不定的问题。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑