
简介这是一份面向卫星通信与强化学习初学者的MATLAB仿真资源演示Q学习算法在卫星网络中完成智能决策与优化的完整流程。压缩包共2个文件均为.m脚本整体大小仅2KB代码结构紧凑却完整覆盖了卫星网络场景参数定义、Q表初始化、智能体与环境迭代交互、奖励获取及Q值更新等核心环节非常适合逐行研读与二次修改。目前已有146人学习资源热度不错。通过运行和分析代码读者可以掌握状态与动作空间的设计方法、奖励函数的构造逻辑以及学习率和折扣因子如何影响算法收敛同时还能对照卫星网络中的路由选择、频谱分配、功率控制等典型问题直观理解强化学习如何根据链路状况动态调整策略。对于想快速入门强化学习在通信网络中应用的研究者或学生这是一份值得动手实践的浓缩范本。1. 卫星网络的Q学习仿真包它到底解决什么问题拿到一份“Matlab系列--卫星网络q学习matlab仿真.zip”第一反应通常是怀疑Q学习这种已经存在几十年的表格算法真的还能在卫星网络场景里派上用场吗答案是能但需要先理解它解决的不是轨道计算而是星间链路调度问题。低轨卫星拓扑动态变化、星间链路通断频繁传统静态路由需要频繁重算而Q学习可以让每颗卫星只凭本地观测积累经验逐步学会“这条链路上此刻该不该转发”。这份资源适合两类人一类是做通信仿真、想快速对比强化学习与传统路由的研究生另一类是刚接触Q学习、想要一份能跑通的Matlab工程作为起点的工程师。下面我会把建模思路、代码结构、收敛实验和典型坑完整拆开讲。2. Q学习建模把星间链路调度写成MDP的四个关键动作2.1 为什么卫星拓扑场景天然适配表格Q学习卫星网络和地面网络最明显的差别是节点在动。低轨场景下星间链路的通断状态每隔几十秒就会刷新一次单纯用最短路径算法需要全局拓扑信息每刷新一次就要在全网范围重新算一遍。Q学习不同它不需要预先知道完整的拓扑矩阵也不需要业务矩阵的全局视图只需要在当前时隙感知到“我在哪颗星、有哪些邻居可选、链路质量如何”执行一次转发动作拿回一个奖励值就能持续修正一张Q表。有人会问现在深度强化学习这么普及为什么不直接上DQN这里有一个很现实的取舍在典型的单层低轨场景里如果只控制数据向哪颗邻居星转发状态空间通常只有几百到几千个离散值表格完全装得下。用Q表的好处是透明、可逐项检查能直接看到每个状态下四个动作的Q值差距。等以后扩展到大星座、每个节点还要同时决策功率和波束时再升级到DQN也来得及。这正是这份zip包采用经典Q学习的原因收敛快、好调试、不会被深度网络的随机性干扰现象吓退一步。2.2 状态、动作、奖励三要素怎么对齐通信指标用Q学习做链路调度最容易出现的失误是直接写算法、不先梳理MDP映射。包里对三要素的定义方式很有代表性我拆开整理如下。这个映射表也是后来我自己搭通信仿真时最常用的模板。MDP要素包内默认定义可以替换的扩展方向状态S当前卫星编号 可选下一跳集合 链路SNR档位 业务队列积压档位加入业务QoS等级、卫星经纬度网格动作A选择一个邻居卫星作为转发目标离散索引加入发射功率档位、选择波束编号奖励R吞吐量正向奖励 时延惩罚 丢包惩罚加入能耗惩罚、链路切换次数惩罚状态转移P由业务到达和信道状态共同决定可以替换成真实轨迹数据或信道测量数据包内奖励函数实现的是单步奖励也就是每完成一跳转发就结算一次。这样设计的最大好处是反馈密集一个回合内Q值就能向源头方向传播。但它也埋着一个隐患如果权重没有调好卫星会变得短视只看当前一跳时延低却把数据转发到了下一跳注定拥堵的节点。我一般会把奖励写成时延负项和队列积压负项的组合且权重先偏向队列积压因为队列积压是卫星本地能直接观测的最强拥塞信号。2.3 状态编码模板先压缩状态再写更新式拿到场景后第一步不是写贝尔曼更新而是先设计状态编码。包内使用的是一个离散编码函数把SNR、队列、邻居组合压成一个整数索引用作Q表行号。下面是典型的实现思路。function s encodeState(satId, snrLevel, queueLevel, candCount) % 将连续物理量离散化为整型状态索引 % satId: 当前卫星编号(1..N) % snrLevel: 链路信噪比档位(1..M) % queueLevel: 业务队列积压档位(1..K) % candCount: 当前可选邻居数量(1..C) s sub2ind([N, M, K, C], satId, snrLevel, queueLevel, candCount); end这里有一个容易忽略的陷阱不要直接对“具体是哪些邻居”做组合枚举。例如某颗星有6个邻居全部组合编码会产生2的6次方种可能其中绝大多数在运行时用不到Q表却已经按最大状态数分配了内存。更稳妥的做法是只对“可选邻居数量”和“最优SNR序号”两个标量编码。这样状态数从指数级降为线性级Q表内存占用和访问稀疏度都更可控。2.4 与固定路由的收敛差异Q表为什么不需要全局重算低轨卫星轨道有强周期性拓扑变化不是完全随机的。很多工程师会说既然轨道可预测为什么不用固定路由表按周期切换问题在于链路拥塞和业务到达是统计变化的固定路由只能在平均意义上接近最优遇到突发流量它会持续把数据送进已经拥塞的链路直到下一个重算周期到来。Q学习相当于在每个节点存了一张“路况经验表”遇到相似状态时直接查历史最优动作不再依赖全网泛洪。更直白地说Q表是本地经验缓存对应的是“这个状态下选这个动作长期回报更大”的统计规律。卫星网络的状态集合虽然会变但轨道周期会反复覆盖大量相似状态Q表在这些重复状态上越滚越准。这就是它在动态拓扑下不需要即时全局重算也能维持较高吞吐量的原因。3. Matlab代码落位demo结构与三处必修参数3.1 解压之后的文件结构与主入口这份zip解压后核心文件的分工比较清楚我在实际复现时按下面这套流程理解代码会顺畅很多main_sat_qlearning.m主仿真脚本负责初始化环境和回合循环env_satellite.m卫星网络环境状态转移、奖励结算都在这里action_select.m动作选择实现epsilon-greedy策略reward_calc.m单跳奖励计算函数q_table.mat预训练Q表可以直接加载做策略评估plot_result.m收敛曲线绘图脚本第一次运行时我建议不要直接在Matlab GUI里点运行而是用命令行模式。它不弹窗、便于批处理调参日志也能完整保存。matlab -batch main_sat_qlearning;这条命令会在当前目录直接执行主脚本跑完后生成q_table.mat和reward_history.mat两个文件。第一次复现的目标很简单就是让整条链路从初始化到出图完整走一遍确认包的代码依赖没有问题。如果这一步报错多半是工作目录没有切换到解压目录或者是用了太老的Matlab版本导致函数名不兼容。3.2 主循环回合、步进、更新三层分开主循环是Q学习的标准骨架代码量不大但三个层次必须分明最外层是回合循环中间是单回合内的步进循环最内层是Q值更新。我复现时把这段核心循环整理如下。for episode 1:maxEpisodes state env.reset(); totalReward 0; for step 1:maxSteps % epsilon概率随机探索否则选择当前Q值最大的动作 if rand() epsilon action randi(numActions); else [~, action] max(Q(state, :)); end [nextState, reward, done] env.step(action); % 贝尔曼更新用当前估计的下一状态最优值逼近真值 Q(state, action) Q(state, action) alpha * ... (reward gamma * max(Q(nextState, :)) - Q(state, action)); totalReward totalReward reward; state nextState; if done break; end end % 每50回合执行一次探索率衰减 if mod(episode, 50) 0 epsilon max(epsilonMin, epsilon * epsilonDecay); end rewardHistory(episode) totalReward; end这段代码里有三个参数决定最终效果。alpha是学习率一般设0.05到0.2取值过大会导致Q值来回震荡过小则收敛速度慢到让人失去耐心。gamma是折扣因子卫星网络这类单次模拟长度有限的场景取0.9附近比较合理太小会让卫星只看眼前一跳太大又会让远期奖励传播过慢。epsilon初始值建议0.9最低0.01衰减系数0.85左右衰减过慢浪费前几百个回合衰减过快容易早早锁死在次优策略上。每回合maxSteps上限必须从小给它设一个值否则链路断开时卫星会在动作空间里空转到超时。3.3 奖励函数别让卫星“贪小利吃大亏”奖励函数是这份资源里最需要动手改的地方。默认实现用的是“吞吐量正向奖励扣掉三个惩罚项”的结构核心代码和注释如下。function r reward_calc(throughput, delay, queuePenalty, dropNum) % 单跳转发后的奖励结算 % throughput: 本跳成功传输的数据量(Mbit) % delay: 本跳时延(ms) % queuePenalty: 队列积压比例(1.0表示满载) % dropNum: 本跳丢包数 base throughput * 10; delayPenalty max(0, delay - dMax) * 2; queuePenalty queuePenalty * 5; dropPenalty dropNum * 8; r base - delayPenalty - queuePenalty - dropPenalty; end这段代码的思路是对的吞吐量是正向指标时延只惩罚超过阈值的部分防止正常时延也被当成负数惩罚队列惩罚让卫星不要一股脑涌向高负载节点丢包惩罚很重目的是避免卫星在低质量链路上反复重传。实际操作中我发现一个规律如果仿真里的卫星学会绕开拥塞节点但整体吞吐量反而下降多半是奖励缺少“向目标星靠近”的正向引导。单跳奖励只看局部收益数据可能被绕到与目的星越来越远的方向。此时需要在到达目标星时增加一个较大的额外正奖励而不是只依赖逐跳惩罚。3.4 探索节奏动作选择函数为什么把epsilon放在回合级很多人第一次改动作选择函数时会顺手把epsilon写进每个步进的判断里结果发现同一个回合内前期探索后期利用策略乱跳。正确做法是在回合级维护epsilon一个回合内保持同一个探索水平。包内action_select函数接口如下。function action action_select(Q, state, epsilon, numActions) % epsilon-greedy先判断是否探索再决定利用 if rand() epsilon action randi(numActions); else [~, action] max(Q(state, :)); end end实际调参时我习惯让探索率按回合线性衰减而不是指数衰减。线性衰减在前期给足探索空间后期稳定利用。如果在某个场景里发现Q表收敛后策略又在跳变可以先怀疑是不是epsilon没有降到足够低。这个函数不需要改逻辑只需要保证传入的epsilon是回合级变量而不是步进级变量。4. 收敛性实验两千回合后和随机策略差多少4.1 场景参数与实验基线拿到包以后第一件事是用默认参数跑通第二件事就是搭建对比实验。包里默认场景是一些低轨卫星组成的环形拓扑我复现时把参数固定为下面这一组保证后续所有对比都在同一基线之上。参数设置值说明卫星数量8节点数太少体现不出调度价值太多又超出表格容量可选动作数4每颗星平均可选邻居数量最大回合数2000足够Q表收敛的参考量单回合最大步数50防止死循环空转业务流数量10随机分布在源宿节点之间这套参数的核心思路是“让状态空间刚好落在表格能承载的上限附近”。业务流太少Q学习和随机策略差距不明显业务流太多单回合步长不够数据送不到目的地。跑完2000回合大约需要几分钟到十几分钟不等取决于机器性能。4.2 学习曲线别只看单回合数值要看滑动平均Q学习单回合奖励波动非常大直接plot出来会看到一条毛刺密集的噪声带。包里提供的plot_result脚本用了滑动平均处理这在实际分析中很关键。window 20; avgReward movmean(rewardHistory, window); figure; plot(1:maxEpisodes, avgReward, LineWidth, 1.5); xlabel(Episode); ylabel(Average Reward); grid on;滑动窗口选20到50都可以。横轴是回合数不是仿真时间每一回合相当于完整跑了一段业务调度过程。看这条曲线时重点看三个特征前200回合是否有明显上升趋势中段是否有持续震荡末尾是否进入一个窄幅平台期。如果800回合以后曲线还在大幅上下抖动说明探索率衰减太慢或学习率偏高如果曲线平滑但一直不涨说明奖励函数对正确行为的区分度过低。4.3 固定随机种子的三策略对比在通信仿真里业务到达和信道变化都是随机过程不控制随机种子对比结果就完全无法复现。我在对比实验中固定使用同一个rng种子对三种策略各跑一遍完整流程。rng(42); randomReward run_policy(random); % 纯随机选择下一跳 greedyReward run_policy(greedy); % 每步选择Q表当前最大值 qlReward run_policy(qlearning); % 带探索和衰减的完整Q学习这里的greedy策略其实是一个很强的基线因为它直接使用训练好的Q表。需要说明的是如果在同一份Q表上跑greedy和qlearning两条曲线两者前几百回合差距明显后期会逐渐接近。我按默认参数跑出来的参考量级大致是随机策略归一化收益为1.0固定最小跳数贪婪路由约1.45左右Q学习2000回合后在1.9附近。不同业务模型下绝对值会有偏差但Q学习显著优于随机、略优于静态贪婪规律是稳定的。4.4 Q表访问频率哪颗星被学得最充分只画学习曲线还不够我还会统计每个状态被访问的次数用来判断Q表里哪些部分可信、哪些部分是“瞎猜”。visitCount accumarray(stateHistory(:), 1, [numStates, 1]); [maxVisit, maxState] max(visitCount);高访问量的状态通常集中在业务源附近的节点上因为这些状态每回合都会出现低访问量的状态往往对应长时间断开的链路。如果在训练结束后发现大量状态访问次数为零说明状态编码粒度过细压缩档位太密实际运行根本覆盖不到。此时应该减少SNR档位数量而不是增加训练回合数因为Q表里那些空白行即使训练再多回合也不会被访问到。5. 避坑指南仿真不收敛、内存爆掉、对比失真的五个排查点5.1 奖励曲线长期不收敛看起来像一条噪声带现象平均回报曲线没有上升趋势从头到尾都在一个区间内抖动。原因最常见的是探索率没有衰减。epsilon一直保持在0.9卫星每步都要做随机选择Q表永远积累不起来有效经验。另一个次要原因是alpha过大单步更新摆动太剧烈经验刚被写入就被新样本覆盖。解决把epsilon改成回合级衰减每50回合乘以一个小于1的系数最低降到0.01alpha从0.1调低到0.05或0.02再跑一次确认曲线毛刺明显减小。衰减系数0.85是起点不是标准答案。5.2 回合奖励在涨业务完成率却在下降现象只看Q学习曲线会觉得效果不错但对比业务完成率后发现它比随机策略更差。原因奖励函数权重失衡。比如吞吐量权重过高卫星学会了把数据绕到长时延链路上抢带宽虽然单跳收益变大但端到端时延和丢包率全崩了。这是单步奖励最典型的翻车方式。解决不要只盯一条回报曲线。我一般会把奖励、业务完成率、平均时延三条曲线画在同一张图里观察趋势。把delayPenalty从超过阈值才惩罚改成线性惩罚把queuePenalty权重提高一档再看业务完成率是否跟随上升。如果完成率和Q值方向不一致说明奖励定义和真实目标脱节。5.3 Q表内存爆炸状态编码太细导致Matlab报错现象运行到中途Matlab提示Out of Memory位置通常在Q zeros(numStates, numActions)这一行。原因状态编码里包含了具体邻居组合或过密的SNR档位。每个维度乘起来之后状态数瞬间达到几十万甚至上百万而一个回合最多几千次访问绝大多数行根本不会被触碰。解决把SNR档位从10档压到4档邻居组合编码改成“可选邻居数量加最优SNR序号”状态数可以降两个数量级。先保证表格能被经验覆盖再谈精细决策。如果压缩后策略效果下降可以换用特征化Q函数而不是继续加宽表格。5.4 卫星在几个节点之间反复横跳现象单回合步数跑满数据没有到达目标星轨迹在相邻几颗星之间循环。原因奖励函数只惩罚时延不惩罚重复访问动作空间里“返回上一跳”和“往目标星方向走”的Q值都差不多卫星学到了绕圈攒奖励的路径。解决在单回合内维护一个已访问节点列表对重复访问动作给出较大的负奖励。另一个补救办法是设置maxSteps超时后直接结束当前回合并扣一个大惩罚让“空转”变成成本极高的行为。这两步同时做基本能消除循环轨迹。5.5 对比实验失真换一个种子结论就反转现象Q学习比随机策略好30%换一个随机种子再跑差距缩小到5%。原因业务流和信道状态只在特定样本下有明显差异固定单一随机种子等于只验证了一个样本。少数回合的波动完全可能盖过策略差异。解决固定多个随机种子每个种子跑完整训练后取平均再比较。我习惯把随机种子列表和参数一起存进结构体每次实验都重放同一组种子最终绘制带误差棒的对比曲线。至少跑5个种子再下结论这是一个写论文和写代码都通用的底线。6. 进阶方向从Q表到多智能体扩展的验证方法如果这份包里的Q表已经能稳定跑出收敛曲线下一步最自然的扩展是把它从单智能体调度升级成多维度协同决策。常见路线是把状态空间拆成两级上层用Q学习负责星间链路选择下层用固定规则做功率分配。这个拆分能有效避免状态爆炸环境文件env_satellite.m基本可以复用只要把动作从“选择邻居”扩展为“选择邻居加功率档位”的组合即可。另一个路线是换DQN用神经网络近似Q函数但要注意神经网络的非确定性会让调试难度明显增加建议先把表格版本理解透再换。验证训练效果时最佳做法是把epsilon强制设为0加载已保存的q_table.mat在全新的业务流样本上评估策略。这样可以确认学到的策略到底是记住了训练样本还是学到了可迁移的规律。我会额外检查“训练集时延”和“测试集时延”的差距差距过大说明过拟合到了训练流量模式。这里有一个我自己的教训曾经只盯着回合平均奖励调权重改完一版后发现回报涨得快业务完成率却悄悄降了折腾了一天才发现是奖励函数里漏了“向目标星靠近”的正向引导。从那以后我每次训练完都会先打印一条“奖励加完成率加时延”的三合一统计曲线再决定要不要进入下一步调参。希望这份拆解能帮你避免同样的弯路。本文还有配套的精品资源点击获取