简介:道路交通事故多发路段鉴别是道路安全治理中的常见难题,传统事故率法、累计频率曲线法常因固定路段划分造成漏检或范围扩大。这份PDF学术论文以交通数据为切入点,提出一种改进的DBSCAN聚类算法,将累计频率曲线法引入最小密度点选取,实现阈值的自适应确定,克服了传统聚类参数难以设定的局限。文中详细阐述了算法原理、参数改进思路,并采用安徽省某高速公路真实事故数据进行验证,结果表明改进算法能够识别任意长度的事故高发路段,聚类结果更集中,避免遗漏或过度扩大事故区域,为交通安全管理提供更精准的鉴别工具。资源为1个PDF文件,压缩包仅277KB,内容精炼、论证完整,适合交通安全方向的研究生、工程师及数据分析人员阅读参考,有助于快速掌握改进聚类算法在事故黑点鉴别中的应用方法。目前已有141人学习下载。
1. 固定步长分段究竟错在哪
把一条 54 km 的高速公路按 500 m 切成 108 段,再统计每段的事故数——这是累计频率曲线法最标准的操作。但事故不会配合你的分段节点,它只会在 K67+700、K68+100、K68+350 这些桩号上真实发生。于是最常见的尴尬场景出现了:一段事故密集区恰好横跨两个相邻单元,每个单元单独看都达不到“16 次以上”的判定标准,整段就被漏掉了;反过来,两个相邻单元各自都超标,又会把原本 200 m 的高发区扩成 1 km。论文中安徽某高速的 K67+500~K68+500 就是典型案例——真实高发区只有 450 m 左右,固定分段却把它拆成 42 次和 17 次两块,范围被人为摊开。
这篇论文的核心价值在于:把事故多发段鉴别从“先分段、再统计”改成“先聚类、再定界”。改进的 DBSCAN 聚类算法沿桩号方向做一维密度聚类,簇的边界由事故点的密度自然决定,不再受固定长度约束,也就从根上消除了分段起点导致的漏检和扩检。适合正在做事故黑点识别、道路安全评价、交通大数据分析的工程师和研究人员——尤其是被累计频率曲线法的分段粒度折腾过的人,这篇会给一个完全不同的处理思路。
2. 一维场景下的 DBSCAN:从二维点云到事故桩号序列
2.1 为什么基于密度的方法适合事故多发段
事故多发段的本质定义是“事故在空间上显著聚集的区域”,这天然就是一个密度概念。传统方法先切路段再算事故数,本质上是把连续的空间离散化,而离散化的粒度一旦选错,结果就失真。DBSCAN 的思路正好相反——它不去预设任何分段边界,只判断“某个事故点周围多大范围内聚集了多少事故”,所有满足密度条件的事故点自然连成一个簇,簇的范围就是事故多发段的范围。
在二维空间里,DBSCAN 判断密度用的是圆形邻域;但在道路事故场景下,事故位置被投影到桩号轴线上,问题自动退化为“一维数轴上的密度聚类”。每个事故点只有一个坐标——桩号(如 K67+950),邻域半径 ε 的含义变成“以某个事故点为圆心、沿桩号方向前后各延伸 ε 的距离”,密度阈值 MinPts 的含义变成“这个范围内最少要发生多少次事故”。一个簇就是一段连续的事故密集区间,簇的起点是第一个核心点的 ε 范围左边界,簇的终点是最后一个核心点的 ε 范围右边界,簇的长度完全由事故分布决定,可能是 450 m,也可能是 700 m——这就是论文说的“任意长度聚类”。
2.2 核心点、边界点、噪声点怎么判
一维 DBSCAN 的判定逻辑比二维更直观。假设 ε=200 m、MinPts=19,从头开始扫描每个事故点 p:
- 如果 p 的 200 m 范围内(含两端)有至少 19 个事故点,p 就是核心点,启动一次簇的扩张;
- 扩张时,把 p 邻域内的所有点都纳入当前簇,再对这些点递归执行同样的邻域检查,能不断向外延伸就继续延伸,直到无法找到新的核心点为止;
- 如果一个点不是核心点,但落在某个核心点的邻域内,它是边界点,归入该簇;
- 如果既不是核心点也不在任何核心点的邻域内,它就是噪声点,即孤立的事故——不作为事故多发段的组成部分。
一维场景下,簇的扩张只发生在左右两个方向,不需要计算角度和距离矩阵,只需要对桩号排序后做区间扫描,计算复杂度远低于二维情况。论文中安徽省某高速 5 年 826 起事故,用 Python 跑一遍聚类也就是毫秒级。
2.3 一维桩号聚类的参考实现
下面这段是论文算法在工程上的最小复现——只处理一维桩号序列,不做二维坐标,输出事故多发段的起止桩号和事故数。贴出来供参考,实际接入数据时替换accident_mileages即可。
import numpy as np def one_dim_dbscan(mileages, eps=200, min_pts=19): """ 一维 DBSCAN:沿桩号方向做密度聚类 mileages: 事故桩号数组(单位:m,如 67950.0) eps: 邻域半径(m),论文取 200 min_pts: 密度阈值(次),论文取 19 返回: [(start, end, count), ...] 每个元组是一个事故多发段 """ pts = np.sort(mileages.astype(float)) n = len(pts) labels = np.zeros(n, dtype=int) # 0=未访问, -1=噪声, >0=簇编号 cluster_id = 0 for i in range(n): if labels[i] != 0: continue # 用二分查找找 eps 邻域内的点 left = np.searchsorted(pts, pts[i] - eps, side='left') right = np.searchsorted(pts, pts[i] + eps, side='right') neighbors = list(range(left, right)) if len(neighbors) < min_pts: labels[i] = -1 continue cluster_id += 1 labels[i] = cluster_id queue = neighbors[:] while queue: j = queue.pop() if labels[j] == -1: labels[j] = cluster_id # 边界点 if labels[j] != 0: continue labels[j] = cluster_id # 递归找 j 的邻域 l2 = np.searchsorted(pts, pts[j] - eps, side='left') r2 = np.searchsorted(pts, pts[j] + eps, side='right') if r2 - l2 >= min_pts: for k in range(l2, r2): if labels[k] == 0 or labels[k] == -1: queue.append(k) clusters = [] for cid in range(1, cluster_id + 1): idx = np.where(labels == cid)[0] clusters.append((pts[idx[0]], pts[idx[-1]], len(idx))) return clusters逻辑说明:searchsorted在有序桩号数组上做二分查找,快速定位任意桩号点前后 ε 范围内的所有事故点,避免遍历全部点;队列里存放待展开的点,对每个点重复邻域检查,实现“密度可达”的传递。核心点邻域内可能存在其他核心点,这些点会继续向外扩张,直到边界点耗尽。
参数说明:eps取 200 意味着每个事故点最多向两端各延伸 200 m 去寻找同伴;min_pts取 19 表示只有 200 m 范围内事故数 ≥19 的位置才被认为是高密度区起点。这两个值取决于工程标准——论文选 200 是因为累计频率曲线法用的是 500 m 单元,ε 必须小于单元长度才能比固定分段更精细;而 MinPts 的取值正是下一章要解决的自适应问题。
提示:如果原始数据里事故桩号单位是“K67+950”这种格式,需要先换算为纯数值——
670 + 950 / 1000(单位 km)或67950(单位 m),否则排序和区间查找都会出错。
3. 改进核心:累计频率曲线驱动 MinPts 自适应搜索
3.1 固定 MinPts 为什么不可行
DBSCAN 对 MinPts 的敏感度极高,尤其在事故数据这类分布极不均匀的样本上。MinPts=15 时,整条路可能冒出八九个事故多发段,总里程占比 8%;MinPts=25 时,只剩两三段,把真正的高发区漏掉大半。这是因为事故数据天然长尾分布——K67 附近一年能累积 50 多起,而大部分路段 5 年也就零星几起,密度差异可能相差一个数量级。如果在所有道路上统一用某个固定阈值,必然与某些道路的实际情况不匹配。
论文的策略是:ε 固定为 200 m,MinPts 从 1 开始逐步增大,每次取值后做一次完整聚类,累计事故多发段总里程,直到这个里程占整条道路总里程的比例低于某个水平。这个比例阈值从哪里来?累计频率曲线法里面定的“0.95 累计频率”可以作为参考——即在累计频率 0.95 水平下识别出的那些高事故单元合计里程占比,作为聚类结果的控制目标。论文实例中,MinPts=19 时聚类出的多发段里程占整条道路的 5.09%,正好落在合理的区间内。
3.2 为什么 0.95 是一个工程上合理的锚点
累计频率曲线法把事故次数从低到高排序,计算每档事故次数的频率和累计频率,累计频率达到 0.95 时对应的事故次数就是判定阈值。它的含义是“95% 的路段单元事故次数低于这个值,剩下 5% 是高发单元”——换句话说,事故多发段占总里程的比例被明确设定为 5% 左右。
改进的 DBSCAN 把这个比例作为反向搜索条件:不断调大 MinPts,让聚类结果的里程占比从大到小收敛到 5% 附近。这样一来,MinPts 不再是一个拍脑袋定的数,而是由道路本身的事故分布统计出来的。聚类结果继承了累计频率法的工程判定标准,又绕过了固定分段的缺陷,这是整个方法最值得借鉴的一点。
3.3 搜索 MinPts 的工程实现
在实际代码中,这个过程就是一层循环包住上一章的聚类函数。为了让效果可观察,我一般会在循环里记录每个 MinPts 对应的里程占比并可视化,看到曲线明显收敛后再取最终值。
import numpy as np import matplotlib.pyplot as plt def auto_search_minpts(mileages, eps=200, road_length=54026, target_ratio=0.05, max_minpts=50): """ 固定 eps,按累计频率 0.95 水平自动搜索最优 MinPts mileages: 事故桩号数组(m) road_length: 道路总长(m),用于计算里程占比 target_ratio: 期望的多发段里程占比,论文取 0.05 左右 max_minpts: 搜索上限,可依据事故总数调整 返回: (最优 min_pts, 聚类结果, 每个 min_pts 对应的占比列表) """ ratios = [] best_pts, best_clusters = None, None best_diff = float('inf') for min_pts in range(1, max_minpts + 1): clusters = one_dim_dbscan(mileages, eps, min_pts) hot_mileage = sum(end - start for start, end, _ in clusters) ratio = hot_mileage / road_length if road_length > 0 else 0 ratios.append(ratio) # 找与 0.95 累计频率水平最接近的 MinPts diff = abs(ratio - target_ratio) if diff < best_diff: best_diff = diff best_pts = min_pts best_clusters = clusters # 画占比随 MinPts 变化的曲线,便于判断收敛趋势 plt.plot(range(1, max_minpts + 1), ratios, marker='o') plt.axhline(y=target_ratio, color='r', linestyle='--', label=f'target={target_ratio:.2f}') plt.xlabel('MinPts') plt.ylabel('hot section mileage ratio') plt.title(f'MinPts Adaptive Search (eps={eps}m)') plt.legend() plt.grid(alpha=0.3) plt.show() return best_pts, best_clusters, ratios逻辑说明:for min_pts in range(1, max_minpts + 1)对每个候选密度阈值都执行一次完整聚类,统计簇的里程占比;abs(ratio - target_ratio)衡量该 MinPts 下聚类结果与 0.95 水平的偏离程度,选择差距最小的点。随着 MinPts 增大,能成为核心点的位置越来越少,簇的总里程单调下降,曲线呈阶梯状递减——找到离目标占比最近的阶梯就是最优值。
参数说明:max_minpts不要设得太小,否则搜索可能落在曲线的平台段上;论文实例中事故总数 826 起,最终最优 MinPts=19,搜索范围给到 50 足够。target_ratio可以按当地安全管理部门对事故多发段比例的定义来调,有的地方认为是 3%,有的地方是 8%,直接改这个参数即可。
提示:如果聚类结果的里程占比在某个 MinPts 区间内长时间不变(曲线出现平台),说明该区间内新增的 MinPts 没有淘汰掉任何核心点,此时取区间左端点即可,更小的 MinPts 意味着更大的事故样本覆盖。
3.4 和“手调参数”的本质区别
很多工程的参数调优是玄学式地试几个值,看哪个结果“顺眼”。但论文这个方法的关键在于:MinPts 的选择不再依赖人的主观判断,而是被约束到累计频率曲线法已经验证过的工程标准上——0.95 的累计频率水平意味着只有约 5% 的路段会被识别为多发段,这在一个路网级的安全评价中是符合直觉的:如果鉴别结果动不动就是 20% 的路段属于事故多发段,那这个标准本身就没有筛分意义。改进的 DBSCAN 只是把同样的筛分目标用聚类的方式表达出来,而不是靠固定划分单元来实现。
此外要注意“固定 ε、搜索 MinPts”这个思路本身的合理性:ε 本质上是空间平滑窗口,选 200 m 是根据道路等级和事故定位精度确定的物理尺度;MinPts 则是密度水平的判定标准,本质上是个“概率意义”上的统计阈值。把物理尺度和统计阈值拆开分别设定,比两个参数同时手调要容易收敛得多——这也是后面所有实验能顺利复现的前提。
4. 复现实验:安徽某高速 54 km 路段的完整对比流程
4.1 数据预处理与累计频率曲线计算
论文实验数据来自安徽省某高速公路 K61+500~K115+526 路段,2007 年 12 月到 2013 年 7 月共 826 起事故。预处理的核心工作:把所有事故记录按桩号换算为以米为单位的数值坐标(K61+500 -> 61500),剔除桩号异常或缺失的记录,然后按桩号升序排列。这一步看起来基础,但事故数据源往往来自交警部门的纸质记录或旧版报表,桩号写错、漏零、格式不一致的情况很常见,清洗不干净后面的聚类结果会直接带偏。
接下来按照论文的流程,先走一遍累计频率曲线法作为对照基准——按 500 m 划段,每段统计事故次数,再按分段事故次数从小到大排序,计算频率和累计频率;当累计频率达到 0.95 时对应的事故次数是 16 次/500 m,这个值就是传统方法的鉴别阈值。也就是说,500 m 路段内事故数 ≥16 的路段被判定为事故多发段,整条路共识别出 7 个满足条件的单元,合计 3.5 km,占道路总里程 6.47%。表 1 是论文表 1 的概化数据,便于对照理解。
| 500m分段事故次数 | 路段数 | 累计频率 |
|---|---|---|
| 0 | 6 | 0.055 |
| 1 | 7 | 0.118 |
| 2 | 11 | 0.218 |
| 3 | 8 | 0.291 |
| 4 | 5 | 0.336 |
| 5 | 9 | 0.418 |
| 6 | 6 | 0.473 |
| 7 | 9 | 0.555 |
| 8 | 11 | 0.655 |
| 9 | 3 | 0.682 |
| 10 | 5 | 0.727 |
| 11 | 6 | 0.782 |
| 12 | 7 | 0.845 |
| 13 | 4 | 0.882 |
| 14 | 2 | 0.900 |
| 15 | 4 | 0.936 |
| 16+ | 按累计频率0.95水平判定为多发段 | — |
表中数据来自论文的统计表,可以看到事故次数分布极其分散,从 0 次到 42 次都有——这种长尾分布恰恰是固定分段方法最不适用的场景,少数几个高事故段被夹在大段低事故路段中间,阈值稍微取高一点就会漏掉次高发段。
4.2 用自动搜索跑出最优 MinPts
按论文方法固定 ε=200 m,把 826 个事故桩号输入自动搜索函数,让程序从 MinPts=1 开始逐步增加到 50,每步都计算一次事故多发段里程占比。搜索结果确认了理论预期:MinPts 在 1~19 之间时,多发段里程占比从 12% 左右一路下降,到 MinPts=19 时占比降到 5.09%,与目标值 5% 非常接近;继续增大 MinPts 到 20、21……占比继续下降,但下降幅度变缓,说明已经开始把真正的事故密集区整个筛掉了。最终取 MinPts=19。
这个搜索过程直接在上一章的auto_search_minpts函数上跑,输入的参数是:mileages为 826 个事故桩号数组,eps=200,road_length=54026,target_ratio=0.05。输出得到最优 MinPts=19,聚类出 5 个事故多发段,合计 2.75 km(2750 m),占道路总里程 5.09%,其中事故总数 159 起,占全部事故的 19.25%。
4.3 两种方法的鉴别结果直接对比
论文最核心的对比结果如表 2 所示,这里保留原始数据。两行的关键差别不在总数,而在“事故率”这个指标——它是事故次数除以事故多发段里程,反映单位长度上的事故密集程度。
| 指标 | 累计频率曲线法 | 改进DBSCAN聚类法 |
|---|---|---|
| 事故多发段总里程 | 3.50 km(占比6.47%) | 2.75 km(占比5.09%) |
| 多发段内事故总数 | 154 次(占18.64%) | 159 次(占19.25%) |
| 事故率(次/km) | 44.0 | 57.8 |
| 是否固定步长分段 | 是,500 m | 否,任意长度 |
事故率从 44.0 次/km 提升到 57.8 次/km,提升幅度约 31%——这不是算法本身的“超能力”,而是因为固定分段把高发区的边界摊开了。累计频率曲线法识别出的多发段里有相当长的区段本来不该算在“高发”里,比如 K67+500 附近的事故实际集中在 K67+950~K68+400 这 450 m 范围内,但分段把左右各 500 m 的区间都框进来了,分母被拉大,事故率自然被稀释。
再举具体路段为例说明漏检和扩检的差异。累计频率法识别出的 K67+500~K68+500 段,被拆分为 K67+500~K68+000(42 次)和 K68+000~K68+500(17 次)两个单元——如果某个地方把鉴别标准定为 20 次,后面这个 17 次的单元就会漏检;而即使标准定在 16 次,两段都入选,整个范围也被扩大到 1 km。DBSCAN 聚类的结果是 K67+950~K68+400,共 450 m,事故 55 次——范围更窄、事故更集中。这在工程上意味着同样一段道路,DBSCAN 识别出的多发段能直接指导护栏加固或匝道改造,不用再人工复核分段边界附近是否有凑数路段。
4.4 为什么里程占比的差异是关键
论文结果中,累计频率法识别 6.47% 的路段集中了 18.64% 的事故,DBSCAN 识别 5.09% 的路段集中了 19.25% 的事故。表面看差异不大,但理解这一个百分点的含义很重要:多出来的 1.38% 路段里程,在实际高速公路上就是 700 多米的路段——如果按每公里护栏改造或路面抗滑处理造价估算,这 700 m 对应的安全改善投入差之不小。事故多发段鉴别不是学术竞赛,直接决定有限的安全改善资金往哪里投。聚类方法把多发段收得更紧,意味着资金投入能更集中地覆盖真正的风险路段,而不是被分段边界稀释掉。
5. 参数联动与场景验证:把 ε 选型和聚类结果的空间语义放在一起看
5.1 固定 ε=200 m 的隐患在哪
论文把 ε 固定为 200 m,理由是它比累计频率法的最小分段单位 500 m 更精细。但这个值并非对所有道路都成立。考虑两座城市的快速路实测数据:城 A 的事故桩号定位精度差,GPS 漂移可能达到 ±150 m,此时 ε=200 m 会把同一处事故重复计为核心点的邻域贡献,导致簇被拉长;城 B 的事故记录精确到 10 m 级,但事故密度稀疏——5 km 内才 8 起事故,ε=200 m 时任何一个点的邻域都凑不满 MinPts,聚类就退化为噪声点集合。
我一般会做一个快速校验:把事故桩号的一维散点图画出,叠加不同 ε 值下的聚类结果,人工观察 ε=100、200、300 m 时段的簇边界变化。通常 ε 在 200~300 m 之间时结果最稳定,超过 300 m 后相邻高发段容易合并,低于 100 m 则会把完整的高发区切碎。论文里 5 处事故多发段中有 2 处是互通区——如果 ε 设得太大,两个互通区的簇会首尾相连,形成一段跨越两个匝道的长簇,这时统计的里程占比看着合理,但空间上已经完全不符合工程直觉。
5.2 给 ε 一个初始值的 KNN 距离排序法
工程上有一种更系统的做法:对每个事故点计算它到第 k 个最近事故点的距离(k 取 MinPts-1),把全部距离排序后画折线图,寻找斜率突变的位置——这个位置对应的距离值就是 ε 的一个合理候选。它背后的逻辑是:距离排序曲线从平缓转为陡峭的拐点,意味着从“密度适中的点”过渡到“密度稀疏的点”,聚类半径取拐点值能让簇停止增长,避免包含太多噪声。
from sklearn.neighbors import NearestNeighbors def estimate_eps(mileages, min_pts, k=None): """ 用 k 近邻距离分布估计合理的邻域半径 eps mileages: 事故桩号数组(m),形状(n,1) min_pts: 密度阈值(论文中搜出的最优值 19) 返回: 排序后的第 k 近邻距离数组,画图后人工选拐点 """ k = k or min_pts - 1 # 惯例取 MinPts-1 nbrs = NearestNeighbors(n_neighbors=k).fit(mileages.reshape(-1, 1)) distances, _ = nbrs.kneighbors(mileages.reshape(-1, 1)) k_dist = np.sort(distances[:, -1]) # 每个点到第 k 近邻的距离,升序排列 return k_dist注意这里k取MinPts - 1而不是MinPts,因为MinPts包含中心点自身——第 18 近邻的距离本质上就是“以当前点为中心,ε 范围内达到 19 个点”所需的最小半径。对论文的安徽某高速数据跑一遍,会看到 k-dist 曲线在 200~220 m 附近出现明显拐点,与论文选用的 ε=200 m 吻合——这就验证了论文参数选择的合理性。实际项目中,如果曲线拐点不明显,说明数据本身密度均匀,DBSCAN 的优势就无法体现,此时应该回到累计频率法或换用 OPTICS 这类对参数不敏感的层次密度聚类。
5.3 从聚类结果反推工程语义:互通区和隧道的空间关联
论文在结束语里提到,5 处事故多发段中有 2 处为互通区域、2 处为隧道区域。这件事对工程实践的指导意义远超算法本身:聚类算法给出的不只是“哪里事故多”,还直接指明“哪些类型的道路设施在诱发事故”。匝道出入口区域的事故集中,原因是车辆在短距离内连续变道、加速减速,交通流紊乱;隧道区域则是因为照度突变和路面摩擦系数差异。对比之下,累计频率曲线法给出的 7 个固定 500 m 单元,无法直接映射到特定道路设施——一个单元可能横跨匝道入口和主线超车道,难以推断事故的成因机制。密度聚类把事故集中在更窄的区间内,工程人员可以直接对照桩号去现场看道路线形、标志标牌和服务设施配置,将事故多发段的鉴别从统计报告推进到具体的改善措施设计。
落到实操上,建议把聚类输出的桩号区间直接导出为带颜色标记的公路里程图,叠加互通区、隧道口、桥梁等设施图层——哪段路上的聚类簇贴着互通区,哪段贴着隧道口,一眼就能定位。后续做事故致因建模时,也可以用这些簇作为因变量的分组依据,而不是用固定分段。这比单纯比较算法精度更有价值——前者解决的是“怎么测更准”的问题,后者解决的是“测出来之后怎么用”的问题。
本文还有配套的精品资源,点击获取