使用 RL 训练无人机悬停策略#

本教程使用强化学习(RL)训练四旋翼在随机放置的目标点悬停。它遵循 Genesis World 中 RL 示例的标准三文件布局:一个把仿真包装成 gym 风格任务的环境、一个训练脚本和一个评估脚本。得到的策略足够小,可以运行在真实的 Crazyflie 上。

奖励设计参考 Champion-level drone racing using deep reinforcement learning (Nature 2023)。这是一个最小起点,而不是生产级流水线:奖励项刻意保持简单,默认批大小也没有压满 Genesis World 的并行吞吐。

这三个文件是完整代码的权威来源:

无人机完全通过四个螺旋桨的转速来驱动。如果你还不了解 RPM 如何转化为推力和姿态,请先阅读 无人机实体;本页假定你已经理解这层映射。

任务#

每个回合会在无人机前方放置一个目标点,并奖励它飞到该点并保持位置。当无人机进入目标的 at_target_threshold(0.1 m)范围时,会重新采样一个新目标,因此单个回合会串联许多次“到达并保持”的机动。一个回合最长持续 episode_length_s(15 s),并在坠毁时提前终止。

环境必须明确任何 RL 问题都需要的三样东西:策略看到的观测、它产生的动作,以及对结果打分的奖励。

环境#

HoverEnv 是一个普通的 Python 类,而不是 gym 基类的子类。它提供 on-policy RL runner 所期望的方法:reset()、step(actions) 和 get_observations()。它的全部状态都存放在形状为 (n_envs, ...) 的批处理张量中,因此单个实例就能在 GPU 上并行驱动成千上万架无人机。

场景与无人机#

构造函数以 100 Hz 的控制频率构建场景,并添加地平面、可选的目标标记以及 Crazyflie 无人机:

self.dt = 0.01  # 100 Hz control loop
self.scene = gs.Scene(
    sim_options=gs.options.SimOptions(dt=self.dt, substeps=2),
    rigid_options=gs.options.RigidOptions(
        dt=self.dt,
        constraint_solver=gs.constraint_solver.Newton,
        enable_collision=True,
        enable_joint_limit=True,
    ),
    show_viewer=show_viewer,
)
self.scene.add_entity(gs.morphs.Plane())
self.drone = self.scene.add_entity(gs.morphs.Drone(file="urdf/drones/cf2x.urdf"))
self.scene.build(n_envs=num_envs)

build(n_envs=num_envs) 分配批处理仿真。训练时为 8192 个环境,评估时为 1 个。

动作#

策略输出四个数,每个螺旋桨一个,并裁剪到 [-1, 1]。环境把它们映射为螺旋桨 RPM,其值为悬停 RPM 的一个比例;悬停 RPM 是总推力与重力平衡时的转速:

self.actions = torch.clip(actions, -self.env_cfg["clip_actions"], self.env_cfg["clip_actions"])
# 14468 rpm is the hover point; actions scale each propeller to [0.2, 1.8] x hover
self.drone.set_propellers_rpm((1 + self.actions * 0.8) * 14468.429183500699)
self.scene.step()

学习悬停 RPM 的比例而不是绝对 RPM,可以让动作范围既小又居中,从而稳定早期训练。RPM 到力的转换见 set_propellers_rpm()。

观测#

每一步之后,环境会读取无人机状态并组装观测。每个环境的观测是一个长度为 17 的向量,每个块都被缩放到大约 [-1, 1],以免某个量主导策略输入:

self.obs_buf = torch.cat(
    [
        torch.clip(self.rel_pos * self.obs_scales["rel_pos"], -1, 1),  # target minus drone position (3,)
        self.base_quat,                                                # attitude, (w, x, y, z) (4,)
        torch.clip(self.base_lin_vel * self.obs_scales["lin_vel"], -1, 1),  # body-frame linear velocity (3,)
        torch.clip(self.base_ang_vel * self.obs_scales["ang_vel"], -1, 1),  # body-frame angular velocity (3,)
        self.last_actions,                                             # previous action (4,)
    ],
    axis=-1,
)  # shape (n_envs, 17)

线速度与角速度都在无人机的机体坐标系中表示(用基座四元数的逆进行旋转),这使策略对无人机的航向不敏感。位置误差 rel_pos 是世界坐标系下的 target - drone_position。姿态是标量在前的 (w, x, y, z) 顺序的四元数。

奖励#

环境每一步对五个项求和,每一项都乘以 dt 和来自 reward_cfg 的权重。这些权重(正为奖励,负为惩罚)位于 hover_train.py 中:

  • target: 奖励缩短与目标之间的距离。它基于势函数:取相对上一步的平方距离减少量,因此只要向目标靠近就会得到正分,与绝对距离无关。

  • smooth: 惩罚动作在相邻步之间的大幅变化,从而抑制抖动并缩小 sim-to-real 差距。

  • yaw: 奖励让航向保持在零附近,使用 exp(yaw_lambda * |yaw|)。

  • angular: 惩罚机体角速度,抑制旋转。

  • crash: 在下面任一终止条件触发时施加的固定惩罚。

终止与重置#

当环境超时或坠毁时,它会终止并重置。坠毁条件用于防范无法恢复的状态:

  • 横滚角或俯仰角超过阈值(180 度),

  • 无人机在 x 或 y 方向偏离目标超过 3.0 m,或在 z 方向超过 2.0 m,

  • 无人机下降到 0.1 m 以下(进入地面)。

reset_idx 只就地重新初始化已终止的环境,因此整个批次永远不会因为等待最慢的回合而停滞。重置会重新采样新目标并清零状态缓冲区。

训练#

训练使用 rsl-rl 中的 PPO。安装依赖:

pip install --upgrade pip
pip install tensorboard "rsl-rl-lib>=5.0.0"

hover_train.py 以性能模式在 GPU 上初始化 Genesis World,构造 HoverEnv,将其包装进 OnPolicyRunner,然后调用 learn:

gs.init(backend=gs.gpu, precision="32", logging_level="warning", seed=args.seed, performance_mode=True)
env = HoverEnv(num_envs=args.num_envs, env_cfg=env_cfg, obs_cfg=obs_cfg,
               reward_cfg=reward_cfg, command_cfg=command_cfg, show_viewer=args.vis)
runner = OnPolicyRunner(env, train_cfg, log_dir, device=gs.device)
runner.learn(num_learning_iterations=args.max_iterations, init_at_random_ep_len=True)

performance_mode=True 会把张量形状固化到编译后的内核中,从而加快步进,代价是首次构建更慢。长时间训练时开启它;交互式迭代时则保持关闭。

actor 与 critic 都是两层 MLP([128, 128],tanh),配置位于 get_train_cfg。开始训练:

python examples/drone/hover_train.py -e drone-hovering -b 8192 --max-iterations 301
  • -e drone-hovering: 实验名;检查点与配置写入 logs/drone-hovering/。

  • -b 8192: 并行环境数量。

  • --max-iterations 301: PPO 迭代次数。

  • -v: 可选,打开查看器以观看训练。

用 TensorBoard 监控训练进度:

tensorboard --logdir logs
TensorBoard 奖励曲线随训练迭代上升并趋于平稳

使用 -v 时,查看器会显示少量环境并行训练:

查看器中多架 Crazyflie 无人机正在学习飞向目标标记

评估#

hover_eval.py 会重新加载保存的配置,以单架无人机并打开查看器重建环境,然后确定性地推出训练好的策略:

python examples/drone/hover_eval.py -e drone-hovering --ckpt 300 --record
  • --ckpt 300: 加载 logs/drone-hovering/model_300.pt。

  • --record: 挂载相机并把 rollout 保存到 out/hover.mp4。

如果评估缓慢或不稳定,去掉 --record 以关闭渲染。

另见#