2026/10/10 4:50:43

多航天器编队变换如何用MAPPO:环境建模、奖励设计与训练调优

多航天器编队变换如何用MAPPO:环境建模、奖励设计与训练调优 简介针对MAPPO多智能体强化学习在航天器编队控制中的应用压缩包提供一套可直接运行的完整工程包含1个Python主程序、2张轨迹与训练结果图、1份功能检查清单和1份任务说明文档整体仅2.87MB。系统实现8个航天器在20个空间碎片环境中从圆形编队到对角线编队的精确变换采用集中训练-分散执行CTDE架构融合控制屏障函数安全约束与PD控制器混合策略达到编队误差≤14m、位置误差≤10m的性能指标。除完整代码外资源还详细解析MAPPO算法原理、混合控制策略设计及安全约束机制并附有功能检查清单便于逐项验证轨迹图可直观对比编队演化过程。已有88人学习下载适合航天器编队控制、强化学习应用方向的工程师和研究生参考。1. 多航天器编队变换为什么要用 MAPPO从奖励稀疏和耦合控制说起把一个三星星座从沿轨串飞构型改成空间绕飞构型看似只差几次目标位置修正真正上手做时就会发现比单星轨道机动难的不是一点半点。每个航天器都在推自己的位置误差控制量又会互相扰动传统控制器在这里很容易出现来回震荡。MAPPO 的价值在于它把编队看作一组共享环境、各自决策的智能体用集中训练、分布执行的 Actor-Critic 结构让每颗星学会在队友也会动的前提下做动作而不是各自死磕目标点。这套系统的完整代码就是围绕这个思路组织的环境、算法、训练入口都齐备适合做星群任务规划、编队重构和相对导航控制的工程师与研究生拿来改改奖励函数就能接自己的任务。2. 拿到的系统长什么样代码结构与环境搭建的完整骨架这套系统能直接跑起来靠的不是算法封装而是环境建模时没有回避关键物理量。多航天器编队变换通常以领航星为参考点跟随星处在几百米到几公里的相对距离上远小于轨道半径所以相对运动可以近似为线性系统。最常见的是 C-W 方程Hill 方程在旋转坐标系下状态量为 6 维——相对位置x, y, z和相对速度vx, vy, vz控制量为推力加速度ax, ay, az。方程里的加速度由三部分叠加径向方向的重力梯度项 3 n² x、轨道面内的科氏力项 2 n [vy, -vx, 0]、以及轨道法向的重力恢复项 -n² z。这里的 n 是领航星的平均运动角速度低轨场景下大约在 0.001 rad/s 量级。很多入门实现喜欢直接把绝对位置塞进去忽略了领航星的轨道运动结果编队背景速度远大于相对控制量训练出来的策略只会一路跟着背景速度走。正确做法是先把绝对轨道运动剥掉只对相对状态做控制。import numpy as np def cw_step(state, thrust, n, dt): 基于 C-W 方程的相对运动单步递推。 state: (x, y, z, vx, vy, vz)单位 m, m/s thrust: 推力加速度 (3,)单位 m/s^2 n: 轨道角速度 rad/s dt: 仿真步长 s x, y, z, vx, vy, vz state ax, ay, az thrust ax_cw 2.0 * n * vy 3.0 * n * n * x ay_cw -2.0 * n * vx az_cw -n * n * z vx_new vx (ax_cw ax) * dt vy_new vy (ay_cw ay) * dt vz_new vz (az_cw az) * dt x_new x vx_new * dt y_new y vy_new * dt z_new z vz_new * dt return np.array([x_new, y_new, z_new, vx_new, vy_new, vz_new])逻辑上这段代码先算 C-W 方程给出的自然加速度再叠加上推力加速度然后做半隐式欧拉积分。半隐式意味着先更新速度、再用新速度更新位置比完全显式欧拉稳定又不需要构造矩阵求逆很适合放在训练循环里每步调用。要改成轨道转移大机动时这个模型就不够用了得切回二体问题做数值积分但编队变换属于相对距离几百米的小偏差问题线性化精度足够。参数 n 和 dt 的匹配非常影响收敛。dt 是控制周期也是强化学习环境的决策周期我一般取 0.5 到 1 秒。如果 dt 太大会让离散误差超过控制量精度训练前期就出现发散如果 dt 太小一个编队变换 episode 要几千步训练速度会慢到让人想放弃。环境写完后先做一件事不给推力让卫星自由积分 1000 秒如果相对位置在闭合椭圆上转而不是直线飞走说明动力学部分基本正确。编队构型描述上这套系统用领航者-跟随者模型。初始构型和目标构型都放在配置里常见的有三类沿轨串飞、水平圆绕飞、垂直平面椭圆绕飞。编队变换就是让每个跟随星的相对位置从初始偏置移动到目标偏置同时把相对速度压下来控制量尽量小。有了这个定义后面状态空间和奖励函数才能跟着落地。2.1 代码包目录与运行前的环境准备强化学习项目最常见的返工原因是环境、算法、超参数混在一起。这套系统按典型的分层方式组织环境层负责状态、动作、奖励算法层只做策略更新完全不接触动力学配置层集中放超参数和编队构型入口脚本把前两层串起来。好处是换编队构型时不用动算法文件换算法时也不用重写环境。formation_mappo/ ├── envs/ │ ├── formation_env.py # 编队变换仿真环境C-W动力学观测奖励 │ └── __init__.py ├── algo/ │ ├── networks.py # Actor 与 Critic 网络定义 │ ├── mappo.py # MAPPO 策略更新核心 │ └── buffer.py # rollout 存储与 GAE 计算 ├── config/ │ └── hyperparams.yaml # 动力学与训练参数含编队构型定义 ├── train.py # 训练入口 ├── test.py # 加载权重做构型变换回放 └── requirements.txt # 依赖清单formation_env.py 是核心环境文件里面包含构型初始化、C-W 单步递推、观测拼接、奖励和碰撞检查。networks.py 只放网络的类定义不写训练逻辑。mappo.py 是 PPO 更新部分接收 buffer 里的数据做裁剪和梯度更新。buffer.py 里做 GAE 计算这段代码单独拎出来是对的因为多智能体场景下每个智能体的 value 估计都要单独算混在训练循环里很难查 bug。python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt运行前先把 Python 环境隔离起来不要直接往系统环境里安装。强化学习训练依赖链很脆弱torch 和 numpy 版本不一致轻则警告重则训练到一半突然爆出一个算不出来梯度的错。requirements.txt 我习惯写成下限版本而不是锁死版本方便在不同机器上安装。如果你要复现论文级的结果就把精确版本记录进去比如 torch2.1.2、numpy1.26.4否则换台机器环境不同曲线完全对不上。numpy1.24 torch2.0 matplotlib3.8 pyyaml6.0装完之后第一次建议直接跑默认配置。跑之前确认没有多个 Python 环境同时生效很多新手在虚拟环境里装好依赖命令行却还指向全局 Python导致 import torch 失败或版本不对。如果机器没有 GPU不需要改代码PyTorch 会自动落到 CPU 上跑只是训练时间会明显拉长。训练命令就是一句python train.py --config config/hyperparams.yaml训练过程会把网络权重保存到 runs/exp1/ 目录测试入口只读权重文件做回放。把训练和测试分开是刻意为之测试时不希望策略还带着探索噪声而是要使用确定性的均值动作来评估构型变换效果。3. MAPPO 算法在编队变换里的落地状态、动作、奖励与网络架构MAPPO 之所以适合多航天器编队变换核心在于集中训练分布执行。编队里每颗航天器决策时只能拿到自己的局部观测但训练时可以让 Critic 看到所有航天器的完整状态。这样缓解了多智能体环境中的非平稳性问题又不牺牲部署时的独立性。单智能体 PPO 直接套上去不是不能用但每个智能体各自训练 Critic 时其他智能体的策略变化会把它学到的价值函数搞废收敛极不稳定。3.1 状态空间与动作空间怎么定义状态空间的设计要遵循一个原则部署时能拿到什么训练观测里就只能放什么。多航天器之间如果假设没有全连通通信那么每颗星只能看到自己的相对位置速度、目标构型偏移以及可以通过星间链路测到的邻居相对位置。目标构型偏移必须放进观测因为编队变换任务本质上是从当前态转移到指定构型不告诉智能体目标位置它只能盲目探索。import numpy as np class FormationEnv: def __init__(self, num_agents3, safe_dist30.0): self.num_agents num_agents self.safe_dist safe_dist self.n 0.001 self.dt 0.5 def _encode_obs(self, agent_id): # 自身相对位置速度6 维做归一化 ego self.rel_state[agent_id].copy() / np.array([500.0, 500.0, 500.0, 5.0, 5.0, 5.0]) # 目标构型偏移3 维 target self.target_offsets[agent_id].copy() / 500.0 neighbor [] for j in range(self.num_agents): if j ! agent_id: d self.rel_state[j][:3] - self.rel_state[agent_id][:3] neighbor.append(d / 500.0) return np.concatenate([ego, target] neighbor)这段代码的关键是归一化。航天器相对位置动辄几百米速度在每秒米量级如果不归一化直接喂给神经网络梯度会被大数值特征带偏训练很容易出现 NaN。尺度因子不是拍脑袋定的我从这些物理量的典型值出发位置 500 米、速度 5 m/s。如果你的场景是几公里的编队把 500 改成 3000如果是近距离操作改成 50。动作空间定义为连续推力加速度维度是 3取值范围正负 0.01 m/s²。这个范围对应小推力器做编队维持的典型量级。如果取值过大智能体会用蛮力快速冲过去碰撞惩罚压不住如果过小智能体永远追不上目标构型。Actor 网络输出经过 tanh 限制到 [-1, 1]再线性映射到实际加速度范围。action_low -0.01 * np.ones(3) action_high 0.01 * np.ones(3) def _map_action(raw_action): return 0.5 * (raw_action 1.0) * (action_high - action_low) action_low这里有个容易踩的细节网络输出的分布是一个高斯分布采样出来的值通过 tanh 变换后log_prob 也要跟着修正。很多直接拿单智能体 PPO 改多智能体的代码忘了修正这项导致策略梯度方向错误训练曲线看起来在涨实际上策略根本没有被正确优化。3.2 奖励函数设计主奖励、构型惩罚与避碰项奖励是这套系统里最需要花时间的部分。多航天器编队变换要同时满足到达目标构型、相对速度衰减、避免碰撞、节省燃料四个目标把这四个目标叠成一个标量奖励需要仔细平衡权重。我常用的奖励结构是一个步进式惩罚加稀疏到达奖励每个控制周期计算相对位置误差误差越小奖励越高同时惩罚相对速度、推力大小和碰撞风险。初始构型和目标构型相差几百米时稀疏奖励完全没有引导意义前期探索会浪费大量时间。def _compute_reward(self, agent_id, thrust): pos_err np.linalg.norm( self.rel_state[agent_id][:3] - self.target_offsets[agent_id]) vel_err np.linalg.norm( self.rel_state[agent_id][3:]) thrust_penalty np.linalg.norm(thrust) r (-0.5 * pos_err - 0.1 * vel_err - 1.0 * thrust_penalty) r self._collision_penalty(agent_id) return r def _collision_penalty(self, agent_id): penalty 0.0 for j in range(self.num_agents): if j agent_id: continue d np.linalg.norm( self.rel_state[j][:3] - self.rel_state[agent_id][:3]) if d self.safe_dist: penalty - (self.safe_dist - d) ** 2 return penalty位置误差项的权重是 0.5速度误差是 0.1推力惩罚是 1.0。这个梯度的含义是优先减小位置误差但不要为了快速到达而用大力矩。推力惩罚权重放到 1.0鼓励智能体走平滑轨迹。如果训练后发现轨迹来回震荡就把推力惩罚调高如果到达目标后还在原地飘就把速度误差权重调高。碰撞惩罚做了平方惩罚距离越近惩罚增长越快。safe_dist 设为 30 米这个值要根据编队尺度调整。如果编队间隔是 200 米30 米安全距离合理如果是 50 米的小编队安全距离要降到 10 米左右否则奖励里避碰项会压过主任务项智能体宁可站在原地也不动。关于额外的到达奖励我会在构型误差小于阈值时加一个正项。这个项不需要很大但能显著告诉智能体“这样做是对的”。没有这个项时策略会把构型误差降到很小但不会精确到位加了这个项后末端收敛精度明显提升。3.3 Actor-Critic 网络与 CTDE 实现Actor 网络负责从局部观测到动作分布。为了让多颗星共享经验系统采用了共享参数 Actor每颗星都使用同一个网络观测中包含自己的目标构型偏移所以网络能区分不同任务。这种方式在多智能体配合类任务里训练效率更高不需要每增加一颗星就重新训练一套参数。import torch import torch.nn as nn class Actor(nn.Module): def __init__(self, obs_dim, act_dim, action_scale0.01): super().__init__() self.mlp nn.Sequential( nn.Linear(obs_dim, 128), nn.Tanh(), nn.Linear(128, 128), nn.Tanh(), nn.Linear(128, 128), nn.Tanh(), ) self.mean_head nn.Linear(128, act_dim) self.log_std nn.Parameter(torch.zeros(act_dim)) self.action_scale action_scale def forward(self, obs): h self.mlp(obs) mean torch.tanh(self.mean_head(h)) * self.action_scale std torch.exp(self.log_std.clamp(-5.0, 0.5)) return mean, std def sample(self, obs): mean, std self.forward(obs) dist torch.distributions.Normal(mean, std) raw dist.sample() action torch.tanh(raw) * self.action_scale # 对 tanh 变换后的分布做 log_prob 修正 log_prob dist.log_prob(raw).sum(-1) log_prob log_prob - torch.log((1 - action / self.action_scale ** 2) 1e-6).sum(-1) return action, log_probActor 输出均值后经过 tanh 压缩再乘 action_scale。log_std 是学习参数初始化为 0也就是方差为 1。训练时 clamp 到 [-5, 0.5]防止标准差学到极端值。这段网络的隐藏层用了三层 Tanh而不是 ReLU因为连续控制任务里 Tanh 的平滑特性通常更好ReLU 在这类小网络上容易出现死神经元。Critic 网络的结构更简单输入是全局状态输出是价值估计。全局状态是所有航天器观测拼接而成包含位置速度目标偏移和邻居信息。集中训练时 Critic 拥有全局信息它评估的是联合状态的价值而不是单颗星自己的价值。class Critic(nn.Module): def __init__(self, global_state_dim): super().__init__() self.mlp nn.Sequential( nn.Linear(global_state_dim, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, 1), ) def forward(self, global_state): return self.mlp(global_state).squeeze(-1)部署时只需要 ActorCritic 只在训练阶段存在。这就是 MAPPO 相对独立 PPO 的核心优势训练时可以用全局信息降低方差执行时每颗星又完全独立。如果你在真实星上部署加载 Actor 权重即可Critic 网络保存下来主要是为了后续继续训练。4. 训练主循环与关键参数调优让系统跑起来并收敛环境、奖励、网络都就位后真正让系统收敛的是训练循环和超参数。多航天器场景的 rollout 数据量比单智能体大得多因为每个环境 step 会同时产生所有航天器的经验。这里最忌讳的是把数据一股脑塞进 PPO 更新却不做 GAE 和优势标准化。4.1 训练入口与 rollout 采集训练主循环的结构可以用一小段伪代码表示def train(): for update in range(num_updates): rollout [] obs_list env.reset() for step in range(steps_per_update): with torch.no_grad(): actions actor.sample(torch.FloatTensor(obs_list)).numpy() next_obs, rewards, done env.step(actions) rollout.append((obs_list.copy(), actions.copy(), rewards.copy(), done.copy())) obs_list next_obs buffer.from_rollout(rollout) buffer.compute_gae(critic, gamma, lam) for epoch in range(update_epochs): batch buffer.sample(mini_batch_size) update_actor_critic(actor, critic, batch, clip_eps, entropy_coef)实际代码里 rollout 还需要存每个 step 的 value 估计和 actor 输出的 log_prob这里画的是训练主干的形状目的是讲清楚数据流。rollout 采集阶段关闭梯度计算因为此时只是环境交互不需要回传梯度。actor.sample 返回的 action 是带探索噪声的测试时不走这个流程而是直接取分布均值。buffer.from_rollout 负责把原始序列转成训练需要的张量。compute_gae 计算广义优势估计这一步需要使用 Critic 的价值函数做时序差分。多智能体场景下每个智能体的 reward 是独立存储的因此 GAE 也要按智能体维度分别计算。最容易犯的错是把所有航天器的 reward 求平均再算优势这样会抹掉个体差异个别星学不动。4.2 PPO Clip、GAE、熵系数这些参数怎么设MAPPO 的超参数大部分继承自 PPO但在多航天器场景里有自己的偏好。这张表是我不太改动训练结构时的常用起点参数常用值作用与调整策略lr3e-4Actor 和 Critic 共用优化器训练不稳时降到 1e-4gamma0.99折扣因子编队变换时间尺度短0.99 足够gae_lambda0.95大一点方差高小一点偏差高clip_eps0.2裁剪幅度任务复杂时降到 0.1entropy_coef0.01探索度越大策略越随机update_epochs10每个 rollout 更新 10 轮mini_batch_size1024显存不足时减小到 512调参顺序不要太跳跃。我一般先固定其他参数单独把学习率从 3e-4 降到 1e-4如果训练曲线明显变稳再考虑调 entropy_coef。entropy_coef 是一个需要反复试的参数太小会让策略过早固化编队变换这一类的多峰最优问题里特别容易陷进局部解太大会让策略一直随机抖动永远学不到精细控制。mini_batch_size 决定了每次更新的数据量它和 update_epochs 搭配。如果 mini_batch_size 太小数据重复使用次数不变的情况下每次梯度估计噪声很大如果太大更新次数少经验利用率低。多航天器环境单次 rollout 产生的数据是环境步数乘以智能体数量通常一个 mini_batch 可以覆盖 1 到 2 个完整 episode。4.3 从训练曲线判断收敛与构型变换效果判断训练结果不能只看 episode reward。多智能体系统的平均奖励容易被个别表现好的智能体拉高掩盖其他智能体没学好的事实。我习惯同时看三个指标episode reward 滑动平均、所有航天器到目标构型的平均距离、以及碰撞惩罚项。python train.py --config config/hyperparams.yaml --run_dir runs/exp1训练脚本会实时输出这些指标。如果 reward 在涨但平均距离下降很慢说明奖励函数里位置误差权重太低如果距离已经收敛reward 还在缓慢下降通常是推力惩罚在起作用策略在微调轨迹平滑度。借助训练日志可以画出曲线。这里不需要 TensorBoardmatplotlib 画个滑动平均图就行。看曲线时注意区分训练初期 reward 快速上升是正常的说明智能体从随机探索中找到了基本控制策略中期进入平台期是正常的策略在优化细节后期如果出现大幅回退说明超参数或奖励权重有问题直接停掉改配置不要硬跑下去。5. 多航天器编队训练的常见问题与避坑排查多航天器强化学习的坑绝大多数不在算法本身而在环境、奖励和数据分布上。下面几条都是我在类似任务里反复踩过的每一条都是先看到现象、再追根因、最后改配置解决。5.1 现象一episode reward 前期上涨训练到一半突然变成 NaN现象训练曲线前几百个 episode 都正常然后 reward 突然变成 NaN训练被迫中断。重启后换了个随机种子又能跑一段但还是会在某个地方崩掉。原因最常见的是状态或奖励里出现了极大数值经过损失函数回传后梯度爆炸。C-W 方程里的相对位置如果因为动力学发散涨到几千公里归一化因子就不再适用网络输入直接爆掉。另一个常见源是 Critic 的 value 估计过大导致 GAE 出现异常值。解决先检查状态归一化确认相对位置的尺度不会被突破。然后把 PPO 更新里对 advantage 做标准化这一步骤打开也就是减去均值除以标准差。给 log_std 加 clamp 也很有用。如果还崩把学习率降到 1e-4 再试一次。我之前在这上面花了两天时间最后发现问题出在某颗星初始位置被随机到了极其接近领航星相对速度又很大一步积分就把位置推到几十公里外归一化彻底失效。解法很简单初始化时限制相对速度和位置的取值范围。5.2 现象二有一两颗星始终不去目标构型沿轨道方向往复飞行现象训练完成后其中一颗星的位置误差已经收敛到几十米以内另一颗星却还在目标构型附近来回飘轨迹像正弦波一样沿飞行方向振荡。整体 reward 看着还行因为那颗学得好的星把平均值拉上去了。原因共享奖励下的信度分配问题。MAPPO 虽然每个智能体有自己的 Actor但如果 reward 用的是全体智能体奖励的均值个体无法准确判断自己的动作对总奖励的贡献。学得好的星会掩盖学得差的星后者在梯度中收到的信号被稀释了。另一个原因是观测里没有包含自身的目标构型偏移导致所有星学习到的其实是同一套控制策略无法区分布置。解决把奖励改成个体主奖励加小量全局辅助奖励。每颗星的距离误差项用自己的目标偏移计算碰撞惩罚保留全局项。观测里已经包含了目标偏移可以再增加一个 one-hot 的 agent_id 向量让共享网络有机会区分不同星。这类问题排查时不要只看 total reward要分别打印每颗星自己的平均距离误差一眼就能看出是哪颗星在摆烂。5.3 现象三训练曲线很好测试时却变成一团散沙现象训练结束时的评估曲线看着很漂亮每颗星距离误差都降到了 30 米以内。把保存的权重加载进测试脚本后构型完全保持不住几颗星四散飞开有的甚至撞在一起。原因训练过程中 Actor 输出带探索噪声且每个 episode 的初始状态是在固定范围内随机采样的。测试时如果继续使用采样动作策略会在轨迹上抖动而这类由随机策略学出来的策略均值动作往往才是稳定解。另一个原因是训练时初始构型扰动范围太小测试时给了一个不同的初始相对速度策略没见过这类状态自然就崩了。解决测试时使用 actor 网络输出的均值而不是从分布中采样。固定测试脚本的随机种子让每次验证落在同一组初始条件上。如果真实使用场景的初始构型散布比训练时大就在训练环境的 reset 函数里增大初始状态扰动范围。我之前正是靠增加初始速度扰动解决这个问题原来训练时初始速度默认为零测试时给了 0.5 m/s 的初始观测量策略直接失稳。增加扰动范围重新训练后泛化性好了很多。5.4 现象四换台机器训练完全跑不动CPU 和 GPU 的结果对不上现象同一份代码在开发机上用 CPU 训练能收敛拿到 GPU 服务器上训练后 reward 曲线完全不同甚至 NaN或者在装有不同版本 numpy 的机器上连环境初始化都会报形状错误。原因一是全局随机种子没有在环境创建前固定不同机器上多线程运算的顺序不同导致经验轨迹不一致。二是 PyTorch 在 CPU 和 GPU 上的浮点累加顺序不同小数值差异会被 PPO 更新放大。三是 numpy 和 torch 的版本组合不一致导致某些操作返回了不同 dtype。解决在训练入口最开始固定所有随机源包括 Python 的 random、numpy 和 torch设置 torch.manual_seed 之后再创建环境。对于需要严格复现的场景把 torch.use_deterministic_algorithms(True) 打开并设置 torch.set_num_threads(1)。依赖环境写成 requirements-lock.txt精确到次版本号。不要迷信 GPU 一定比 CPU 好多航天器环境规模不大时CPU 单线程加确定性算法的训练结果更容易复现。6. 验证与进阶从仿真曲线到一次完整的编队变换演示验证这套系统最简单的方式是直接用 test.py 加载训练好的权重跑一次从串飞构型到水平圆绕飞构型的完整变换。python test.py --checkpoint runs/exp1/model.pt \ --scenario transform_A_to_B \ --seed 42脚本会把每个航天器的相对轨迹画出来同时输出终端时刻位置误差和碰撞次数。验收标准我习惯这样定位置误差小于 15 米、相对速度误差小于 0.05 m/s、全程无碰撞。满足这三个条件训练才算真正成功而不是只看 reward 曲线。进阶方向有几个值得尝试。第一个是把奖励函数里的推力惩罚拆成平滑项和燃料项平滑项惩罚相邻两个时刻控制量的差值可以进一步抑制震荡。第二个是把安全距离从固定值改成动态值构型变换初期用大安全距离后期逐渐收紧这样既能避免训练前期碰撞又不会让策略因为避碰要求而不敢靠拢。我现在已经养成一个习惯训练结束后不急着调参而是把测试阶段每个智能体的到达误差单独打印出来看是谁拖了后腿。多智能体系统最容易出现的情况就是平均奖励好看、个别智能体摆烂。你拿到这套系统后建议也先用同样的方式验证确认每颗星都真的到达目标构型再决定要不要调奖励权重。希望帮到你。本文还有配套的精品资源点击获取