三周前,我跑完一版轨迹预测器评估。榜单上的平均位移误差(ADE)数值看着挺美好,比上一版又降了几个厘米,可一到现场测试,车辆从右侧匝道汇入主路、目标前车正在急刹的场景,预测器给出的轨迹还是把两车之间的距离算到了接近零。那次体验让我彻底明白,“平均数的漂亮”和“现实世界的安全”之间隔着的不是一两个指标,而是整套评估口径。后来换成场景驱动评价的思路,一个个场景回放,才发现原来的结论有多脆弱。
这篇文章适合正在做轨迹预测、规划评估、端到端驾驶模型的人,尤其是想从“demo 指标爽一爽”切换到“可审计指标系统”的团队。我会把位移误差为什么是“善意的谎言”、场景驱动评估怎么落地成可执行脚本、以及用什么指标衡量“安全”而不是“平均对齐程度”,一次讲清楚。
1. 为什么要场景驱动:平均数骗人的地方
1.1 传统位移误差到底在衡量什么
轨迹预测任务里,最常用的两个指标是 ADE(Average Displacement Error,平均位移误差)和 FDE(Final Displacement Error,最终位移误差)。公式不复杂:
- ADE = \frac{1}{T} \sum_{t=1}^{T} |\hat{p}_t - p_t|
- FDE = |\hat{p}_T - p_T|
其中 \hat{p}_t 是预测轨迹在 t 时刻的位置,p_t 是真实位置。通常我们让模型输出多条轨迹,比如 6 条,然后用“与真值最接近的那一条”(minADE / minFDE)去算,或者用“覆盖所有真值轨迹”的方式去打分。
这套指标本身没有错,它在学术排行榜上依然有参考价值,用来衡量一个模型的“基础预测能力”。但它有个致命前提:它假定“预测误差最小”等于“决策最安全”。
我在实际项目里见过不少模型,它们会把主干道直行预测得极好,因为这种样本占了训练集的大头;但一旦遇到切出主路、前车突然变道、行人横穿这样的小概率场景,误差直接翻倍。ADE 是加总平均,几条主干道样本的优秀误差,就把这些极端误差“抹平”了。最后你看到的模型精度很高,可放在自动驾驶系统里,恰恰是那些极端场景,才是撞车或急刹的源头。
1.2 从“预测准”到“决策稳”:评估目标正在后移
如果你的预测器只是一个独立学术模块,那优化 ADE 的行为非常正当。但放在自动驾驶全链路里,预测器不是终点,规划器才是消费预测结果的下一环。规划器拿到轨迹后,要判断“这条轨迹安全吗”“它跟自车路线冲不冲突”“要给对方让出多少横向空间”。
所以,评估的重心很自然地从“预测轨迹离真值有多近”后移到了“预测轨迹能不能支撑安全决策”。这个变化才是场景驱动评估的核心动因。
举个例子。主路直行且无交互的样本,预测误差低得离谱,但规划器根本不关心,因为自车也是直行,只要不踩油门过度,不会出事。真正要评估的是:
- 前车是否在减速并切入自车车道;
- 旁车是否有连续变道的意图;
- 行人过街时,会不会走到自车路径上;
- 对面来车是否占用回转空间。
这些场景中,预测器输出误差哪怕只是 30 厘米,也有可能改变规划器对安全间距的判断。用 ADE 这种“平均量”去观测,你根本看不出来哪段场景会死亡,哪段场景只是“数学上稍微不准”。于是场景驱动的评估方案就变成了刚需:把数据拆成具有明确驾驶语义的场景,逐个场景去追问“在这类情况里,预测器的伤有多重”。
2. 场景的定义与构建:先把“测什么”说清楚
2.1 一个可用于评估的场景包长什么样
很多人觉得“场景驱动”就是把测试集按时间片切一下,或者干脆按地图区域分类,那太粗糙了。真正可用的评估场景,至少要包含四层信息:
第一是地图语义。车道数、车道连接关系、人行横道位置、路口类型、匝道结构,这些要素决定了车辆的可行驶区域。缺少地图语义,很多轨迹预测问题就变成了无约束的“自由发挥”,你还不如直接看玩具模型。
第二是交通参与者的动态真值。每辆车的类型、速度、加速度、历史轨迹、当前意图标签。这能帮助判断场景是“高速接驾”还是“低速跟车”。
第三是自车与目标的相对关系。自车速度、目标车横向偏移、目标车纵向距离、TTC(Time to Collision,碰撞时间)。这些不算传统的预测标签,但却是场景切分和风险分层最有用的特征。
第四是场景标签或注释。比如“Cut-in左侧切入”“Hard Brake 急刹”“Occluded 遮挡”“Roundabout 环岛”“Merge 汇入”。标签可以来自人工标注,也可以来自规则自动分类,比如用横摆角速度和车道交叠程度判断是否发生切入。
我建议你把地图语义和场景标签定义为最外层索引,把相对关系定义为内层候选集。做评估时,先按标签圈定场景桶,再按风险特征做筛选,这样每个场景样本都能被复现和追溯。
2.2 如何从原始数据切出场景并保证多样性
拿一段原始驾驶日志来说,长度可能十几分钟,里面 70% 是匀速直行。你不能直接拿全量数据去测,因为直行样本会淹没你想要关注的危险场景。我的思路是三步:
第一步,用滑窗按固定长度切片段。窗口长度一般是 8 到 10 秒,预测器要预测未来 3 到 6 秒,所以需要至少 4 秒的历史输入和 6 秒的将来的真值。滑窗步长设 1 秒或 2 秒,保证连续性和覆盖。
第二步,做场景标签自动分类。用地图拓扑判断是否在弯道,用横向速度判断是否换道,用目标车的急减速判断是否急刹,用相对距离变化判断是否插入。这些规则不一定完美,但能在一开始把样本分到粗略的桶里。
第三步,分层采样。每一类场景桶里,都随机抽一批样本,同时故意把“稀有但高风险”的样本权重调高,例如遮挡恢复、行人鬼探头、多车交互。这个做法是为了避免评估集被高频场景主导,否则你最后又是用平均指标糊弄自己。
另外还要注意时间相关性。同一个场景段前后帧高度相关,如果你把一个 10 秒片段内的所有滑窗样本都塞进测试集,模型可能重复见过类似轨迹,评估结果会有偏差。我一般会做“按片段去重”,同一段日志只抽少量样本,确保每个场景有独立信息。
2.3 场景权重与优先级:用风险收益比排列
场景不是等权重的。真正部署时,自动驾驶最怕的是高危场景漏判,而不是主干道直行的小误差。所以我们需要给场景赋予不同权重。权重怎么定?我采用的一个简单办法是“风险 × 频率”矩阵。
| 场景类型 | 出现频率 | 如果不预测好的后果 | 建议权重 |
|---|---|---|---|
| 高速直行 | 高 | 低,规划器可修正 | 0.3 |
| 城市跟车 | 高 | 中,可能出现急刹 | 0.8 |
| 侧向切入 | 中 | 高,容易碰撞 | 1.5 |
| 行人横穿 | 中 | 很高 | 1.8 |
| 环岛合流 | 低 | 高中,视几何而定 | 1.2 |
| 遮挡恢复 | 低 | 很高 | 2.0 |
| 极端交互(鬼探头) | 很低 | 极高 | 2.5 |
这里的权重不是“出现频率越高权重越高”,而是“频率 × 后果”。我会建议团队在项目早期固定一套权重,不要每周改来改去,否则对比模型时你无法判断到底是模型进步了,还是权重偏好变了。
3. 评估指标体系:从位移误差到可操作信号
3.1 基础量:ADE、FDE、多模态命中率与它们的适用边界
先承认基础量的价值。ADE、FDE 是快速对比不同模型的“体温计”。训练时拿它做 loss 和早停,开发时拿它粗筛模型,这些都没问题。但我要强调它们更适合做“模型能力排名”,不适合做“安全评估”。
为什么?因为它们没有考虑“预测轨迹和自车路径之间的几何关系”。我举一个很极端的例子:一个模型预测其他车辆会跑到自车中心线上,预测误差只有 10 厘米,但按照这 10 厘米误差的轨迹,规划器算出碰撞不可避免。另一个模型预测出偏离真值 50 厘米的轨迹,但偏离方向是远离自车,规划器选择了安全避让。前者 ADE 很漂亮,后者被罚很大。你说哪个模型更安全?显然是后者,可是纯位移指标把结论完全搞反了。
所以在场景驱动评估里,我会保留 ADE 和 FDE 作为诊断指标,但绝不作为验收指标。它们负责回答“模型内在精度高不高”,但回答不了“模型在交通流中会不会把别人带进沟里”。
3.2 加入碰撞时间、最小距离、车道占位这类交互指标
要评估“决策稳不稳”,必须引入交互感知指标。下面这些指标是我在实际评估里常用的。
一是TTC(Time to Collision,碰撞时间)。定义是两车相对距离除以相对速度:TTC = \frac{\Delta d}{\Delta v}。如果预测轨迹显示未来 3 秒内 TTC 小于某个阈值,比如 1.5 秒,那么这个预测提示规划器必须介入。评估时,我们统计“预测 TTC 低于阈值的帧数占比”和“最小 TTC 分布”。最小 TTC 越低,说明预测出的危险越迫近。
二是MAD(Minimum Average Distance,最小平均距离)。它衡量预测轨迹和其他障碍物之间的最小空间距离。简单实现是逐帧计算预测轨迹与所有障碍物包围盒中心点的欧氏距离,取最小值。如果一辆车的预测轨迹贴着自车 0.3 米擦过去,而实际真值其实是 0.8 米,那么 MAD 误差大,告诉我们模型缺乏对横向间距的敏感度。
三是Lane Violation(车道占位违规)。这个指标很实用。做法是拿预测轨迹打点到地图车道上,看看轨迹是否越过了实线、骑跨车道线、跑到逆行车道。传统位移指标很难反映“轨迹放在地图上好不好看”,但规划器非常在乎,因为车道违规轻则让乘客不适,重则危险。
四是Cut-in Lead Time(切入提前量)。目标车是否比某时刻提前切入本车道,以及模型在多早能预测到这种切入。比如真值里前车在第 5 秒开始压线,模型如果第 6 秒才给出压线轨迹,那这个“反应延迟”就是安全风险。
这些指标不再关心“平均误差”,而是在每一个场景中去问:预测轨迹是否破坏了安全空间,以及破坏得早不早。
3.3 权重与打分卡:怎么把多个指标揉成一个可审计的结论
多个指标揉在一起,最简单的做法是加权和,但我建议你做“分层打分卡”,而不是泡在一个数字里。
我推荐的结构:先按场景桶分组,在每个场景桶内计算它的代表性指标,比如 Cut-in 场景看 TTC 和最小横向距离,直行跟车场景看纵向误差和速度误差。然后把同一场景桶内的样本得分做分位数统计,比如 P5(最差 5%)和 P50(中位数)。最后再看全局分层汇总,比如“所有高危场景的 P5 值是否越界”。
这样做的原因是,单一加权平均分会被某类性能特别好的场景拉高,而 P5 分位数则能把“最差 5% 的情况”凸现出来。自动驾驶永远更关心自己最差表现,而不是平均表现。官方有个说法叫“墨菲驾驶”:如果某个危险情况可能发生,而且模型没有预测准,那它一定会在测试时出现。你用 P5 去守底线,用 P50 去追踪总体水平,双管齐下比较稳。
4. 实操:搭一个场景驱动评估的最小闭环
4.1 准备数据、标注意图和基础预测输出
具体落地时,我们假设你已经有了一份带预测结果的轨迹数据,格式大致如下:
agent_id:目标车编号timestep:帧号pred_traj:模型输出未来 6 秒的轨迹,形状可表示成[K, T, 2],K 是候选条数,T 是预测帧数,2 是 x/y 坐标gt_traj:真实轨迹真值,[T, 2]map_lanes:当前帧所在场景的地图车道线scenario_tag:场景标签,比如cut_in_left
如果你没有现成地图数据,可以先做简化版:把所有障碍物看作带半径的圆,用圆间距离模拟安全距离。这样虽然少了车道约束,但足以跑通指标流程。
我的建议是先选 200 到 300 个场景样本,覆盖至少 10 类场景,每个场景 20 到 30 条轨迹预测。样本太多会让调试变慢,样本太少又得不出统计结论。这个规模适合在一个晚上内完成第一版评估运行。
4.2 分场景计算指标:一段可复用的 Python 参考
我通常写一个scenario_eval.py,核心是循环场景桶,计算多个指标。下面给出一段非常小的参考代码,用来帮你建立骨架。
import numpy as np def compute_min_ttc(pred_traj, obstacle_states, min_threshold=1.5): """ pred_traj: ndarray [T, 2] predict ego position obstacle_states: ndarray [T, O, 2] obstacle positions over time returns: min_ttc, ttc_below_flag """ ttc_values = [] for t in range(1, len(pred_traj)): ego_pos = pred_traj[t] for o in range(obstacle_states.shape[1]): obs_pos = obstacle_states[t, o] dist = np.linalg.norm(ego_pos - obs_pos) prev_dist = np.linalg.norm(pred_traj[t-1] - obstacle_states[t-1, o]) rel_speed = max(1e-6, prev_dist - dist) # 简化:相对接近速度 if rel_speed > 0: ttc = dist / rel_speed ttc_values.append(ttc) min_ttc = min(ttc_values) if ttc_values else 999.0 return min_ttc, min_ttc < min_threshold def evaluate_scenario(predictions, ground_truth, obstacles, tag): min_fde = np.min(np.linalg.norm(predictions[:, -1] - ground_truth[-1], axis=-1)) min_ade = np.min(np.mean(np.linalg.norm(predictions - ground_truth, axis=-1), axis=1)) min_ttc, risky_flag = compute_min_ttc(predictions[0], obstacles) return { "scenario": tag, "min_ade": round(min_ade, 3), "min_fde": round(min_fde, 3), "min_ttc": round(min_ttc, 3), "ttc_risky": risky_flag, "sample_size": len(predictions) }注意,这段代码刻意做了简化。真实工程里障碍物是有朝向和大小的,我会用 oriented bounding box 做距离计算;预测轨迹和障碍物未来轨迹都需要做时间对齐。但核心骨架不变:按场景遍历 → 计算空间最近距离 → 计算 TTC → 标记风险。
4.3 跑完结果怎么读:两张表和三条判断规则
跑完后,不要急着打印一张大表,我习惯先做两个汇总:
第一张表是场景桶维度的汇总表,行列分别是场景类型、样本数、minADE 中位数、minFDE 中位数、minTTC 中位数、ttc_failure 比例。看到这张表时,我第一眼不看中位数,它最容易骗人,我只看“ttc_failure 比例”。如果一个 Cut-in 场景里,有 40% 的预测输出 TTC 小于 1.5 秒,说明这模型在切入场景里根本无法支撑安全决策。
第二张表是高风险样本清单。把每个高风险样本的 ID、最小 TTC、场景标签列出来,方便去可视化工具里逐帧回放。我会专门看重叠帧段,比如模型是不是提前 2 秒就已经预测到切入,还是到最后 0.5 秒才勉强修正。
三条判断规则我记在便签上:
- 规则一:如果高风险场景占比超过 10%,模型不能上车。
- 规则二:如果某些高风险场景占比很低,但 minTTC 出现小于 0.5 秒的样本,继续调参优先级高于刷 ADE。
- 规则三:对比两个模型时,先看 P5 的 TTC 分布,再看平均 FDE。哪个模型 P5 更安全,哪个模型优先用。
实测下来,这套规则能避免我和同事在“一个模型 ADE 降了 0.1 米,但切入场景 TTC 反而变差”的局面里扯皮。
5. 常见的坑与经验
5.1 场景标注的“死角”会造成误解
场景驱动评估最花时间的地方不是算指标,而是给场景打标签。我之前偷懒,只用“变道”“直行”这种粗糙标签,结果发现同一个变道标签里,有“前车让行变道”和“强行抢隙变道”两种截然不同的交互,预测难度差很多。
后来我做了标签分级:粗标签用于快速分组,细标签用于高优场景分桶。比如变道下面再分“目标车道无车”“目标车道有车但减速让行”“目标车道有车但加速强行并线”三个子类。这个分级看着琐碎,实际效果立竿见影:你能看到模型到底在哪种子类上稳定失败。
5.2 榜单病:拿全局分数排名,越排越焦虑
我见过不少团队,内部算法组每周比 ADE/FDE,排名低的人疯狂调参,排名高的人沾沾自喜。结果做集成测试时,排名高的方案反而在复杂场景上更差。
原因不复杂:全局指标天然喜欢那些输出平滑、平均偏置小的模型,但这样的模型往往不敢预测激进动作,比如抢先变道或紧急制动。它在平滑场景里表现好,在交互场景里偏向保守和滞后。所以我现在和团队约定,周度只看 minADE,月度看场景桶指标和 P5 风险指标,周度排名不作为晋升依据。真要排模型,就让它们先过场景评估这一关,再谈全局得分。
5.3 我这边的几条 checklist 和心得
我现在给团队留了一份 checklist,每次跑场景评估前都要过一遍:
- 场景桶是否覆盖了你部署区域的典型路型?比如有高架匝道、无保护转弯、学校区域,至少各挑一组。
- 每个桶里样本量是否足够?少于 15 个样本时,统计意义低,只能当参考,不能下结论。
- 是否排除了时间重复样本?否则一个连续危险片段会反复拉低指标。
- 是否同时看了 P50 和 P5?只看中位数容易被平均掩盖,只看 P5 又会被个别噪声样本带偏。
- 是否记录了模型耗时?如果指标变好但推理时间翻倍,规划器可能来不及用。
我带教经验里,最深的体会是:评估体系不是在项目最后才开始搭的,而是应该和模型训练同步演进。如果没有场景驱动的标准,你会沿着“训练指标最好看”的方向迭代,最后得到的是一个善于做平均数却不懂避险的模型。反过来,如果守住了场景驱动的底线,哪怕 ADE 稍微高一点点,至少你知道自己的系统在“危险即将发生”的片段里,是有机会提醒规划器做出正确反应的。
这套流程我迭代了三个版本才稳定下来,现在每次看到有人只拿 ADE 说事,我都会真诚建议:先拿十个危险场景跑一遍,看看那上面的 TTC 是多少。你大概率会发现,原来自己模型的“漂亮指标”,还没真正经历过路考的考验。