MATLAB实现Q-Learning强化学习入门:Q表更新、迷宫实战与参数调优

发布时间:2026/9/10 9:00:28
MATLAB实现Q-Learning强化学习入门:Q表更新、迷宫实战与参数调优 简介面向强化学习初学者的Q-learning Matlab实现主要针对计算机、电子信息工程、数学等专业学生课程设计、期末大作业或毕业设计环节提供了可直接运行的完整代码与配套说明。压缩包内共4个文件包括核心的.m源程序、PDF格式算法解析、Markdown版README说明以及一张结果示意图整体大小约750KB结构紧凑便于查看。代码采用参数化编程思路奖励、折扣因子、学习率等参数可方便调整注释明细并附赠案例数据运行后即可观察智能体逐步收敛的过程。目前已有46人学习下载适合希望快速理解Q-learning原理、搭建实验环境并在此基础上修改拓展的读者。搭配压缩包中的说明文档与代码注释可以较顺畅地完成从状态更新到奖励迭代的完整闭环是一份轻量实用的强化学习入门资料。1. Q-Learning 在 ReinforcementLearning 里为什么是最适合 MATLAB 起步的算法ReinforcementLearning_QLearning附Matlab代码.rar这类压缩包在课程群和资料站里反复出现不是因为算法新鲜而是因为 Q-Learning 是强化学习入门路径里工程门槛最低的一种。它不需要深度网络、不需要连续动作处理、不需要知道环境转移概率一张 Q 表加上一条更新公式就能让智能体在离散状态空间里学会决策。MATLAB 用户在这件事上有天然优势状态可以离散成行索引动作离散成列索引Q 表本身就是二维矩阵更新核心代码只有一行。真正花时间的是把环境建模、奖励设置、探索策略这三件事想清楚。这篇文章就从这三个方向展开给出能在 MATLAB 里直接运行的实验流程。只要装了 MATLAB 本体不依赖任何扩展工具箱就能照着下面的步骤把训练跑起来。2. Q-Learning 更新规则拆解Q 表、Bellman 公式与关键超参数2.1 MDP 建模Q(s,a) 在存什么Q-Learning 解决的强化学习问题被形式化为马尔可夫决策过程MDP。环境由五个要素定义状态集合 S、动作集合 A、奖励函数 R(s,a,s)、状态转移概率 P(s|s,a) 以及折扣因子 gamma。马尔可夫性质要求下一个状态只取决于当前状态和当前动作与更早的历史无关。这个假设一旦不成立Q 表按 (s,a) 索引的方式就会失效因为相同的状态编号实际上对应着不同的历史情境更新值会在同一行来回跳动。因此在动手写 MATLAB 代码之前先确认问题能不能用离散状态描述清楚这一点比选参数更关键。Q(s,a) 的含义是从状态 s 出发、先执行动作 a之后继续按照某一策略行动所能获得的累计折扣回报期望。注意“期望”二字单次采样得到的结果受随机性影响不足以代表真实价值必须让同一状态-动作对被反复访问Q 值才会逐步逼近真实期望。这也是为什么 epsilon 不能过早降到 0 的原因否则初始阶段没被探索过的动作永远停留在初始值上。2.2 Bellman 方程与单步更新公式Q-Learning 的更新规则是整个算法最短也是最核心的部分落到 MATLAB 里就是这样一行Q(s, a) Q(s, a) alpha * (r gamma * max(Q(s_next, :)) - Q(s, a));r 是当前步实际获得的奖励gamma * max(Q(s_next,:)) 是下一状态所有动作中最大的 Q 值两者相加构成时间差分目标。目标值减去当前 Q(s,a) 得到时间差分误差再以学习率 alpha 对旧值做修正。误差为正时说明 Q 值被低估向上调误差为负时说明被高估往下修。这个逻辑和梯度下降的迭代方向在精神上是一致的每步都朝损失最小的方向挪一小步。关键点全在 max(Q(s_next,:)) 这个写法上。无论当前策略下一步实际执行哪个动作这里一律取下一状态的最大值作为未来回报估计。这是 off-policy 学习的特点Q-Learning 学习的始终是“假设之后每一步都按最优策略行动”的价值函数。Sarsa 算法则改用 Q(s_next, a_next)其中 a_next 由当前策略实际选择于是学习结果会把随机探索带来的影响保留在最终策略里行为偏保守。同一个环境里两种算法收敛出来的最优路径很可能不一样这一点到调参时要记住。2.3 alpha、gamma、epsilon 三个参数的设置方向alpha 0.1; % 学习率控制每次更新的修正步长 gamma 0.9; % 折扣因子控制未来奖励折算到当前的比例 epsilon 0.2; % 探索率控制随机动作的选择概率alpha 直接影响更新震荡的幅度。设得太大单个异常奖励会让 Q 值在相邻回合之间大幅摆动学习曲线上表现为步数忽高忽低太小则收敛非常慢尤其对迷宫这种需要大量探索的任务。gamma 控制远期收益的权重接近 0 时智能体只看眼前接近 0.99 时会为遥远的目标绕路但收敛也随之变慢。epsilon 负责维持探索越大越容易发现新路径也让训练曲线的毛刺更多。参数典型范围偏大时的现象偏小时的现象alpha0.05 ~ 0.3Q 值振荡回合间路径长度波动剧烈收敛慢探索阶段被迫拉长gamma0.8 ~ 0.99远期收益权重太大收敛慢只盯着眼前一步策略偏短视epsilon0.05 ~ 0.5随机动作过多成功回合占比低探索不足容易卡在局部最优三个参数互相牵制。一次只改一个改完跑完整训练看趋势再恢复原值测另一个这是调参的基本纪律。在长训练任务里固定 epsilon 的效果不够好常见做法是让探索率随回合数线性衰减epsilon max(0.01, epsilon0 * (1 - ep / numEpisodes));这行写在每回合开始的位置就可以让 epsilon 从初值逐步缩到 0.01 附近。学习率也可以做类似衰减一般最多降到初始值的十分之一避免后期单一样本对已经稳定的 Q 表产生过大扰动。这类调度方式在 Q-Learning 的实现里属于标配几乎任何课程实验都会用到。2.4 初始 Q 值的选取初始 Q 值是一个常被忽略的隐藏超参数。全零初始化最常见但不是唯一选项。把 Q 表初始化为一个偏正数比如 5会让早期所有动作都显得乐观负奖励在前期被缓冲探索更积极反过来设负数会让初始估计悲观动作保守。实际项目中先用全零跑通逻辑链路再根据学习曲线调整初始值的符号和幅度通常比盲目选一个看似合理的值要可靠。提示如果训练开始后长时间没有任何有效回合到达终点先不要调 alpha 和 gamma先检查奖励向量里终点状态号是否与状态编号一致。这类索引错误在 MATLAB 里尤其容易发生。2.5 为什么 Q-Learning 比动态规划更适合工程环境Value Iteration 需要完整的转移概率矩阵 P(s|s,a)这在真实仿真器里几乎不可得只能通过大量统计去估计。Q-Learning 直接与黑盒环境交互执行动作、观察奖励和下一状态环境只需要像一个函数那样提供输出即可。这也是为什么在游戏、机械臂仿真和调度问题里Q-Learning 被用作强化学习基线方案它不要求环境提供解析模型只要能从当前状态按照动作迁移到新状态就行。3. MATLAB 从零实现 Q-Learning网格环境、Q 表更新与路径可视化3.1 建立 5×5 网格环境状态编号、奖励与文件组织在 MATLAB 里可以直接建一个最小的 5×5 网格世界。状态按行优先编号第一行是 1 到 5第二行是 6 到 10右下角是 25。目标状态设为 25禁区设为 13智能体从状态 1 出发每一步执行四个动作之一碰到边界留在原地。奖励向量按状态编号逐项定义默认每步 -1达到终点 10进入禁区 -5。奖励向量这样设置比“终点 100其他 0”更合理。每步扣 1 分相当于时间惩罚路径越短累计奖励越高Q 表就能区分出不同长度路径的优劣。如果所有非终止步都是 0 奖励任何能到达终点的路径总回报相等学习过程就失去了“追求最短路径”的压力最终策略对绕路不敏感。按 .rar 分发代码的习惯文件组织一般分成主脚本、环境函数和实验脚本。可以用这样的目录结构文件作用关键输出gridworld_qlearning.m主脚本建环境、训练并绘图stepsPerEpisode、QgridStep.m无墙网格的状态转移函数s_next、donemazeStep.m带障碍物的迷宫转移函数s_next、donerun_experiments.m多次独立实验与统计平均步数、成功率全部文件放在同一个目录下使用者切到该目录直接运行主脚本即可不需要把 MATLAB 路径配置到别处。这套组织方式对有 .rar 分发需求的代码包来说基本是标配。% gridworld_qlearning.m clear; clc; rng(2024); N 5; % 网格边长 nS N * N; % 状态总数 25 nA 4; % 动作数 4 Q zeros(nS, nA); % Q 表全零初始化 R -ones(nS, 1); % 奖励向量默认每步 -1 R(25) 10; % 终点奖励 R(13) -5; % 禁区惩罚 alpha 0.1; % 学习率 gamma 0.9; % 折扣因子 epsilon 0.2; % 探索率 numEpisodes 800; % 训练回合数 maxSteps 100; % 单回合上限步数R 按状态编号做列向量是因为当前奖励只依赖落到哪个状态不依赖上一个动作。后面扩展到迷宫时可以把奖励扩展成 nS×nA 的矩阵以便区分撞墙、正常移动和抵达终点三种情况。3.2 状态转移函数边界处理与行列换算function [s_next, done] gridStep(s, a, N) % 5x5 网格状态转移a1 上2 下3 左4 右 row ceil(s / N); % 换算行号 col mod(s - 1, N) 1; % 换算列号 switch a case 1, row max(1, row - 1); case 2, row min(N, row 1); case 3, col max(1, col - 1); case 4, col min(N, col 1); end s_next (row - 1) * N col; % 回到一维编号 done (s_next N * N); % 到达终点 end行列换算用 ceil(s/N) 和 mod(s-1,N)1这是 MATLAB 里最常见的行优先编码方式。mod 在 1 索引语言和 0 索引语言之间的差异经常导致差一错误写成 mod(s-1,N)1 能保证列号落在 1 到 N。边界处理直接用 max 和 min 限制坐标碰到边界就留在原地状态编号始终合法。此时不需要额外的负奖励来惩罚越界因为“留在原地”本身就相当于一步时间惩罚。提示函数写成独立 .m 文件主脚本里直接调用。用 MATLAB 的实时脚本也可以但独立函数文件在多次实验里更好维护也方便后面跑 20 次独立实验。3.3 训练主循环epsilon-greedy 决策与 Q 表更新stepsPerEpisode zeros(numEpisodes, 1); for ep 1:numEpisodes s 1; % 每回合从状态 1 出发 done false; stepCount 0; while ~done stepCount maxSteps % epsilon-greedy随机探索或贪心利用 if rand() epsilon a randi(nA); else [~, a] max(Q(s, :)); end [s_next, done] gridStep(s, a, N); % 环境返回下一状态 r R(s_next); % 读取即时奖励 % Q 表更新一行公式完成全部学习 Q(s, a) Q(s, a) alpha * (r gamma * max(Q(s_next, :)) - Q(s, a)); s s_next; stepCount stepCount 1; end stepsPerEpisode(ep) stepCount; end训练主循环的每一步都遵循“选动作—执行—观察—更新”的标准顺序。stepCount 限制在 maxSteps 以内防止早期探索阶段找不到终点而无限循环。该回合若因超时中断stepCount 会等于 maxSteps后续统计时需要把这类回合标记为失败不能把它当成有效收敛。stepsPerEpisode 保存了每个回合的总步数画出来就是学习曲线。[~, a] max(Q(s,:)) 这个写法值得单独说明。max 的第一个返回值是最大值第二个是最大值所在的列索引。如果直接写 a max(...)a 会变成 Q 值本身而不是动作编号后续 Q(s,a) 索引会指向错误列。必须用波浪号忽略第一个输出只保留列下标。这是 MATLAB 版 Q-Learning 最容易踩的语法坑。3.4 策略提取、路径绘制与 Q 表保存训练收敛后从 Q 表提取策略的方式是对每一行取最大值对应的列% 提取策略每行最大 Q 值对应的动作 [~, policy] max(Q, [], 2); % 从起点回溯完整路径 s 1; path s; while s ~ 25 numel(path) 30 a policy(s); [s_next, ~] gridStep(s, a, N); path [path; s_next]; s s_next; end % 绘制 5x5 网格上的路径 figure; imagesc(reshape(1:nS, N, N)); colormap(parula); hold on; [rowIdx, colIdx] ind2sub([N, N], path); plot(colIdx, rowIdx, ro-, LineWidth, 2); grid on; xlabel(列); ylabel(行); title(Q-Learning 学到的路径);if 学到的路径经过禁区状态 13说明训练还不充分或禁区的惩罚相对时间惩罚太小Agent 觉得穿过禁区更划算。另一种做法是在验证路径时检查路径上是否包含任何障碍物状态如果包含则即使步数不长也不能算有效路径。路径可视化是整个实验里最直观的一环也是课程报告里最常放的那张图。Q 表训练完成后应当保存便于后续做策略复用或结果对比。用一行 save 就能把 Q 表和超参数一起存下来save(qtable_5x5_alpha01.mat, Q, alpha, gamma, epsilon);之后用 load 加载回来可以直接计算策略不需要重新训练。这个保存动作在做参数实验时尤其有用每组参数训练完成就存一份既不浪费前面几千回合的计算量也能在报告里按参数逐个重现结果。4. 迷宫环境扩展与 Q-Learning 收敛判断的三条标准4.1 迷宫地图与带障碍物的状态转移5×5 规则网格结构太简单Q-Learning 只需要几次探索就能找到最短路径。换成一个带墙的迷宫才有代表性。在 MATLAB 里用矩阵表示迷宫是常见做法1 为可通行格0 为墙。mazeMap [1 1 1 1 0 1; 1 0 1 0 0 1; 1 0 1 1 1 1; 1 0 0 0 1 0; 1 1 1 1 1 0; 0 0 0 0 1 1];迷宫的尺寸是 6 行 6 列状态总量变成 36。Q 表相应扩展为 36×4奖励向量长度 36。mazeStep 函数在 gridStep 基础上增加障碍物判断目标格是墙时状态不变仍停留在原位置。实现如下function [s_next, done] mazeStep(s, a, N, mazeMap) row ceil(s / N); col mod(s - 1, N) 1; switch a case 1, row max(1, row - 1); case 2, row min(N, row 1); case 3, col max(1, col - 1); case 4, col min(N, col 1); end s_next (row - 1) * N col; % 障碍物格不可通行留在原地 if mazeMap(row, col) 0 s_next s; end done (s_next N * N); end撞墙后停留在原地是最自然的模拟方式但奖励设计要跟上。如果撞墙没有额外惩罚智能体可能会在墙边来回试探而不付出代价浪费大量探索回合。常见做法是给撞墙一个 -2 的即时奖励让这种行为被明确惩罚。奖励读取可以改成先调用 mazeStep 再判断是否撞墙s_previous s; [s_next, done] mazeStep(s, a, N, mazeMap); if s_next s_previous r -2; % 撞墙惩罚 else r R(s_next); end这样最多两行判断就把迷宫版奖励逻辑补全了。主循环的其他部分不需要改动这也是把环境封装成函数带来的好处环境细节不影响学习算法本身的代码结构。4.2 判断 Q-Learning 是否收敛的三条标准训练循环写完后最常被问的问题是“到底跑多少回合才算训好”。单一的指标容易误判常见做法是同时看三条标准。第一条是回合步数的下降趋势。训练成功后每回合步数应稳定在某个较小区间内。用滑动平均可以平滑掉单回合的偶然波动windowSize 20; smoothed movmean(stepsPerEpisode, windowSize); plot(smoothed);注意如果某回合被 maxSteps 截断stepCount 等于 maxSteps这一档数据不能当作成功收敛的样本。统计时要先筛掉这些被截断的回合剩下的才反映真实学习效果。第二条是 Q 表更新的波动量。每回合开始时保存一份 Q_prev更新后计算最大差异maxChange(ep) max(max(abs(Q - Q_prev))); Q_prev Q;maxChange 在训练初期较大因为初始 Q 和真实值差距明显才能驱动修正随着训练推进应当逐步减小并趋于 0。如果 maxChange 长期稳定在一个不可忽略的值上说明单一奖励样本对 Q 表的冲击过大最直接的调整是降低 alpha。第三条是策略稳定性。记录每个回合结束时的 policy如果连续多回合策略完全一致说明 Q 表不再明显变化。策略稳定本身不能证明收敛到全局最优只能说明 Q 表不再被训练样本推动。所以实际使用中要把步数下降、Q 波动和策略稳定三条标准合在一起判断缺一条都可能被偶然情况误导。指标计算方式判定条件常见误用回合步数每回合实际步数下降并稳定在最短路径附近把被截断的长回合当正常样本Q 表波动max(max(abs(Q - Q_prev)))逐步降至接近 0只凭曲线平滑就下结论策略稳定连续回合 policy 是否变化连续 50 回合完全一致只看一次策略就认为收敛这三条指标在 5×5 网格、迷宫和更复杂的离散环境里普遍适用。训练结束后把三条线的趋势都画出来很容易定位问题出在探索不足还是学习率过大。4.3 epsilon 衰减与学习率调整的实践序列调参的顺序有讲究。先用固定 epsilon 跑出一版能收敛的基线再考虑是否叠加衰减。若基线阶段步数曲线持续下降但波动大先降低 alpha若曲线一直降不下去先降低 epsilon而不是同时拉动 alpha 和 epsilon。每次只改一个参数记录改完后的三段曲线对比后再动下一个。如果已经加上 epsilon 衰减仍然发现后期训练不稳定多半是学习率始终为固定值。可以在回合循环里嵌入一个线性衰减版本alpha max(0.01, 0.1 * (1 - ep / numEpisodes));这行写在每 epoch 的开头让后期单次更新的影响力逐步变小。这样组合出来的效果通常是最好的训练前期大步幅快速试错后期小步幅稳定收敛。4.4 迷宫环境里的乱码与常见坑调试迷宫时最隐蔽的问题是状态编号和 mazeMap 的坐标换算不一致。mazeStep 内部用 row、col 计算 s_next并在同一坐标系里访问 mazeMap(row,col)这两处只要有一处用了错误的顺序就会导致路径穿墙或原地不动。排查时打印 (row, col, s) 三元组逐帧对比即可。第二个常见坑是奖励向量越界。R 的长度必须等于 N^2一个 6×6 迷宫对应 36 个状态。如果把 R 的长度设成了 25访问 R(30) 就会报出索引超界的错误。这种情况在从 5×5 扩展到任意迷宫时经常出现检查迷宫尺寸和 R 长度即可。第三个坑是把动作编号和策略矩阵混用。策略提取用的是 max(Q, [], 2)返回的行向量每一列是动作编号路径回溯里用 policy(s) 作为动作编号。如果某处误把策略值当成状态编号传给 gridStep状态会直接越出边界。提示遇到 Q 值为全零但训练曲线也在下降的现象先检查奖励向量是否被程序覆盖。有些人在主脚本里重复定义 R后一句把前一句的终点与禁区标记冲掉了。5. 进阶实践用 20 次独立实验验证 Q-Learning 的策略稳定性5.1 为什么单次训练结果不能下结论单次 Q-Learning 训练得到的最优路径只能说明当前随机种子下算法能学到有效策略。换一个随机种子探索路径可能完全不同在迷宫这种非规则环境里表现差异更大。有些运行在 300 回合内收敛有些可能到 800 回合仍然卡在某个死胡同附近。课程报告和方案评估里只贴单次运行曲线说服力明显不足。更严谨的做法是把训练封装成函数循环运行多次统计成功率和平均步数。5.2 循环实验与统计输出numRuns 20; finalSteps zeros(numRuns, 1); success zeros(numRuns, 1); for run 1:numRuns rng(run); % 固定随机种子结果可复现 [Q, policy, stepsPerEpisode, ok] trainQLearning(...); % 取后 50 个回合的平均步数作为该次运行的收敛水平 finalSteps(run) mean(stepsPerEpisode(end-49:end)); success(run) ok; end fprintf(mean steps : %.2f ± %.2f\n, mean(finalSteps), std(finalSteps)); fprintf(success rate : %.2f%%\n, 100 * mean(success));rng(run) 让第 run 次实验的随机序列完全固定重跑代码会得到一致结果。finalSteps 取后 50 回合的平均值而不是最后一回合因为单回合步数受探索动作影响波动很大。成功率则统计这 20 次运行中有多少能完整到达终点。平均步数和标准差两个指标配合一个反映收敛水平一个反映稳定性。5.3 CSV 导出与参数网格对比多条实验曲线对比时把结果落到磁盘上会更方便。用 writematrix 导出为 CSVresults [(1:numRuns), finalSteps, success, alpha*ones(numRuns,1), gamma*ones(numRuns,1)]; writematrix(results, qlearning_results.csv);导出的 CSV 可以直接在 Python、Excel 或其他工具里继续处理。把 alpha 从 0.05 到 0.3 划分成多组每组跑 20 次再把各组平均步数和成功率汇总就能形成一张完整的参数网格对比表。整套流程跑下来Q-Learning 的调参结论就建立在重复实验和统计指标之上而不是“我试了几下感觉这个参数不错”。本文还有配套的精品资源点击获取