车辆跟随
本例使用车辆跟随轨迹学习纵向运动模型,再构建 MPC 或残差 PID 控制器。通过仿真比较车距跟踪、近距离风险和平顺性。
1. 任务背景与目标
车距、相对速度与执行器响应
车辆跟随控制通过调节本车纵向加速度,使本车随前车加减速,同时保持合适的车距。期望距离采用“固定间距+时间间隔×本车速度”:速度越高,期望留出的距离越大。
相同车距下,前车正在加速与正在减速需要不同的动作,因此状态必须包含相对速度。加速度指令经过执行器后也不会瞬间成为实际加速度;本例用一阶滞后描述这一过程,并把实际加速度纳入状态,帮助模型预测制动或加速的持续影响。
控制目标同时涉及跟踪、近距离风险和平顺性。快速加减速可能改善车距,却让乘坐体验变差,因此奖励中加入加加速度代价。前车运动是外部条件,不能由本车策略修改;数据和模型要单独保留前车序列。
跟随目标与评价量
本车跟随前车,通过加速度指令控制实际加速度,目标是维持恒定时距对应的期望车距:
图:任务对象及其作用与反馈关系。实线表示物理作用,虚线表示观测与控制信号。
d_des = 5 + 1.5 × v_ego
gap_error = d_des - gap # 正值表示跟得太近
v_rel = v_lead - v_ego当前仿真动作范围为 [-3, 2] m/s²,控制周期 0.2 s,执行器有时间常数 0.4 s 的一阶滞后。 这些边界是示例设定,不能泛化成所有车辆的物理能力。状态为 ego = [gap_error, v_rel, v_ego, a_ego],保留实际加速度以表达执行器状态。
examples/vehicle_acc/reward.py 使用以下代价:
jerk = (a_ego_next - a_ego) / 0.2
gap_next = 5 + 1.5 × v_ego_next - gap_error_next
r_t = -0.10 × gap_error_next²
-0.05 × jerk²
-20 × max(4 - gap_next, 0)目标误差为 0,回报越接近 0 越好。 4 m 是奖励开始重罚的车距阈值,不是控制器保证满足的硬约束。 安全项是低于阈值后的线性软惩罚,不是二值罚,也没有由此自动获得防碰撞保证。 动作裁剪、车距软惩罚、仿真器的车距下限是三类不同机制。
2. 数据与准备
每个 0.2 秒时刻记录本车状态、发出的加速度指令和前车运动。实际加速度用于识别执行器滞后,前车速度和加速度用于解释相对运动;动作之后的 next_ego 是预测标签。轨迹应覆盖巡航、加速、减速和恢复跟随。
默认 data/vehicle_acc.npz 为 40 条轨迹 × 300 步,共 12000 个转移,每条 60 s。
| Key | 标准 Shape | 含义 |
|---|---|---|
ego | [12000, 4] | 当前车距误差、相对速度、本车速度、实际加速度 |
action | [12000, 1] | 加速度指令 |
lead | [12000, 2] | 前车速度、前车加速度 |
next_ego | [12000, 4] | 下一步本车状态 |
index | [40] | 轨迹结束下标(exclusive) |
采集使用线性跟车律 a = -k_d × gap_error + k_v × v_rel, 每条轨迹重抽增益,并在闭环动作上叠加抖动及短时偏置,随后裁剪。 这里不是把执行器替换成固定开环指令的采样方式。 前车序列由巡航、缓加、缓减和较强减速片段随机拼接。
合成数据包含接近车距下限的工况,训练后应单独检查这些片段的预测误差。 已知的安全代价可以直接写入奖励;未知动力学在稀疏危险区域的可信度仍需独立验证。
数据可直接训练,无需重新派生下一状态。已有文件直接复用; 只有缺失且允许仿真采集时,才从源码仓库根目录运行:
cd examples/vehicle_acc
python prepare_data.py此命令会调用仿真并覆盖输出 NPZ。 纯离线任务应取得已有文件,跳过采集。
数据生成参数
| 参数 | 默认值 |
|---|---|
--trajectories | 40 |
--steps | 300 |
--seed | 20260822 |
3. 建模与配置
下面先展示 examples/vehicle_acc/config.yaml 的配置,再结合本任务逐块解释。训练命令使用同一文件。
name: vehicle_acc
version: '2.0'
graph:
nodes:
action:
inputs: [ego]
network:
backbone: mlp
hidden_dims: [128, 128]
activation: leakyrelu
output_dist: TanhNormal
delta_ego:
inputs: [ego, action, lead]
network:
backbone: mlp
hidden_dims: [256, 256]
activation: leakyrelu
output_dist: TanhNormal
next_ego:
inputs: [ego, delta_ego]
function: builtin.delta_add
transitions: auto
columns:
ego: [gap_error, v_rel, v_ego, a_ego]
action:
- {name: a_cmd, min: -3.0, max: 2.0}
lead: [v_lead, a_lead]
data: {train_ratio: 0.75, batch_size: 256}
training:
device: auto
stages:
- name: venv
algorithm: venv.revive_p
hyperparameters:
epochs: 500
bc:
optimizer: {lr: 0.0003, weight_decay: 1.0e-06}
grad_clip: 50
loss: nll
adversarial:
start_epoch: 0
enabled: true
ood: {d_lr: 0.0006}
rollout: {horizon: 40, batch_size: 512}
validation:
rollout: {enabled: true, interval: 10, horizon: 40, num_trajectories: 8}
selection: {metric: val/rollout/mae, mode: min}
- name: policy
algorithm: controller.mpc
inherit_from: venv
hyperparameters:
method: cem
horizon: 20
num_samples: 128
num_elites: 16
num_iterations: 4
temperature: 1.0
gamma: 0.99
deterministic: true
use_policy_prior: false
warm_start: false
future_exogenous_keys: [lead]
seed: 0
action_bounds:
action: [-3.0, 2.0]
tune: {enabled: true, mode: validate_only, fast_eval_segments: 8, full_eval_segments: 16, eval_horizon: 120,
seed: 42}
policy_nodes: [action]
reward: {path: reward.py, function: get_reward}
output:
save_freq: 20
tensorboard: true
onnx: {required: true, atol: 0.0001}配置入口
| 配置 | 世界模型阶段 | 控制器阶段 | 用途与运行规模 |
|---|---|---|---|
examples/vehicle_acc/config.yaml | venv.revive_p | controller.mpc | 默认完整路线;世界模型 500 epoch,MPC 验证与导出 |
examples/vehicle_acc/configs/revive_p_residual_pid.yaml | venv.revive_p | controller.residual_pid | 对照路线;世界模型 500 epoch,最多 16 个增益组合 |
examples/vehicle_acc/config.min.yaml | venv.revive_p | controller.mpc | 最小声明,仍有两阶段;未写参数由默认层补齐 |
两条完整路线的图、数据配置和世界模型阶段相同,但单独训练不会自动复用同一模型文件。 MPC 和残差 PID 的控制结构、优化方式和未来信息接口不同,因此不是只改变“前瞻能力”的对照。
config.min.yaml 未显式保留完整配置的规划窗、未来前车键、调参验证设置和导出容差, 不能视为主配置方案的等价缩写,也不是只训练世界模型。主线使用 config.yaml, 默认值可通过 revive validate --show-defaults 检查。
配置逐块讲解
graph:本车、动作与前车
action ← ego 使用两层 [128, 128] MLP; delta_ego ← ego, action, lead 使用两层 [256, 256] MLP, 两者为 leakyrelu、TanhNormal。next_ego 用 builtin.delta_add 合成, transitions: auto 建立 ego ← next_ego。
lead 是外生列,模型内推演从数据回放。缺失前车输入会影响相对运动建模, 但不能把所有建模问题都归为未见过急刹。gap_error 观测只在跟得过远的一侧截断; 日志反推车距在该截断区间不能恢复任意真实距离,接近阈值区间的安全统计则保留所需信息。
data:切分与批量
train_ratio: 0.75、batch_size: 256,其余采用默认层; 保持轨迹边界,从解析配置核对实际划分。稀有危险片段的覆盖不能仅由总样本量证明。
training.stages[venv]:世界模型
venv.revive_p 训练 500 epoch,监督学习率 3e-4、nll 损失、梯度裁剪 50。 从第 0 轮启用对抗,horizon 40、batch_size 512; 每 10 轮评估 8 个长度 40 的窗口,按 val/rollout/mae 最小值选模。 40 步对应 8 s,不代表模型已经准确覆盖所有减速/恢复过程或安全边界。
training.stages[policy]:MPC 与残差 PID
inherit_from: venv 消费选出的世界模型。 MPC 使用 CEM,horizon 20(4 s)、128 个候选、16 个精英、4 次迭代; warm_start: false、use_policy_prior: false 保留当前行为,不使用动作网络作 CEM 采样先验。
future_exogenous_keys: [lead] 在当前测试中获得的是仿真前车未来轨迹真值, 不是实际 ACC 可直接获得的信息,也不是已经验证的前车预测器。 它是理想信息条件下的基准设置,不能把分数直接称为实际道路上的性能上界。
残差 PID 读取 ego[0],目标 0,因此误差为 -gap_error:过近时比例动作趋向减速。 本配置没有加载原线性控制器作为 baseline,而是零基线,残差直接形成加速度指令。 误差微分包含本车加速度的影响,调节参数时应结合车距与相对速度一起观察。
当前 ki: 0、delta_max: 3、rate_limit: 2、i_max: 1; sample_dt_h: 1 按一个控制步解释,不是一小时的仿真周期。 kp 网格为 [0.5, 1, 2, 4],kd 网格为 [2.5, 5, 10, 20]。 动作变化率限制与奖励中的实际加加速度代价不是同一个量,不能互相替代。
控制器评估规模
| 参数 | config.yaml | configs/revive_p_residual_pid.yaml |
|---|---|---|
tune.mode | validate_only | two_stage |
tune.max_trials | — | 16 |
tune.top_k | — | 4 |
tune.fast_eval_segments | 8 | 20 |
tune.full_eval_segments | 16 | 40 |
tune.eval_horizon | 120 | 120 |
MPC 不搜索固定增益,只做模型内验证及导出;PID 快速排序后完整评估前 4 个候选。 120 步是 24 s 的模型内控制窗口;40 个评估片段不等于完整覆盖全部 40 条日志中的每个危险事件。 — 表示没有显式配置该字段,不表示无限预算。 reward.path 相对 YAML 所在目录解析,两种完整路线共用本目录奖励文件。
output:输出文件与导出
默认 best_only 保留最优模型及同轮续训态,tensorboard: true 记录训练指标;save_freq 仅在 legacy 模式生效。 onnx.required: true、 onnx.atol: 1e-4 沿用当前设置。导出校验检查执行一致性, 既不提供安全证明,也不等于实际传感器或控制误差达标。
4. 训练与选模
以下命令在 examples/vehicle_acc 目录执行,数据已存在:
revive validate --config config.yaml --data data/vehicle_acc.npz
revive train --config config.yaml --train-data data/vehicle_acc.npz \
--run-id full_mpc --log-dir logs --seed 42残差 PID 使用以下替代路线:
revive train --config configs/revive_p_residual_pid.yaml --train-data data/vehicle_acc.npz \
--run-id full_pid --log-dir logs --seed 42查看 logs/<run_id>/report.md、config.resolved.yaml、世界模型验证指标、 控制器候选排序及导出文件 models/policy.pt。这一阶段只完成离线构建,不是安全验收。
仅检查流程时可加 --profile smoke 并换新 run ID。 当前 smoke 不压缩控制器的 CEM 采样数、tune 候选数或验证窗口,仍可能耗时; 正式比较控制效果时使用完整训练配置;每次训练使用独立 run ID。
一键路线与文件
bash run_all.sh full_mpc
bash run_all.sh full_pid configs/revive_p_residual_pid.yaml一键脚本替代分步操作,默认仍为 config.yaml、训练 seed 42。 复用已有数据,缺失或设置 FORCE_PREPARE 时重新采集。 它评固定基线一次、候选两次,最后一次写 logs/<run_id>/flagship.json; 相同协议的重复候选评估不是两组新增独立样本。一键命令不是纯离线命令。 主要脚本为 examples/vehicle_acc/prepare_data.py、examples/vehicle_acc/evaluate.py、examples/vehicle_acc/run_all.sh。
5. 模型使用与评估
固定候选后分别运行:
python evaluate.py --dataset --json
python evaluate.py --baseline --episodes 20 --steps 300 --seed 20260901 --json
python evaluate.py --model logs/full_mpc/models/policy.pt \
--episodes 20 --steps 300 --seed 20260901 --jsonPID 对应 logs/full_pid/models/policy.pt。--dataset、--baseline、--model 只能选一个。数据模式不推进仿真,直接从 NPZ 复算,边界由 index 决定; 逐步均值使用第一个回合长度归一,自有变长轨迹需重新核对统计方法。
评估协议
| 参数 | 默认值 |
|---|---|
--episodes | 20 |
--steps | 300 |
--seed | 20260901 |
基线与候选使用同一组初态和前车曲线(起始 seed 加回合序号),每次测试重置控制器会话。 固定基线使用采集增益区间中点,不加抖动和偏置激励。 MPC 额外获得未来真值,比较中必须保留这一信息差异说明。
| 指标 | 含义 |
|---|---|
real_return_mean/std | 回合奖励求和后的跨集均值/标准差 |
real_reward_mean_per_step | 平均每步奖励 |
real_min_gap_m | 各回合最小车距的均值,不是全程平均车距 |
real_worst_gap_m | 所有回合中最小的车距 |
real_unsafe_fraction | 车距低于 4 m 的步数比例 |
real_jerk_rms | 先计算每回合实际加加速度 RMS,再跨回合平均 |
real_collisions | 触及仿真车距下限的累计步数,不是独立事故次数 |
仿真车距下限为 0.5 m;日志碰撞复算使用 1 mm 容差处理 float32 舍入。 零碰撞、零越界只描述所测工况,不是任意条件下的保证。 框架的 val/rollout/reward_mean 为每步量,val/rollout/return_mean 为整段量; 模型内 120 步与外部 300 步的回报和不能直接对比。
6. 结果与分析
数据包含 40 条、每条 300 步的跟随轨迹。固定基线与学习控制器使用相同的初态和前车曲线,评估 20 集、每集 300 步,起始 seed 为 20260901。
| 指标 | 数据行为 | 固定线性基线 | REVIVE-P + 残差 PID | REVIVE-P + MPC |
|---|---|---|---|---|
| 平均每步奖励 | -1.549 | -0.773 | -0.589 | -0.704 |
| 平均最小车距(m) | 14.61 | 15.51 | 16.62 | 16.85 |
| 最坏车距(m) | 0.50 | 6.23 | 5.66 | 9.06 |
| 车距低于 4 m 的步数占比 | 0.74% | 0 | 0 | 0 |
| 加加速度 RMS | 0.561 | 0.438 | 0.671 | 1.506 |
| 触及仿真车距下限的步数 | 24 | 0 | 0 | 0 |
残差 PID 的平均奖励较好,MPC 的最坏车距更大,固定线性基线的加加速度最小。候选改善综合回报的同时也增加了动作变化,说明车距跟踪与平顺性需要分别分析。
本例的奖励对低于 4 m 的车距施加软惩罚。表中的零越界描述所测工况;扩展场景时,应单独检查急减速、感知延迟和前车预测误差。数据采集与评估的增益和激励不同,控制器比较以相同测试条件下的结果为准。
7. 迁移到实际业务
不要直接把本示例策略部署到实车。真实任务需要重新定义观测、执行器、预测接口、约束与失效处理, 并由相应的安全验证流程评估。本教程只提供合成对象上的研发流程。
前车更强减速是否可避免还取决于初始距离、速度和延迟,不能只根据制动幅值给出结论。 实际前车预测存在误差,应把“未来真值”与“决策时可用预测”分开评估; 现有软惩罚也不能替代独立安全机制。
仿真实现的数学测试见 tests/unit/test_vehicle_acc_env.py, 工程回归见 tests/perf/baselines.json,两者均不是道路安全认证。
阅读任务页约定,区分数据准备、训练验证和独立评估。