无人机建模
本例使用飞行日志训练无人机动力学世界模型,预测给定速度和偏航角速度指令下的后续运动。流程涵盖数据对齐、物理先验与 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 的划分文件记录:
| 集合 | 轨迹数 | 转移数 |
|---|---|---|
| 训练 | 81 | 138667 |
| development holdout | 9 | 12433 |
留出轨迹 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.bias | 6 维截距 | 同样不是冻结的物理常数 |
metric.primary_scales | 6 维 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 安装包分发;使用数据提供方授权的版本,并核对下述字段。
后续命令均在案例目录执行:
cd examples/drone_worldmodel已有有效处理数据时直接复用。需要从原料重建时使用以下两个等价入口之一:
bash scripts/prepare_all.shpython prepare_data.py两者都按下面顺序运行,不需要执行两遍:
| 顺序 | 脚本 | 实际输入与输出 |
|---|---|---|
| 01 | examples/drone_worldmodel/scripts/01_prepare_physics.py | 读取 raw,拟合先验并写 full_physics.npz、physics_contract.json |
| 02 | examples/drone_worldmodel/scripts/02_prepare_sincos.py | 读取 raw 和约定,写 full_sincos.npz |
| 03 | examples/drone_worldmodel/scripts/03_prepare_builtin.py | 读取 raw 和约定,写 full_sincos_builtin.npz 与研究用 full_sincos_custom.npz |
| 04 | examples/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 的核心片段(非完整配置),包括图结构和课程训练字段。完整文件还包含损失、判别器与指标参数;后续命令直接使用完整文件。
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.yaml | venv.revive_p → venv.revive_p → venv.revive_p → venv.revive_p | 完整物理先验 GRU + 对抗课程,100 + 100 + 100 + 100 epoch |
examples/drone_worldmodel/config.bc.yaml | venv.bc → venv.bc → venv.bc → venv.bc | 同图和监督课程的 BC 对照,100 + 100 + 100 + 100 epoch;不产对抗快照 |
examples/drone_worldmodel/config.min.yaml | venv.revive_p → venv.revive_p → venv.revive_p → venv.revive_p | 最小结构定义,仍有四段;未显式指定本例自定义网络、运行规模和完整选模规则,省略项用默认层 |
完整配置用于训练物理先验 GRU;最小配置用于了解图与阶段声明。 完整配置已随源码提供,不需要先运行配置生成脚本。
计算图
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_obs6 个节点中只有 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 配置。
四段课程
| 阶段 | epoch | batch | lr | 训练 horizon | ood.weight | zero_epoch |
|---|---|---|---|---|---|---|
p1_step | 100 | 4096 | 0.001 | 1 | 0.100 | 400 |
p2_h16 | 100 | 1024 | 0.001 | 16 | 0.075 | 300 |
p3_h64 | 100 | 512 | 0.0007 | 64 | 0.050 | 200 |
p4_h200 | 100 | 256 | 0.0003 | 200 | 0.025 | 100 |
每段监督序列训练均启用,每 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 | 明确已知公式、可学习参数、状态表示和外部指令 |
data | batch_size 256、z_score;路径由命令行传入 | 各阶段覆盖监督 batch;显式提供 train/val 两份数据 |
training.stages | 四段世界模型;AdamW、按 update 的 cosine、grad_clip 1.0 | 目标为 yaw_state 的 composite,不是控制奖励;没有策略阶段 |
training.stages[].validation | rollout 每 5 epoch、H=200、按轨迹宏平均 | 统一验证设置用于选模;每段训练 horizon 可以不同 |
training.stages[].hyperparameters.adversarial | REVIVE-P 的 OOD 判别器、权重调度与快照 | BC 对照删除这一块;分数不是自动校准的不确定性概率 |
output | best_only、显式允许有上限的研究判别器快照、关闭绘图、ONNX required、float32、atol 0.001 | 只保留最优模型及同轮续训态;REVIVE-P 判别器快照每阶段最多 50 份;导出数值检查不证明模型精度 |
ONNX 容差 0.001 是现有配置方案,不应声称其他容差必然无法满足; 迁移后应按变量单位与累计误差重新检查,不能用放宽容差掩盖接口错误。
4. 训练与选模
准备完成后,先检查数据与配置:
revive validate --config config.yaml --train-data data/derived/holdout10_train.npz \
--val-data data/derived/holdout10_val.npz --show-defaultsREVIVE-P 完整训练:
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 0BC 对照使用独立 run ID:
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 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 秒 |
| 约定支持的 horizon | 1, 16, 64, 200 |
| 起点 | 每条轨迹起点,每条仅推演一次 |
| target | 逐步注入历史指令,不使用新策略生成 |
| 状态 | 从测量初态出发,后续由模型递推,不逐步喂真实状态 |
| 集合 | 81 条训练与 9 条 development holdout,非独立封存测试 |
部署加载路径
完成数据准备和 bash train.sh 后,评估默认训练目录 logs/my_run/models/env.pt:
python load_env_rollout.py --horizon 200 --device cpu --strict-source --json评估前面训练得到的模型:
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 | 验证误差与训练误差的比值 |
逐轨迹诊断
需要定位误差较大的飞行轨迹时,可以运行:
python load_and_evaluate.py --model logs/drone_adv/models/env.pt \
--horizon 200 --device cpu --per-trajectory该辅助脚本重建 sincos_builtin 图并计算逐轨迹指标,适用于本例固定结构;日常评估使用前面的部署加载入口。
指标定义
composite = 0.65 × 逐维加权 NMAE + 0.35 × 积分位移 NMAE在 6 维角度状态上计算,姿态误差按圆周折返; 速度三维权重 2.25、姿态三维权重 1.0。先计算每条轨迹的指标,再对轨迹宏平均。 位移项是速度预测误差乘 dt 后的累计积分,按步与方向归一化平均, 不是只取末端误差,也不是直接对独立位置传感器求 MAE。
lib/metric.py 从约定读取尺度、维度权重和 dt,但角度/速度切片与混合系数 还有模块常量;修改约定时要同步核对训练目标、验证配置与离线公式,不能假定全部自动跟随。 各训练阶段使用各自 horizon 的位移尺度,验证与终读使用 H=200 的尺度; 公式相同不等于训练损失数值可与验证值直接比较。
部署导出
如需导出自己的已训练模型,在案例目录执行:
revive export --artifact logs/drone_adv/models/env.pt --project .GRU 部署是带显式循环状态的 bundle。按导出 manifest 的 init/step 接口初始化并传回状态, 开始新轨迹时重置隐状态;同一轨迹内按步延续隐状态,不同轨迹之间分别维护。 参考评测从轨迹起点初始化,没有提供实测历史预热的 CLI。 完整协议见使用与部署模型。
6. 结果与分析
评估窗口为 200 步,即 20 秒。每条轨迹从测量初态出发,按给定指令序列递推预测;使用 81 条训练轨迹和 9 条开发验证轨迹。
| 项目 | 数值 |
|---|---|
| 选中阶段 / epoch | p3_h64 / 24 |
| 选模 composite | 0.0813673884 |
| 部署加载路径的验证 composite | 0.081365 |
| 重建图路径的验证 composite | 0.081367 |
| 训练轨迹 composite | 0.020416 |
| 验证 / 训练 composite | 约 3.99 |
以下为历史参考实验,模型权重不随安装包交付,新训练结果应单独验收。两种加载路径的验证指标接近,可用于核对模型加载与归一化是否一致。验证误差高于训练误差,应进一步查看逐轨迹、逐速度方向和逐姿态维度的误差,定位模型较难预测的工况。
composite 同时考虑状态误差与速度积分得到的位移误差。较低的单步误差不一定意味着长时位移偏差更小,因此需要并排检查 H=1、16、64、200 的预测结果。
开发验证轨迹参与模型选择,且数据准备中的先验拟合使用了全量轨迹。迁移到新飞行数据时,先划分训练、验证与最终测试轨迹,再拟合先验和统计量,以评估未参与建模的飞行条件。
7. 迁移到实际业务
迁移到其他无人机时,先核对坐标系、速度和角速度单位、姿态表示及指令的时间对齐。物理先验参数应只从训练数据辨识,并为不同飞行工况保留独立测试轨迹。
部署时按轨迹管理循环状态,并分别检查单步与长时预测误差。本例仅提供动力学建模;接入飞控前还需单独验证控制闭环、运行约束与异常处理。
阅读任务页约定,区分数据准备、训练验证和独立评估。