基于STK11与多智能体强化学习的卫星调度实验解析
2026/9/20 10:25:58 网站建设 项目流程

简介:基于Python与STK11的多智能体强化学习卫星调度实验资源,面向人工智能、通信工程、自动化、电子信息等专业的高校学生与科研工作者,适用于毕业设计、课程作业或项目初期演示。内容涵盖完整源码、详细设计文档、实验数据与报告,可帮助理解多智能体强化学习在卫星调度场景中的应用与实现。资源包共203个文件,主要包括Python脚本、PNG图表、CSV实验数据、STK场景快照(sn/sa/sc等)、模型权重pth文件、Word文档等,压缩包大小79.62MB,目录规整便于检索。已有150人学习下载。资源提供经过功能测试的完整工程,除核心算法外还附带多组CSV训练数据与增强数据集、飞行场景文件及配置说明,便于直接运行复现实验,也支持二次开发拓展其他调度场景,适合入门到进阶学习。

1. 从“单星单任务”到“多星协同”的调度实验

如果只是单颗卫星按预定时间窗口拍地面目标,传统回溯算法基本够用;可当卫星数上到 4 颗、目标数上到几百,再考虑侧摆、存储和下行时间,这就不再是匹配问题,而是一个带时间窗约束的多机调度问题,复杂度直接落在 NP-Hard。这个实验正是围绕这个场景展开:用 Python 连接 STK11 生成访问窗口,再将窗口数据喂给多个智能体,让每颗星自己学“什么时候拍、拍完是否下传、要不要把余量留给后续目标”。项目包里同时给了完整源码、设计文档和多种 CSV 数据,适合做毕设或课程设计,也适合刚接触强化学习的人从数据读到策略输出。下面我会按数据生成、环境建模、训练调参、验证扩展这条主线拆开,关键参数和坑直接用命令和代码说明。

2. 用 STK11 生成访问窗口:数据是调度策略的地基

2.1 STK11 在实验里的角色:几何计算交给 STK,决策留给 Python

多星调度最耗时也最不适合手写编码的部分,是“可见时间窗”。做这个实验时,我们拿到的通常是轨道根数和目标经纬度,能不能在某段时间内看到某个目标,涉及地球掩星、地形遮挡、侧摆角度等一系列几何计算。如果全部在 Python 里自己算,一个中等规模场景的校验时间就可能超过策略训练的时间。

stk11是 STK 的 Python 封装,它把 STK 当作计算引擎,Python 只负责发起场景构建、查询访问、导出结果。这样状态空间不是抽象编号,而是跟仿真秒时间对齐的真实窗口,强化学习智能体学出来的策略才更接近工程可用。很多人一上来就调强化学习算法,忽略这一步,结果训练曲线很漂亮,换一批卫星轨道数据后策略却彻底失灵,问题多半出在窗口数据本身。

动手前先确认 STK 许可可用,并检查stk11能不能正常连上。我一般用 3.8 以上版本的 Python,在命令行里做一次最简单的连通性测试:

python -c "from stk11 import STK11; print(STK11())"

能打印出对象说明通信链路正常。若导入失败,先看是否有 STK 安装目录和许可文件,而不是急着换库。项目里的 CSV 都是预先从 STK 场景导出的,所以训练流程并不依赖 GUI,但复现新实验时这一步绕不开。

2.2 用 stk11 建立场景并批量导出访问区间

搭建实验时,我不会在 STK GUI 里手动点选,而是写一个统一脚本。下面这段代码对应项目里数据准备模块的核心逻辑,重点是把访问开始时间和结束时间批量写入 DataFrame。注意,这只是演示思路,完整 API 以你本机 stk11 版本为准。

from stk11 import STK11 import pandas as pd stk = STK11() stk.new_scenario("sat_sched_demo") sc = stk.get_scenario() sat = sc.create_satellite("SAT01") sensor = sat.create_sensor("SENSOR01") for idx, (lat, lon) in enumerate([(31.2, 121.5), (40.4, 116.6), (22.5, 113.9)]): sc.create_target(f"T{idx}", lat=lat, lon=lon) for tgt in sc.get_children(type="Target"): access = stk.calculate_access(sat, tgt) intervals = access.compute_interval() for i in intervals: print(tgt.name, i.start, i.end)

这里的思路是:先建场景,再创建卫星和传感器,最后对每个目标计算访问区间。intervals中每个元素都带有开始和结束时间,但单位可能是 UTCG 日期字符串,直接写进 CSV 后处理会很麻烦。常见做法是转成相对场景起始时间的秒数:

epoch = sc.get_epoch() rows = [] for i in intervals: rows.append({ "target_id": tgt.name, "start_sec": (i.start - epoch).total_seconds(), "end_sec": (i.end - epoch).total_seconds(), }) df = pd.DataFrame(rows) df.to_csv("1_access_200_500.csv", index=False)

epoch是场景起始时间,把绝对时间统一转成秒后,强化学习环境可以用连续时间轴处理窗口,不用在每个 step 里解析字符串时间,训练速度会有可感知的提升。项目里的1_access_200_500.csvlab1.csvlab4.csv等文件基本都遵循类似结构,只是字段顺序可能不同,解析时以start_secend_sec这两个字段为主线。

2.3 从文件名看数据规模和用途

源码包里的 CSV 命名不是随机的。数字部分通常暗示任务规模和窗口数量,后缀则说明是否做过数据增强。我按项目文件的实际用途整理了一张速查表:

文件可读出的信息用途
1_access_200_500.csv200 个目标、500 个时间窗附近小规模训练用,快速验证环境是否跑通
lab1.csv/lab2_4.csv/lab3_400.csvlab 代表不同实验分组不同约束或目标数量,用于消融对比
MRL_data_400_1000_augmented.csv400 个目标、1000 个窗口,增强数据做过时间抖动等扩增,用于最终效果评估
lab1_augment.csvlab1 的扩增版本测试数据增强对策略泛化能力的影响

在读数据时,建议先用pandas检查空行和缺失时间窗。同一个目标可能在多个不连续窗口出现,这意味着智能体必须决定用哪个窗口,而不是看到窗口就抢。这样一个 CSV 文件,实际上同时包含了调度任务和可见性图,省去再跑一轮 STK 的重复计算。把这一层理解为“预计算出来的环境数据”,后面写环境类时的数据量就清楚多了。

STK 导出的数据只是几何可见性,还不等于调度约束。还需要从项目附带的文档里手工整理出任务收益、截止时间、单星存储上限等字段。这是最费精力但最值得做的一步,因为后续所有奖励函数和动作掩码都要从这里引用。

3. 把 CSV 转成多智能体强化学习环境:状态、动作与奖励的落地

3.1 调度问题先写成马尔可夫博弈

卫星调度如果从单星视角看是序贯决策问题,从多颗卫星同时决策看就是马尔可夫博弈。这个实验把每颗卫星当成一个智能体,在决策时刻选择一个目标或一个空闲动作。我通常这样定义状态、动作和奖励:

组成内容说明
观测当前时间、剩余存储、可访问目标编号列表每个智能体各一份
动作观测某个目标、空闲离散动作空间,受窗口掩码限制
奖励目标优先级收益 + 冲突罚 + 错过窗口罚量纲控制在单步 [-1, 1] 附近

把这些写下来不是为了规范漂亮,而是为了确定环境接口。项目内设计文档里有一张 UML 类图,把环境主体设计成SatelliteSchedEnv。初学者不需要重写整个设计,只需要看懂数据流:CSV 文件 → 窗口列表 → 每个智能体的动作掩码。

3.2 用 PettingZoo 风格的 AEC 环境封装数据

在多智能体场景里,独立训练多个 DQN 时最省事的接口是 AEC(Agent Environment Cycle)。下面代码演示如何把 CSV 窗口读成每个智能体的观测项:

import pandas as pd import numpy as np class SatelliteSchedEnv: def __init__(self, csv_path, num_agents=4, slot_interval=10): df = pd.read_csv(csv_path) df["start_slot"] = (df["start_sec"] // slot_interval).astype(int) df["end_slot"] = (df["end_sec"] // slot_interval).astype(int) self.df = df self.num_agents = num_agents self.time_horizon = df["end_slot"].max() + 1 self.agent_states = {i: {"storage": 0, "current_time": 0} for i in range(num_agents)} def reset(self): self.agent_states = {i: {"storage": 0, "current_time": 0} for i in range(self.num_agents)} return self._get_obs() def _get_obs(self): obs = {} for agent_id in range(self.num_agents): usable = self.df[ (self.df["sat_id"] == agent_id) & (self.df["start_slot"] >= self.agent_states[agent_id]["current_time"]) ] obs[agent_id] = usable[["target_id", "start_slot", "end_slot"]].values[:20] return obs

这里把秒时间切成slot_interval的离散时隙,是为了让 DQN 的 Q 网络拿到固定长度观测。如果直接塞变长窗口列表,需要额外分组 mask,代码复杂度会高很多。obs长度固定取最近 20 个可选窗口,超过 20 的窗口在训练时被截断,所以紧急度越高的目标应该手动放到列表前面,否则容易被网络忽略。

字段sat_id不一定在 CSV 里存在。如果原始数据没有,可以通过窗口归属反推,通常会有一条规则:某个访问窗口属于哪颗卫星,是由 STK 中的 access 对象决定的。在训练代码里,这个字段的主要意义是生成动作掩码,避免某颗星去抢不属于自己可视窗口的目标。这一步是环境调试中最容易出错的地方。

3.3 奖励函数:收益先放大,时间罚重

奖励设计对多智能体卫星调度的收敛速度影响很大。很多失败的训练,问题不是算法不行,而是奖励太稀疏或量纲不一致。一个简单可运行的版本如下:

def compute_reward(self, agent_id, action_target, current_time, conflict=False): reward = 0.0 if action_target is None: reward -= 0.01 else: priority = self.targets.loc[ self.targets["target_id"] == action_target, "priority" ].values[0] w = self.df[(self.df["agent_id"] == agent_id) & (self.df["target_id"] == action_target)].iloc[0] if current_time > w["end_sec"]: reward -= 10.0 elif conflict: reward -= 5.0 else: reward += priority * 10.0 return reward

奖励函数里,空闲小惩罚是为了避免智能体一直发呆;错过窗口惩罚到 -10,是为了压低“瞎试”行为;priority * 10是为了让高价值目标在初始策略中占优。实际训练时要观察 reward 分布,如果平均每步奖励长期小于 0,说明环境难度过高,需要先调短 episode 或减少智能体数量。

3.4 算法选型:独立 DQN 起步,再看 CTDE 框架

多智能体强化学习没有银弹,这个项目我建议先跑独立 DQN(I-DQN),因为每颗卫星的动作空间相对独立,窗口约束天然过滤掉大量无效动作,I-DQN 已经能打败构造式贪心启发式。训练后期如果出现多颗星抢同一个目标造成冲突,再切到 MAPPO 或 QMIX 这类集中训练、分布执行(CTDE)算法。

算法训练方式优势代价
I-DQN每个智能体独立训练实现简单,稳定忽略环境非平稳性
MAPPO集中 critic,分布式 actor更好利用全局信息内存与实现复杂度更大
QMIX集中训练并分解 Q 值适合大规模动作空间需要单调性假设

项目源码里保留了两条训练入口,一条是train_idqn.py,一条是train_mappo.py。我的建议是初学者先完整跑通 IDQN,确认数据链路和奖励尺度没问题,再切换 MAPPO。直接上 MAPPO 的人,往往最后分不清是环境 bug 还是算法 bug。

4. 训练时序、超参数与多智能体一起卡死的排查

4.1 一回合到底有多长:时间步不是越多越好

在这个实验里,一回合对应一段仿真周期,比如 24 小时。如果按 10 秒一个时隙切分,24 小时就是 8640 步。这个步数对强化学习来说太长,完整探索一遍非常慢,而且稀疏奖励会让智能体在前几百回合根本收集不到有效样本。

我不建议直接走完整时长。常见做法是把场景切成 30 分钟或 60 分钟的子场景,先让策略在短场景收敛,再逐步拉长时间窗。项目源码里的环境类带了一个truncate_horizon参数,默认是 300 个时隙,对应大约 50 分钟仿真时间。如果开发前不检查这个参数,看到训练曲线每回合都是负分,很容易误判为 bug。

4.2 能直接落地的超参数组合

lab1.csv这类 200 目标数据集上,下面这组参数能稳定收敛,同时不会把显存撑爆:

参数数值备注
learning_rate1e-4多智能体环境下太大会震荡
gamma0.99调度奖励延迟较长,不宜降到 0.9
batch_size128每次梯度更新使用样本数
buffer_size100000经验池容量
epsilon1.0 → 0.05前 100k 步线性退火
target_update500DQN 目标网络更新步数
num_agents4基准卫星数量

这些参数可以原样传入 PyTorch 训练循环,也可以对应stable-baselines3里的字典参数。如果使用ray[rllib],多智能体环境的 wrapper 开销较大,数据流不透明,所以我更倾向于用原生 PyTorch 写一个小训练循环。项目里的train_idqn.py就是这条路径。

import torch import numpy as np class ReplayBuffer: def __init__(self, capacity=100000): self.buffer = [] self.capacity = capacity def push(self, transition): self.buffer.append(transition) if len(self.buffer) > self.capacity: self.buffer.pop(0) def sample(self, batch_size): idx = np.random.choice(len(self.buffer), batch_size, replace=False) return [self.buffer[i] for i in idx] buffer = ReplayBuffer() for epoch in range(200): obs = env.reset() done = False while not done: actions = {} for agent_id in range(env.num_agents): if np.random.rand() < eps: actions[agent_id] = np.random.choice(env.get_action_space(agent_id)) else: with torch.no_grad(): q_vals = agent_nets[agent_id](torch.FloatTensor(obs[agent_id])) actions[agent_id] = int(q_vals.argmax()) next_obs, rewards, dones, _ = env.step(actions) for agent_id in range(env.num_agents): buffer.push((obs[agent_id], actions[agent_id], rewards[agent_id], next_obs[agent_id], dones[agent_id])) obs = next_obs # 训练更新省略,但必须包含 target_net 冻结、定期同步

这里所有智能体共用一个经验池,可以提升样本利用率,前提是各智能体动作空间维度一致。如果卫星配置差异很大,建议每个智能体单独开 buffer,否则采样时向量长度对不齐,训练会直接报错。

4.3 “所有智能体都不动”的检查顺序

多智能体强化学习最磨人的现象是训练曲线一直平,甚至回合回报等于负数。遇到这种情况,我按下面的顺序排查:

  1. 检查env.reset()后的第一个观测是否为全零。如果 CSV 第一行就是空窗口,说明时间槽没有对齐。
  2. 检查动作掩码。如果某颗星在绝大多数时间步里没有可选窗口,它的策略会倾向永远 idle,因为探索不到有效奖励。解决办法是提高空闲惩罚,或者初始化时让智能体固定从高优先级目标开始。
  3. 检查奖励是否被存储超限惩罚抵消。增强文件MRL_data_400_1000_augmented.csv窗口密度高,多星很容易同时选中同一目标,冲突罚项会压制收益,让策略趋于保守。
  4. 打印一个完整 episode 的剪影,例如T=100, agent0 观测到第 3 个目标窗口,执行动作 2,reward=0.7。加一行if step % 50 == 0: logger.info(...)就能看清策略是否在按预期推进。

4.4 在 Windows 和 Linux 下跑这套实验的坑

很多同学第一次跑是在 Windows 上,STK 的许可和 Python 库的通信绕不开一个坑:COM 注册。我的经验是启动训练前先做一次依赖检查:

python -c "from stk11 import STK11; print(STK11())" python -c "import pandas, numpy, torch; print('deps ok')"

第二行确认 Python 侧依赖完整。如果缺失stk11,可以执行pip install stk11,但要注意库本身不会代替 STK 安装,它只负责连接。只训练不生成新数据时,不需要启动 STK GUI 或持有许可;要复现全新场景时,STK 环境和许可就必须配置好。项目提供的运行方式也是分离的:训练入口不依赖仿真器,数据导出才依赖 STK。

5. 训练完成之后:用随机/贪婪基线和扩增数据验证策略

5.1 不看训练曲线,先看调度收益

训练收敛不代表调度结果可用。我会把策略输出的动作序列还原成一张调度甘特图,检查每个目标是否在窗口内完成。项目里给出的评测脚本通常生成类似下表的对比结果:

方法完成任务数平均优先级收益冲突次数
随机选择86120.537
贪心按优先级132214.312
训练后的 I-DQN158268.73

多智能体强化学习在这个场景里的主要优势,不是单纯提高完成任务数量,而是冲突次数明显下降。多颗卫星在共享目标竞争信息后,学会了在窗口重叠时主动退出,把机会让给时间余量更大的星。这种协同行为在贪心策略里很难手工写出来,也是 MARL 区别于单智能体调度的核心价值。

5.2 验证时请带上增强样本

项目里有lab1_augment.csvMRL_data_400_1000_augmented.csv这类文件。增强数据不是拿来替代训练集的,它更像一种隐式正则。我的验证命令如下:

python evaluate.py --policy_dir ./checkpoints/exp_03 \ --csv_path lab1_augment.csv \ --episodes 20 \ --output metrics.json

这里--policy_dir是训练产生的 checkpoint 目录,--csv_path指定评估数据,测试时不要把训练集和测试集混在一起;--episodes我一般设 20 以上,太少时一次随机种子变化就让指标波动 10% 以上;--output只是汇总指标的位置。如果增强数据上表现下降明显,说明策略过拟合到原始窗口分布,需要回主训练集里加入 5% 以内的时间抖动再训。

5.3 把实验接进更大场景前的三个调整

如果想把实验扩展成 10 星以上,不要着急堆算法,先处理以下三点。

第一,观测里不要把窗口编号直接裸用,改成目标经纬度、剩余窗口时长、目标收益三个特征,否则智能体对不同数量的目标泛化能力很差。第二,把训练中的存储约束从硬限制改成软罚,硬限制会让某些星策略极端保守。第三,在环境层加入冲突保护,同一时间槽内只允许一颗星对一个目标发起动作,这样神经网络结构完全不用改,冲突率直接下降。

5.4 最后一招:把时间槽从 10 秒改成 30 秒

如果训练一直慢,一个最有效的办法是把环境里的slot_interval从 10 秒调到 30 秒。这样时间步数从 8640 降到 2880,动作空间不变,训练速度提升接近三倍。代价是窗口边界精度下降,但只要目标成像时间普遍小于 30 秒,结果几乎不受影响。这个参数在源码里属于隐藏参数,很多人只盯着学习率调,忘了时间粒度本身就是最关键的一层抽象。

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

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

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

立即咨询