基于STK11的卫星任务调度强化学习数据生成与训练实践
简介基于STK11场景的卫星任务调度与强化学习训练数据生成系统面向卫星任务规划与机器学习交叉领域的研究者或工程师提供从随机观测任务生成、卫星可访问时段计算、数据对齐与批次排序到数据增强、模型训练及奖励可视化的完整链路解决方案。压缩包共包含205个文件约79.58MB主要有13个Python处理脚本、39个CSV训练数据、63张PNG可视化图表、7个PTH模型权重以及STK场景文件SA、SN、SC等和DOCX/TXT说明文档方便直接对照工程结构和说明进行复现。目前已有67人学习下载适合需要构建大规模卫星观测任务数据集的读者参考。资源内随机任务生成、访问时段计算、数据对齐、批次排序、数据增强等模块均有对应代码与样例数据配合模型训练和奖励可视化结果能够帮助快速理解强化学习在卫星调度中的应用流程相关脚本和工程结构可直接修改复用服务于航天、地理信息、环境监测等方向的算法验证与仿真应用。1. 基于STK11场景的卫星任务调度强化学习数据生成卡点从来不在算法基于STK11场景做卫星任务调度与强化学习训练数据生成最反直觉的一点是训练不卡算法卡在数据。公开的卫星调度数据集要么只有几十个场景要么把访问时段提前算好、丢给你一个抽象矩阵换个星座布局立刻失效。这套系统的思路是把STK11当几何真值源STK负责算卫星对地面目标的访问时段外部脚本负责随机任务生成再把访问时段和任务窗口做数据对齐处理经过批次排序与数据增强后进入模型训练最后用奖励可视化确认策略学到了调度规律。适合在轨任务调度、遥感星座运营以及想从运筹优化转强化学习的工程师。2. STK11场景建模与随机任务生成先建沙盘再撒任务整套系统按三层来拆最容易落地STK11提供场景与轨道几何脚本层负责随机任务生成和落盘训练层消费前两层产出的数据。常见做法不是手工在GUI里逐颗卫星点而是通过STK11的COM接口把场景创建、轨道设置、目标增删全部参数化这样换一套星座布局只是换一个配置文件。2.1 用Windows COM接口把STK11变成可编程调度沙盘STK11在Windows下暴露了Application对象Python用pywin32就能直接驱动。先拿Application再取Personality2根对象后续的对象创建、命令执行都挂在root上。import win32com.client from win32com.client import constants as C stk win32com.client.Dispatch(STK11.Application) stk.Visible True stk.UserControl False root stk.Personality2 root.NewScenario(obs_gen) sc root.CurrentScenario sc.SetTimePeriod(1 Jan 2025 00:00:00.00, 1 Jan 2025 06:00:00.00) sat sc.Children.New(C.eSatellite, Sat-0) sat.SetPropagatorType(C.ePropagatorJ4Perturbation) root.ExecuteCommand( SetState */Satellite/Sat-0 Classical J4Perturbation 1 Jan 2025 00:00:00.00 J2000 6878.0 0.00124 51.6 30.0 0.0 0.0 60 1 Jan 2025 06:00:00.00 )说明场景设6小时是权衡结果。太短单星对同一目标的访问次数太少训练样本里“二次访问”这类关键状态根本不会出现太长J4传播步数多批量化生成脚本慢到没法迭代。SetState里J2000之后依次是半长轴(km)、偏心率、倾角、升交点赤经、近地点幅角、平近点角然后是积分步长和结束时刻。这条命令在不同补丁版本里对关键字大小写有差异批量跑前先拿单星试一条确认返回success再放开循环。提示不要在一开始就上HPOP。调度训练数据要的是访问几何而不是精密定轨HPOP考虑阻力、光压和第三方引力一颗星6小时弧段要算几十秒J4只要秒级。用来生成训练样本两者访问窗口差异通常在10秒以内远小于后面要引入的任务时间窗抖动范围。批量化建模用配置文件控制星座布局我一般把半长轴6800~7200km、倾角51.6°和太阳同步两种倾向按比例混合升交点赤经均匀散布。这样生成的访问时段能覆盖不同纬度带训练出来的策略不会只认识一个星座。目标用PointTarget而不是AreaTarget点目标参与访问计算比区域边界快一个量级等最终验证集再换AreaTarget重算一批做比对。2.2 随机任务生成四个参数组而不是随机撒点随机任务如果只随机经纬度生成的数据没有调度难度梯度策略会停留在“见窗就接”的水平。我一般把生成逻辑拆成四个参数组参数组字段典型取值作用空间分布lon/lat范围、区域密度全球或按区域加权决定星座几何覆盖难易时间分布释放时间、截止时间、窗口长度900/1800/3600 s决定时间竞争强度服务需求观测时长、任务类型成像/数传/应急5~40 s决定单星可串行的任务数优先级priority、惩罚权重1/2/3按0.5/0.35/0.15决定奖励函数的相对大小import numpy as np import pandas as pd def gen_tasks(n, epoch_sec, rng): 生成n个原始任务只写属性不做任何几何计算 rows [] for i in range(n): lon rng.uniform(-180.0, 180.0) lat rng.uniform(-55.0, 55.0) tw_len float(rng.choice([900, 1800, 3600])) tw_start rng.uniform(0.0, epoch_sec - tw_len) # 防窗口越界 dur rng.uniform(5.0, 40.0) prio int(rng.choice([1, 2, 3], p[0.50, 0.35, 0.15])) ttype rng.choice([IMG, COMM, EMER], p[0.6, 0.3, 0.1]) rows.append(dict(task_idi, lonlon, latlat, tw_starttw_start, tw_endtw_start tw_len, durdur, prioprio, typettype)) return pd.DataFrame(rows)说明tw_start必须在[0, epoch_sec - tw_len]里取否则一批任务全挤在场景末尾可访问时段样本几乎为零。EMER对应应急观测窗口给得更短用来模拟高时间竞争。窗口长度、时长、优先级三个分布直接决定数据集难度生成完先画三个字段的直方图再决定要不要调参。2.3 落盘格式用Parquet组织大规模观测任务数据任务池和访问记录要跨训练循环反复读取这套系统的目标是“大规模卫星观测任务数据”动不动几百个episode、上千万行访问记录CSV的解析开销吃不消。我按episode一个Parquet文件落盘时间统一转成相对场景起点的浮点秒。import pyarrow as pa import pyarrow.parquet as pq schema pa.schema([ (episode_id, pa.int32()), (task_id, pa.int32()), (sat_id, pa.int32()), (acc_start, pa.float64()), (acc_end, pa.float64()), (tw_start, pa.float64()), (tw_end, pa.float64()), (dur, pa.float64()), (prio, pa.int32()), ]) tab pa.Table.from_pandas(access_df[schema.names], schemaschema) pq.write_table(tab, fep_{ep_id:04d}.parquet, compressionzstd)说明文件名里ep是episode编号训练脚本按编号读取即可复现。zstd压缩对浮点时间列压缩比一般在3~5倍。每个episode目录里再放一个meta.json记录生成种子、轨道参数、增强参数和丢弃率训练脚本启动时读meta并写进TensorBoard的hparams回头对比实验不用翻git记录。3. 卫星可访问时段计算与数据对齐处理从几何可见到调度可用STK算出来的“可见”和调度能用的“可观测”是两码事。几何可见只代表传感器能看到目标调度层还要满足任务窗口、最短观测时长、卫星资源余量三个条件。这一章就是把STK的访问计算结果加工成RL能读的状态矩阵。3.1 访问计算的批量组织先粗过滤再做STK Access卫星目标做全排列访问计算N颗星乘M个目标就是N×M次ComputeAccess。假设6颗星对1000个目标6000次计算每轮场景都跑脚本会慢到没法迭代。我一般先做纬度带粗过滤再调用STK Access。def compute_access_batch(root, sats, targets): 按纬度带粗过滤后再算Access减少无效STK调用 results [] for sat in sats: for tgt in targets: if abs(sat.subpoint_lat - tgt.lat) 30.0: continue acc sat.GetAccessToObject(tgt) acc.ComputeAccess() dp acc.DataProviders.Item(Access Data) res dp.Exec(root.CurrentScenario.StartTime, root.CurrentScenario.StopTime, ) ds res.DataSets[0] rows list(zip(ds.GetValues()[Start Time], ds.GetValues()[End Time])) results.extend((sat.id, tgt.id, s, e) for s, e in rows) return results说明subpoint_lat是上一轮传播后缓存的最大星下点纬度属于近似值只用来滤掉明显没有交集的星地配对阈值宁可设大不要设小漏掉真实访问段影响的是样本覆盖。真正口径以STK的Access结果为准粗过滤只影响运行速度不影响精度。3.2 时间窗对齐重叠区间与最短观测时长的判定拿到访问段后和任务窗口求交集这就是整套系统里“数据对齐处理”的核心。两个时间窗的重叠如果覆盖不了观测时长这个样本即使写进数据集训练时也永远学不到正奖励。def align_access(acc_df, task_df): 对齐访问时段与任务窗口只保留能满足观测时长的记录 merged acc_df.merge(task_df, ontarget_id) ov_s merged[[acc_start, tw_start]].max(axis1) ov_e merged[[acc_end, tw_end]].min(axis1) merged[ov_s] ov_s merged[ov_e] ov_e merged[feasible] (ov_e - ov_s) merged[dur] return merged[merged[feasible]].copy()说明ov_s取两个时间窗各自开始时刻的较大值ov_e取较小值两者之差就是几何重叠区间。feasible列是后面模型训练里action mask的依据feasibleFalse的候选在状态里直接置0不允许智能体选择。对齐处理最容易踩的坑是时间单位不一致STK报告能导出UTC字符串也能导出距起点秒数混用会让重叠区间凭空多几小时生成阶段统一用浮点秒落盘前用pandas校验数据类型。注意merge之前不要删掉没有访问记录的任务id。全程不可见的任务要保留成负样本这类样本对训练“拒绝”动作至关重要。3.3 状态特征对齐把轨道几何转成强化学习观测向量访问时段对齐解决“能不能接”状态特征对齐解决“怎么描述当前局面”。卫星ECI坐标是几千公里量级任务窗口是秒级直接拼在一起网络很难收敛。我做两类处理时间特征相对化几何特征归一化。def build_obs(feat, ep_start, dims): 把一帧调度状态拼成定长向量dims保存归一化上下界 obs np.zeros(dims[total], dtypenp.float32) obs[0] np.sin(2 * np.pi * (feat[t] - ep_start) / 86400.0) obs[1] np.cos(2 * np.pi * (feat[t] - ep_start) / 86400.0) obs[2:5] ((feat[sat_pos_eci] - dims[pos_lo]) / (dims[pos_hi] - dims[pos_lo])) obs[5] feat[energy] / feat[energy_max] obs[6:8] feat[tgt_lonlat] / np.array([180.0, 90.0]) obs[8] feat[prio] / 3.0 obs[9] np.clip(feat[slack] / 3600.0, 0.0, 1.0) return obs说明时间用sin/cos编码避免凌晨0点前后时间值跳变导致的不连续位置用固定上下界线性压缩energy和slack压到0~1。slack是tw_end减观测时长再减当前时刻代表“还有多少富余时间”对训练晚点惩罚很敏感值得单独拆出来。归一化上下界必须在生成阶段冻结训练阶段不允许拿训练集统计量重算否则验证集特征分布会漂移。4. 批次排序与数据增强让训练集不再被场景顺序绑架生成脚本按场景顺序落盘如果训练器按文件顺序读前几十个batch全来自同一批场景时间相关性极强。批次排序和数据增强是配套动作排序负责控制分布增强负责扩大覆盖。4.1 批次排序的三种组合策略与离线强化学习敏感性离线强化学习对batch内分布比在线训练敏感得多。以IQL这类离线Q学习方法为例它不与环境交互只能从给定batch里估计Q值batch里高优先级任务占比和全量数据集偏差大估计出的动作价值就会带偏策略更新。批次排序在这里的作用是把数据集真实分布重新暴露给优化器。常用组合是三种按场景规模分桶以任务数把episode分进S/M/L/XL四档训练时按桶采样保证每个batch任务密度一致。按截止时间升序episode内部任务按tw_end排序让策略先看到紧迫任务与人工调度习惯一致。按难度分层用“平均每任务可访问卫星数”当难度指标先简单后复杂形成课程学习。df[n_tasks] df.groupby(episode_id)[task_id].transform(count) df[bucket] pd.cut(df[n_tasks], [0, 50, 200, 800, 100000], labels[S, M, L, XL]) df df.sort_values([bucket, episode_id, tw_end])说明排序键依次是规模桶、场景编号、任务截止时间这样同一batch内任务密度接近且场景不交叉混入。不要把episode_id的原始编号当排序键生成顺序里隐含着发射时间、轨道参数的递进变化按它排序等于把干扰因素也排进batch。4.2 数据增强的五个操作与合法性约束选择数据增强方法不要跟图像的风。图像里翻转裁剪是对称性先验调度数据里能确定的先验只有一条访问几何可信任务属性可变。所以我只对任务属性做增强访问时段保持STK真值不变既产生新样本又不会引入伪造几何。增强操作具体做法约束条件时间窗抖动tw_start/tw_end整体平移±5%不修改访问时段平移后重叠不足观测时长的样本丢弃时长缩放观测时长乘U(0.8,1.2)缩放过长导致重叠不足时丢弃优先级置换同一episode内prio随机互换只换数值不换任务几何卫星掩蔽随机把某颗卫星全部访问置空模拟单星掉线检验策略冗余场景裁剪取任务较少的子集组成新episode子集内保持原时间顺序def augment_time_window(df, rng, jitter0.05): 任务窗口整体平移访问时段保持不动 shift rng.uniform(-1.0, 1.0, len(df)) * df[win_len] * jitter df df.copy() df[tw_start] df[tw_start] shift df[tw_end] df[tw_end] shift return df def augment_sat_drop(avail, rng, p0.08): 把指定卫星在全部任务上的可访问窗口置为NaN out avail.copy() for s in range(avail.shape[1]): if rng.random() p: out[:, s, :] np.nan return out说明时间窗抖动的shift按窗口长度比例计算而不是固定秒数短窗口和长窗口保持各自的紧迫度。卫星掩蔽的p我控制在0.08左右超过0.2会让“无解场景”占比过高策略退化成大量拒绝。augment_sat_drop返回三维可用性矩阵形状是n_task×n_sat×2第三维存窗口起止NaN代表不可用。4.3 增强之后的合法性复检与种子链增强不是做完就结束每个操作都可能把可行样本变不可行。复检规则只有一条重新执行第3章的align逻辑feasibleFalse的样本直接丢弃但丢弃率必须记下来。丢弃率超过5%说明增强参数过猛训练出来的策略偏保守丢弃率接近0说明增强没有产生新样本等于白做。def recheck(df): before len(df) ok df[df[ov_e] - df[ov_s] df[dur]] drop_ratio 1.0 - len(ok) / before return ok, drop_ratio说明增强、复检、落盘要写进同一个流水线drop_ratio作为元数据随数据集一起落盘。另外整个流水线里所有rng要用同一条种子链每个episode的种子由episode_id加全局种子派生。这样中途新增一个增强操作旧样本仍能按原种子复现不会让新旧数据集混在一起没法对比。5. 模型训练与奖励可视化不让曲线欺骗你5.1 奖励函数拆成四条可追溯曲线调度类的奖励往往是加权和曲线不降时最难判断哪个分量出了问题。不管用DQN还是PPO这类actor-critic结构做法都一样把奖励拆成完成、拒绝、晚点、能耗四个分量分别写进TensorBoard比只盯episode均值有用得多。from torch.utils.tensorboard import SummaryWriter writer SummaryWriter(runs/obs_sched_dqn) writer.add_scalars(reward/decompose, { complete: ep_complete.mean(), reject: ep_reject.mean(), delay: ep_delay.mean(), energy: ep_energy.mean(), }, step)说明complete是完成任务的优先级加权和reject是拒绝惩罚delay是平均迟到比例energy是资源消耗。四个量纲不同add_scalars会为每个键单独建曲线方便比对。记录频率建议每50个episode一次每一步都记噪声太大看不出收敛方向。5.2 完成率热力图与失败样本回放两个落地技巧一是画完成率热力图。把目标区域按经纬度分格统计每个格子的任务完成比例用matplotlib渲染成图再add_figure进TensorBoard。如果热力图上某些格子和星座覆盖空洞完全重合说明策略没有学会在无访问时段果断拒绝而是在做无效尝试。grid, _, _ np.histogram2d( tgt_lat, tgt_lon, bins[36, 72], range[[-90, 90], [-180, 180]], weightscompleted) plt.imshow(grid, originlower, aspectauto) writer.add_figure(accept/heatmap, plt.gcf(), global_stepstep)二是失败样本回放。维护一个FIFO队列存最近若干个低奖励episode每训练若干步混入这些episode重放。这个技巧本质是带优先级的经验回放专门治训练中段奖励停滞。最后是部署侧的小技巧策略网络训好后用torch.onnx.export导出ONNX调度器用C侧ONNX Runtime加载推理延迟压到毫秒级训练阶段用Python、部署阶段用C两套权重不会出现不一致。本文还有配套的精品资源点击获取