ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

卫星调度用多智能体强化学习stk11与Python实战

卫星调度用多智能体强化学习stk11与Python实战 简介这是一套基于Python与STK11的多智能体强化学习卫星调度实验项目面向人工智能、自动化、电子信息等专业的高校学生与科研人员适合用于毕业设计、课程设计或科研初期立项。项目代码完整、通过运行测试并提供详细设计文档与实验报告既可直接复现也可在其基础上开展算法改进。压缩包共203个文件约79.62MB主要包含Python源码、CSV实验数据、PNG结果图表、模型权重pth、STK场景与飞行器文件sn3/sa/sc等以及Markdown和Word说明文档目录层级清晰便于按模块查找。已有150人学习下载。从数据预览看内置多组不同规模与增强后的调度实验数据如MRL_data_400_1000_augmented.csv及lab系列数据可支撑多智能体强化学习的训练、对比与结果复现配套设计资料详细梳理了建模思路、训练流程与评估方法下载后遇到配置或运行问题还可远程交流能帮助初学者快速上手进阶。1. 卫星调度用多智能体强化学习先把问题拆对很多人拿到这个标题时第一反应是“调度算法用什么”然后扎进PPO或QMIX的调参里实际做完一轮才意识到整条链路里最容易卡住人的是stk11和python之间的数据流转以及可见窗口的坐标与时间对齐。这个实验包做的事可以概括成一句话用stk11算出高精度的卫星对地可见窗口用python做多智能体强化学习的建模与训练最后把训练好的调度方案拿回stk11验证。它面对的是遥感星座里常见的“多颗卫星争抢多个观测任务、资源有限”的组合优化问题比单星调度多出的难点在于智能体之间互相竞争又彼此协作。适合刚入门航天任务规划和强化学习的工程师也适合已经在跑单环境RL、想把实验扩展到多智能体场景的人。读完应该能自己动手搭一套从场景仿真到训练评估的小型实验。2. 用pythonstk11批量生成可见窗口数据2.1 stk11负责算窗口python负责批量跑卫星调度的决策对象不是目标本身而是“卫星和目标之间的可见时间窗”。窗口受轨道预报精度、传感器视场角、地球遮挡、光照条件甚至侧摆角限制这些在stk11里会被严格建模。python生态里的pyorbital、skyfield这类轨道计算库适合快速原型但把手伸到分钟级调度实验时J2摄动、地形遮蔽和传感器约束往往算不齐肉眼也很难立刻发现错误等训练出一个花哨的策略后才发现窗口本身就是错的返工成本非常高。所以这类工程的通用架构是stk11负责“预计算可见窗口”python负责“把窗口加工成强化学习环境的观测和奖励”。预计算意味着调度决策发生在窗口已经算好的基础上决策时不再实时调用轨道预报。拿典型场景说一个中等规模的遥感星座仿真每天窗口数可能上万条。如果训练过程每步都查询STK的Access单次episode就会慢一个数量级。因此我会把STK当作离线数据源而不是在线服务先生成全量可见窗口CSV再在python里做裁剪、排序和编码。标题里提到的完整源码、详细设计资料、报告和数据在这类工程里通常就分别对应src/、docs/、results/和data/四个目录。拿到源码包先不要急着跑算法先把data/目录翻一遍看看窗口数据是从哪个场景、哪种坐标和时间系统导出的再决定环境脚本怎么接。2.2 用Connect接口在python里加载场景并跑Accessstk11为自动化提供了一条Connect通信通道STK进程监听一个TCP端口外部程序发送文本命令STK执行后回ACK。常见做法是把监听端口开在本地python用socket发送命令即可。python环境配置完成后的第一件事不是import一堆库而是确认STK的Connect端口开着、场景文件路径没中文和空格问题。下面这段代码是我常用的连接函数它在每批命令发送后等待ACK保证命令顺序执行避免STK的命令队列出现异步错乱import socket class StkConn: 通过STK Connect通道驱动STK11的最小客户端 def __init__(self, hostlocalhost, port5001, timeout15): self.sock socket.create_connection((host, port), timeouttimeout) def send(self, cmd: str) - str: # STK要求每条命令以换行结束 self.sock.sendall((cmd \n).encode(utf-8)) buf b while not buf.endswith(bACK): data self.sock.recv(4096) if not data: raise ConnectionError(STK Connect连接已断开) buf data return buf.decode(utf-8).replace(ACK, ).strip() def close(self): self.sock.close()初始化场景时先加载已在STK GUI里做好的星座场景文件再统一设时间起点。这里用Load命令而不是从python重建所有卫星轨道是因为在命令行里手写轨道根数容易因单位、参考系填错而得到完全不同的窗口conn StkConn() # 加载已有场景obs.scenario里已包含卫星星座和区域目标 conn.send(Load / C:/work/python_stk_marl/scenario/obs.scenario) conn.send(SetEpoch 1 Jun 2024 04:00:00.00) conn.send(SetAnimationTime 1 Jun 2024 04:00:00.00)SetEpoch后面的参数是UTCG格式小时数必须按24小时制写这是STK场景时间轴的对齐锚点直接影响后续每条窗口的起止时间。python安装和基础环境配置完成后最容易踩的坑不是python代码本身而是这里的时间格式和坐标参考系不一致。场景加载完成后可以用Access把卫星和区域目标做可见性计算再对Access报告执行导出CSV得到data/access_windows.csv。把导出动作固化成Connect脚本命令也可以但不同版本命令差异较大第一版实验先用GUI导出最稳定。2.3 窗口数据的清洗过滤短窗口、转换UTCG时间STK导出的Access报告通常长这样前几列是卫星名和目标名中段是可见起点/终点最后是总的可见时长Satellite1, Target_23, 1 Jun 2024 04:10:23.41, 1 Jun 2024 04:13:05.02, 161.61 Satellite1, Target_23, 1 Jun 2024 05:01:43.90, 1 Jun 2024 05:04:12.30, 148.40 Satellite2, Target_11, 1 Jun 2024 04:02:11.67, 1 Jun 2024 04:03:20.15, 68.48拿到CSV后的第一件事不是建模而是清洗。常见做法是过滤掉低于最小观测时长的短窗口并按目标ID排序因为时间窗太短的窗口即使被调度上也常被姿态机动时间和数据回传时间吃掉没有资格进入训练状态空间import pandas as pd win pd.read_csv(data/access_windows.csv, names[sat_id, target_id, t_start, t_end, duration_s], skiprows1) # STK返回的UTCG字符串转成python datetime供后续按时间轴对齐 win[t_start] pd.to_datetime(win[t_start], format%d %b %Y %H:%M:%S.%f) win[t_end] pd.to_datetime(win[t_end], format%d %b %Y %H:%M:%S.%f) # 只保留至少能执行10秒观测的窗口 win win[win[duration_s] 10.0] win win.sort_values([target_id, t_start]).reset_index(dropTrue)那个pd.to_datetime是python类型转换最常出问题的位置如果系统区域设置不是英文STK导出的月份缩写“Jun”“Jul”可能解析失败稳妥做法是固定format参数而不是让pandas自动猜测。另一种容易被忽略的情况是STK的时长列精度只到0.01秒在奖励函数里不要依赖秒级以下的精度。清洗完的窗口数据先存成parquet或csv固定版本之后的每个训练任务都从这份文件读不要每次重新生成否则对比实验结果时说不清每次用的窗口集是不是同一个。3. 多智能体强化学习建模状态、动作与奖励怎么定3.1 从单星问题变成多智能体博弈单星调度是一个经典组合优化问题遗传算法、混合整数规划都能求解但引入星座后就出现了“多颗卫星抢同一个目标”的竞争关系。每颗卫星是一个决策智能体它们共享同一个任务池目标被某个智能体执行后就消耗掉这构成典型的多智能体强化学习问题。在MARL里这种多智能体环境的稳态用马尔可夫博弈描述每个智能体都有自己的观测和策略全局奖励是所有智能体联合行动的结果。和单智能体RL最大的区别是一个智能体的策略变化会改变其他智能体的观测分布训练过程中不把这一点考虑进去容易出现每个人都学了但系统总性能没涨的现象。处理这个问题有两条路线一是集中训练分布式执行CTDE二是直接把其他智能体当作固定环境的一部分。CTDE更贴合星座场景因为地面训练时所有卫星的调度结果都在同一个仿真器里可以拿到全局信息部署时每个智能体又只按本地数据决策天然适合星上执行。下表的选型逻辑是我做实验时对照用的路线训练信息部署信息适合场景独立学习Independent PPO只用本机状态本机状态星间弱耦合、任务不重叠CTDEMAPPO/QMIX全局观测联合奖励本机观测强竞争、共享任务池完全通信协作全局观测星间通信可接收其他星信息星间链路已建模仿真规模小卫星调度实验里大多数情况落在第二行所以我默认选CTDE框架先把竞争关系在训练阶段消化掉不用等部署期再去处理星间冲突。3.2 状态、动作设计的具体方案状态空间设计上我给每个智能体的观测由三部分组成轨道状态、任务池状态和资源状态。轨道位置不必给完整J2000坐标多数实验用一个降维表示就够经度方向的相位角用来描述卫星相对地球的进动快慢。任务池状态包含当前所有待观测目标的ID列表、权重和剩余截止时间这两部分直接决定“该不该抢这个目标”。资源状态包括星上存储余量、电量余量在多星协同里还要加一条代表“已分配给其他卫星的目标数”否则两个智能体可能反复决策去做同一件事。动作空间按调度语义设计每个决策点是卫星从任务池里选一个目标执行或者选择待机这是离散动作。如果实验里还要考虑侧摆角就得把动作扩展成“目标ID侧摆角度”的混合空间混合空间在强化学习里的实现比纯离散和纯连续都麻烦第一版建议先把侧摆角离散成几个档位比如0°、10°、20°等模型稳定了再扩。下面这张状态表可以直接映射到代码里的观测向量方便和RLlib的spaces.Dict对应观测分量维度含义归一化方式phase_angle1卫星所在的轨道相位归一化到[0,1]target_remaining目标数每个目标剩余权重按初始权重归一化target_deadline目标数距截止时间的剩余量除以计划周期storage_used1已用存储占比除以容量battery_used1已用电量占比除以容量3.3 奖励塑形与约束惩罚奖励是这里最花时间的部分。如果只用“任务完成率”作为奖励训练信号太稀疏一个episode可能只有完成和没完成两种结果梯度信息少。我习惯做两层奖励主奖励是完成一个目标获得的加权回报辅奖励是每步的小惩罚让智能体倾向于少做无效机动。加性结构大致如下reward completion_reward - agility_penalty * switch_occurred - storage_penalty * overflowcompletion_reward按目标权重给比如高优先级目标给5.0普通目标给1.0这样优先级稀缺性会自动进入学到的策略switch_occurred表示本次决策是否切换了观测目标频繁切换会消耗姿态机动和能源所以给一个小负反馈overflow是星上存储超出容量时的惩罚。这套塑形规则比直接用覆盖率容易收敛最终评估阶段再报任务完成率不会因为塑形偏离业务目标。塑形系数不必反复调把三个系数固定成1.0、0.1、2.0起步先观察训练曲线再动。3.4 算法选型先跑MAPPO再试QMIX在CTDE框架下可用的算法有MADDPG、QMIX、MAPPO等。MADDPG适合连续动作和卫星调度的离散动作不匹配除非把动作做成对目标打分后排序再采样的形式但那已经偏离MADDPG的设计初衷。QMIX把多智能体Q值分解成单智能体Q值的单调组合适合团队共享奖励的任务星座整体覆盖率这种目标很契合MAPPO则是把PPO扩展到多智能体框架训练稳定、超参不那么敏感工程上有RLlib现成组件。我一般先跑MAPPO确认建模没问题再跑QMIX看性能上限。如果Q率明显更高说明团队分红模式更适合当前业务目标如果两者差不多MAPPO因为调参成本低会留作主线算法。4. 在RLlib里跑通多智能体PPO重点盯训练日志和参数4.1 用RLlib搭一个最小可运行的多智能体环境RLlib把多智能体环境约定为一个返回局部观测和局部奖励的环境外层套MultiAgentEnv。我用gymnasium实现一个简化版卫星调度环境任务池和可见窗口在构造时传入每次episode开始时从清洗好的窗口csv里截取一段作为当天的任务序列卫星只看与自己相关的窗口并在时间轴上推进import gymnasium as gym from gymnasium import spaces import numpy as np from ray.rllib.env.multi_agent_env import MultiAgentEnv class SatSchedMultiEnv(MultiAgentEnv): observation_space spaces.Box(low0, high1, shape(3,), dtypenp.float32) action_space spaces.Discrete(8) def __init__(self, config): self.windows config[windows] # 清洗后的可见窗口DataFrame self.n_agents config[n_agents] self.agents [fsat_{i} for i in range(self.n_agents)] self.reset() def reset(self, *, seedNone, optionsNone): self.t 0 self.task_state np.ones(self.windows[target_id].nunique()) self.initial_n_tasks self.task_state.size return {a: self._obs(a) for a in self.agents}, {} def _obs(self, agent): # 简化观测当前时刻、剩余任务、固定存储占用 return np.array([self.t / 100.0, self.task_state.min(), 0.3], dtypenp.float32) def step(self, action_dict): rewards {} for ag, act in action_dict.items(): if act self.task_state.size and self.task_state[act] 0: rewards[ag] 1.0 self.task_state[act] 0 else: rewards[ag] -0.01 self.t 1 terminated self.t 30 truncated False return ({a: self._obs(a) for a in self.agents}, rewards, {a: terminated for a in self.agents}, {a: truncated for a in self.agents}, {})然后定义策略映射和PPOConfig。因为所有卫星结构相同策略只需要一个共享策略这会明显缩短训练时间from ray.rllib.algorithms.ppo import PPOConfig config ( PPOConfig() .environment(SatSchedMultiEnv, env_config{windows: win, n_agents: 3, n_actions: 8}) .multi_agent( policies{sat_policy: (None, SatSchedMultiEnv.observation_space, SatSchedMultiEnv.action_space, {})}, policy_mapping_fnlambda agent_id, **kw: sat_policy, ) .training(lr2e-4, train_batch_size4096, lambda_0.95, clip_param0.2) .resources(num_gpus0) ) trainer config.build() for i in range(200): result trainer.train() if i % 20 0: print(fiter {i}, episode_reward{result[episode_reward_mean]:.2f}, ftask_done{result[hist_stats].get(task_done, 0)})train_batch_size是这个最小实验里最值得调的参数窗口条数多就加大到8192窗口条数少可以减半。lr用2e-4起步如果episode_reward中途振荡明显先降到1e-4。clip_param保持0.2不要动它只在连续控制场景里会做0.1到0.3的微调。共享策略会让所有卫星共用同一套网络适合同型卫星如果星座里包含不同传感器类型的卫星就得按卫星类型拆成多个policy。4.2 训练日志怎么看回报曲线和业务指标对应不起来时MARL训练最常见的困惑是“episode_reward涨了但调度结果却没有变好”。原因通常是奖励塑形和业务指标不完全对齐。所以我在训练循环外单独挂一个评估器每训练20轮用当前策略重放20个episode统计任务完成率、平均时间窗利用率和卫星负载均衡度。这三个指标直接对应实验报告里的业务口径比episode_reward更可信。下面是评估器里统计完成率的部分def eval_policy(trainer, windows, episodes20): scores [] for _ in range(episodes): env SatSchedMultiEnv({windows: windows, n_agents: 3, n_actions: 8}) obs, _ env.reset() done False while not done: act {a: trainer.compute_single_action(o) for a, o in obs.items()} obs, rew, term, trunc, _ env.step(act) done all(term.values()) or all(trunc.values()) scores.append(1.0 - env.task_state.sum() / env.initial_n_tasks) return np.mean(scores)如果episode_reward涨而完成率不涨先检查是不是动作永远选同一个值再看reward里是否存在“来回切换任务也能刷奖励”的空子。这两个问题在卫星调度实验里几乎每次都会遇到。空子修复后把评估器接进训练循环当early stop条件比只盯episode_reward靠谱得多。4.3 超参与基线对比至少要和启发式算法打一架训练收敛后必须回答一个问题学出来的策略比最早的基线好多少基线至少有随机调度和优先级贪心条件允许再加遗传算法。优先级贪心的逻辑很简单每个决策时刻选择当前窗口内权重最高且还没超过截止时间的任务。它不需要训练就能达到相当不错的完成率MARL策略如果不能稳定超过它说明状态或奖励设计有问题而不是算法问题。下面是在一组3星8目标小规模数据上常用的参数与对比格式算法lrbatch_size任务完成率说明随机调度--约0.2~0.3只做下界优先级贪心--约0.5~0.65必须超过的下界单智能体PPO2e-44096与贪心相近动作空间增大后收敛慢MAPPO共享策略2e-440960.55~0.75第一版用这个QMIX1e-48192看实验对比上限用对比实验的公平性要注意两件事一是所有算法必须用同一份清洗后的可见窗口数据不能各用各的时间段二是固定随机种子让任务序列生成、策略初始化都一致否则训练5次取最好成绩没有统计意义。种子这步放到最后一节专门说。5. 把训练好的调度计划拿回stk11回验省掉一整天返工5.1 用Access报告校准一次训练数据很多实验在python里跑完就结束但标题既然包含stk11就应该把调度计划导回去做一次“仿真回验”。做法是从训练好的策略里输出一张调度计划表包含卫星、目标、观测起点和终点然后回到stk11场景用Access的报告功能重新计算同一时间窗口内的可见性。如果计划表里的观测时间和回验看到的可见区间重叠说明这条计划真实可执行如果不重叠问题往往出在训练时用的窗口是预测值而回验时场景已经推进到了另一个时间点。校准一次之后再批量回验剩余计划。这里能省时间的关键是无需重算整个时间窗只需要做重叠度检查。写一个简单脚本读两份csvschedule pd.read_csv(results/schedule.csv, parse_dates[start, end]) access pd.read_csv(data/access_windows.csv, parse_dates[t_start, t_end]) def overlap(a_s, a_e, b_s, b_e): return max(0, min(a_e, b_e) - max(a_s, b_s)).total_seconds() 0如果计划表里超过90%的观测都能在Access报告里找到覆盖它的可见区间这条计划就算通过stk11回验可以直接作为实验报告的结论数据使用。5.2 时间格式和决策延迟最容易在回验时暴露回验时多数失败来自时间系统错位STK场景时间还是UTCG调度计划里的时间却是从python datetime转换来的两者可能差出时区甚至整整一天。全工程最好统一用UTC时间日期格式统一成“1 Jun 2024 04:10:23.41”不要混用时间戳和格式化字符串。另外训练时如果可见窗口数据的生成时刻和决策时刻之间有时间差需要预留决策延迟裕量真实星上调度不可能在窗口起点瞬间决策完毕至少要往每条观测的起点前推一个固定处理时间比如5秒否则回验结果再好实际执行时也会追不上窗口起点。5.3 端到端复现的关键种子和窗口数据版本实验报告里最容易被认为不可复现。我一般做两步第一在训练脚本入口统一固定python、numpy、torch的随机种子第二给STK导出的原始窗口数据算一个哈希值写进报告的实验设置一节。任何一个人拿到同一份窗口数据、同一个种子、同一套超参训练结果在统计意义上应该相似。下次同事来问为什么结果对不上先把种子和窗口哈希值拿出来对一遍基本都能定位到是数据版本漂移还是随机种子没锁。这两样记录进报告比多贴十张训练曲线更有说服力。本文还有配套的精品资源点击获取
返回列表