GONOGO改进Qlearning强化学习Matlab代码:自适应状态与似然探索

发布时间:2026/9/24 0:24:47
GONOGO改进Qlearning强化学习Matlab代码:自适应状态与似然探索 简介一份面向强化学习初学者的GONOGO_Qlearning改进算法Matlab实现适合计算机、电子信息工程、数学等专业学生的课程设计、期末大作业和毕业设计。代码基于传统Q-learning优化通过机制改进让学习过程更稳定高效同时采用参数化编程学习率、探索率、折扣因子等均可按需调整在Matlab 2014、2019a、2024a等版本中均可顺畅运行。压缩包共5个文件大小仅1.2MB包含3个.m源码文件含主程序与辅助函数、1份Q学习PDF说明文档和1张运行结果图附赠案例数据可直接运行复现帮助读者直观对比不同参数下的学习效果。程序注释详细、思路清晰已有64人学习下载无论用于课程作业还是科研入门都能帮助读者加深对强化学习原理的理解。1. GONOGO_Qlearning 改进强化学习 Matlab 代码跑通一个能复现的改进 Qlearning 案例GONOGO_Qlearning 改进强化学习 Matlab 代码我拆完后确认了一件事它不是为了炫技而是把强化学习里最劝退的两个问题——连续状态怎么离散化、探索怎么不白费——用能直接跑的代码解决了。传统 Qlearning 要求状态网格手工划分状态一连续Q 表就变成黑匣子GONOGO 让状态节点自己长出来再用似然函数引导动作选择替代纯 epsilon-greedy 的盲目随机。main.m 采用参数化编程学习率、探索率、折扣因子全部集中在一个参数区里。无论你是做 Matlab 课程设计、期末大作业还是想要一个能直接复现的强化学习案例这套代码都值得完整跑一遍再改参数。下文按运行顺序拆 main.m、GNG_loglikeli_action.m、RunningMean_edge.m把参数改法和踩坑记录一次说清。2. 从 Q-learning 到 GONOGO改进发生在哪两层2.1 标准 Q-learning更新公式和四个关键参数理解 GONOGO 之前先把传统 Q-learning 的更新式子写出来。Q 表是一个行表示状态、列表示动作的矩阵每一步从环境拿到奖励 r 和下一个状态 s按下面的公式把这张表朝目标值压一步Q(s,a) Q(s,a) alpha × (r gamma × max_a Q(s,a) - Q(s,a))这里的 alpha 是学习率gamma 是折扣因子max_a 表示在下一个状态里挑收益最大的动作整个括号里的内容就是 TD-error。收敛的前提有两层每个状态动作对都被访问过足够多次且学习率随迭代次数下降。离散状态空间、动作数量有限时这是最简单可靠的强化学习算法。% q_learning_step.m —— 单步参数更新对应上面的贝尔曼方程增量形式 function [Q, td_error] q_learning_step(Q, s, a, r, s_next, alpha, gamma) max_next max(Q(s_next, :)); % 从下一个状态的所有动作里取最大 Q 值 td_error r gamma * max_next - Q(s, a); % 计算 TD 偏差 Q(s, a) Q(s, a) alpha * td_error; % 按学习率把 Q 值朝目标拉近一步 end代码逻辑很直白先算下一个状态的最优价值再用真实奖励加折扣后的最优价值减去当前估计得到偏差最后乘上学习率修正当前表项。学习率 alpha 控制每一步更新的步子大小推荐在 0.05 到 0.2 之间试gamma 越接近 1智能体越重视远期收益但也越容易在前期产生较大方差。下载包里没有单独抽这个函数而是把更新逻辑写在 main.m 的训练循环里逻辑与上面完全一致。你可以在 Matlab 2019a 里单步调试观察 td_error 的变化趋势收敛正常时它应该整体递减如果一直剧烈振荡优先怀疑 alpha 偏大其次怀疑状态离散化太粗。2.2 GNG 聚类状态节点怎么“长”出来标准表格 Q-learning 要求预先定义状态网格。比如一个二维连续状态空间每个维度均匀切成 10 格就是 100 个状态维度升到 4每维 10 格就变成 10000 个状态Q 表规模指数膨胀多数格子可能整个训练过程都没被访问到一次。GNGGrowing Neural Gas生长型神经气的思路完全不同先随机铺少量节点每个节点带一个权重向量输入样本进入网络时距离最近的节点和它的拓扑邻居一起朝样本方向拉每隔固定步数往累计误差最大的区域插入一个新节点。最后样本密集区域节点多稀疏区域节点少状态空间被自适应地切成若干 Voronoi 单元。% 状态映射把连续状态 x 映射到 GNG 节点编号 d2 sum((gng_units - x).^2, 2); % 当前状态到每个节点的欧氏距离平方 [~, state_idx] min(d2); % state_idx 直接作为 Q 表行索引这段代码是训练主循环里最常用的一步。gng_units 是当前所有节点的坐标矩阵x 是从环境拿到的连续状态向量。每步先把状态归类到最近的节点再用节点编号查 Q 表。注意距离计算用的是平方距离而不是绝对距离省一次开方运算训练循环里每步都执行时能省下不少时间。从原理上说GNG 相当于把“状态空间长什么样”这个问题交给数据回答而不是靠人肉网格。使用这份资源时你不需要手工设计状态边界只需要控制两个量最大节点数和节点插入频率。这两者在参数区里分别对应 gng_max_units 和插入间隔。节点太少状态划分太粗策略曲线有明显台阶节点太多Q 表同步膨胀训练时间变长反而丢失泛化能力。2.3 为什么这个改进值得用而不是直接上 DQN很多读者看到“改进强化学习”就会想为什么不直接用深度强化学习算法比如 DQN这里要给边界泼一盆冷水。DQN 的优势在于处理高维输入比如图像像素用神经网络代替 Q 表做函数逼近代价是训练不稳定对网络结构、优化器、经验回放池容量都很敏感复现一次要调大量超参数。GONOGO_Qlearning 的定位是低维连续状态、动作数量少、样本量可控的场景。GNG 负责把连续状态变成离散索引Q 表负责维护每个状态动作对的价值整套方法在普通笔记本上几分钟就能跑完而且每次运行行为确定性高得多。方法状态表示适用场景主要开销标准表格 Q-learning手工网格离散状态维数低、网格数量可预知网格数随维度指数膨胀GONOGO Q-learningGNG 自适应节点 似然探索连续状态、动作少、调试期短节点数和探索参数需要调DQN神经网络逼近高维输入、动作空间大训练不稳定复现成本高课程设计和期末大作业场景里老师更看重你是否理解算法流程、参数对结果的影响而不是把训练时间从半小时堆到三天。GONOGO 这种轻量方案反而更容易把收敛过程讲清楚答辩时每个参数都能说出依据。3. main.m 主流程拆解参数初始化、训练循环与收敛曲线3.1 文件清单与运行方式拿到压缩包解压后核心文件其实就四个外加一张结果图。下面这张表建议保存下来调试时对照着看比每次翻文件夹快得多。文件作用main.m主程序入口参数初始化、训练循环、绘图全部串在这里GNG_loglikeli_action.m基于 GNG 节点状态和对数似然的动作选择函数RunningMean_edge.m滑动平均滤波把单 episode 的奖励曲线抹平Q_learning.pdf理论说明文档调参之前先翻一遍更新公式推导运行结果.jpg作者在默认参数下跑出来的收敛效果图用于核对复现第一次运行建议按下面这三步走把解压后的文件夹放到纯英文路径下比如 D:\qlearn_gonogo打开 Matlabcd 到该目录在命令窗口执行下面两条命令先设置路径再运行主程序。addpath(genpath(pwd)); % 把当前目录及子目录全部加入搜索路径 main; % 运行主程序第一行命令的目的是避免“未定义函数或变量”的低级报错。直接双击 m 文件时Matlab 默认只搜索当前工作目录子函数如果不在正确目录里就会找不到文件。addpath(genpath(pwd)) 是通用做法能一次性把目录树上所有 matlab 文件都注册进搜索路径以后换一台电脑重新解压只要执行这一行都不会有路径问题。3.2 参数区哪些数字决定收敛快慢main.m 的最大优点是参数化编程所有超参数集中在文件开头改一个数字就是一组新实验。我自己习惯先把这些参数复制到注释里备份一份再做单变量实验避免改乱后找不到原来的效果。%% ---------- 参数区改参数之前先想清楚要调什么 ---------- num_episodes 500; % 训练周期数太少不收敛太多浪费时间 num_steps 200; % 每个 episode 内最大步数防止单回合过长 alpha 0.1; % 学习率太大震荡太小收敛慢 gamma 0.95; % 折扣因子接近 1 重视长期收益 epsilon 0.1; % 初始探索率动作选择时随机探索的概率 epsilon_decay 0.995; % 探索率每 episode 衰减系数 gng_max_units 50; % GNG 最大节点数防止状态节点无限增长 reward_goal 10; % 到达目标状态的奖励 reward_step -0.05; % 每步惩罚越小越逼智能体走快这里几个参数要联系起来看。epsilon 是初始探索率epsilon_decay 决定探索率每隔多少回合衰减一次两者共同控制“探索和利用的平衡”。如果 epsilon 初始设到 0.5智能体前期一半时间在乱走训练曲线前期会很平甚至下探但状态空间覆盖得更充分如果只设 0.05前期收敛快但容易陷进局部最优。reward_step 也是容易被忽略的参数。它设成 -0.05相当于每多走一步都扣分智能体不得不学习最短路径设成接近 0智能体对路径长度不敏感收敛后行为可能绕远路。做课程设计时如果想演示“参数影响策略效果”这个值是最容易产生显性差异的调试点。3.3 训练主循环与 RunningMean_edge 平滑曲线主体的训练循环结构比较标准外层循环控制 episode内层循环控制单回合步数每一步完成状态转移和 Q 表更新。下面是简化后的骨架与 main.m 的流程一致。%% ---------- 训练主循环 ---------- hist_reward zeros(num_episodes, 1); % 记录每个 episode 的累计奖励 for ep 1:num_episodes s env_reset(); % 环境重置返回初始状态 total_r 0; for t 1:num_steps a GNG_loglikeli_action(Q, loglik_vec, s, actions, epsilon); [s_next, r, done] env_step(s, a); % 与环境交互 [Q, ~] q_learning_step(Q, s, a, r, s_next, alpha, gamma); s s_next; total_r total_r r; if done, break; end % 到达目标则提前结束本回合 end hist_reward(ep) total_r; epsilon epsilon * epsilon_decay; % 探索率按回合衰减 end % 用滑动平均平滑曲线直接画原始数据全是毛刺 smooth_reward RunningMean_edge(hist_reward, 20); plot(1:num_episodes, smooth_reward);注释里已经标出每个关键步骤。env_reset 是环境初始化函数在 main.m 里通常以内联代码实现作用是随机生成起点状态env_step 接收当前状态和动作返回下一状态、即时奖励和终止标志。这套接口设计是强化学习最常见的写法换环境时只需要改这两个函数训练循环完全不用动。RunningMean_edge 是这个包里最值得抄走的工具函数。它的本质是滑动平均滤波器窗口为 20 时每个点取前后共 20 个 episode 的均值把单次运行的随机波动抹平。默认窗口 20 比较折中窗口太短平滑效果差太长会把真实的收敛拐点也抹掉曲线看起来一直在爬升反而看不出算法什么时候收敛。画出来的最终图就是复现运行结果.jpg 的效果如果你跑出来的曲线整体趋势不一致优先检查第 5 章里的随机种子问题。4. GNG_loglikeli_action.m 与奖励塑形探索要建立在概率基础上4.1 似然动作选择比 epsilon-greedy 聪明在哪传统 epsilon-greedy 的做法是以 epsilon 的概率随机挑一个动作其余时间选 Q 值最大的动作。这个策略有个明显缺陷——随机探索时对所有动作一视同仁不管这个动作在当前状态是“陌生”还是“已知较差”。在连续状态空间中很多状态节点访问次数很少Q 表里对应行的数值可信度很低随机探索时选到的动作可能完全无效白白浪费训练步数。GNG_loglikeli_action.m 的改进思路是把“探索概率”和“状态节点的访问置信度”绑定每个 GNG 节点都维护一个似然估计访问次数越少、似然越低探索概率越高访问充分的节点探索概率自动压低。下面这个代码片段展示了核心逻辑。function a GNG_loglikeli_action(Q, loglik_vec, state_idx, actions, epsilon) % loglik_vecGNG 为每个状态节点输出的对数似然向量 % 节点越陌生对数似然低探索概率越高 exp_prob epsilon * (1 - exp(loglik_vec(state_idx))); exp_prob min(max(exp_prob, 0.01), 0.9); % 钳制在安全区间 if rand() exp_prob a actions(randi(length(actions))); % 低置信度随机探索 else [~, a] max(Q(state_idx, :)); % 高置信度利用已有经验 end end代码里最关键的是 exp_prob 的构造方式。当对数似然为较大的负数时1 - exp(loglik) 接近 1探索概率被放大当对数似然接近 0 时探索概率趋近于 epsilon 的基线水平。min(max(...), ...) 的作用是把探索概率限制在 1% 到 90% 之间防止个别冷门节点造成无穷大探索或者完全放弃探索。param 说明这里有一点要注意epsilon 仍然存在但语义从“所有动作的固定探索率”变成了“探索概率的下界”真正起动态调节作用的是似然值。所以调这套算法时epsilon 的初始值可以比标准 Q-learning 设得更小比如 0.05 到 0.1因为冷门状态不会因为你调小 epsilon 就失去探索机会。4.2 奖励塑形与状态价值函数的关系GONOGO 这个名字本身就在暗示奖励设计的方向每步决策本质上是一个“去或者不去”的二值选择。动作 1 表示执行动作 2 表示放弃这种场景在信号检测、电机控制、路径规划里很常见。奖励矩阵的设计直接影响 Q 表的学习效果。这里要说清楚一个概念Q(s, a) 是状态动作价值表示“在状态 s 下执行动作 a 的长期回报期望”而状态价值函数 V(s) max_a Q(s, a)表示“在当前状态下不指定动作时的最优价值”。状态价值函数的作用是回答一个问题如果我现在什么都不做未来还能得到多少收益。在 go/no-go 任务里V(s) 低的状态意味着“当前处境糟糕需要尽快行动”V(s) 高的状态意味着“维持现状就是好策略”。所以奖励塑形时reward_goal 和 reward_step 的差值实际上是在调整“行动”和“等待”的边界。reward_step 设成负值等得越久扣分越多V(s) 的等高线会向终点收缩reward_step 设成零或正数智能体可能学会原地踏步。对课程设计来说你可以故意把 reward_step 改成 0.01 跑一遍观察策略是否明显变懒这个对比实验能让你在答辩时把奖励塑形讲得很扎实。4.3 它和深度强化学习算法列表里的那些模型差在哪一打开搜索引擎就能看到各种深度强化学习算法列表对比DQN、PPO、A3C、SAC 一抓一大把。GONOGO_Qlearning 不属于这条路线它保留 Q 表这个显式数据结构而不是用一个黑盒神经网络去逼近 Q 函数。这个选择在特定条件下反而有优势Q 表每个格子都可以单独导出解释性极强答辩证“为什么这个状态选这个动作”时可以直接把表格打印出来逐行讲。深度强化学习算法解决高维状态问题GONOGO-Qlearning 解决中低维连续状态问题。如果环境状态是二维坐标、速度、角度这类可解释量而且动作只有两个到四个先跑 GONOGO 是性价比最高的方案。只有当状态维度高到 GNG 节点数失控、Q 表膨胀到内存吃不消时才需要考虑换深度强化学习管线。这个边界在课程设计阶段基本碰不到放心用即可。5. 踩坑与排查Matlab 版本、随机种子与收敛判断下面这几条坑都不是概念层面的是实打实在不同版本 Matlab 上跑出来的问题每个都按现象、原因、解决三部分写清。5.1 现象Matlab 2014a 打开 main.m 中文注释全部乱码2019a 正常原因代码文件按 UTF-8 编码保存Matlab 2014a 默认按 GBK 解析编码错位导致注释乱码。这不影响代码运行但会严重影响阅读。解决如果只是阅读直接用 Matlab 2019a 或 2024a 打开必须在 2014a 上运行时在编辑器中手动切换编码方式或者把中文注释替换成英文注释再保存。注意保存时不要再存回 UTF-8否则 2019a 上反而会乱。5.2 现象第一次运行结果和附带运行结果.jpg 对不上曲线趋势明显不同原因没有固定随机种子。epsilon-greedy、GNG 节点初始位置、环境重置都依赖随机数不同种子会得到完全不同的训练轨迹。解决在 main.m 训练循环前加一行 rng(0)或者换用任意固定整数之后每次运行都得到相同结果。想要更强的统计结论就用多个种子各跑一遍取均值这比调大训练回合数更有效。rng(0); % 固定随机种子保证结果可复现5.3 现象画出来的累计奖励曲线毛刺特别多看不出收敛趋势原因直接用了原始 hist_reward 数据而单 episode 奖励方差本来就大特别是路径越长、随机性越强时整条曲线会像锯齿一样。解决调用 RunningMean_edge(hist_reward, window) 平滑后再绘图。窗口建议 20 到 50窗口太短无效太长会把真正的收敛拐点抹平让算法看起来“一直在收敛”。5.4 现象GNG_loglikeli_action 报错“索引超出矩阵维度”原因GNG 继续生长时产生的新节点编号超过了 Q 表预分配的行数。也就是说 Q 表行数和 gng_max_units 没有对齐state_idx 指向了不存在的那一行。解决检查参数区 gng_max_units 是否小于等于 Q 表行数如果 Q 表是动态扩展的每新增一个 GNG 节点就要同步执行 Q(end1, :) zeros(1, num_actions)。调试时可以打印 size(Q, 1) 和 max(state_idx)二者差 1 以上就要补行长。5.5 现象训练到一半内存逐步上涨几个 episode 后越来越卡原因GNG 节点只增不减如果又设置了过大的 gng_max_units状态节点和 Q 表一起膨胀退化成了近邻记忆表。解决给 gng_max_units 设一个合理上限30 到 100 之间比较稳妥训练中后期节点更新只调整已有节点位置不再插入新节点。另外Matlab 里每 episode 都做矩阵动态拼接会产生大量内存碎片预先按最大上限初始化矩阵能明显缓解卡顿。6. 进阶拿 GONOGO 和别的强化学习算法做对比实验横向对比实验是期末大作业里最常被要求的环节。常见做法是把标准 Q-learning、GONOGO_Qlearning、条件允许再拿 DQN 三条收敛曲线放进同一张图。最容易被答辩老师问倒的问题是横轴迭代次数到底取多少我的标准流程是固定三步。第一步预跑。设一个足够大的 episodes 上限比如 2000观察累计奖励曲线在哪个回合后不再出现整体上升趋势记下这个值 T。第二步把正式实验的 episodes 设为 1.5T 到 2T。为什么不设更大因为收敛后的数据段占比会被拉高曲线后半段全是一条平线算法之间的差距反而不明显而且 DQN 这类深度强化学习算法几千个回合就要跑很久横轴数量级不一致时对比就没有意义了。第三步也是最重要的一步每条曲线用 10 个不同随机种子跑取均值和标准差画成带阴影带的曲线而不是只画单次结果。单条曲线是玄学十个种子平均出来的才勉强算结论。所有对比算法必须使用同一套 epsilon 衰减规则或统一关闭探索否则对比的是探索率差异而不是算法本身差距。mu mean(curve, 1); % curve 是 10 行 × episodes 列的矩阵 sd std(curve, 0, 1); % 每列的标准差 x 1:size(curve, 2); fill([x, fliplr(x)], [mu sd, fliplr(mu - sd)], ... [0.8 0.8 0.8], EdgeColor, none); % 灰色标准差带 hold on; plot(x, mu, LineWidth, 1.5); % 均值线叠加这里 fill 的坐标顺序是按逆时针闭合先画上边界 musd再用 fliplr 反转横轴画下边界形成封闭区域。很多初学者在这里画成一团乱麻就是没用 fliplr。标准差带能让答辩老师一眼看出算法稳定性比十条曲线叠在一起强得多。那次期末大作业我因为只跑单条曲线被老师指出曲线差距在噪声范围内回去补了十个种子的实验才过关。从那以后我做强化学习对比实验都强制走一遍固定种子、单次预跑、多种子平均的流程画收敛曲线时尤其如此。希望这套 GONOGO 代码和这个实验习惯能帮到你。本文还有配套的精品资源点击获取