
简介二维迷宫环境下的MATLAB强化学习源代码包面向希望结合实例理解最优策略搜索的算法初学者。压缩包体积仅2KB共包含三个源码文件分别用于环境反馈计算、动作选择与主程序调度代码量小、结构清晰非常适合逐行阅读和二次修改。目前已有218人学习/下载。这套示例以起点到终点的最短路径为优化目标完整呈现了状态、动作、奖励、策略四要素的交互闭环智能体每一步根据当前位置和动作获得奖励并通过Q值迭代持续改进决策。通过学习该迷宫代码读者可以掌握强化学习环境建模的基本方法理解贪心策略、奖励函数设计与迭代更新之间的关系并可将同样的思路迁移到路径规划、智能体寻路等更复杂的应用场景中。1. 用 Matlab 源代码跑通二维地图强化学习这套 .rar 到底在解决什么你大概率是在课程作业或者毕设仿真阶段搜到这个标题的手里攥着一个“matlab源代码强化学习算法二维地图.rar”解压前最想知道的是——这套代码打开后是不是真的能让一个智能体在一个二维网格地图上自己学会走迷宫、避障碍、找终点答案是能而且这类压缩包在工程仿真的场景里相当常见多数是一个基于 Q-learning 或简化 DQN 的完整训练脚本外加二维地图的可视化界面。它可以解决三类问题第一把强化学习算法落地到可以看见的二维格子里每步移动、每个奖励、每次碰撞都有反馈第二作为入门和复现实验你能在一台普通笔记本上跑完整个训练不需要 GPU也不需要装额外的深度学习框架第三给后续算法改进留了入口比如把 Q 表换成神经网络、把离散地图换成连续坐标。适合的人是那些已经听过 Q-learning、但缺一个能改能跑的 Matlab 源码模板的人拿到手不是看一遍就完而是要动手改地图、改奖励、改超参数最后看着训练曲线从震荡到收敛。接下来我从解压这一步开始把它拆开讲清楚。2. 先拆包看结构这套二维地图强化学习代码包里通常有哪些文件2.1 源码文件的典型布局拿到 .rar 后第一件事不是双击运行而是先解压并看清文件结构。这类 Matlab 强化学习算法源码包常见做法是分成三层主脚本、环境函数、工具箱类文件。主脚本一般叫main.m或run_training.m是算法入口负责初始化参数、跑训练循环、画结果图环境函数负责描述二维地图本身比如地图矩阵长什么样、智能体碰到边界怎么办、走到终点给多少奖励剩下的是算法实现文件常见的有q_learning.m、agent.m、env.m有的包还会拆出plot_map.m和reward_design.m。% 典型的压缩包内部文件结构解压后 . ├── main.m % 训练主入口改这里面的超参数 ├── q_learning.m % Q-learning 核心迭代逻辑 ├── env_step.m % 环境的一步转移输入状态和动作返回下一个状态和奖励 ├── map_def.m % 二维地图定义0空地 1障碍 2起点 3终点 ├── plot_path.m % 把训练好的策略画在二维地图上 └── utils/ └── state_transfer.m % 状态编号与行列坐标互转很容易出错的一个文件这里要提醒一下压缩包里的文件名未必和我写的一致有人叫DQN_train.m有人把环境函数写在grid_world.m里但核心职责是固定的。你拿到代码后应该先打开main.m看它调用了哪些函数再顺藤摸瓜打开对应的函数文件确认三个关键点地图矩阵用什么变量存、状态是“编号”还是“行列坐标”、奖励函数是写在环境里还是写在训练脚本里。2.2 这一步的选型理由为什么用离散二维地图 表格型 Q-learning在二维地图上做强化学习最常见的入门方案是表格型 Q-learning而不是一上来就上 DQN。原因很实际二维网格地图的状态空间是有限的。一张 10×10 的地图只有 100 个状态每个状态有 4 个动作上、下、左、右所以 Q 表就是一个 100×4 的矩阵存下来不到 4KB。用表格型 Q-learning训练收敛快、代码直白、每一步更新都能在纸上推导出来。如果你拿到的是深度强化学习算法的版本通常是把状态喂进神经网络输出 Q 值这在 8×8 或 10×10 的小地图上反而容易出现过拟合和训练不稳定调试成本高得多。所以第一优先级是先把离散版本跑通再去谈神经网络扩展。跑通这个压缩包等效于把“状态转移、奖励、策略更新”这一整条强化学习链路串起来了这是后续所有算法改动的基础。2.3 保留一份原版再修改我一般会在解压后做两件事一是用 7-Zip 直接解压到纯英文路径下比如D:/RL_2Dmap/不要解压到带中文或空格的目录里Matlab 对中文路径的兼容性问题会浪费你很多时间二是用 git 初始化这个目录提交一次初始代码再做任何改动都能回滚。很多人忽略这个习惯改坏一个函数就只能从头再来在修改强化学习代码时这是最疼的教训因为你不一定记得自己改过哪一行而且训练结果往往要在几十轮之后才暴露问题。# 工作目录初始化保留一份干净的原始骨架 mkdir D:/RL_2Dmap cd D:/RL_2Dmap git init git add . git commit -m 原始代码包未修改3. 从地图定义到训练收敛把整条强化学习链路跑通3.1 二维地图的矩阵定义与坐标约定这一章是核心。我们以最常见的 8×8 网格地图为例0 表示可通行的空地1 表示障碍2 表示起点3 表示终点。Matlab 的矩阵索引是先行后列这和多数代码里的坐标直觉一致但容易和某些 Python 代码的“先列后行”混淆需要格外留意。在实际的二维地图定义里起点放左上角、终点放右下角是最直观的安排。% map_def.m % 二维地图定义返回地图矩阵、起点行列坐标、终点行列坐标 function [mapGrid, startPos, goalPos] map_def() mapGrid zeros(8, 8); % 8 行 8 列全空地 mapGrid(3,3) 1; mapGrid(3,4) 1; % 横向障碍墙 mapGrid(6,2) 1; mapGrid(6,3) 1; % 纵向障碍墙 mapGrid(2,7) 1; mapGrid(7,7) 1; % 右上方和右下方的单点障碍 startPos [1, 1]; % 起点第1行第1列 goalPos [8, 8]; % 终点第8行第8列 end注意地图矩阵的坐标方向第一维度是行越小越靠上第二维度是列越小越靠左。所以在动作设计里“上”对应行号减 1“左”对应列号减 1这一点用错会导致智能体在起点原地打转。在设置障碍时尽量保证地图对称且通视起点和终点之间至少留一条可行路径否则无论训练多久都学不会。许多源码包自带的地图会刻意设置一些“陷阱”比如终点在墙后面这正是测试探索能力的好用例。3.2 状态编号与坐标互转最容易翻车的转换层强化学习算法内部通常不直接处理“行列坐标”而是把它们映射成一个整数编号比如第 2 行第 3 列对应状态编号sub2ind([8,8],2,3)算出来是 11。把状态编号转回坐标用ind2sub([8,8],11)。这个映射层之所以容易翻车是因为很多人忘了sub2ind的第一参数必须是[行数, 列数]的向量写反了之后整个训练过程会立即发散而且报错信息不直观看起来只是“Q 表访问越界”或者“坐标负数”。我建议单独抽取这个转换层写成一个独立函数然后先用一个简单的循环验证映射正确再跑训练。% state_transfer.m % 状态编号与行列坐标互转 function [row, col] state2rowcol(state, numRows, numCols) [row, col] ind2sub([numRows, numCols], state); end function state rowcol2state(row, col, numRows, numCols) state sub2ind([numRows, numCols], row, col); end参数说明numRows和numCols必须与地图矩阵的尺寸一致如果地图改成矩形比如 8 行 10 列这里要同步修改。自检方法是先取一个靠地图中央的点比如第 4 行第 5 列转成状态编号再转回来如果行列还是 4 和 5说明映射正确。这个转换层贯穿训练全程值得最先测试。3.3 环境交互一步转移、边界与撞墙的奖励环境函数负责回答一个问题给定当前状态和动作下一个状态是什么、能拿多少奖励、会话是否结束。这里的细节决定训练质量。常见的奖励设计是到达终点 10撞墙或越界 -2正常空地移动 -0.1给一个小的“时间成本”促使智能体走最短路径。也有的设计把正常移动奖励设成 0只靠碰撞惩罚和终点奖励来驱动学习这在简单地图上可以收敛但地图稍微变大就会出现学习缓慢的问题。% env_step.m % 环境一步转移返回下一状态编号、奖励、是否结束 function [nextState, reward, done] env_step(state, action, mapGrid, goalPos) [numRows, numCols] size(mapGrid); [row, col] ind2sub([numRows, numCols], state); switch action case 1 % 上 row row - 1; case 2 % 下 row row 1; case 3 % 左 col col - 1; case 4 % 右 col col 1; end % 越界或撞墙留在原地给负奖励 if row 1 || row numRows || col 1 || col numCols nextState state; reward -2; done false; return; end if mapGrid(row, col) 1 nextState state; reward -2; done false; return; end % 正常移动 nextState sub2ind([numRows, numCols], row, col); reward -0.1; % 到达终点 if row goalPos(1) col goalPos(2) reward 10; done true; end end逻辑说明越界和撞墙时状态保持不变相当于这次移动无效但会积累一个 -2 的教训。终点奖励必须最大而且要足够大让智能体愿意承受大量负奖励去换取那一次的 10。done标志控制训练循环是否终止只有到达终点才会置为 true所以理论上智能体可能一直在地图上游荡永不结束因此训练脚本里要设置最大步数来兜底。参数说明numRows和numCols用size(mapGrid)动态获取这样改地图尺寸时环境函数不用动。3.4 训练主循环epsilon-greedy 探索与 Q 表更新训练核心是 Q-learning 的更新方程每次从状态 s 执行动作 a 得到奖励 r 和下一状态 s更新公式是Q(s,a) alpha * (r gamma * max(Q(s,:)) - Q(s,a))。探索策略用 epsilon-greedy以 epsilon 的概率随机选动作其余时候选当前 Q 值最大的动作。训练初期的 epsilon 设大一些比如 0.3 到 0.5让智能体充分探索地图随着训练轮数推进epsilon 逐渐退火到 0.01 以下让策略趋于稳定。% main.m % 二维地图 Q-learning 训练主脚本 clear; clc; close all; [mapGrid, startPos, goalPos] map_def(); [numRows, numCols] size(mapGrid); numStates numRows * numCols; numActions 4; % 超参数 alpha 0.1; % 学习率 gamma 0.9; % 折扣因子 epsilon 0.5; % 初始探索率 epsilonMin 0.01; epsilonDecay 0.995; % 每轮衰减系数 maxSteps 200; % 单轮最大步数 numEpisodes 1500; % 训练轮数 Q zeros(numStates, numActions); episodeRewards zeros(numEpisodes, 1); episodeSteps zeros(numEpisodes, 1); startState sub2ind([numRows, numCols], startPos(1), startPos(2)); goalState sub2ind([numRows, numCols], goalPos(1), goalPos(2)); for episode 1:numEpisodes state startState; done false; step 0; totalReward 0; while ~done step maxSteps step step 1; % epsilon-greedy 选择动作 if rand epsilon action randi(numActions); else [~, action] max(Q(state, :)); end % 执行动作观察转移结果 [nextState, reward, done] env_step(state, action, mapGrid, goalPos); % Q-learning 更新 bestNextQ max(Q(nextState, :)); Q(state, action) Q(state, action) alpha * ... (reward gamma * bestNextQ - Q(state, action)); state nextState; totalReward totalReward reward; end % 记录并退火 episodeRewards(episode) totalReward; episodeSteps(episode) step; epsilon max(epsilonMin, epsilon * epsilonDecay); end逻辑说明内层 while 循环负责单轮训练外层 for 循环控制训练轮数。Q 表在训练中不断更新最终收敛到最优动作价值的近似。max(Q(state, :))取下一状态所有动作的最大 Q 值这体现了 off-policy 特性——更新时假设下一步采取的是最优动作而不是实际执行的动作。参数说明alpha太大比如 0.5会导致 Q 值震荡太小比如 0.01则收敛极慢gamma越接近 1智能体越看重远期回报越接近 0 就越短视epsilonDecay控制探索与利用的平衡衰减太快会过早陷入局部最优太慢则后期仍在乱逛。3.5 策略可视化把学到的路径画在地图上训练结束后最有说服力的输出是直接展示一条从起点到终点的无碰撞路径。常见做法是在主脚本末尾调用plot_path.m从起点开始每一步都在当前状态选 Q 值最大的动作按“沿着墙边走”的逻辑一直走到终点% plot_path.m % 沿最优策略画路径 function plot_path(mapGrid, Q, startPos, goalPos) [numRows, numCols] size(mapGrid); state sub2ind([numRows, numCols], startPos(1), startPos(2)); goalState sub2ind([numRows, numCols], goalPos(1), goalPos(2)); path []; while state ~ goalState path [path; state]; [~, action] max(Q(state, :)); [row, col] ind2sub([numRows, numCols], state); switch action case 1, row row - 1; case 2, row row 1; case 3, col col - 1; case 4, col col 1; end if row 1 || row numRows || col 1 || col numCols ... || mapGrid(row, col) 1 break; end state sub2ind([numRows, numCols], row, col); if size(path,1) numRows * numCols % 防死循环 break; end end figure(Name, 训练路径); imagesc(mapGrid); colormap(gray); hold on; if size(path,1) 0 [pathRows, pathCols] ind2sub([numRows, numCols], path); plot(pathCols, pathRows, r-, LineWidth, 2); end plot(goalPos(2), goalPos(1), go, MarkerSize, 12, MarkerFaceColor, g); plot(startPos(2), startPos(1), bo, MarkerSize, 12, MarkerFaceColor, b); axis ij; % 让坐标轴方向与矩阵行列一致 title(训练后的最优路径); end这段代码有一个关键点画图时plot的横坐标是列、纵坐标是行所以传入pathCols作为 xpathRows作为 y方向和imagesc(mapGrid)保持一致需要加axis ij否则路径在地图上是镜像的。如果你训练正确得到的是一条从左上角绕过全部障碍、最终到达右下角的折线而不是一条穿越墙体的直线。4. 从表格 Q-learning 到深度强化学习算法什么时候改 DQN、怎么改4.1 表格 Q-learning 的边界为什么地图一变大就失效表格型 Q-learning 在二维地图上的优势是可以精确收敛但它的瓶颈也很明显状态数量随地图尺寸线性增长Q 表大小等于状态数乘动作数。一张 100×100 的地图就有 10000 个状态Q 表存 40000 个浮点数训练轮数需要大幅增加而且每个状态只能存储离散值无法泛化到从未见过的状态。更难受的是障碍一旦改变位置Q 表必须重新训练。这时常见的选择是转向深度强化学习算法用一个神经网络来拟合 Q 函数输入是状态的特征表示输出是各个动作的 Q 值。4.2 用 Matlab 手写一个最小 DQN 骨架如果你拿到的压缩包本身只提供了表格版本而你的课题需要展示“深度强化学习算法”有一个可行的过渡方案在共享环境函数的前提下把 Q 表换成一个两层全连接网络并用最朴素的梯度下降做更新。这里给出一个可在 Matlab 中运行的最小骨架它没有经验回放和 Target 网络只用于理解从表格到深度网络的核心迁移。% dqn_minimal.m % 最小 DQN 骨架用两层网络替代 Q 表 % 网络参数 numStates 64; numActions 4; hiddenSize 32; lr 0.001; % 初始化权重必须小随机数防止过大的初始输出 W1 randn(numStates, hiddenSize) * 0.1; b1 zeros(1, hiddenSize); W2 randn(hiddenSize, numActions) * 0.1; b2 zeros(1, numActions); % 前向输入状态 one-hot 特征向量输出 4 个动作的 Q 值得分 s zeros(1, numStates); s(state) 1; % 当前状态编号转 one-hot h max(0, s * W1 b1); % ReLU 激活 qPred h * W2 b2; % 预测 Q 值然后更新的核心逻辑是把 Q-learning 的时序差分误差应用到网络参数的梯度上。完整训练循环中你需要先根据epsilon-greedy选动作执行环境并拿到reward和nextState再计算目标值target reward gamma * max(qNext)再用梯度下降更新权重。注意这个最小骨架收敛速度比表格版慢训练轮数要多 3 到 5 倍。如果你手头有 Deep Learning Toolbox建议直接用dlnetwork搭一个两层网络借助内置的adamupdate自动求梯度比手写反向传播稳定得多。手写版本适合教学验证不适合大规模训练。4.3 深度版本必调的三个参数深度版本的调试重点和表格版本完全不一样。第一个是学习率表格 Q-learning 用 0.1 可以稳定收敛DQN 一般要用 0.001 甚至 0.0005大了会直接发散表现为episodeRewards一路下滑到负几百。第二个是训练轮数表格版本 1500 轮就能在 8×8 地图上收敛DQN 在相同地图上需要 5000 轮以上因为神经网络每步更新对参数的改变更小需要更多样本来累积梯度。第三个是是否引入经验回放最小骨架没有它时连续采样前后状态高度相关训练容易震荡建议实现一个小型的经验缓冲随机采样打破相关性。这里比较推荐的做法先跑通 DQN 骨架观察一轮的累计奖励是否稳定上升再逐步加入回放机制不要一上来就追求完整版 DQN 的所有组件。5. 强化学习训练避坑指南四个常见问题与排查思路5.1 现象训练了一千轮智能体还在起点附近乱转这个是最常见的失败模式。我先说原因再给排查方法。最常见的原因是奖励太稀疏或负奖励过低。如果你把每一步移动的奖励设成 0碰撞也设成 0终点 1智能体在前几百轮完全碰不到终点没有任何梯度指引方向纯靠随机探索发现终点的概率极低。正确做法是给每一步移动一个小的负奖励比如 -0.1同时撞墙给更大的负奖励比如 -2这样智能体即使没到终点也能从“少撞墙、少绕路”中感受到进步信号。排查方法是在训练循环里打印每轮的totalReward观察它是否在逐步上升——如果一直停留在某个固定值附近说明奖励设计没有给算法提供有用的学习信号。第二个原因是 epsilon 退火太慢。如果 epsilon 一直维持在 0.5 不降智能体有一半的时间在随机乱走即便学到了最优策略也表现不出来。我一般把 epsilon 的初始值设为 0.5训练到一半时降到 0.1结束时不超过 0.01。有些源码包会在每轮训练的末尾执行epsilon epsilon * 0.995这个衰减率在 1000 轮的设置下可以降到 0.007如果设置成0.999常见误设1500 轮后依然有 0.22探索过度收敛不足。5.2 现象智能体学会了路径但不是最短路径当你看到一条能到终点的路径但明显绕了远路别急着判定代码出错。这个问题通常是负奖励不够大导致的。Q-learning 的收敛目标是最小化累计负奖励、最大化终点奖励如果一步的负奖励是 -0.01而终点奖励是 1那么绕路 50 步只多损失 -0.5仍比找到终点的收益高算法就会认为“绕远路也划算”。解决方法是把单步负奖励调大到 -0.1 或 -0.2。另一种情况是gamma太小比如 0.5会让算法只看近几层的回报宁愿先冲出去再考虑后续。建议先调大负奖励再考虑调gamma因为gamma变化会直接影响 Q 值的收敛值排查优先级上奖励系数更直接。5.3 现象地图上明明没有障碍智能体却卡住不动原因基本都出在状态编号和坐标转换层。检查一下你写的是sub2ind([numRows, numCols], row, col)还是sub2ind([numCols, numRows], row, col)。后者在行数和列数不相等的地图上会让状态编号错乱。此外还要检查边界判定如果地图是 8 行 10 列边界判断必须用row numRows和col numCols写反了会导致智能体跑到地图外面还“站在原处”。排查方法是单步调试手动构造一个状态和动作用env_step走一步打印前后状态编号肉眼核验是否合理。5.4 现象训练后期 episodeRewards 突然暴跌训练曲线在中后期突然大幅下跌通常和两个因素有关一是 Q 表容量太小导致误差累积这在地图状态数不大时很少见二是 epsilon 过低导致策略固化。如果第 1000 轮时 epsilon 已经降到 0.01 附近智能体基本固定走一条策略此时它一旦走错一步由于没有探索的随机性来纠正就会在错误的 Q 值指引下连续出错表现为单轮奖励剧烈下降。缓解方法是不要让 epsilon 降到完全为 0保留 0.01 的探索率如果你发现这个低谷出现在“绕路更远”的路线上可以先杀掉进程用figure; plot(episodeRewards)画整体曲线判断是收敛的噪声还是趋势性下降。5.5 现象同一份代码同学电脑能出结果自己电脑报错这类.rar源码包最常见的兼容性问题有三个第一是 Matlab 版本问题老代码里的randperm(4)或accumarray等函数在较老的 R2016a 上有的存在输出类型差异建议至少使用 R2018b 以上版本第二是路径问题工作目录包含了中文字符或空格导致addpath无法识别全部改用英文目录即可第三是工具箱缺失代码里调用rlenv或deep.QNetwork的需要已安装 Reinforcement Learning Toolbox 或 Deep Learning Toolbox。排查办法是打开主脚本逐段运行报错后用dbstop if error定位到具体行。这五个问题依次排查下来大概率 10 分钟内能定位故障。6. 收尾进阶用收敛曲线和路径热力图验证训练质量训练跑通并不是结束真正有价值的环节是验证并调优。我最常用的一组验证和调优技巧如下先画出episodeRewards随训练轮数的曲线如果曲线从负值说明刚开始在大量撞墙这符合预期如果曲线在中后期稳定在一个平台且不再上升说明策略已经收敛。然后用plot_path画出最短路人工与地图对照确认路径没有穿过障碍。最后是用热力图叠加显示每个状态被访问的频率这能直观看到智能体是否集中在某条固定路径上、有没有去过地图角落的区域。调参时我习惯按以下顺序操作先调奖励项再调gamma最后调epsilonDecay不要同时改多个参数。举例来说如果路径绕路把单步奖励从 -0.1 调到 -0.2重新训练如果曲线平台更高且路径更短说明正确如果训练发散把alpha从 0.1 降到 0.05。每一轮只改一个参数保存一次训练曲线这样你能明确知道是哪次改动带来的提升。深度版本则是把学习率当作第一个排查对象其次是训练轮数经验回放缓冲大小放在最后。我自己踩过最深的一个坑是在 8×8 小地图上跑通了整套代码直接拿到 50×50 的大地图上训练结果 Q 表初始化过大导致max(Q(state, :))返回的第一个索引值永远是固定方向智能体在起点的四个方向上来回打转。后来我强制把 Q 表初始化为 0并增大初始 epsilon问题立刻消失。从那以后每拿到一个强化学习代码包第一件事都是检查初始化方式和探索率设置。这些经验希望你拿到这套“matlab源代码强化学习算法二维地图”时用得上希望帮到你。本文还有配套的精品资源点击获取