深度强化学习驱动多星观测规划:从MDP建模到PPO落地
2026/9/13 14:36:27 网站建设 项目流程

简介:多星对区域目标观测规划是航天任务中的典型复杂决策问题,该项目包提供了基于深度强化学习(DRL)的完整实现方案,适合航天工程、运筹优化及强化学习方向的开发者与研究者参考。项目围绕环境模拟器、深度Q网络(DQN)、经验回放缓冲区、策略网络与价值网络等模块展开,并配套训练、评估及超参数调优代码,帮助读者理解如何通过DRL解决多星调度中的状态决策与覆盖率优化问题。资源共492个文件,以npz数据文件、gz压缩数据、fits天文数据为主,同时包含Python脚本、CSV表格、ipynb笔记等,压缩包约198.33MB。内容亮点在于Fermi、Integral、Swift等多卫星观测数据集的整合处理,对于研究真实观测场景下的规划算法落地具有参考意义。目录结构清晰,数据与代码分层明显,便于拆解学习。目前已有约192人学习浏览,适合具备Python基础和初步深度学习知识、希望借助完整案例掌握深度强化学习建模与训练流程的进阶学习者。

1. 基于深度强化学习的卫星观测规划,为什么值得重做一遍

多星对区域目标观测规划,本质上是一个带时间窗口、资源约束和收益最大化的组合优化问题。传统做法是把可见时间窗口切碎,建模成整数规划或用遗传算法、粒子群算法去搜,但在卫星数量超过十颗、目标区域动态变化、应急观测需求插队的场景下,重规划耗时太长,往往算完窗口已经过期。深度强化学习把这个问题从“离线寻优”改写成“序贯决策”:让智能体观察当前卫星位置、目标区域覆盖情况和剩余能量,逐步决定“哪颗星在什么时间以什么角度观测哪块区域”。训练好的策略网络推理一次只需毫秒级,可以直接嵌入任务管控系统做滚动重规划。这篇文章会从 MDP 建模、PPO 实现、仿真环境搭建到工程部署,把完整路径拆开讲,适合已经在做任务规划但想引入深度学习算法的工程师,也适合刚接触强化学习、想找一个真实落地点入手的算法同学。

2. 多星区域观测问题的 MDP 建模:状态、动作、奖励怎么设计

2.1 为什么不能直接把原问题丢给强化学习

常见的误区和风险需要先声明:直接把卫星轨道、目标多边形、云量预报全部塞进神经网络,然后把覆盖率当奖励,大概率训练不收敛。原因是原问题里同时存在“选哪颗星”的离散决策和“侧摆角多少度”的连续决策,还叠加了时间连续性——上一颗星的观测结果会影响后续星的能量余量和存储余量。强化学习适合处理的是“状态转移有规律、奖励稀疏但可塑形”的问题,所以第一步不是选算法,而是把原问题压缩成一个可解的 MDP。

我一般会先做三件事:把区域目标离散成条带(strip),把连续时间离散成可见时间窗口的起止时刻,把每颗星的资源状态压缩成几个标量。这样状态空间就从“轨道根数+多边形顶点”变成“固定维度的向量”,网络才能稳定学习。

2.2 状态空间的具体定义

状态s_t要覆盖三部分信息:卫星自身状态、目标任务状态、环境约束状态。以常见的 6 颗敏捷卫星、20 个区域目标为例,可以这样定义:

# 状态向量构造(伪代码) def build_state(satellites, targets, time_idx): state = [] for sat in satellites: state.extend([ sat.energy_ratio, # 剩余能量/总能量 sat.storage_ratio, # 剩余存储/总存储 sat.current_roll_angle, # 当前侧摆角(度) sat.next_visible_target_cnt # 未来1小时内可见目标数 ]) for tgt in targets: state.extend([ tgt.covered_ratio, # 已覆盖面积/总面积 tgt.remain_visible_windows, # 剩余可见窗口数量 tgt.urgency_level # 紧急程度(1-5) ]) state.append(time_idx / total_time) # 任务推进进度 return np.array(state, dtype=np.float32)

这段代码的关键点在于:状态必须是归一化的标量,而且是“当前决策时刻”的全局快照。不要放轨道六根数这种原始数据,网络学不动;也不要放云量图像,除非你打算用视觉模型,这会大幅增加训练复杂度。energy_ratio 和 storage_ratio 是约束的核心,模型需要学会“省着用”而不是“一次用完”。time_idx 归一化到 0~1,是为了让网络在不同任务时长下都能泛化。

2.3 动作空间:离散和连续混合怎么处理

动作空间的设计直接影响训练难度,我的选择是将动作拆成两段式决策:先选卫星,再选观测动作。第一段用离散动作(从 N 颗星里选一颗),第二段用连续动作(侧摆角调整量)加离散决策(是否执行观测)。

PPO 本身支持混合动作,但实现上更简洁的做法是:把“卫星编号 × 执行/放弃观测”合并成一组离散动作,侧摆角单独作为连续动作输出。这样构造的动作为:

action = { "satellite_id": 3, # 选择第3颗卫星 "execute_observe": True, # 本次执行观测 "roll_adjust": -2.5 # 侧摆角调整量,单位度 }

这里要注意 roll_adjust 的范围必须限制在卫星机动能力的物理范围内,比如 ±10 度。如果超出范围,要在环境里做 clamp 并给一个小惩罚,否则智能体会学到“反正动作会被截断,不如大胆输出”的坏习惯。离散动作的数量是卫星数乘以 2(执行/不执行),通常不超过几十个,适合用 Categorical 分布采样。

2.4 奖励函数:覆盖率为主,惩罚项辅助

奖励函数是整个 MDP 里最影响收敛质量的部分。只用覆盖率做稀疏奖励,前期智能体几乎拿不到信号;只加权重的密集奖励,智能体会钻漏洞,比如反复调整侧摆角刷时间分。

我的设计思路是四项叠加:

reward = w1 * coverage_gain + w2 * (1 - energy_cost_ratio) - w3 * roll_penalty - w4 * conflict_penalty

coverage_gain 是本次观测新增覆盖面积占目标区域总面积的比例,是主奖励;energy_cost_ratio 是本次动作消耗能量占卫星总能量的比例,引导省电;roll_penalty 是侧摆角变化量的绝对值,抑制不必要的频繁机动;conflict_penalty 是当两颗卫星同时指向同一区域时给的小惩罚,避免动作冲突。权重的初始推荐值是 w1=1.0, w2=0.3, w3=0.05, w4=0.1,后续可以根据训练曲线微调。这个公式的关键是 coverage_gain 必须是“新增”覆盖,而不是“累计”覆盖,否则智能体会反复观测同一块区域刷分。

3. 算法选型与训练架构:PPO 是默认起点,但不是唯一解

3.1 为什么 PPO 适合这类任务

深度强化学习算法里,DDPG、TD3、SAC 这类 actor-critic 方法在连续控制任务上表现好,但多星观测规划本质上是“离散选星 + 连续调角度”的混合动作任务,DDPG 处理离散动作天然不如 PPO 自然。DQN 可以处理离散动作,但连续侧摆角只能离散化,精度受粒度限制。PPO 的优势在于:

  • 支持离散 + 连续混合动作,两个头共享特征提取层
  • 训练稳定性好,对超参数不那么敏感
  • 可以在线收集样本,适合在仿真环境里滚动训练
  • 推理时只走 actor 网络,单次前向计算耗时低于 10ms,满足实时重规划

PPO 的核心是 clipped surrogate objective,它限制了每次策略更新的幅度,避免步子太大导致策略崩坏。这个特性在观测规划里很重要,因为奖励函数本身带了多个加权项,训练早期容易出现某个惩罚项突然过大。PPO 在这种非平稳奖励分布下比 TRPO 更容易调参。

3.2 网络结构:共享底层,双头输出

模型建议用共享底层加双头输出的结构。底层用两层全连接网络提取状态特征,然后分开两个头:一个输出离散动作的 logits,另一个输出连续动作的均值和对数方差。代码骨架如下:

import torch import torch.nn as nn class PolicyNet(nn.Module): def __init__(self, state_dim, n_sat_actions, roll_scale=10.0): super().__init__() self.fc1 = nn.Linear(state_dim, 256) self.fc2 = nn.Linear(256, 256) # 离散动作头:卫星选择 + 执行/放弃 self.discrete_head = nn.Linear(256, n_sat_actions) # 连续动作头:侧摆角调整量 self.roll_mean = nn.Linear(256, 1) self.roll_log_std = nn.Parameter(torch.zeros(1)) self.roll_scale = roll_scale def forward(self, x): x = torch.relu(self.fc1(x)) x = torch.relu(self.fc2(x)) logits = self.discrete_head(x) roll_mean = torch.tanh(self.roll_mean(x)) * self.roll_scale return logits, roll_mean, self.roll_log_std

这个设计的两个要点:roll_mean 输出后经过 tanh 乘 roll_scale,把动作限制在 ±10 度范围内;log_std 作为可学习参数而不是网络输出,可以让智能体自己决定探索力度。state_dim 要和环境返回的状态维度严格对齐,推荐先打印一次 state.shape 确认。

3.3 训练循环与关键参数表

训练循环采用标准 PPO 框架,关键区别在于环境交互方式——每回合是一次完整的观测任务规划(比如 6 小时任务),而不是单步决策。每步智能体决定一颗星的动作,环境推进到下一个决策时刻。

# PPO 训练主循环伪代码 for update in range(total_updates): trajectories = [] for episode in range(num_envs): state = env.reset() done = False while not done: action, log_prob = select_action(policy, state) next_state, reward, done, info = env.step(action) trajectories.append((state, action, log_prob, reward, done)) state = next_state # 计算 GAE 优势 advantages = compute_gae(trajectories, critic, gamma=0.99, lam=0.95) # PPO 更新策略 for _ in range(ppo_epochs): update_policy(trajectories, advantages, clip_eps=0.2, lr=3e-4)

推荐的核心超参数如下表,这是多组实验里比较稳的起点,不建议直接照搬学术默认值:

参数推荐值说明
gamma0.99折扣因子,任务长度 6 小时内够用
GAE lambda0.95偏差方差折中,偏小会更稳
clip_epsilon0.2策略更新幅度上限
actor 学习率3e-4用 Adam,比默认 1e-3 稳
critic 学习率1e-3可以独立设置,建议比 actor 略高
ppo_epochs10每条样本重复更新次数
mini_batch_size4096从经验池中采样的小批量
entropy_coef0.01熵正则系数,后期可衰减到 0.001

3.4 为什么标准 PPO 也需要改:动作掩码

多星观测规划里有个常见问题:不是所有动作在任意时刻都合法。比如某颗卫星当前没有可见目标,选择它执行观测就是非法动作;某块区域已经完成全覆盖,再观测它没有意义。处理方式是在离散动作头加掩码(action mask),把非法动作的 logits 置为负无穷。

这个改动对收敛速度的影响很大。不加掩码时,智能体要大量试错才能学会“避开不可见目标”;加了掩码后,探索空间直接砍掉一大半。实现时只要在采样前把 mask 加在 logits 上,损失函数不需要变。这个细节最能体现“深度强化学习算法落地的工程经验”,比堆模型容量有效得多。

4. 仿真环境搭建与训练数据生成:不要等卫星真的上天再调参

4.1 仿真器的最小实现

训练深度强化学习算法需要成千上万次环境交互,真实卫星任务系统显然不支持,所以必须有一个轻量级仿真器。最小实现需要四件事:轨道计算(用 SGP4 或简化 J2 模型)、可见窗口计算、覆盖判定、资源消耗模拟。不要一上来就上高精度 STK 联合仿真,训练速度会慢到无法迭代。

我的做法是写一个 Gym 风格的环境类:

import numpy as np from gym import Env, spaces class SatObsPlanningEnv(Env): def __init__(self, config): self.satellites = config["satellites"] self.targets = config["targets"] self.time_step = 10 # 决策间隔,单位秒 self.action_space = spaces.Dict({ "sat_id": spaces.Discrete(len(self.satellites)), "execute": spaces.Discrete(2), "roll": spaces.Box(-10, 10, shape=(1,)) }) self.observation_space = spaces.Box(-1, 1, shape=(state_dim,)) def step(self, action): # 1. 校验动作合法性 # 2. 计算本次观测的覆盖增量 # 3. 扣减能量、更新存储 # 4. 计算奖励 # 5. 推进时间 return next_state, reward, done, info

覆盖判定这一步是性能瓶颈。常见做法是把区域目标网格化,每个网格点记录是否被覆盖,然后判定观测条带覆盖了哪些网格点。网格大小直接影响精度和速度,我一般取目标区域短边长度的 1/50,兼顾精度和训练速度。网格太粗会让奖励函数对侧摆角变化不敏感,太细则训练一回合要跑几秒。

4.2 训练数据的多样性与课程学习

深度强化学习算法很容易过拟合到单一场景。如果训练时卫星轨道和目标区域固定不变,测试换一组数据性能会大幅下降。解决办法是随机化环境参数:卫星轨道摄动、目标位置与面积、可见窗口时长、初始能量。每回合 reset 时重新采样,让智能体学到策略而不是背答案。

更有效的手段是课程学习(curriculum learning):先在小规模问题(3 颗星、5 个目标)上训练,收敛后逐步增加难度。这个过程可以手动控制,也可以参考自动课程学习的做法:根据最近 100 回合的平均覆盖率自动调整目标数量。实际项目中,我在 6 颗星、20 个目标场景下直接训练很难稳定超过 70% 覆盖率;先从 3 颗星开始,迁移到 6 颗星后 200 回合内就能达到 75% 以上。

4.3 对比基线:贪心、遗传算法和粒子群算法

单纯看训练曲线无法判断算法好坏,需要和经典方法对比。常见做法是用同一个仿真环境实现三个基线:

  • 贪心算法:每步选覆盖率提升最大的可用卫星动作
  • 遗传算法:以观测序列为个体,交叉变异迭代固定代数
  • 粒子群算法:把每个观测窗口的开始时间当作粒子位置,速度更新迭代

需要注意对比时要统一算力上限。遗传算法和粒子群算法给 60 秒计算时间,深度强化学习策略只允许单次前向推理。这样才能体现深度强化学习在“规划时效性”上的优势,而不是单纯追求覆盖率最高——覆盖率上遗传算法往往会更高,但算得太慢没有实战价值。

评估维度至少包括三个:覆盖率、规划耗时、能量均衡度。能量均衡度用各卫星剩余能量的标准差衡量,标准差越小说明算法不会把资源集中压在一两颗星上。

5. 从仿真到落地:部署方式、验证技巧和三个容易踩的坑

5.1 导出策略并封装成推理服务

训练收敛后,把 actor 网络导出为 TorchScript 或 ONNX,然后封装成一个无状态推理函数。输入是当前状态向量,输出是动作。部署时注意环境的状态向量构造逻辑必须和训练时完全一致,包括字段顺序和归一化方式。一个常见的生产级做法是:

@torch.no_grad() def plan_action(state_dict): state = build_state_identical_to_training(state_dict) state_tensor = torch.from_numpy(state).unsqueeze(0) logits, roll_mean, _ = policy_net(state_tensor) mask = build_action_mask(state_dict) logits = logits.masked_fill(~mask, -1e9) sat_action = logits.argmax(dim=-1).item() # 推理时用贪心 roll_angle = roll_mean.item() return {"sat_id": sat_action, "roll": roll_angle}

推理时不需要采样,直接取 argmax 和均值,稳定性更好。如果想让输出更平滑,可以加一个滑动窗口,对最近几帧的侧摆角做指数平均。

5.2 在历史任务数据上做回放验证

部署前最有效的验证方法不是算覆盖率,而是在历史任务数据上做回放。把过去一个月的真实任务输入(包括卫星状态和目标区域)逐一喂给策略,比较“实际执行的观测计划”和“算法建议的计划”之间的覆盖率差和能耗差。回放过程要特别关注异常样本:云量变化导致窗口缩短、临时插入紧急目标、卫星故障降级。策略网络在这些场景下的行为决定了它能不能真正上线。

如果回放中发现某个约束被突破,不要急着调奖励权重,先检查是否在环境建模时遗漏了该约束。模型对“环境里根本没有的约束”是无能为力的。

5.3 三个容易踩的坑

第一个坑是奖励函数被破解。比如能量惩罚项权重太低时,智能体会学会“为了增加覆盖率不惜耗尽所有卫星能量”,因为覆盖率奖励远大于能量惩罚。解决办法不是调大权重,而是给能量低于阈值加一个硬性终止条件,让该回合提前结束并且不给额外奖励。终止条件比权重惩罚更有效。

第二个坑是训练和推理状态分布不一致。训练时状态是随机初始化的任务集合,推理时任务可能高度相似(比如连续几天观测同一片区域),导致网络输入分布偏移。对策是训练时加入一小部分固定场景反复出现,让网络对“见过但不常见”的状态保持稳定输出。

第三个坑是 PPO 的 value loss 不下降但 policy loss 也不动了。这通常是 critic 网络容量不足或输入状态缺少关键信息。先确认价值函数是否能看到全部相关状态,再看是否需要对奖励做 scale(除以最大单步奖励量级)。奖励太大或太小都会让 critic 训练不稳定。

5.4 一个可用的小技巧:把规划结果转成排程表并可视化验证

最后分享一个实战中很实用的验证技巧。把策略输出的动作序列转成标准排程表,格式为“卫星编号、目标编号、开始时间、结束时间、侧摆角”,然后用甘特图可视化。人眼对时间冲突的判断比任何指标都直观,能快速发现“两颗星同时观测同一目标”“观测时间过短低于传感器最小驻留时间”这类问题。

排程表同时是业务系统需要的最终交付格式,建议写成标准 CSV,字段名固定。这一步虽然看起来跟算法无关,但往往是决定深度强化学习方案能否被工程团队接受的关键——业务人员不会看训练曲线,但能看懂甘特图。做可视化这一步,比多调 5% 的覆盖率重要得多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询