简介:这是一套面向卫星调度与多智能体强化学习初学者的完整实验资源,将Python与STK11联合使用,覆盖任务随机生成、可访问时段计算与策略训练等环节。包内共241个文件,约79.6MB,除py脚本、csv数据与STK场景文件外,还包含png示意图、pth模型权重、训练日志与场景备份,可支撑从环境搭建、数据准备到模型训练和结果分析的完整流程。核心代码分工明确:mission.py用于定义带经纬度等属性的任务实例,create_mission.py可批量生成任务并写入missions.csv,compute_access.py读取任务并计算各任务的可访问时段,场景文件RLSTAR.sc需连接已打开的STK11使用。资源中附带多组实验数据与训练过程记录,便于对照关键参数、复现调度策略并在此基础上修改任务规模与目标函数。目前已有132人学习下载,适合作为毕业设计、课程设计或工程实训项目参考。
1. 用 Python 把 STK11 变成卫星调度实验台:多智能体强化学习不再卡在数据上
做卫星调度的人大多有过这种经历:算法论文读了一堆,QMIX、MADDPG 的公式能默写,但一上手就卡在仿真数据上——要么手动在 STK 里点卫星加传感器,要么对着几十个 CSV 不知道哪个能用。这套实验资源解决的是这个断层:STK11 负责算出真实的星地访问窗口,Python 负责批量生成任务、解析访问时段,最后把数据喂给多智能体强化学习算法做调度决策。它适合两类人:一类是正在做卫星调度方向毕设或课程设计的同学,另一类是刚入门多智能体强化学习、想找一个带真实仿真约束的练手场景的开发者。核心工作量被拆成了 mission 生成、访问计算、数据增强三段,每一段都能独立跑通。
2. 先把数据流水线拆清楚:mission、access、STK 三者的分工
2.1 四个 Python 脚本各管一段,别混着用
这套实验的核心是四个脚本:mission.py定义任务类,create_mission.py批量生成任务,compute_access.py计算每个任务的可访问时段,最后任务和访问窗口被写进missions.csv和access.csv。理解它们的关系,比急着跑代码更重要。
mission.py里的Mission类每次实例化都会随机生成经纬度、任务时长、优先级这些关键属性。你可以直接在类里面改随机范围,比如把经度限制在东经 110°~130°,或者把任务时长从 5 分钟拉到 30 分钟——这就是第一层参数控制。create_mission.py是在这个类外面包了一层批量逻辑,生成的任务数量决定了后续调度的规模,代码里通常有一个num_mission参数,改成 200 就有 200 条任务进missions.csv,改成 500 就是 500 条。
compute_access.py是另一个逻辑层级:它读取missions.csv里的每条任务,配合 STK11 的场景文件RLSTAR.sc,计算每个任务在卫星过境时的可访问时段,写入access.csv。注意,它读的是missions.csv,不是直接操作 STK 的 Python API——如果你的 STK 是 11 及更早版本,这基本是唯一靠谱的路线,因为老版本 STK 的 Python 接口很弱,走文件交换最稳。
2.2 场景文件和数据集 CSV 怎么对应起来
资源里带了scenario/RLSTAR.sc,这是 STK11 的场景文件,卫星的轨道参数、传感器约束都在里面。你先在 STK11 里打开它,compute_access.py才有东西可算。这句话值得再强调一遍:这个脚本设计的前提就是「STK 场景已经打开」,不是脚本自己拉起 STK,更不是离线算访问。
数据集文件我整理了一下,功能差异很大:
| 文件名 | 推测来源 | 在实验里的作用 |
|---|---|---|
lab1.csv ~ lab4.csv | 不同规模下的任务/调度记录 | 单智能体或较早版本的训练集 |
lab3_200.csv、lab3_400.csv、lab4_300.csv | 按任务数量切分的子集 | 做规模对比实验时用 |
lab1_augment.csv | lab1.csv的数据增强版本 | 测增强前后模型稳定性 |
access_200_500.csv | 200~500 任务规模下的预计算访问窗口 | 免去部分 STK 计算,直接训练 |
MRL_data_400_1000_augmented.csv | 400~1000 任务规模的增强数据 | 多智能体强化学习主训练集 |
从上到下,任务规模是递增的。做实验时不要一上来就拿MRL_data_400_1000_augmented.csv跑,那个数据量大、状态维度高,新手容易在调参上迷失。我的建议是先从lab3_200.csv这类小规模开始,确认环境闭环再换大规模。
2.3 跑通的最小步骤集
环境准备阶段,requirements.txt按需安装,核心是 numpy、pandas、argparse 三个库。解析参数可以渐进式——先跑默认参数:
python create_mission.py --num_mission 200 python compute_access.py--num_mission控制任务生成量,默认值可能不是 200,以你本地代码为准。生成完后先看missions.csv和access.csv的行数和时间列格式,再用 pandas 快速检查:
import pandas as pd access = pd.read_csv('data/access.csv') print(access.columns.tolist()) print(access['AccessStartTime'].min(), access['AccessEndTime'].max())这段代码的作用是验证访问窗口的时间字段是否合法,常见问题是 STK 导出时间比 Python 解析时间早几十年(比如 1958 年),后面避坑章会专门讲。拿到合法的访问窗口列之后,才算真正进入强化学习建模,也就是下面要说的重点:多智能体强化学习怎么和这些访问窗口数据对接。
3. compute_access 深挖:STK11 Connect 接口和访问窗口的生成逻辑
3.1 访问窗口是调度问题的约束骨架
卫星调度和普通车间调度的最大区别在于约束来源:不是所有任务随时可做,而是只有当卫星运行到地面站或目标上方时,任务才进入「可执行窗口」。这个窗口的计算由 STK 完成,compute_access.py只负责把它拉下来转成训练用的结构化数据。
在 STK11 里,访问计算通常需要先给卫星加 Access 约束对象,再运行 Access 工具,最后导出访问时段。老版本没有方便的 Python API,所以常见做法是用AGI STK Connect的 TCP 接口发送文本命令。下面是一个连接 STK11 的 Python 封装,这类代码在很多自动化脚本里能看到:
import socket def connect_stk(host='localhost', port=7070): # STK Connect 默认监听 7070 端口,注意不能在 11 里被占用 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) sock.sendall(b'STK 11\r\n') # 握手命令,版本号要和 STK 一致 welcome = sock.recv(1024) return sock这段代码的逻辑是三步:建立 TCP 连接、发送握手命令、接收 STK 的欢迎确认。STK11 的 Connect 端口默认是 7070,但也有可能被安装时改过,常见的排查对象是C:\ProgramData\AGI\STK 11\stkConnections.xml里的配置。握手命令里的版本号写错会导致连接直接被断开,所以如果返回为空,先检查这一行。
连接建立后,真正取访问窗口的命令因场景结构而异。最省事的做法是在 STK 界面里先把访问计算做完、用 ASTGATOR 之类的方法导出access.csv,Python 只负责解析。这也解释了资源的目录结构:compute_access.py更准确的角色是「访问窗口的后处理脚本」,负责把 STK 导出的原始访问记录清洗成训练可用的状态-动作对。
3.2 清洗访问记录的最小代码模板
STK 导出的访问记录通常是每一行一条过境弧段,包含任务 ID、卫星名、起始时间、结束时间,但时间格式往往带着DD MMM YYYY HH:MM:SS.sss这种带英文月名的形式,直接给 pandas 用会解析失败。清洗逻辑如下:
import pandas as pd df = pd.read_csv('data/access.csv') df['start'] = pd.to_datetime(df['AccessStartTime']) df['end'] = pd.to_datetime(df['AccessEndTime']) df['duration'] = (df['end'] - df['start']).dt.total_seconds() # 过滤掉异常窗口:时长小于 30 秒的直接丢弃,STK 计算中偶尔出现零时长伪窗口 df = df[df['duration'] >= 30] df = df.sort_values(['mission_id', 'start']).reset_index(drop=True)逻辑说明:先把时间列转成datetime类型,再算窗口时长,然后过滤掉过短的窗口。为什么要过滤 30 秒以下的?STK 在密集过境计算时可能产生持续几秒的伪窗口,如果保留进训练数据,会把这些杂音当成有效约束,强化学习智能体很容易学到「窗口很短也去抢」的错误策略。mission_id是关联missions.csv的外键,排序是为了后面按序构造多智能体状态的邻居窗口查询。
参数上,30 秒这个阈值不是固定的。如果你是 LEO 卫星,访问窗口普遍在 5~15 分钟,阈值可以设成 60 秒;如果是高轨卫星,窗口很长但过境次数少,阈值反而要调低,防止把有效短窗口滤掉。
3.3 把访问窗口组织成调度决策的可行性矩阵
窗口清洗完,还需要转成强化学习能用的形式。卫星调度最常见的建模方式是可行性矩阵:行是任务,列是卫星,矩阵元素表示该任务能不能被对应卫星执行。这个矩阵可以由访问窗口直接构造:
import numpy as np def build_feasibility(access_df, num_tasks, num_sats): matrix = np.zeros((num_tasks, num_sats), dtype=int) for _, row in access_df.iterrows(): t = int(row['mission_id']) s = int(row['sat_id']) matrix[t, s] = 1 return matrix这里的核心假设是每条访问记录已经带上了sat_id。如果 STK 导出的数据里没有,就用卫星名称映射:df['sat_id'] = df['sat_name'].map(sat_name_to_id)。可行性矩阵的意义在于,多智能体强化学习的动作掩码可以直接由它生成——智能体不需要学会「不选不可行的卫星」,因为动作掩码已经把不可行动作禁掉了,这是减少探索空间最直接的手段。
4. 多智能体强化学习接入:数据增强、状态设计和奖励塑形
4.1MRL_data_400_1000_augmented.csv训练前先做兼容检查
名字里带augmented的数据通常经历过一轮增强。常见的增强方式是把原始任务经纬度做小幅扰动、对访问窗口做时间平移,但这类增强有一个前提:增强后的数据仍然要满足轨道约束。这个问题我在实践里踩过——直接对lab1.csv做经纬度高斯扰动,生成出的任务大部分落在卫星星下点轨迹之外,access.csv里的窗口数量骤减,模型训练直接震荡。
正确的检查方法是抽样对比增强前后数据集里的任务分布:
import pandas as pd orig = pd.read_csv('lab1.csv') aug = pd.read_csv('lab1_augment.csv') print(orig['lat'].describe()) print(aug['lat'].describe()) print(len(aug) / len(orig)) # 增强倍率重点观察lat和lon的取值范围是否仍在目标区域内。如果增强后的任务纬度跑到了 ±90° 之外,或经度越界,说明增强脚本的随机范围没设置好。卫星调度任务里,增强不是越多样越好,而是在轨道约束框住的范围内增加多样性——这个观念决定了训练能不能收敛。
4.2 状态空间的特征选择:哪些字段真正影响决策
多智能体强化学习场景下,卫星是智能体,任务是环境给予的工作负载。每个智能体在每个决策步需要知道三类信息:自身剩余资源、当前任务的属性、附近任务的竞争情况。对应到 CSV 里的字段,可以构造成下面的状态向量:
def build_state(task_row, sat_resources, neighbor_count): return { 'task_priority': task_row['priority'], 'task_duration': task_row['duration'], 'deadline_gap': (task_row['deadline'] - task_row['start']).total_seconds(), 'sat_energy': sat_resources['energy_remaining'], 'sat_memory': sat_resources['storage_remaining'], 'neighbor_cnt': neighbor_count }deadline_gap是截止时间与当前时间的差值,代表任务紧迫度;neighbor_cnt是当前时刻附近待调度的任务数量,代表局部竞争激烈程度。这两个字段是调度的关键信息,如果原始 CSV 里没有对应列,可以在预处理时从访问窗口的密度估算。
特征不是越多越好。卫星调度场景下,把通信速率、姿态角、光照条件全塞进状态向量,会让多智能体算法的探索空间爆炸式增长,训练半天也看不到调度成功率的起色。先做减法,只用上面五个字段,把基线跑通,再逐步加维度。
4.3 动作空间和奖励塑形:用访问窗口做天然约束
每个卫星的动作空间建议设计成二选一的方式:执行当前任务或跳过。如果有多个候选任务,可以用「选择第 k 个候选任务」作为动作,k 为候选任务数。候选任务列表由可行性矩阵生成。
奖励塑形是实验最容易翻车的地方。只给「全部任务完成时 +1」的稀疏奖励,在 400 个任务规模下几乎不可能收敛。实战里建议用密集奖励加分惩罚:
def reward_fn(row, success, wait_time, energy_cost): r = 0.0 if success: r += 2.0 * row['priority'] # 高优先级任务给更高奖励 r -= 0.05 * wait_time # 等待越久惩罚越重,鼓励早调度 else: r -= 0.3 * row['priority'] # 错过任务惩罚,但不能盖过成功信号 r -= 0.01 * energy_cost # 能耗约束作为软惩罚 return r这个函数的逻辑是:成功执行任务获得与优先级成正比的奖励,同时扣掉等待时间和能耗成本;错过任务扣分,但扣分幅度要小于成功得分,否则智能体会倾向「什么都不做」这种安全策略。多智能体场景下,所有卫星的奖励相加形成团队奖励,但要注意加和会导致「搭便车」问题——个别卫星不做任何事也能分享团队奖励。缓解办法是加入个体贡献项,比如统计每个智能体单独完成的任务数,在总奖励中按比例分配。
4.4 算法选型参考:MADDPG 还是 QMIX
这个资源的数据格式是典型的「集中式训练、分布式执行」场景,QMIX 和 MADDPG 都适用,但差异不小。QMIX 是值函数方法,动作空间离散,适合卫星调度这种「执行/跳过」的二元决策;MADDPG 是演员-评论家方法,动作连续,需要额外加掩码处理离散动作。对毕设或课程设计来说,QMIX 更容易先收敛。
不过我的建议是不要一上来就套 MARL 框架。先用单智能体 DQN 在access_200_500.csv上跑一版调度基线,记录调度成功率;再把同等的状态、动作、奖励用到多智能体上。这样如果多智能体版本效果差,你能定位到是算法问题还是环境问题,而不是两个问题混在一起。这个流程大概会占掉整个实验周期的三分之一,但它能救回大量盲目调参的时间。
从lab3_200.csv这种小规模数据出发,单智能体 DQN 收敛时间通常在半小时内,足以验证奖励函数和数据流水线没有 bug。确认无误后,再切到MRL_data_400_1000_augmented.csv跑多智能体版本。
5. 卫星调度实验避坑:五个最容易让人想删库的坑
5.1compute_access.py连接不上 STK
现象:脚本运行几分钟后卡死或直接抛socket timeout,没有任何访问窗口生成。
原因:最常见的是 STK11 场景根本没打开,或打开了但 Connect 服务端口没启用。STK 11 默认不会把 Connect 端口 7070 开放,需要手动在桌面端确认 TCP 服务在监听。
解决:先用netstat -ano | findstr 7070确认端口是否存在监听。没有监听时,打开 STK11 点击Connect插件面板启用 TCP。如果端口被其他程序占用,在 STK 连接配置里换取一个空闲端口,并同步修改 Python 侧的port参数。
5.2 访问窗口时间列解析出来的年份莫名其妙
现象:pd.to_datetime()把AccessStartTime解析成 1958 年或 1970 年的日期,访问窗口全部失效。
原因:STK11 导出时间的基准通常是「场景时间之前某个参考点」,或者是 UTCG 格式带d Sep 2020这种字符串,pandas 默认解析模式把20解析成两个世纪前。
解决:显式指定时间格式:
pd.to_datetime(df['AccessStartTime'], format='%d %b %Y %H:%M:%S.%f')在 STK11 里把场景时间基准改为UTCG,导出时选择完整年月日格式。改完后再跑一次compute_access.py,时间列正常,后续的训练数据才值得信任。
5.3 增强数据集训练时 loss 乱跳、调度成功率横盘
现象:和原始数据相比,使用增强数据后模型指标反而变差,甚至训练 loss 像心电图。
原因:augment数据里的经纬度扰动超出了轨道覆盖范围。卫星的星下点轨迹只在有限纬度带内摆动,你把任务随机生成到轨道覆盖不到的位置,访问计算自然得到空窗口,训练信号变成纯噪声。
解决:对照access_200_500.csv里任务经纬度的真实分布,给mission.py的lat_range和lon_range设置边界,让增强出的任务落在同样地理范围内,再重新计算访问窗口。另外一个参考是直接用原有的MRL_data_400_1000_augmented.csv作为训练数据,它已经验证过可行性,不要重复增强。
5.4 多智能体训练时优势方退化成「躺平」
现象:整体奖励还在上升,但统计各卫星单独完成的任务数,发现其中一两颗卫星执行了绝大多数任务,其他卫星基本都是零。
原因:团队奖励的加总方式让部分智能体搭便车,它们发现不干活也能收到同样的团队奖励,Q 值更新逐渐收敛到「不行动」。
解决:在奖励函数里加上个体激励项,统计每个智能体实际执行的调度动作数量,再折算成个体奖励加进各自损失函数中。QMIX 这类值函数方法还要检查q_total的单调性约束是否正确,否则部分智能体的贡献会被掩盖。
5.5 换 Python 3.11 后 numpy/pandas 频繁出兼容问题
现象:pip install -r requirements.txt装完,import numpy正常但import pandas报错,或者 sklearn 版本的 API 变了跑不通。
原因:STK11 时代的代码依赖的老版本库和 Python 3.11+ 不兼容,特别是pandas的时间解析接口和numpy的随机接口在旧版本中存在差异,直接升级大版本反而触发坑。
解决:使用 Python 3.8 搭建虚拟环境:
conda create -n stk11_mar python=3.8 pip install numpy==1.21.0 pandas==1.5.3numpy 1.21 和 pandas 1.5 是 STK11 自动化场景里经过足够多人验证过的组合,改这几个版本能省掉至少一个下午的排错时间。
6. 用工厂模式统一 mission 生成入口,把实验参数变成配置文件
到了多智能体训练稳定跑通之后,最值得做的一件事,是把mission.py从「文件里改随机范围」升级成「配置文件驱动」。原因很实际:跑实验时你需要在 200、400、1000 任务规模之间反复切换,每次改完一看 CSV 忘了备份,后面想对比基线都对不上。我一般会把要调的核心参数抽到一个experiment_config.py里,再给 mission 写一个简单工厂函数:
import csv from mission import Mission def create_missions(num, lat_range=(20, 45), lon_range=(70, 135)): missions = [] for _ in range(num): m = Mission() m.lat_range = lat_range m.lon_range = lon_range missions.append(m.__dict__) return missions def save_missions(missions, filename): with open(filename, 'w', newline='') as f: writer = csv.DictWriter(f, fieldnames=missions[0].keys()) writer.writeheader() writer.writerows(missions) # 200 任务规模,华东到东南亚走廊 missions_200 = create_missions(200, (20, 45), (70, 135)) save_missions(missions_200, 'data/lab3_200_custom.csv')这样设计的核心收益是:任务规模、经纬度范围、生成数量全部参数化,你不需要进入mission.py逐行找随机范围在哪。后面的增强、访问计算、训练脚本都能把这些参数串起来。跑对比实验时的标准动作是「配置三件套」:任务规模、访问窗口阈值、奖励函数系数,存进实验日志文件名。
从那以后,我每次开始新一组实验都强制先跑一遍这个工厂函数生成新 CSV,再走compute_access.py管道,确认 CSV 的时间和窗口数据都正常才开始训练。参数写在代码里是玄学,写进配置里才是工程。这套资源真正的价值不在于帮你把多智能体算法写好,而在于它给了你一条从 STK 到 CSV 再到训练集的数据通路——通路通了,算法反而不是瓶颈。希望帮到你。
本文还有配套的精品资源,点击获取