跳转到内容

车辆跟随 ​

本例使用车辆跟随轨迹学习纵向运动模型,再构建 MPC 或残差 PID 控制器。通过仿真比较车距跟踪、近距离风险和平顺性。

1. 任务背景与目标 ​

车距、相对速度与执行器响应 ​

车辆跟随控制通过调节本车纵向加速度,使本车随前车加减速,同时保持合适的车距。期望距离采用“固定间距+时间间隔×本车速度”:速度越高,期望留出的距离越大。

相同车距下,前车正在加速与正在减速需要不同的动作,因此状态必须包含相对速度。加速度指令经过执行器后也不会瞬间成为实际加速度;本例用一阶滞后描述这一过程,并把实际加速度纳入状态,帮助模型预测制动或加速的持续影响。

控制目标同时涉及跟踪、近距离风险和平顺性。快速加减速可能改善车距,却让乘坐体验变差,因此奖励中加入加加速度代价。前车运动是外部条件,不能由本车策略修改;数据和模型要单独保留前车序列。

跟随目标与评价量 ​

本车跟随前车,通过加速度指令控制实际加速度,目标是维持恒定时距对应的期望车距:

前车速度变化影响车距和相对速度,本车执行加速度指令,并根据跟随状态调整下一步动作

图:任务对象及其作用与反馈关系。实线表示物理作用,虚线表示观测与控制信号。

text
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 使用以下代价:

text
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, 每条轨迹重抽增益,并在闭环动作上叠加抖动及短时偏置,随后裁剪。 这里不是把执行器替换成固定开环指令的采样方式。 前车序列由巡航、缓加、缓减和较强减速片段随机拼接。

合成数据包含接近车距下限的工况,训练后应单独检查这些片段的预测误差。 已知的安全代价可以直接写入奖励;未知动力学在稀疏危险区域的可信度仍需独立验证。

数据可直接训练,无需重新派生下一状态。已有文件直接复用; 只有缺失且允许仿真采集时,才从源码仓库根目录运行:

bash
cd examples/vehicle_acc
python prepare_data.py

此命令会调用仿真并覆盖输出 NPZ。 纯离线任务应取得已有文件,跳过采集。

数据生成参数 ​

参数默认值
--trajectories40
--steps300
--seed20260822

3. 建模与配置 ​

下面先展示 examples/vehicle_acc/config.yaml 的配置,再结合本任务逐块解释。训练命令使用同一文件。

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.yamlvenv.revive_pcontroller.mpc默认完整路线;世界模型 500 epoch,MPC 验证与导出
examples/vehicle_acc/configs/revive_p_residual_pid.yamlvenv.revive_pcontroller.residual_pid对照路线;世界模型 500 epoch,最多 16 个增益组合
examples/vehicle_acc/config.min.yamlvenv.revive_pcontroller.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.yamlconfigs/revive_p_residual_pid.yaml
tune.modevalidate_onlytwo_stage
tune.max_trials—16
tune.top_k—4
tune.fast_eval_segments820
tune.full_eval_segments1640
tune.eval_horizon120120

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 目录执行,数据已存在:

bash
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 使用以下替代路线:

bash
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
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. 模型使用与评估 ​

固定候选后分别运行:

bash
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 --json

PID 对应 logs/full_pid/models/policy.pt。--dataset、--baseline、--model 只能选一个。数据模式不推进仿真,直接从 NPZ 复算,边界由 index 决定; 逐步均值使用第一个回合长度归一,自有变长轨迹需重新核对统计方法。

评估协议 ​

参数默认值
--episodes20
--steps300
--seed20260901

基线与候选使用同一组初态和前车曲线(起始 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 + 残差 PIDREVIVE-P + MPC
平均每步奖励-1.549-0.773-0.589-0.704
平均最小车距(m)14.6115.5116.6216.85
最坏车距(m)0.506.235.669.06
车距低于 4 m 的步数占比0.74%000
加加速度 RMS0.5610.4380.6711.506
触及仿真车距下限的步数24000

残差 PID 的平均奖励较好,MPC 的最坏车距更大,固定线性基线的加加速度最小。候选改善综合回报的同时也增加了动作变化,说明车距跟踪与平顺性需要分别分析。

本例的奖励对低于 4 m 的车距施加软惩罚。表中的零越界描述所测工况;扩展场景时,应单独检查急减速、感知延迟和前车预测误差。数据采集与评估的增益和激励不同,控制器比较以相同测试条件下的结果为准。

7. 迁移到实际业务 ​

不要直接把本示例策略部署到实车。真实任务需要重新定义观测、执行器、预测接口、约束与失效处理, 并由相应的安全验证流程评估。本教程只提供合成对象上的研发流程。

前车更强减速是否可避免还取决于初始距离、速度和延迟,不能只根据制动幅值给出结论。 实际前车预测存在误差,应把“未来真值”与“决策时可用预测”分开评估; 现有软惩罚也不能替代独立安全机制。

仿真实现的数学测试见 tests/unit/test_vehicle_acc_env.py, 工程回归见 tests/perf/baselines.json,两者均不是道路安全认证。

阅读任务页约定,区分数据准备、训练验证和独立评估。