做轨迹预测(Trajectory Predictor)这个方向的人,大概率都经历过这样的时刻:一个模型在测试集上表现不错,排行榜上的数字漂亮得让人放心,但到了实际道路测试,偏偏在个别路口、某些拥堵场景下连续出问题。回头看数据才发现,那些场景在整体指标里被稀释了——九成的高架巡航样本把平均误差拉得很低,剩下那点复杂路口的预测误差,对整体数字几乎没有影响。这就是自动驾驶(Autonomous Driving)里单一聚合指标评估的典型困境,也是场景驱动评估(Scenario-Driven Evaluation)要解决的核心问题。
这篇内容想聊的就是对症下药的办法:把评估拆到具体的驾驶场景里去,看模型在路口、匝道、换道、行人穿行这些不同场景下的表现差异,而不是只看一个总分。适合正在做预测模型研发、算法评测、或者准备把预测模块推向量产的工程师参考,我会从场景体系怎么搭、指标怎么选、管线怎么落地、坑在哪儿,一步步讲清楚。
1. 为什么轨迹预测评估必须场景化
1.1 单一聚合指标是怎么掩盖问题的
先补一个背景:轨迹预测任务,简单说就是给自车周围的每个交通参与者预测未来几秒的位置序列。目前主流评估指标就那几样,minADE(最小平均位移误差)、minFDE(最小终点位移误差)、Miss Rate(漏检率,一般算的是预测轨迹与真值最大距离超过阈值,比如2米,就算miss)。这些指标本身都没问题,问题出在它们几乎总是被聚合成一个数字,在整个测试集上求平均。
举个例子你就明白了。假设一个测试集里有90%的高速巡航样本和10%的路口左转样本,两个场景难度完全不同。高速上大家都在直行,预测未来三秒基本就是一条直线,误差做到0.4米不算难;路口无保护左转,需要预测对向直行车的让行行为、行人的穿行意图,误差1.5米都算不错。如果直接算加权平均,0.9乘0.4加0.1乘1.5再归一化,得到0.51米。从数字上看模型性能非常好,可实际上模型在路口场景的表现远达不到可用水平,下游的规划模块拿到这个预测结果,可能做出危险的决策。
这就是典型的均值掩蔽效应。打个比方,一个班学生的平均分是90分,你一打听才知道,那是因为30个尖子生拿满分,剩下3个只考了40分。平均分一样好看,但"全班都学得很好"这个结论是错的。单一指标评估其实就是拿这个平均分在做决策,根本发现不了模型短板在哪,更别说指导下一步怎么改了。
1.2 场景驱动评估到底在评估什么
场景驱动评估做的事情,本质上是把评估对象从"整个测试集"拆成"语义一致的场景单元"。一个场景单元,指的是一段时间和空间范围内、道路结构和交互状态相对一致的数据片段,比如一个十字路口的直行通过、一个高速匝道的汇入、一段行人在路边等待后横穿马路的片段。评估时按这些单元分别计算性能指标,再横向对比。
这里有一个容易想歪的地方:场景驱动评估并不是要取代全局指标,也不是为了再算一个加权平均的新总分,它的真正价值有两个。第一个是研发过程里的短板定位。模型迭代的时候,与其等实车测试发现问题,不如在评估阶段就把"这个版本在哪个场景变差了"直接暴露出来。第二个是部署决策时的风险量化。一个模型能不能上量产车,不是看总体minFDE多少,而是看在无保护左转、行人横穿这类关键场景里的碰撞风险指标能不能过红线。
所以场景驱动评估本身不算一个"模型",更像是一个评测框架,或者说是一套评估体系的组织方式。框架的核心模块就三个:场景定义与切分、分层指标计算、结果可视化和研判。下面我会逐个展开,讲清楚每一步怎么做、有哪些坑。
2. 场景驱动评估体系的搭建思路
2.1 场景要素怎么拆解与分类
场景怎么分类,是整个评估体系里最需要花时间想清楚的地方。我在项目里把场景要素拆成三个层次,这个分法和主流自动驾驶公司的场景库建设思路是一致的。
第一个层次是静态道路结构。包括道路类型(高速、城市快速路、普通道路、交叉口)、车道数、车道宽度、曲率、是否有红绿灯、是否有让行标志等。静态要素决定了一个场景的基础难度,也是场景分类的主干。第二个层次是动态交互对象。包括自车周围有哪些类型的交通参与者(车辆、行人、骑行者、静态障碍物),目标对象的当前运动状态(速度、加速度、朝向)、与自车或目标之间的相对距离和TTC(碰撞时间)。动态要素决定了这个场景是否存在强交互。第三个层次是语义事件。比如目标车辆是否在换道、是否在礼让行人、自车是否在匝道汇入、是否有车辆从停车状态起步等。语义事件是最贴近"驾驶任务难度"的一层,但也最难自动获取,通常需要结合地图、融合结果和规则去推断。
我习惯用一张表来辅助场景分类设计,你可以直接拿去做底稿:
| 层次 | 核心要素 | 典型取值或特征 | 说明 |
|---|---|---|---|
| 静态结构 | 道路类型 | 高速/城市快速路/交叉口/环路 | 决定基础速度与几何约束 |
| 静态结构 | 车道数、路沿 | 2车道/3车道,有/无路沿 | 影响目标可偏离自由度 |
| 动态对象 | 目标类型 | 车辆/行人/骑行/VRU | 预测行为的复杂度差异很大 |
| 动态对象 | 目标速度区间 | 静止/低速/中速/高速 | 决定运动学先验是否可用 |
| 动态对象 | 相对距离和TTC | 小于3米/3到10米/大于10米 | 反映交互紧迫程度 |
| 语义事件 | 目标运动模式 | 直行/换道/转弯/靠边停车 | 刻画当前行为意图 |
| 语义事件 | 交互事件 | 礼让行人/被加塞/通行冲突 | 最难标注,一般靠规则推断 |
实际落地时不用每个要素都做成独立标签。我建议先定主干标签,比如"道路类型加目标类型加目标速度带"这三个组合,大概能分出十来个常用场景。再配置细粒度的特征字段,比如"换道方向""是否红绿灯""行人是否低头看手机"这些,用于场景内二次过滤和问题定位。这样既不会因为标签太粗找不到问题,也不会因为标签太细导致样本量崩塌。
2.2 指标体系怎么设计才不打架
有了场景划分,第二步就是在每个场景桶里选什么指标。最常见的误区是:直接把全局指标在多场景里各算一遍,然后写个报告完事。这能说明一些问题,但会漏掉后面的重要线索——不同场景里的风险特征不一样,只用同一套精度指标会失真。
我的建议是把指标分成三层来设计。第一层是精度指标,包括minADE、minFDE、Miss Rate。这些指标在大多数场景里都能用,但要注意它们只度量"预测位置和真值的接近程度",不度量"会不会撞"。第二层是安全指标,主要是碰撞率(也叫穿透率)和激进程度,具体就是预测轨迹与其他障碍物的最小距离、平均侵入距离。这一层在弱交互场景(比如高速巡航)里通常不需要重点关注,但在无保护左转、匝道汇入、行人穿行这些强交互场景里,安全指标比精度指标更能反映模型是否可用。第三层是实时性指标,模型推理延迟的P50和P99。这个指标大多数场景评估里都会测,但场景化之后有个细节值得注意——不同场景下的输入尺寸、目标数量差异很大,延迟也要分场景报,不能只给一个全场景均值。
指标选好了,还要应对"打架"的问题。比如在换道场景里,模型为了降低minFDE,可能会输出多模态轨迹中最贴近真值的那一条,结果精度指标很好看,但概率分配非常集中,导致实际执行时缺乏避险能力;反过来,如果过于保守,碰撞率会低,但minADE会变差,因为预测的平均偏差变大了。这种冲突本质上是因为不同指标在刻画不同维度的质量,你不能期待一个模型在所有指标上同时最优。我的处理办法是分层约束:精度指标作为趋势参考,安全指标作为硬约束。评估一个版本是否可用,先看安全指标有没有越过红线,再讨论精度指标的高低。
2.3 场景标签从哪来:标注还是聚类
场景标签的获取方式,决定了评估体系的稳定性和可维护性。业界有两种极端做法:一种是投入大量人力做纯规则标注,写了上百条规则,结果被真实数据的多样性打得措手不及;另一种是完全黑盒的聚类,跑出来一堆"场景",但专家完全看不懂为什么这么分,也没法和具体路段、具体驾驶问题对应。
实际项目里我推荐"规则打底加聚类辅助"的混合方式。规则打底的意思是先用地图和感知输出的关键字段,写一套确定性规则来定义主干场景。比如拿到路段的地图信息,知道车道数小于等于2、有交叉口属性,再加目标类型是行人,就可以定义为"交叉口行人穿越"场景。这套规则不用覆盖所有边缘情况,只需要把80%的主流场景稳定切分出来。
聚类辅助主要用于探索未知场景。把每条样本的静态要素、动态要素编码成特征向量,用无监督聚类(比如DBSCAN或者GMM)跑一遍,看哪些聚类簇样本量比较大、且没有被现有规则覆盖,就可以考虑把它们定义成新场景,再请标注员人工校验和命名。这样既保住了可解释性,又能在数据分布变化时自动发现新类型。整个过程记得定期重跑,因为路测数据的地域、时段分布会变化,场景结构也会跟着变,标签体系需要跟着数据一起迭代。
3. 实操:从场景划分到评估落地
3.1 评估管线整体设计
先看整体数据流。一条离线和在线两用的评估管线可以这样设计:原始路测日志和数据集进入场景切分模块,按时间窗和语义条件切成片段,打上场景标签;片段再进入特征提取模块,把每个目标的历史轨迹、地图要素、上下文编码成模型输入;模型推理得到多模态预测轨迹;最后进入指标计算模块,按场景标签分组计算精度、安全、实时性指标;结果落到评估报告和看板。
伪代码大概是这样的:
def scenario_driven_eval(pred_model, dataset, scenario_rules): scenarios = [] for seq in dataset: labels = extract_scene_labels(seq, scenario_rules) for label in labels: inputs = build_model_input(seq, label.track) preds = pred_model.predict(inputs) scenarios.append({ "label": label.name, "preds": preds, "gt": label.gt_traj, "meta": seq.meta }) report = compute_metrics_by_scenario(scenarios) return report这条管线有几个设计要点。第一,场景切分和模型推理解耦。切分模块只负责贴标签,不影响后来模型输入的构建,这样换模型版本时不用重跑切分。第二,结果输出要保留原始片段ID。每个评估样本要能追溯到是哪一条日志、哪一帧、哪个目标,方便后续可视化回查。第三,统计模块和展示模块要分开,统计只输出原始数据,展示端负责画图,否则每次想加一个视图都要改统计逻辑。
在线监控是另一条线。跟离线评估不同,在线监控跑在影子模式或车队回传数据上,不可能每一条都做全量标注和推理,所以只做采样和快速统计。通常会按场景标签做分层采样,比如每个场景桶保持最小样本量,其余样本按固定比例抽,然后跑轻量指标(minFDE、穿透率)。在线和离线共用一个场景标签字典,这是两条管线能对得上话的前提。
3.2 场景数据预处理与质量控制
场景评估里最容易出问题的环节,其实不是指标计算,而是数据预处理。多发的问题有三类:轨迹跳点、目标ID切换、地图错位。
轨迹跳点指的是目标在某几帧里位置突然不连续,一般是因为感知丢失后重新关联,或者不同传感器融合时出现异常。跳点样本如果不处理,计算出来的minADE会异常偏大,还会污染场景标签,比如把一段直行的目标误判成换道。我建议在场景切分前先跑一遍清洗规则:单帧位移超过物理上限的直接标记,比如车辆在0.1秒内位移超过3米就不合理;连续跳点超过3帧以上的,整个片段丢弃。
目标ID切换比跳点更隐蔽。一辆车在感知里从ID 42变成ID 87,它的历史轨迹会断成两截。如果评估模块把它当作两条目标轨迹,就会多出两个虚拟的"静止到爆发"样本,把指标搅浑。这种问题没有完全自动的解法,我的做法是加一个ID连续性检查和最小轨迹长度约束,低于阈值的轨迹不进入评估。同时保留原始ID变更记录,方便抽样排查。
地图错位在室内停车场、隧道这些定位信号不好的地方尤其常见。场景标签依赖于地图要素,地图一旦错位,标签就偏了。处理办法是建立地图和感知的可视化校验流程,批量检查坐标系与高清地图是否对齐,错位比例超过阈值的路段,评估时剔除并标记。另一个细节是时间对齐。模型输入的历史轨迹、当前帧的地图、未来的真值轨迹,必须保证使用同一套时间戳体系。这个问题在处理异步传感器数据时特别烦,比如视觉是30帧、雷达是20帧,融合后的时间戳对不齐,评估结果会凭空多出几十毫秒的误差。我的经验是统一用融合输出后的同步主时钟,每帧数据落盘时都带时间戳,评估端用插值对齐到统一频率。
3.3 分层指标计算与显著性检验
场景分开以后,指标计算有一个关键细节:不能只算点估计,要算置信区间。原因是很多场景的样本量其实很小。比如"无保护左转"场景,你可能只有几百个片段,一个版本的minFDE变化了0.1米,有可能是真实改进,也有可能是采样噪声。没有置信区间,这个数字没法用来做决策。
我一般在脚本里用Bootstrap自助法来算。对每个场景桶,有放回地重采样N次,比如1000次,每次计算目标指标,然后取2.5%和97.5%分位数作为置信区间。Python代码很简单:
import numpy as np def bootstrap_ci(errors, n_iter=1000, alpha=0.05): rng = np.random.default_rng(0) means = np.array([ rng.choice(errors, size=len(errors), replace=True).mean() for _ in range(n_iter) ]) lo = np.percentile(means, 100 * alpha / 2) hi = np.percentile(means, 100 * (1 - alpha / 2)) return lo, hi跑完Bootstrap你会发现一个反直觉的规律:样本量越小的场景,置信区间越宽。如果你只报点估计,模型版本A和B看起来差了0.15米,但两个置信区间重叠得很厉害,那就说明这个差异统计上不显著。我的经验是,做模型迭代决策时,宁可多看两个星期的评测报告,也不要因为一个置信区间重叠的数字就回滚版本。
另一个显著性问题来自多场景比较时的多重检验。场景一多,每个都要做显著性检验,假阳性的概率会整体抬高。这里有一些常规修正办法,比如Bonferroni校正,但我觉得这比较学院派。工程上更有效的思路是先看安全指标,再谈精度指标——安全指标没过红线,精度指标显著不显著都无所谓,不用浪费算力去管那些假阳性。
报告展示也有一点经验。全场景指标放在一张大表里,大家很容易盯着排名最高的那个看。我更建议做成"场景矩阵加关键场景雷达图"的组合:场景矩阵展示每个场景桶的核心指标和置信区间,雷达图只展示三到四个最高风险场景,任何人在会上都能一眼看出问题出在哪。我个人很反感那种把所有场景曲线叠在一张图里的做法,信息密度太高,反而掩盖了短板。
4. 真实项目里踩过的坑与排查方法
4.1 场景样本量不足导致的假阳性
第一个坑就是场景切分过细。早期我们把"左转"继续拆成"有保护左转""无保护左转""左转后汇入"等等,标签体系非常好看,但实际路段数据里"无保护左转"每个月能收集到的片段可能就一两百条。模型一更新,这个桶的指标波动剧烈,根本分不清是模型变化还是数据噪声。
排查方法很简单,先看每个场景桶的样本量和置信区间宽度,样本量低于阈值的场景桶不参与版本对比。阈值怎么定?我一般要求每个桶至少500个评估片段,置信区间宽度大约在指标真实水平的正负10%以内,如果达不到就持续收集,不急着下结论。
如果项目周期紧,还有一个权宜之计:相似场景合并。比如"无保护左转"和"右转汇入"在动态交互特征上相似,可以先在"交叉口转向"的大类下评估,等样本量够了再细化。这个操作的前提是合并产生的评估损失可接受,毕竟分类粗了,短板定位就没那么准,但总比用噪声数据得出错误结论强。
4.2 场景标签与模型输入不一致
第二个坑是标签和模型实际接收的信息不一致。举个例子,我们用高清地图的语义信息来定义"车辆正在换道"这一场景,标签是"换道"。但模型本身是基于视觉的纯感知方法,它在逻辑上根本没有收到"换道"这个语义标签,它的"换道"概念是从图像特征里隐式学到的。于是出现了一种尴尬局面:评估端说模型在换道场景上miss率很高,研发同学打开输入一看,模型压根不知道地图语义,目标在最远端的画面里还被遮挡着。
这类问题的排查手段是抽样可视化。把评估端挑出来的困难样本对应的历史帧画面、感知结果、地图要素全部可视化,人工看一遍,基本能立刻发现标签和模型输入之间的错位。解决的方向有两个:要么让评估端把场景标签降维成模型能够感知到的特征,比如用相对位置、相对速度定义场景;要么让模型接入与评估统一的语义输入,比如在模型输入端加入轻量级地图语义嵌入。我建议优先考虑前者,因为改动模型往往成本和风险更高。
还有一类不一致是时间上的。场景标签是在评估阶段用全量未来信息打上的,比如"这个目标接下来3秒会左转"。如果用这种"上帝视角"来定义评估场景,模型的输入里根本没有未来信息,那评估结果在设计实验时就会绕晕。正确的做法是:场景定义可以依赖未来标签,但要在报告中明确标注;当你要评估模型实时预测能力时,场景标签必须只用当前帧和过去帧的信息来打。也就是说,标签要分成"前瞻型"和"当前型"两套,各用各的,不能混。
4.3 指标冲突时怎么取舍
第三个坑是关于指标冲突的。我们曾经遇到过一个典型case:某个版本的模型在"匝道汇入"场景里,minFDE下降了0.2米,精度提升了,但穿透率(预测轨迹与自车或其他目标的侵入距离比例)反而上升了0.5个百分点。从平均值看是进步,从安全角度看是退步。
这种冲突出现时,我的原则是安全指标优先。精度指标是模型能力的一种度量,而安全指标直接关联碰撞风险,是需要拉红线的。判断是否发布一个版本,先看穿透率、碰撞率、最小距离这些安全指标有没有恶化;如果在安全指标可控的前提下精度提升了,那才是一个值得发的好版本。
具体操作上,我会在评估报告里设置一个"场景安全看板":每个高风险场景都有一行安全指标,任何版本要过评估,这一行的值不能超过设定的阈值。阈值怎么定没有通用答案,一般结合车辆动力学和安全分析,比如最大侵入距离不得超过0.5米、碰撞率不得超过0.1%。这些数字在不同公司差别很大,建议先跑几次基线版本,取三个月内统计值的中位数加上一个余量,作为初始红线,再根据实车测试修正。记录在案的红线比拍脑袋定的数字更有说服力,也更容易被上下游团队接受。
4.4 场景驱动的评估结果如何反哺训练
最后聊一个稍高阶的话题:场景驱动评估结果不只是用来发报告,它的最大价值是反哺训练过程,形成闭环。最常见的做法是基于场景的困难样本挖掘。
操作逻辑很简单:在每次评估后,把场景桶里指标最差的那部分样本找出来,做困难样本重采样,提高它们在训练数据集里的出现概率。举个例子,"行人横穿"场景原来在训练数据里占3%,评估发现它在预测误差和miss率上都是重灾区,那就可以把训练采样权重上调到8%,跑几个epoch再看评测。这个做法相当有效,尤其是对于长尾场景,因为长尾场景天然难收集、难上量,直接反向加大采样权重是成本最低的改善路径。
第二种做法是场景自适应的模型微调。在主干模型上挂几个轻量化的场景分支,评估时动态选择分支。比如高速巡航场景走通用分支,路口场景走路口分支。这个做法比较重,不是每个团队都有资源做,但如果你想做,评估框架就需要额外输出"每个场景桶最优模型版本"的索引,这在后续做场景路由时很有用。
还有个细节:评估结果反哺训练时,别只看单一指标。你可以把每个场景桶的安全指标也当成训练奖励权重的一部分。比如在模仿学习里,对安全指标差的场景样本赋予更高loss权重,让模型更关注这些"危险样本"。这一步讲起来简单,但在代码实现里需要对loss口和数据采样器做组合改造,工作量不小,值得单独开一个任务。
我在实际项目里推场景化评估,前后改了差不多三轮才稳定下来。第一版场景分得太细,几十个标签,样本量崩了,统计上全是噪声;第二版砍成七八个大类,问题又浮不出来。最后稳定下来的是这套"主干标签粗粒度加细粒度特征描述辅助"的方案,评估报告里大类和细类分开展示。这里面的小心得是:宁可指标粗糙一点,也要保证每个场景桶里有足够、可信的样本再看趋势,不然你优化了三个版本,看到的可能都是噪声在跳舞。还有一点很重要,场景库要跟着数据一起迭代,别把一套标签体系当成一劳永逸的标准。那些排行榜上平均数字靠前的模型,一旦把场景拆开,往往能看到完全不同的另一幅画面——这正是我们做场景驱动评估的原因。