首页 / 资讯中心 / 文章详情

深度强化学习用于多星区域目标观测规划的建模训练与落地实践

深度强化学习用于多星区域目标观测规划的建模训练与落地实践 ★ FEATURED ARTICLE
简介一套基于深度强化学习的多星对区域目标观测规划项目面向航天任务规划研究人员、强化学习爱好者及Python开发者主要解决多颗卫星协同调度以最大化区域覆盖率的优化问题。包内共492个文件约198MB数据与代码分工明确247个npz、56个fits文件提供卫星轨道与观测数据集27个py与5个ipynb实现算法与实验流程8个csv记录观测日志另有h5/torch/pkl等模型权重文件整体结构覆盖环境模拟、DQN训练、经验回放、策略/价值网络和超参数调优等完整模块。目前已有193人学习下载。通过研读代码与数据可以掌握深度强化学习在航天调度中的落地流程理解如何构建卫星状态与目标区域特征的环境模型并利用DQN等算法实现观测策略的迭代优化其环境模拟器、经验回放缓冲区及训练评估代码可直接复用适合作为课程设计、毕业设计或科研对比的基线项目对开展多星任务规划或类似组合优化问题的研究具有直接参考价值。1. 把“多星对区域目标观测的规划”当序列决策来做这条路到底行不行如果丢给你一个 500 平方公里的区域观测需求再给你一支 12 颗卫星的遥感星座你会立刻撞上一个比“有没有可见窗口”更麻烦的问题几百个候选条带里按什么顺序、分配给哪颗星、侧摆多少度才能把覆盖率拉到最高同时不把星上存储塞爆。这个组合优化问题正是基于深度强化学习算法实现多星对区域目标观测的规划要解决的它把“下一块观测谁”训练成一个策略网络上线后根据当前轨道窗口和剩余待观测网格做在线决策替代传统整数规划那种“规模一大就求不动”的离线求解。这篇笔记适合正在做卫星任务规划、遥感应用调度的算法工程师和仿真验证人员讲清楚建模、训练、落地验证的完整闭环和值得注意的坑。2. 把区域目标观测规划写成 MDP从网格化到奖励函数的四个关键设计2.1 区域目标先转成网格网格尺度决定覆盖率的“分辨率”和计算量多星对区域目标观测规划和传统的点目标调度有一个本质差别点目标可以精确到经纬度而区域目标是一块连续地表“覆盖了没有”必须靠离散网格来定义。常见做法是把目标区域按等间隔经纬度切成行列网格网格中心点作为覆盖判据当卫星的传感器视场覆盖某个网格中心点就认为这个网格被观测到。但等经纬度网格在高纬度地区面积会收缩如果目标区跨纬度大直接用经纬度网格会让覆盖率虚高或虚低。工程上更稳的做法是先用 UTM 或等面积投影把区域转到平面上再切网格每个网格的面积才一致覆盖率统计横跨不同纬度时才具备可比性。网格尺度直接决定预计算量和规划质量这里有一个经验上的取舍关系。以 100 公里乘 100 公里的区域为例0.5 公里的网格会有约 4 万个网格点每颗卫星经过时都要对这 4 万个点做一次视场包含判定预计算量会到千万级1 公里网格是 1 万个点预计算量下降一个数量级10 公里网格只有 100 个点做快速预演很合适但输出结果的精度也基本没法看。我的习惯是先用 10 公里网格跑通训练闭环确认奖励曲线在涨后再把网格缩到 1 公里重新训练不要一上来就上细网格否则一次环境 step 的耗时会直接把你劝退。网格尺度网格数量约可见性预计算复杂度覆盖率分辨率推荐用途0.5 公里4 万个极高高小范围高精度出图1 公里1 万个中等中高常规规划训练10 公里100 个低低算法验证与预演除了网格尺寸还有一个常被忽视的细节网格与条带的关系。卫星一次过境拍到的是一条带状影像覆盖的是连续一排网格不是单个网格。所以规划动作的单位应该是“条带”一个条带是否有效要看它覆盖了多少尚未被覆盖的网格。这个“剩余网格集合”就是状态的一部分奖励计算时也只统计新增网格否则很快就会出现多颗星重复观测同一片区域、覆盖率数字虚高的假象。2.2 动作空间为什么要按“条带序列”来设计把调度写成 MDP 最需要想清楚的是动作怎么表示。给定 12 颗星和一段规划周期每颗星在每个可见窗口内可以侧摆若干个角度每个角度对应一条不同的条带。如果动作定义为“在某时刻把某颗星摆到某个角度开始成像”组合会立即爆炸窗口数量乘侧摆档位乘条带长度序列长度又随卫星数量线性增长。这种结构不适合直接用 DQN 那种单步 Q 值输出更适合用一个序列决策模型逐步生成“下一个条带”。我一般把动作建模成两层第一层选卫星和过境窗口第二层在窗口内选条带起始网格。一个完整规划就是一条决策序列长度等于实际分配的条带数。这么做的好处是自然贴合指针网络这类模型的输出形式——它本来就是从候选集合里逐步挑元素挑过的元素用 mask 屏蔽掉。状态则包含三部分候选窗口矩阵每条轨道对应的可用时间段、剩余未覆盖网格列表、每颗星的剩余存储和电量。三者拼接成特征张量输入到策略网络。这里要注意MDP 的“状态转移”不是传统的物理状态转移而是“挑了一个条带后环境把已覆盖网格从剩余集合中删除并扣掉对应存储”。环境里维护的是一本覆盖账本和一个资源账本每步只做账本更新不需要模拟卫星动力学因为可见窗口和条带几何已经预计算好了。这也是为什么这个任务能跑深度强化学习算法的主要原因单步成本极低可以大规模并行采样。2.3 奖励函数的三段式设计覆盖收益、时间窗代价与负载均衡多星区域观测规划的奖励设计我踩过一个很深的坑只给最终覆盖率一个稀疏奖励训练时覆盖率会在前几千步完全不动因为随机策略几乎没有概率凑出有效覆盖。所以奖励必须拆成三段让智能体每一步都能收到反馈。核心奖励是新增覆盖网格数每一步从环境账本里读新覆盖数作为主要正反馈其次是时间窗代价如果策略选择的条带与已选条带存在侧摆切换时间冲突或者成像时长超出过境可见时长直接扣分第三段是负载均衡代价星上存储超限或电量不足做硬约束扣分。系数上常用的起步组合是新增覆盖权重 1.0时间窗违约权重 0.3负载超限权重 0.5侧摆切换代价 0.05。这个组合有一个关键动机让覆盖收益永远占主导同时让约束代价大到足以阻止非法动作又不至于让网络只顾避罚而不敢行动。如果违约权重超过 1.0策略会趋向于“少做少错”覆盖率停在很低的位置不再增长这是同行经验里最容易出现的失败模式。训练到中后期再把违约权重慢慢提上来让策略在保住覆盖率的同时学会满足约束这就是“reward shaping 退火”。2.4 轨道外推与可见性预计算训练环境与验证环境必须同一套星历来源规划依赖的底层数据是“哪颗星在什么时候能看到哪片网格”。做这部分预计算时常见选择有两种SGP4 配合 TLE 根数做高精度外推或者用 J2 解析模型快速生成轨道。工程上我强烈建议训练环境和验证环境用同一套轨道外推方式否则模型很容易学到某套星历下的时间窗拓扑换一套根数就失效。J2 计算快适合批量生成训练环境SGP4 精度高适合最后评估阶段复现真实运行情况。至少要在数据接口层留一个切换开关不要在两套外推结果之间混用可见窗口。可见窗口的判定参数也直接影响规划难度。卫星传感器半视场角决定地面覆盖带宽度侧摆范围决定单颗星能覆盖的横向跨度常见的遥感星侧摆范围是 ±40 度或 ±45 度最小仰角约束控制观测几何质量太低的角度影像畸变严重。把这些参数列成一张配置表放在环境类顶部比散落在代码各处好维护得多。预计算完成后就把可见窗口转成一个三维张量卫星数乘候选条带数乘时间片数后续所有调度都在这个张量上做序列决策不再回到轨道计算层。参数常见取值对规划的影响传感器半视场角1°5°决定条带覆盖宽度影响网格覆盖判定侧摆范围±40° / ±45°扩大单星候选条带数显著增大动作空间最小观测仰角15°25°过滤低质量可见窗口减少无效动作星上存储上限按任务配置限制连续观测时长影响序列长度3. 用深度强化学习算法训出序列决策指针网络加 PPO 的最小可跑方案3.1 为什么我没有选 DQN而是选了指针网络加 PPO如果你的第一个念头是“用 DQN 打一套观测规划”多半会在动作维度上卡住。DQN 要求动作空间固定且有限但条带序列的长度随卫星数量和网格分布变化而且候选条带数动不动就是几千Q 网络输出层根本没法定义。策略梯度类方法天然适合可变长度的序列生成其中注意力机制加持的指针网络效果最稳定。指针网络的做法是把状态编码成一组节点特征解码时每一步用注意力打分从候选节点里选一个选过的节点用 mask 屏蔽这正好匹配“逐步挑条带”的决策逻辑。训练算法我一般配 PPO而不是原版 REINFORCE。原因有两条一是 PPO 用重要性采样和 clip 机制限制策略单步更新幅度训练更稳网格奖励噪声很大时尤其重要二是 PPO 自带 actor-critic 结构critic 学习覆盖率预期值作为 baseline能有效降低策略梯度方差。REINFORCE 在这类稀疏奖励任务是出了名的方差大不加 baseline 很难收敛。所以架构上就是actor 用指针网络生成条带序列critic 用一个小 MLP 把状态编码回归到预期累计奖励。3.2 指针网络的核心实现Transformer 编码加 Masked 解码打分实际写代码时编码器我直接用了 TransformerEncoder解码时不用自回归那套逐词生成而是用带 mask 的注意力打分。下面是去掉了工程封装后的核心片段能跑通一次前向import torch import torch.nn as nn class PointerNet(nn.Module): def __init__(self, feat_dim, d_model256, n_heads8, layers3): super().__init__() self.embed nn.Linear(feat_dim, d_model) self.encoder nn.TransformerEncoder( nn.TransformerEncoderLayer(d_modeld_model, nheadn_heads, batch_firstTrue, dropout0.1), num_layerslayers ) self.Wq nn.Linear(d_model, d_model, biasFalse) self.Wk nn.Linear(d_model, d_model, biasFalse) self.logit_clip 10.0 def decode_step(self, node_embeds, context, mask): # node_embeds: [B, N, d_model] 所有候选条带节点 # context: [B, d_model] 当前规划上下文 # mask: [B, N] True 表示该候选条带不可选 q self.Wq(context).unsqueeze(1) # [B, 1, d_model] k self.Wk(node_embeds) # [B, N, d_model] logits torch.matmul(q, k.transpose(-2, -1)) / (node_embeds.shape[-1] ** 0.5) logits logits.squeeze(1).masked_fill(mask, -1e9) logits self.logit_clip * torch.tanh(logits / self.logit_clip) return logits这个片段里有两个容易被忽略的点。一是 masked_fill 用了 -1e9 而不是负无穷再经过 tanh 限制输出范围可以有效避免 logits 极端值在 softmax 阶段产生 NaN二是 logit_clip 是为了控制策略熵让网络在训练初期不会因为极端得分分布而陷入确定性策略。实际使用时node_embeds 由编码器统一生成context 是当前已选条带序列的汇总向量mask 则由环境根据已覆盖网格、星上剩余资源和存储实时构造。3.3 训练循环环境采样、PPO 截断损失与逐步解码训练部分的核心是采样一条完整规划轨迹然后计算 PPO 损失。下面是一个 rollout 的简化流程突出“环境提供 mask 和奖励网络提供动作分布”的分工def rollout_one_episode(env, model, horizon64): state env.reset() log_probs, rewards, dones [], [], [] for t in range(horizon): feats, mask env.get_state_features(state) node_embeds model.encode(feats) logits model.decode_step(node_embeds, state[context], mask) dist torch.distributions.Categorical(logitslogits) action dist.sample() log_probs.append(dist.log_prob(action)) state, reward, done env.step(action.item()) rewards.append(reward) dones.append(done) if done: break return torch.stack(log_probs), torch.tensor(rewards, dtypetorch.float32), donerollout 代码里的 horizon 决定单条轨迹最多分配多少条带一般设为卫星数量的 3 到 5 倍给策略留出足够决策空间mask 由环境生成已经观测过的网格对应的条带会被屏蔽掉避免重复覆盖。env.step 内部除了更新覆盖账本还要检查存储和侧摆时间冲突如果违反约束则返回一个负收益并把该动作计入日志。训练时我一般开多个环境并行采样每个环境持有不同的目标区域和随机轨道根数这样采样的轨迹多样性足够支撑 PPO 更新。PPO 损失计算时critic 网络对每个状态输出价值估计用于计算优势函数actor 损失要带上 clip 项把当前策略与旧策略的概率比限制在 0.2 以内。这里有一个细节每次更新用 5 个 epochbatch size 64学习率 1e-4熵系数从 0.01 开始训练到覆盖率平台期再降到 0.001避免策略过早熵太小而丧失探索能力。3.4 超参推荐和一个 200 行的可跑通基线把上面所有部分合起来一个最小可跑的深度强化学习观测规划项目在 200 行左右结构是环境类负责网格化、覆盖判定和约束检查网络类负责编码解码训练脚本负责采样更新。这个规模对新手最友好可以很快跑通一个 10 颗星、400 个网格的小场景。我常用的超参组合如下表覆盖“可用性优先”和“精度优先”两档。参数可用性优先精度优先d_model128256Transformer 层数23学习率3e-41e-4并行环境数832horizon3264PPO clip0.20.15熵系数0.010.005拿到代码包第一件事不要急着训练先解压看一下依赖清单和数据目录把环境里的轨道参数、网格参数和真实任务参数对齐。这个习惯能避掉后面至少一半的排错时间。如果训练三五小时后覆盖率曲线还在 10% 附近波动不要加网络层数先回去检查奖励是不是太稀疏、mask 是不是把合法条带屏蔽了这两件事比模型容量更容易导致不收敛。4. 深度强化学习规划落地避坑覆盖不涨、换星历翻车与 mask 溢出4.1 覆盖率长期停留在个位数先查奖励地形不要一上来调网络这种现象几乎每个做过这类项目的人都会遇到训练几千步覆盖率纹丝不动像被冻住了一样。我在早期做这个题目时也卡在这里一度怀疑是网络容量不足加了层数、换了激活函数都没有动静。后来排查到环境里的真实情况是区域网格切得太细可见窗口又稀疏随机策略采样到的条带几乎不与目标区域相交导致绝大多数步的奖励是零稀疏到一个正样本都采不到策略梯度自然导不动网络。原因清楚了解决办法就是把奖励从“最终覆盖率”改成“新增覆盖网格数加阶段完成加成”。环境端维护一个已覆盖集合每步执行后统计新增网格数量立刻返回给策略而不是等整条轨迹结束后才给一个覆盖率数字。另外网格去重必须在环境端做不要在奖励函数里做裁剪否则同一网格被多颗星重复覆盖时奖励会先虚高、后骤降训练曲线会出现莫名其妙的锯齿。4.2 训练好的模型换一组轨道根数就翻车典型域偏移问题这是多星观测规划里最常见的“部署翻车”场景训练时用的是圆轨道加 J2 外推模型部署时切换成真实 TLE 或大偏心率轨道覆盖率直接掉二十个百分点。根因在于策略网络把轨道根数对应的可见窗口拓扑当作了一种“模式”来记忆而不是真正学到了“根据当前窗口结构做调度”的能力。训练环境里轨道根数永远不变模型就会偷懒去背那条固定路径。解决方法是训练时对轨道参数做随机扰动这属于域随机化的标准动作。具体做法是把轨道高度在正负百分之十范围内变化、倾角在正负五度范围内变化、升交点赤经随机打散让每个训练环境都有一组不同的可见窗口矩阵。同时建议训练环境用 J2 解析外推验证阶段用 SGP4 加真实 TLE 重算窗口两套结果做一致性对比。如果干扰幅度偏小验证时照样暴露泛化问题。4.3 mask 用 -inf 填充导致 NaN数值稳定性是黑匣子训练到某个阶段 loss 突然变成 NaN日志里出现 inf这种情况在注意力模型里很常见不用怀疑是学习率太大。问题基本都出在 mask 填充用的负无穷上。当某个候选节点因为已分配而需要被屏蔽时常见的模板写法是logits logits.masked_fill(mask, -float(inf))但一旦遇到整行 mask 全真或者遮蔽值参与了 softmax 的指数计算就会产生 NaN。用 -1e9 加 tanh clip 只能缓解更稳的做法是在解码时单独实现对数值的 masked log softmax先减去最大值再算 logsumexp把中间量控制在安全范围。def masked_log_softmax(logits, mask): logits logits.masked_fill(mask, -1e9) logits logits - logits.max(dim-1, keepdimTrue).values log_probs logits - torch.logsumexp(logits, dim-1, keepdimTrue) return log_probs这段代码把数值稳定性分成两步处理先减最大值保证指数部分不超过零再用 logsumexp 做归一化。代码里的 mask 是布尔张量为 True 的位置被排除在候选集之外。如果训练时发现输出分布过度集中也可以在这个函数里加大 logits 的缩放系数等价于降低策略熵但不建议直接调节温度系数到极端值。4.4 智能体学会“刷中间奖励”shaping 权重失衡会让目标被架空覆盖率涨到百分之四十左右开始停滞但智能体还在不停做动作这是 reward shaping 权重失衡的典型症状。奖励函数里中间奖励权重如果远大于真实目标项策略就会找到漏洞反复选择一个已经被覆盖过但仍能产出少量正奖励的地块因为每个 step 都能拿到正的 shaping 收益累计起来甚至比完成整个区域观测的最终收益更划算。这个问题在加中间奖励时极其容易埋下隐患。我的处理原则是中间奖励系数压到主覆盖收益的十分之一以下并且覆盖产出只在“新增网格”时结算已经覆盖过的网格再次被选时奖励恒为零。训练到中后期还要把 shaping 系数按计划衰减让最终覆盖率逐步成为唯一优化目标。否则你最后看到的漂亮曲线可能只是网络在迎合环境的一个 bug不是真正学会了多星对区域目标观测规划。5. 验证模型做得对不对用 CP-SAT 当裁判用星历扰动测泛化5.1 什么才算真正收敛覆盖率上限和求解速度要同时看我习惯在每个训练周期结束时把同一个场景交给 OR-Tools 的 CP-SAT 求解器做一个同规模整数规划求解得到覆盖率上界作为基准线。CP-SAT 在几十颗星、几百个网格的规模下能在一个限定的时间内给出一版可行解虽然不一定是最优但足够当一个参照系。如果 DRL 策略的覆盖率能达到 CP-SAT 上界的九成以上且单步决策时间在毫秒级这个模型才真正有说服力。如果 gap 超过两成先别急着把锅扣给网络结构回到奖励和 mask 排查。这种做法还会暴露一个容易被忽视的问题DRL 模型训练时的覆盖率是在环境里去重统计的和 CP-SAT 的目标口径必须一致否则两个数字根本不具可比性。我一般会在评估脚本里输出三个指标去重覆盖率、累计时间窗违约次数、平均单条轨迹规划耗时三张表放一起才能说明模型值不值得用。5.2 用星历扰动和区域形状变化验证泛化能力验证泛化的另一个低成本做法是批量生成扰动场景。我会在一百个随机环境里做一次评估每个环境的轨道高度、倾角和目标区域摆放位置都不同然后统计覆盖率分布的均值和标准差。标准差如果超过五个百分点说明策略对星历敏感鲁棒性不达标。这个测试不需要重新训练只是把训练好的策略放到新环境里推理跑一轮很快但信息量非常大。细心点还能顺便发现某些极端场景里 mask 是否漏掉了合法条带这种 bug 在单一验证场景里根本看不出来。验证项方法参考阈值覆盖率上界 gap与 CP-SAT 可行解对比gap 小于 10%星历泛化100 个随机轨道根数环境覆盖率标准差小于 5%约束满足统计违约次数平均每轨迹小于 1 次决策耗时单条轨迹推理计时毫秒级5.3 我自己的交付习惯留一条后路别让模型无保护上线最后分享一个我交过学费换来的习惯训练好的策略网络部署到规划系统时必须保留一层越控逻辑。模型给出条带序列后先用一套规则做安全校验包括可见窗口合法性、存储上限、侧摆切换时间任何一条过不去就回退到次优候选条带。不要迷信一个纯 DRL 模型可以端到端直接跑任务观测规划的约束多策略网络学了大部分正确模式但偶尔会给出一个明显不合法的组合。这层越控消耗极少却能让系统在真实任务里稳很多。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站