SatNav Classic Baselines
本文介绍如何使用 SatNav-v0.1 数据准备、训练和评测 Seq2Seq 与 CMA。两个模型共享 Episode、 GeoTIFF、离线 trajectory、vocabulary 和 GloVe embedding,但使用不同的模型配置和 checkpoint。
如果只需要运行仓库内置的两个示例 Episode,请先阅读模型训练。本文以下 内容面向完整 SatNav-v0.1 数据。
1. 模型概览
Seq2Seq 和 CMA 都使用离线模仿学习。训练阶段读取预先生成的 RGB frame、导航指令和 expert action;评测阶段在 SatSim 中根据当前 observation 逐步预测动作。
模型 |
指令编码 |
视觉特征 |
导航状态 |
|---|---|---|---|
Seq2Seq |
单向 LSTM,使用最终指令状态 |
ResNet50 全局特征 |
单层 GRU |
CMA |
双向 LSTM,保留全部 token |
ResNet50 空间特征 |
两层 GRU 与跨模态注意力 |
两个模型都接收:
当前 RGB observation;
tokenized instruction;
上一个 SatNav action;
Episode continuation mask。
模型输出四个 SatNav primitive action 之一:STOP、MOVE_FORWARD、TURN_LEFT 或
TURN_RIGHT。
2. 准备环境
在 SatNav 仓库根目录创建 classic 环境:
conda env create -f environments/satnav/conda.yml
conda activate satnav
python -m pip install --upgrade pip
python -m pip install torch==2.4.1 torchvision==0.19.1 \
--index-url https://download.pytorch.org/whl/cu121
python -m pip install -e '.[classic,applications]'
如果使用其他 CUDA 版本,请安装与本机驱动匹配的 PyTorch 和 TorchVision。确认训练入口 可以导入:
python -c "from satnav.training.offline_trainer import OfflineTrainer; print('Classic training import OK')"
python -m baselines.classic --help
训练和评测脚本依赖仓库中的 configs/ 与 scripts/,因此应从源码目录运行,并保留
editable install。
3. 准备 SatNav-v0.1 数据
3.1 Episode 与 GeoTIFF
按照 Episode 数据下载和卫星场景下载 准备数据,然后在当前终端设置:
export SATNAV_DATA_ROOT=/path/to/SatNav-v0.1
export SATNAV_SCENES_DIR=/path/to/satnav-scenes
export SATNAV_TRAIN_EPISODES_PATH="$SATNAV_DATA_ROOT/episodes/train/all_episodes.json"
export SATNAV_TRAJECTORY_DIR="$SATNAV_DATA_ROOT/trajectory_data"
标准数据应包含:
SatNav-v0.1/
└── episodes/
├── train/all_episodes.json
└── eval/
├── val_seen/all_episodes.json
└── val_unseen/all_episodes.json
SATNAV_SCENES_DIR 应包含与 Episode logical scene_id 对应的 59 个 GeoTIFF。运行数据配置
检查:
bash scripts/validation/data_validation.sh
完成时应显示 train 105,164、val_seen 4,574、val_unseen 8,756 个 Episode,以及 59 个 GeoTIFF scene。
3.2 生成离线 Trajectory
Seq2Seq 和 CMA 使用同一份 trajectory export。完整 train split 使用并行生成入口:
python -m applications.trajectory_generation.generate_parallel \
--config applications/episode_processing/configs/trajectory_generation.yaml \
--output_dir "$SATNAV_TRAJECTORY_DIR"
完整数据约占 233 GB,建议至少预留 250 GB 可用空间。生成中断后,使用相同配置和输出目录 重新执行命令即可继续。
输出结构为:
trajectory_data/
├── annotations.json
├── summary.json
└── images/
└── <episode>/
└── rgb/
├── 001.jpg
└── ...
生成完成后应显示:
Success (incl. cached): 105164
Discarded (max steps): 0
Failed: 0
Generated annotations: 105164 / 105164 episodes
完整的生产配置、worker 调整和续跑说明参阅轨迹数据生成。
3.3 构建 Vocabulary 与 GloVe Embedding
Seq2Seq 和 CMA 应使用从同一份 train Episode 构建的 vocabulary。先选择公共产物目录:
export SATNAV_CLASSIC_ARTIFACTS="$PWD/output/baselines/classic/artifacts"
export SATNAV_VOCAB_PATH="$SATNAV_CLASSIC_ARTIFACTS/satnav_v0_1_vocab.json"
export SATNAV_EMBEDDING_PATH="$SATNAV_CLASSIC_ARTIFACTS/satnav_v0_1_glove50d.json.gz"
mkdir -p "$SATNAV_CLASSIC_ARTIFACTS"
构建 vocabulary:
python -m satnav.utils.build_vocab \
--dataset "$SATNAV_TRAIN_EPISODES_PATH" \
--output "$SATNAV_VOCAB_PATH"
准备 GloVe 6B 50d 文本文件,然后构建与 vocabulary 行号一致的 embedding:
export SATNAV_GLOVE_TXT=/path/to/glove.6B.50d.txt
python -m satnav.utils.build_glove_embeddings \
--vocab "$SATNAV_VOCAB_PATH" \
--glove "$SATNAV_GLOVE_TXT" \
--output "$SATNAV_EMBEDDING_PATH" \
--embedding-dim 50
查看 vocabulary 大小:
python - "$SATNAV_VOCAB_PATH" <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as handle:
vocabulary = json.load(handle)
print(vocabulary["vocab_size"])
PY
记录该数值,后续四个本地配置中的 MODEL.INSTRUCTION_ENCODER.vocab_size 必须使用相同
大小。训练、checkpoint 和评测不能更换为另一份 vocabulary。
4. 创建本地配置
公共 YAML 默认指向仓库 tiny example。正式训练时先复制为 Git ignored 的本地配置:
cp configs/baselines/seq2seq_offline_train.yaml configs/local_seq2seq_train.yaml
cp configs/baselines/seq2seq_eval.yaml configs/local_seq2seq_eval.yaml
cp configs/baselines/cma_offline_train.yaml configs/local_cma_train.yaml
cp configs/baselines/cma_eval.yaml configs/local_cma_eval.yaml
4.1 训练配置
在 configs/local_seq2seq_train.yaml 和 configs/local_cma_train.yaml 中设置:
字段 |
值 |
|---|---|
|
|
|
|
|
train |
|
GeoTIFF scene 目录 |
|
|
|
|
|
|
|
|
|
vocabulary JSON 中的 |
生产 trajectory 保存 448 × 448 RGB。Classic 默认 IL.OFFLINE.rgb_size: 224 会在加载时将
图像缩放为模型训练尺寸,可以保留该设置。
模型专属字段必须保持一致:
字段 |
Seq2Seq |
CMA |
|---|---|---|
|
|
|
|
|
|
|
|
|
4.2 评测配置
在 configs/local_seq2seq_eval.yaml 和 configs/local_cma_eval.yaml 中:
将
BASE_TASK_CONFIG_PATH设置为configs/satnav_eval_task.yaml;使用与训练相同的
MODEL结构;使用同一份 vocabulary 和 embedding;
将
MODEL.INSTRUCTION_ENCODER.vocab_size设置为实际 vocabulary 大小。
评测启动脚本会根据 split 覆盖 Episode 和 scene 路径。不要修改模型 hidden size、encoder 方向、embedding size 或 backbone 后再加载已有 checkpoint。
4.3 本地环境变量
复制共享、训练和评测模板:
mkdir -p .local \
scripts/seq2seq/.local \
scripts/cma/.local \
baselines/classic/.local
cp local.env.example .local/env.sh
cp scripts/seq2seq/local.env.example scripts/seq2seq/.local/env.sh
cp scripts/cma/local.env.example scripts/cma/.local/env.sh
cp baselines/classic/local.env.example baselines/classic/.local/env.sh
在 scripts/seq2seq/.local/env.sh 中设置 Seq2Seq 训练参数:
export SEQ2SEQ_TRAIN_CONFIG_PATH="${SEQ2SEQ_TRAIN_CONFIG_PATH:-configs/local_seq2seq_train.yaml}"
export SEQ2SEQ_EVAL_CONFIG_PATH="${SEQ2SEQ_EVAL_CONFIG_PATH:-configs/local_seq2seq_eval.yaml}"
export SEQ2SEQ_OUTPUT_ROOT="${SEQ2SEQ_OUTPUT_ROOT:-output/seq2seq_offline}"
export SEQ2SEQ_CUDA_DEVICES="${SEQ2SEQ_CUDA_DEVICES:-0,1,2,3,4,5,6,7}"
在 scripts/cma/.local/env.sh 中设置 CMA 训练参数:
export CMA_TRAIN_CONFIG_PATH="${CMA_TRAIN_CONFIG_PATH:-configs/local_cma_train.yaml}"
export CMA_EVAL_CONFIG_PATH="${CMA_EVAL_CONFIG_PATH:-configs/local_cma_eval.yaml}"
export CMA_OUTPUT_ROOT="${CMA_OUTPUT_ROOT:-output/cma}"
export CMA_CUDA_DEVICES="${CMA_CUDA_DEVICES:-0,1,2,3,4,5,6,7}"
在 baselines/classic/.local/env.sh 中设置统一评测参数:
export SATNAV_DATA_ROOT="${SATNAV_DATA_ROOT:-/path/to/SatNav-v0.1}"
export SATNAV_SCENES_DIR="${SATNAV_SCENES_DIR:-/path/to/satnav-scenes}"
export SATNAV_VOCAB_PATH="${SATNAV_VOCAB_PATH:-/path/to/satnav_v0_1_vocab.json}"
export SATNAV_SEQ2SEQ_EVAL_CONFIG="${SATNAV_SEQ2SEQ_EVAL_CONFIG:-configs/local_seq2seq_eval.yaml}"
export SATNAV_SEQ2SEQ_CHECKPOINT="${SATNAV_SEQ2SEQ_CHECKPOINT:-/path/to/seq2seq/best.pth}"
export SATNAV_CMA_EVAL_CONFIG="${SATNAV_CMA_EVAL_CONFIG:-configs/local_cma_eval.yaml}"
export SATNAV_CMA_CHECKPOINT="${SATNAV_CMA_CHECKPOINT:-/path/to/cma/best.pth}"
5. 训练 Seq2Seq
5.1 单 GPU Smoke
先使用单 GPU 和一个 epoch 确认数据、optimizer 与 checkpoint 链路:
CONFIG_PATH=configs/local_seq2seq_train.yaml \
CUDA_DEVICES=0 \
GPUS_PER_NODE=1 \
NUM_EPOCHS=1 \
PER_GPU_BATCH_SIZE=1 \
NUM_WORKERS=0 \
USE_SWANLAB=false \
SWANLAB_EXP_NAME=seq2seq-v0-1-smoke \
bash scripts/seq2seq/train_offline_ddp.sh
即使只使用一张 GPU,launcher 也通过 torchrun 启动相同训练入口,使单卡和多卡使用一致
的配置覆盖方式。
5.2 多 GPU 训练
Smoke 完成后再增加 GPU、batch size、worker 和 epoch:
CONFIG_PATH=configs/local_seq2seq_train.yaml \
CUDA_DEVICES=0,1,2,3,4,5,6,7 \
GPUS_PER_NODE=8 \
NUM_EPOCHS=10 \
PER_GPU_BATCH_SIZE=8 \
NUM_WORKERS=8 \
USE_SWANLAB=false \
SWANLAB_EXP_NAME=seq2seq-v0-1 \
bash scripts/seq2seq/train_offline_ddp.sh
Seq2Seq 的有效 batch size 为 PER_GPU_BATCH_SIZE × GPUS_PER_NODE。多卡训练仅支持单节点;
GPUS_PER_NODE 必须与 CUDA_DEVICES 中的 GPU 数量一致。
默认输出结构为:
output/seq2seq_offline/
├── checkpoints/
│ ├── seq2seq-v0-1/
│ │ └── best.pth
│ └── latest -> seq2seq-v0-1
└── logs/
└── seq2seq-v0-1.log
6. 训练 CMA
6.1 单 GPU Smoke
CONFIG_PATH=configs/local_cma_train.yaml \
CUDA_DEVICES=0 \
GPUS_PER_NODE=1 \
NUM_EPOCHS=1 \
PER_GPU_BATCH_SIZE=1 \
NUM_WORKERS=0 \
USE_SWANLAB=false \
SWANLAB_EXP_NAME=cma-v0-1-smoke \
bash scripts/cma/train_ddp.sh
6.2 多 GPU 训练
CONFIG_PATH=configs/local_cma_train.yaml \
CUDA_DEVICES=0,1,2,3,4,5,6,7 \
GPUS_PER_NODE=8 \
NUM_EPOCHS=10 \
PER_GPU_BATCH_SIZE=4 \
NUM_WORKERS=4 \
USE_SWANLAB=false \
SWANLAB_EXP_NAME=cma-v0-1 \
bash scripts/cma/train_ddp.sh
CMA 使用空间视觉特征与两层 recurrent state,通常比 Seq2Seq 占用更多显存。发生 OOM 时,
先减小 PER_GPU_BATCH_SIZE,再降低 NUM_WORKERS。
默认输出结构为:
output/cma/
├── checkpoints/
│ ├── cma-v0-1/
│ │ └── best.pth
│ └── latest -> cma-v0-1
└── logs/
└── cma-v0-1.log
7. Checkpoint 与继续训练
Seq2Seq 和 CMA 的 best.pth 都包含:
config, epoch, loss, optim_state, state_dict, step_id
训练后可以检查 checkpoint 是否至少完成一个 optimizer step:
python - /path/to/best.pth <<'PY'
import sys
from baselines.classic.common.checkpoints import read_training_checkpoint
checkpoint = read_training_checkpoint(sys.argv[1])
print("epoch", checkpoint["epoch"])
print("step_id", checkpoint["step_id"])
print("loss", checkpoint["loss"])
PY
继续训练时,在相应的 local train config 中设置:
IL:
load_from_ckpt: true
ckpt_to_load: /path/to/previous/best.pth
epochs: 20
epochs 表示目标总 epoch 数,不是额外增加的 epoch 数。Trainer 会恢复模型、optimizer、
已完成 epoch 和 step_id,并将本次结果写入 launcher 创建的新实验目录。
8. 配置统一评测
评测前更新 baselines/classic/.local/env.sh 中的 checkpoint:
export SATNAV_SEQ2SEQ_CHECKPOINT=/path/to/seq2seq-v0-1/best.pth
export SATNAV_CMA_CHECKPOINT=/path/to/cma-v0-1/best.pth
检查本地 eval config 能否解析:
python -m baselines.classic \
--method seq2seq \
--config configs/local_seq2seq_eval.yaml \
--split val_seen \
--print-config
python -m baselines.classic \
--method cma \
--config configs/local_cma_eval.yaml \
--split val_seen \
--print-config
Seq2Seq 和 CMA checkpoint 会严格加载。
9. Smoke 评测
Smoke 建议选择 8 个 Episode,每个 Episode 最多运行 5 步。为每个 checkpoint 设置独立 输出目录:
SATNAV_MAX_STEPS=5 \
SATNAV_CLASSIC_RUN_OUTPUT=output/baselines/classic/seq2seq-v0-1/val_seen/5steps/1rank \
bash scripts/classic/eval.sh seq2seq val_seen 8
SATNAV_MAX_STEPS=5 \
SATNAV_CLASSIC_RUN_OUTPUT=output/baselines/classic/cma-v0-1/val_seen/5steps/1rank \
bash scripts/classic/eval.sh cma val_seen 8
结果结构为:
<output-dir>/
├── rank_00000/
│ ├── episodes.jsonl
│ └── done.json
└── summary.json
检查汇总状态:
python - output/baselines/classic/seq2seq-v0-1/val_seen/5steps/1rank/summary.json <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as handle:
summary = json.load(handle)
print(json.dumps(summary, indent=2, sort_keys=True))
assert summary["status"] == "complete"
assert summary["error_episode_count"] == 0
PY
CMA 使用相同的结果格式,只需替换 summary 路径。
10. 多 GPU 评测
在线评测按 Episode 分片,每个 rank 在一张 GPU 上加载独立模型进程。两张 GPU 的 smoke:
SATNAV_MAX_STEPS=5 \
SATNAV_CLASSIC_RUN_OUTPUT=output/baselines/classic/seq2seq-v0-1/val_seen/5steps/2rank \
bash scripts/classic/eval_parallel.sh seq2seq val_seen 2 0,1 8
SATNAV_MAX_STEPS=5 \
SATNAV_CLASSIC_RUN_OUTPUT=output/baselines/classic/cma-v0-1/val_seen/5steps/2rank \
bash scripts/classic/eval_parallel.sh cma val_seen 2 0,1 8
eval_parallel.sh 会等待所有 rank 完成,然后自动运行统一聚合器。limit 先作用于全局
Episode 排序结果,再进行 stride sharding;上例的两个 rank 各处理 4 个 Episode。
11. 完整评测
正式评测将 SATNAV_MAX_STEPS 设置为 500,并令 limit=-1:
SATNAV_MAX_STEPS=500 \
SATNAV_CLASSIC_RUN_OUTPUT=output/baselines/classic/seq2seq-v0-1/val_seen/500steps/8rank \
bash scripts/classic/eval_parallel.sh \
seq2seq val_seen 8 0,1,2,3,4,5,6,7 -1
SATNAV_MAX_STEPS=500 \
SATNAV_CLASSIC_RUN_OUTPUT=output/baselines/classic/cma-v0-1/val_seen/500steps/8rank \
bash scripts/classic/eval_parallel.sh \
cma val_seen 8 0,1,2,3,4,5,6,7 -1
val_seen 完成后,将 split 和输出目录分别改为 val_unseen 再运行一次。标准 Episode 数和
评测参数参阅 Evaluation - SatNav-v0.1 标准设置。
评测脚本默认启用 resume。同一运行中断后,使用完全相同的数据、checkpoint、seed、
world_size、最大步数和输出目录重新执行即可。评测另一个 checkpoint 或修改运行参数时,
必须使用新的输出目录。
完整结果应满足:
summary.json的status为complete;error_episode_count为 0;unique_record_count等于当前 split 的 Episode 数;metrics包含distance_to_goal、success、oracle_success、spl和path_length。
结果字段、Episode 分片和 resume 规则参阅统一评测。
12. 常见问题
为什么训练时找不到 RGB frame?
确认 IL.OFFLINE.annotations_path 指向最终 annotations.json,IL.OFFLINE.images_root
指向同一 trajectory export 的 images/。不要组合来自不同生成批次的 annotation 和图像。
为什么 vocabulary size 不匹配?
训练 config、eval config、embedding 和 SATNAV_VOCAB_PATH 必须来自同一次 vocabulary
构建。检查 JSON 中的 vocab_size,并同步更新两个模型配置中的
MODEL.INSTRUCTION_ENCODER.vocab_size。
为什么 checkpoint 无法加载?
确认选择了正确模型的 eval config。Seq2Seq 与 CMA 的 encoder、recurrent state 和参数名 不同,不能互换 checkpoint;同一模型修改 hidden size、backbone 或 instruction encoder 后 也不能直接加载旧 checkpoint。
为什么多 GPU 训练的所有进程都在同一张卡上?
通过 train_offline_ddp.sh 或 train_ddp.sh 启动训练,并确保 GPUS_PER_NODE 等于
CUDA_DEVICES 中的 GPU 数量。不要直接为多个进程手工设置相同的 LOCAL_RANK。
为什么评测重新运行后没有执行 Episode?
评测默认 resume,并会跳过输出目录中已经存在的 Episode。继续同一次运行时这是正常行为;
如果更换了 checkpoint 或评测参数,应指定新的 SATNAV_CLASSIC_RUN_OUTPUT。
为什么 Smoke 的 Success 和 SPL 很低?
5-step smoke 只用于检查 checkpoint 加载、模型推理、环境交互和结果写入。正式指标应来自
500-step 完整 val_seen 和 val_unseen 评测。