跳转到内容

无人机建模 ​

本例使用飞行日志训练无人机动力学世界模型,预测给定速度和偏航角速度指令下的后续运动。流程涵盖数据对齐、物理先验与 GRU 建模、多阶段训练及离线递推评估。

1. 任务背景与目标 ​

从飞行指令预测实际运动 ​

无人机接收到速度和偏航角速度指令后,飞控系统、机体姿态和气动响应共同决定实际运动。指令速度与实测速度通常存在延迟和偏差;转向时,姿态变化还会影响不同方向的速度响应。准确的动力学模型可用于给定指令下的轨迹预测和模型仿真。

本例使用 NED 坐标系:北向、东向、向下为三个速度方向,速度单位为 m/s。yaw 描述航向,pitch 和 roll 描述俯仰与横滚,角度单位为弧度。输入是飞控层的目标速度和偏航角速度,不是电机转速或推力。

建模结合物理知识与数据学习。已知坐标和姿态变换由函数完成;可训练的物理先验提供主要响应,GRU 学习未建模的动态与延迟。yaw 使用正弦、余弦表示,并在状态更新后投影回单位圆,避免跨越角度边界时出现数值跳变。

模型任务与使用方式 ​

给定无人机当前速度、姿态和速度/偏航角速度指令,预测后续状态。 将已知特征变换、角度表示和单位圆投影写成解析函数; 将可辨识的物理参数与未知动态残差放在一个可训练网络中。

已有速度和偏航角速度指令作用于无人机,世界模型从指令与状态日志学习动态响应并预测后续状态

图:给定日志指令的飞行动力学建模;世界模型预测不代表新的飞控策略。

本例只训练世界模型,没有 RL、PID 或 FFPID 策略构建阶段,也没有独立飞行仿真器测试入口。 它演示的是“已有数据 → 模型训练与挑选 → 离线递推评测 → 部署与快照检查”, 不能据此宣称已经训练出更好的飞控策略。

评测中的“闭环 rollout”指状态由模型自己递推,指令仍按历史日志逐步回放, 并不根据模型状态重新运行采集控制器。因此更准确地说,它是给定指令序列的动力学评估, 不是新策略与真实对象的反馈控制对照。

2. 数据与准备 ​

飞行数据先统一坐标、单位与采样周期,再把指令和响应对齐到同一时间轴。保留整条飞行轨迹,使用 next_dyn_obs 表示下一采样时刻的监督目标;递推预测时输入当前模型状态和给定指令,不输入未来测量值。

原料 data/raw/full.npz 有 90 条轨迹、151100 条转移,采样周期 0.1 秒。 原始状态为 [fVn, fVe, fVd, yaw, pitch, roll]; 速度是 NED 坐标下的 m/s,角度为弧度。指令为 [tVn, tVe, tVd, yaw_rate],不是电机级动作。

处理后的字段 ​

data/derived/full_sincos_builtin.npz 保留以下字段:

字段形状含义
dyn_obs[151100, 7]速度三维、sin_yaw、cos_yaw、pitch、roll
target[151100, 4]每一步已记录的控制指令
next_dyn_obs[151100, 7]下一步状态标签
yaw_state[151100, 6]下一步状态的角度表示,供训练目标和指标使用
rollout_velocity[151100, 3]下一步速度标签,供位移积分指标使用
index[90]每条轨迹的右开结束下标,int64

其余字段为 float32。完整文件不保存 physics_features、pair_norm 或显式 delta 列; 前两者由函数计算,增量列由框架按转移关系派生。 yaw_state 与 rollout_velocity 是未来标签,不能作为递推时的外部输入。

当前 seed 0 的划分文件记录:

集合轨迹数转移数
训练81138667
development holdout912433

留出轨迹 ID 为 [5, 13, 20, 27, 50, 55, 67, 73, 81],保存在 data/derived/holdout10_split.json。轨迹长度不同;本地原料的长度范围为 254–3838 步。

数据划分与验证用途 ​

准备流程先在全部 90 条轨迹上拟合先验 gain/bias、计算指标尺度和角度分支, 然后才切出 81/9。9 条验证轨迹虽未作为后续网络训练的监督批次, 但参与了前置先验拟合和统计;同时还用于检查点选择。

因此这是 development holdout,不是“全流程从未见过”的封存测试集。 holdout/train 比值可以提示误差差异,不能单独证明过拟合程度或泛化能力。 迁移到新任务时应先划分, 再只用训练集辨识先验和拟合统计,并另外保留最终测试集。

物理约定中的常量 ​

examples/drone_worldmodel/data/contracts/physics_contract.json 记录先验初值、归一化尺度与指标定义:

字段当前值或来源使用边界
prior.gain约 [0.12509, 0.12546, 0.20694]全量最小二乘拟合的参数初值,训练中可更新
prior.bias6 维截距同样不是冻结的物理常数
metric.primary_scales6 维 next_dyn_obs.std(0)用于将不同单位的预测误差转换到可比较尺度
metric.dim_weights[2.25, 2.25, 2.25, 1.0, 1.0, 1.0]人工设置的速度/姿态权重
metric.angle_wrap_low[0, -π, -π]根据全量语料角度范围确定分支
metric.displacement_scales_by_horizon固定 H=1/16/64/200 的三维尺度用于各预测窗口的累计位移误差归一化
residual_std_diagnostic角度表示下的残差统计诊断用;不是当前 sin/cos 网络的输出归一化尺度

网络输入与输出的归一化统计由 NormModule 保存到部署模型中。部署时加载模型中已保存的统计与训练参数。

获取数据与预处理 ​

准备本任务的飞行数据 full.npz,放入 data/raw/。原始业务数据单独提供,不随 SDK 安装包分发;使用数据提供方授权的版本,并核对下述字段。

后续命令均在案例目录执行:

bash
cd examples/drone_worldmodel

已有有效处理数据时直接复用。需要从原料重建时使用以下两个等价入口之一:

bash
bash scripts/prepare_all.sh
bash
python prepare_data.py

两者都按下面顺序运行,不需要执行两遍:

顺序脚本实际输入与输出
01examples/drone_worldmodel/scripts/01_prepare_physics.py读取 raw,拟合先验并写 full_physics.npz、physics_contract.json
02examples/drone_worldmodel/scripts/02_prepare_sincos.py读取 raw 和约定,写 full_sincos.npz
03examples/drone_worldmodel/scripts/03_prepare_builtin.py读取 raw 和约定,写 full_sincos_builtin.npz 与研究用 full_sincos_custom.npz
04examples/drone_worldmodel/scripts/04_split_holdout.py划分 builtin 文件,写 holdout10_train.npz、holdout10_val.npz 与 split JSON

不是每一步都读取紧邻前一步的 NPZ;01 的约定是 02/03 的共同依赖,04 读取 03 的 builtin 输出文件。 直接运行准备入口会重写派生数据和受版本控制的约定,没有 --force 或自动跳过机制。 准备脚本针对本例数据字段和形状实现;使用其他飞行日志时,先完成同样的坐标、单位和时序转换。 跨 NumPy/BLAS 版本运行时,需重新核对数值结果。

04 默认 --seed 0、--holdout 9,按整条轨迹切分; 它检查留出/全量轨迹长度中位数比在 [0.75,1.33] 内,不合格就报错,不会自动重抽。 改变训练 seed 不会自动改变数据 split;改变 split 后须同步所有评测所用文件与实验记录。

3. 建模与配置 ​

下面展示 examples/drone_worldmodel/config.yaml 的核心片段(非完整配置),包括图结构和课程训练字段。完整文件还包含损失、判别器与指标参数;后续命令直接使用完整文件。

yaml
graph:
  transitions: auto
  nodes:
    physics_features:
      inputs: [dyn_obs, target]
      function: drone_sincos_features
      differentiable: true
    delta_dyn_obs:
      inputs: [physics_features, dyn_obs, target]
      network:
        backbone: drone_prior_sincos_gru
        custom_params: {hidden: 256, rnn_layers: 2, bottleneck: 8, blocks: 3}
      output_dist: deterministic
    next_dyn_obs:
      inputs: [dyn_obs, delta_dyn_obs]
      function: builtin.delta_add_project
      function_params:
        project: unit_pair
        dims: [3, 4]
    yaw_state:
      inputs: [next_dyn_obs]
      function: drone_sincos_yaw_state
      differentiable: true
    pair_norm:
      inputs: [next_dyn_obs]
      function: drone_sincos_pair_norm
      differentiable: true
    rollout_velocity:
      inputs: [next_dyn_obs]
      function: drone_rollout_velocity
      differentiable: true
  columns:
    dyn_obs: [fVn, fVe, fVd, sin_yaw, cos_yaw, pitch, roll]
    target: [tVn, tVe, tVd, yaw_rate]
    physics_features: [vN, vE, vD, sin_yaw, cos_yaw, pitch, roll, cmd_vN, cmd_vE, cmd_vD, yaw_rate,
      err_N, err_E, err_D, forward_error, lateral_error]
    delta_dyn_obs: [d_fVn, d_fVe, d_fVd, d_sin, d_cos, d_pitch, d_roll]
    yaw_state: [y_fVn, y_fVe, y_fVd, y_yaw, y_pitch, y_roll]
    pair_norm: [sincos_norm]
    rollout_velocity: [next_fVn, next_fVe, next_fVd]
data: {batch_size: 256, normalization: z_score}
training:
  stages:
  - name: p1_step
    algorithm: venv.revive_p
    hyperparameters:
      epochs: 100
      bc:
        optimizer: {type: adamw, lr: 0.001, weight_decay: 0.0001, scheduler: cosine, scheduler_interval: update,
          eta_min_ratio: 0.05}
        sequence_train: {enabled: true, horizon: 1}
    validation:
      selection: {metric: val/rollout/primary_composite_nmae}
  - name: p2_h16
    algorithm: venv.revive_p
    inherit_from: p1_step
    hyperparameters:
      epochs: 100
      bc:
        optimizer: {type: adamw, lr: 0.001, weight_decay: 0.0001, scheduler: cosine, scheduler_interval: update,
          eta_min_ratio: 0.05}
        sequence_train: {enabled: true, horizon: 16}
    validation:
      selection: {metric: val/rollout/primary_composite_nmae}
  - name: p3_h64
    algorithm: venv.revive_p
    inherit_from: p2_h16
    hyperparameters:
      epochs: 100
      bc:
        optimizer: {type: adamw, lr: 0.0007, weight_decay: 0.0001, scheduler: cosine, scheduler_interval: update,
          eta_min_ratio: 0.05}
        sequence_train: {enabled: true, horizon: 64}
    validation:
      selection: {metric: val/rollout/primary_composite_nmae}
  - name: p4_h200
    algorithm: venv.revive_p
    inherit_from: p3_h64
    hyperparameters:
      epochs: 100
      bc:
        optimizer: {type: adamw, lr: 0.0003, weight_decay: 0.0001, scheduler: cosine, scheduler_interval: update,
          eta_min_ratio: 0.05}
        sequence_train: {enabled: true, horizon: 200}
    validation:
      selection: {metric: val/rollout/primary_composite_nmae}
output:
  checkpoint_policy: best_only
  allow_research_snapshots: true
  plotting: {enabled: false, trend: false}
  onnx: {required: true, export_dtype: float32, atol: 0.001}

配置入口 ​

配置阶段顺序用途与运行规模
examples/drone_worldmodel/config.yamlvenv.revive_p → venv.revive_p → venv.revive_p → venv.revive_p完整物理先验 GRU + 对抗课程,100 + 100 + 100 + 100 epoch
examples/drone_worldmodel/config.bc.yamlvenv.bc → venv.bc → venv.bc → venv.bc同图和监督课程的 BC 对照,100 + 100 + 100 + 100 epoch;不产对抗快照
examples/drone_worldmodel/config.min.yamlvenv.revive_p → venv.revive_p → venv.revive_p → venv.revive_p最小结构定义,仍有四段;未显式指定本例自定义网络、运行规模和完整选模规则,省略项用默认层

完整配置用于训练物理先验 GRU;最小配置用于了解图与阶段声明。 完整配置已随源码提供,不需要先运行配置生成脚本。

计算图 ​

text
dyn_obs(7), target(4) ──→ physics_features(16)          解析特征
physics_features, dyn_obs, target ──→ delta_dyn_obs(7)  可训练先验 + GRU 残差
dyn_obs, delta_dyn_obs ──→ next_dyn_obs(7)              加法 + 单位圆投影
next_dyn_obs ──→ yaw_state(6), pair_norm(1), rollout_velocity(3)
下一时刻:dyn_obs ← next_dyn_obs

6 个节点中只有 delta_dyn_obs 是网络,其余为函数。 yaw 展开为 sin/cos,避免跨角度分支时的 2π 跳变;builtin.delta_add_project 将第 3/4 维投回单位圆。它维护状态表示约束,不保证飞行动力学预测准确。

网络结构 ​

drone_prior_sincos_gru 内含可训练物理先验与 GRU 残差。 先验的 gain/bias 默认 prior_learnable=True,以全量拟合值初始化,并非“不参与学习”。 GRU 为 hidden 256、2 层,bottleneck 8,残差块 3 个; 残差输出头最后一层零初始化,使初始预测从先验出发。 bottleneck 限制了表示容量,但不能据此保证网络不会记忆训练轨迹。

网络只接受明确的状态与指令;未来标签用于损失和指标。 若调整 dropout,使用 network.custom_params.dropout, 本自定义网络尚不支持通用 network.dropout 配置。

四段课程 ​

阶段epochbatchlr训练 horizonood.weightzero_epoch
p1_step10040960.00110.100400
p2_h1610010240.001160.075300
p3_h641005120.0007640.050200
p4_h2001002560.00032000.025100

每段监督序列训练均启用,每 epoch 配置 40 batch,按真实 target 序列递推。 BC 保留相同的监督课程;对抗臂另有 batch 128 的生成推演和 H=40 的判别器语料, 这些与表中的监督 batch/horizon 不是同一参数。

inherit_from 按 p1→p2→p3→p4 连接阶段,继承所选模型,而不是四个独立随机模型。 每段有自己的优化器/调度配置,不能把它理解为完整优化器状态不变的 400 epoch 单阶段续训。 ood.weight 和 zero_epoch 将同一条指向全局 epoch 400 零权重的线性曲线按阶段表达; 不是每段 100 epoch 都独立衰减到零再跳升。

四段验证都用 H=200、9 条 development holdout 的 val/rollout/primary_composite_nmae,方向为 min; 在可比指标下跨段选择部署文件,不默认使用 p4 最后一轮。

配置逐块讲解 ​

配置块当前设置解读
graph函数、可训练先验 GRU、确定性增量输出;7 组列定义与 auto transition明确已知公式、可学习参数、状态表示和外部指令
databatch_size 256、z_score;路径由命令行传入各阶段覆盖监督 batch;显式提供 train/val 两份数据
training.stages四段世界模型;AdamW、按 update 的 cosine、grad_clip 1.0目标为 yaw_state 的 composite,不是控制奖励;没有策略阶段
training.stages[].validationrollout 每 5 epoch、H=200、按轨迹宏平均统一验证设置用于选模;每段训练 horizon 可以不同
training.stages[].hyperparameters.adversarialREVIVE-P 的 OOD 判别器、权重调度与快照BC 对照删除这一块;分数不是自动校准的不确定性概率
outputbest_only、显式允许有上限的研究判别器快照、关闭绘图、ONNX required、float32、atol 0.001只保留最优模型及同轮续训态;REVIVE-P 判别器快照每阶段最多 50 份;导出数值检查不证明模型精度

ONNX 容差 0.001 是现有配置方案,不应声称其他容差必然无法满足; 迁移后应按变量单位与累计误差重新检查,不能用放宽容差掩盖接口错误。

4. 训练与选模 ​

准备完成后,先检查数据与配置:

bash
revive validate --config config.yaml --train-data data/derived/holdout10_train.npz \
  --val-data data/derived/holdout10_val.npz --show-defaults

REVIVE-P 完整训练:

bash
revive train --config config.yaml --train-data data/derived/holdout10_train.npz \
  --val-data data/derived/holdout10_val.npz --run-id drone_adv --log-dir logs --seed 0

BC 对照使用独立 run ID:

bash
revive train --config config.bc.yaml --train-data data/derived/holdout10_train.npz \
  --val-data data/derived/holdout10_val.npz --run-id drone_bc --log-dir logs --seed 0

两条路线使用不同 run ID。首次检查流程时,可以在训练命令后增加 --profile smoke,并使用新的 run ID。

输出文件用途
logs/<run_id>/models/env.pt已选世界模型,部署加载入口
models/model_info.json来源阶段、epoch、指标、状态;需确认运行是否完成
report.md、config.resolved.yaml配置、运行与验证信息
venv/<阶段>/metrics/阶段指标
venv/<阶段>/checkpoints/ood_discriminators/对抗臂启用的判别器与打分快照

BC 对照使用相同的图、数据、监督目标与课程。比较两种训练方法时,保持数据划分、随机种子集合和评估窗口一致,并报告逐轨迹与跨种子的差异。

一键训练 ​

完整训练后离线复算可使用以下替代入口:

bash
bash run_all.sh drone_adv_full config.yaml
bash run_all.sh drone_bc_full config.bc.yaml

默认 run ID 为 repro、配置 config.yaml、训练 seed 0。 SEED 控制训练种子,GPU 通过 CUDA_VISIBLE_DEVICES 选择。 脚本仅检查 train/val 两个文件是否存在来决定跳过准备, 但最终评测还需要 full_sincos_builtin.npz、split JSON 和约定;不能只复制两份训练文件。 评测使用 strict-source,分别执行一次文本复算和一次 JSON 复算, 后者写 logs/<run_id>/flagship.json,不是独立两组数据的验证。

另一个入口 bash train.sh my_run 0 只训练并打印评测提示: 第二个位置参数是 GPU 编号,不是配置;固定使用 config.yaml,训练 seed 默认 0, 不自动准备数据、不实际执行提示中的评测。重复 run ID 可能覆盖 tee 的文本日志, 应主动使用新名字,保留旧输出文件。

5. 模型使用与评估 ​

评估协议 ​

参数或条件当前值
--horizon 默认值200,即 20 秒
约定支持的 horizon1, 16, 64, 200
起点每条轨迹起点,每条仅推演一次
target逐步注入历史指令,不使用新策略生成
状态从测量初态出发,后续由模型递推,不逐步喂真实状态
集合81 条训练与 9 条 development holdout,非独立封存测试

部署加载路径 ​

完成数据准备和 bash train.sh 后,评估默认训练目录 logs/my_run/models/env.pt:

bash
python load_env_rollout.py --horizon 200 --device cpu --strict-source --json

评估前面训练得到的模型:

bash
python load_env_rollout.py --model logs/drone_adv/models/env.pt \
  --horizon 200 --device cpu --strict-source --json
python load_env_rollout.py --model logs/drone_bc/models/env.pt \
  --horizon 200 --device cpu --strict-source --json

脚本通过公开 load_env 读取图与归一化,并声明 env.graph.set_static_vars({"target"}),按步注入外部指令。 自定义组件须以训练时的包限定名注册;网络导入顺序为 drone_prior 后 drone_prior_sincos。 函数、网络及其 lib 数学模块仍需要可用,不能把 env.pt 当成完全无代码依赖的文件。

本地训练输出使用 --strict-source 校验自定义组件来源。若校验失败,核对模型与组件版本后再评估。评估需要处理后的全量数据、split 文件及物理参数文件。

--json 输出以下指标:

字段含义
horizon每条轨迹的预测步数
holdout_trajectories开发验证轨迹数
holdout_composite开发验证轨迹的综合预测误差
train_composite训练轨迹的综合预测误差
holdout_train_ratio验证误差与训练误差的比值

逐轨迹诊断 ​

需要定位误差较大的飞行轨迹时,可以运行:

bash
python load_and_evaluate.py --model logs/drone_adv/models/env.pt \
  --horizon 200 --device cpu --per-trajectory

该辅助脚本重建 sincos_builtin 图并计算逐轨迹指标,适用于本例固定结构;日常评估使用前面的部署加载入口。

指标定义 ​

text
composite = 0.65 × 逐维加权 NMAE + 0.35 × 积分位移 NMAE

在 6 维角度状态上计算,姿态误差按圆周折返; 速度三维权重 2.25、姿态三维权重 1.0。先计算每条轨迹的指标,再对轨迹宏平均。 位移项是速度预测误差乘 dt 后的累计积分,按步与方向归一化平均, 不是只取末端误差,也不是直接对独立位置传感器求 MAE。

lib/metric.py 从约定读取尺度、维度权重和 dt,但角度/速度切片与混合系数 还有模块常量;修改约定时要同步核对训练目标、验证配置与离线公式,不能假定全部自动跟随。 各训练阶段使用各自 horizon 的位移尺度,验证与终读使用 H=200 的尺度; 公式相同不等于训练损失数值可与验证值直接比较。

部署导出 ​

如需导出自己的已训练模型,在案例目录执行:

bash
revive export --artifact logs/drone_adv/models/env.pt --project .

GRU 部署是带显式循环状态的 bundle。按导出 manifest 的 init/step 接口初始化并传回状态, 开始新轨迹时重置隐状态;同一轨迹内按步延续隐状态,不同轨迹之间分别维护。 参考评测从轨迹起点初始化,没有提供实测历史预热的 CLI。 完整协议见使用与部署模型。

6. 结果与分析 ​

评估窗口为 200 步,即 20 秒。每条轨迹从测量初态出发,按给定指令序列递推预测;使用 81 条训练轨迹和 9 条开发验证轨迹。

项目数值
选中阶段 / epochp3_h64 / 24
选模 composite0.0813673884
部署加载路径的验证 composite0.081365
重建图路径的验证 composite0.081367
训练轨迹 composite0.020416
验证 / 训练 composite约 3.99

以下为历史参考实验,模型权重不随安装包交付,新训练结果应单独验收。两种加载路径的验证指标接近,可用于核对模型加载与归一化是否一致。验证误差高于训练误差,应进一步查看逐轨迹、逐速度方向和逐姿态维度的误差,定位模型较难预测的工况。

composite 同时考虑状态误差与速度积分得到的位移误差。较低的单步误差不一定意味着长时位移偏差更小,因此需要并排检查 H=1、16、64、200 的预测结果。

开发验证轨迹参与模型选择,且数据准备中的先验拟合使用了全量轨迹。迁移到新飞行数据时,先划分训练、验证与最终测试轨迹,再拟合先验和统计量,以评估未参与建模的飞行条件。

7. 迁移到实际业务 ​

迁移到其他无人机时,先核对坐标系、速度和角速度单位、姿态表示及指令的时间对齐。物理先验参数应只从训练数据辨识,并为不同飞行工况保留独立测试轨迹。

部署时按轨迹管理循环状态,并分别检查单步与长时预测误差。本例仅提供动力学建模;接入飞控前还需单独验证控制闭环、运行约束与异常处理。

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