news 2026/9/24 0:24:35

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 表规模指数膨胀,多数格子可能整个训练过程都没被访问到一次。

GNG(Growing 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;打开 Matlab,cd 到该目录;在命令窗口执行下面两条命令,先设置路径再运行主程序。

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_vec:GNG 为每个状态节点输出的对数似然向量 % 节点越陌生(对数似然低),探索概率越高 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(end+1, :) = 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 的坐标顺序是按逆时针闭合:先画上边界 mu+sd,再用 fliplr 反转横轴画下边界,形成封闭区域。很多初学者在这里画成一团乱麻,就是没用 fliplr。标准差带能让答辩老师一眼看出算法稳定性,比十条曲线叠在一起强得多。

那次期末大作业我因为只跑单条曲线,被老师指出曲线差距在噪声范围内,回去补了十个种子的实验才过关。从那以后,我做强化学习对比实验都强制走一遍固定种子、单次预跑、多种子平均的流程,画收敛曲线时尤其如此。希望这套 GONOGO 代码和这个实验习惯能帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 0:18:34

零成本自建企业H5场景秀平台:响应式框架与源码二次开发实战

做一个企业自己的H5场景秀平台&#xff0c;这个需求这几年越来越多。市场部的同事拿着第三方H5工具的报价单来找我时&#xff0c;那种感觉大概就是——你说它贵吧&#xff0c;一年大几千确实不便宜&#xff0c;你说自己开发吧&#xff0c;又怕搞不定。其实这事没有那么玄乎&…

作者头像 李华
网站建设 2026/9/24 0:13:34

Cesium地形开挖实战:裁剪平面原理、代码实现与避坑指南

简介&#xff1a;面向Cesium初学者与前端开发者的地形开挖示例包&#xff0c;通过单个HTML文件完整演示了基于Cesium的三维地形开挖核心实现。压缩包内仅含1个HTML文件&#xff0c;大小仅1KB&#xff0c;代码集中&#xff0c;可直接在浏览器中运行&#xff0c;适合作为入门模板…

作者头像 李华
网站建设 2026/9/24 0:12:31

SSM垃圾分类系统课程设计:从环境搭建到二次开发全攻略

简介&#xff1a;这是一套面向Java初学者与课程设计需求的SSM框架垃圾分类管理系统完整源码包&#xff0c;适合作为框架入门练手项目或课程作业参考。系统采用SpringSpringMVCMyBatis架构&#xff0c;前端以JSP页面实现展示与交互&#xff0c;数据库选用MySQL&#xff0c;整体结…

作者头像 李华
网站建设 2026/9/24 0:12:12

SVM手写数字识别实战:预处理、LibSVM调参与避坑指南

简介&#xff1a;这份资源围绕基于支持向量机&#xff08;SVM&#xff09;的手写字体识别展开&#xff0c;面向计算机视觉与机器学习入门者、课程设计或实验项目开发者&#xff0c;帮助理解从图像预处理、特征提取到分类器训练与评估的完整流程。压缩包共85个文件&#xff0c;约…

作者头像 李华
网站建设 2026/9/24 0:09:47

LSTM时序预测实战:数据预处理、状态管理与滚动预测

简介&#xff1a;本资源是一份面向深度学习初学者与时间序列建模实践者的LSTM预测算法入门级代码实现&#xff0c;聚焦金融价格等一维时序数据的未来值预测任务。资源核心为单文件Python脚本&#xff08;LSTM.py&#xff09;&#xff0c;完整涵盖LSTM网络结构定义、滑动窗口数据…

作者头像 李华