简介:面向对强化学习感兴趣的初学者以及路径规划方向的研究人员,这份基于MATLAB的Q-Learning迷宫路径规划资源,以迷宫逃脱为典型场景,首先讲解通过试错更新Q表来逼近最优策略的基本原理,再系统演示从环境建模、Q表初始化与更新规则设定,到智能体策略学习与最优路径输出的完整流程,非常利于快速建立强化学习整体认知。压缩包共包含593个文件,其中以.m源码文件为主(481个),并配有.mat实验数据、.fig结果图、.txt说明、.c/.cpp及MEX辅助函数、.pdf参考资料等多种类型,压缩包整体仅7.32MB,结构组织清晰,便于按需查阅。目前已有314人学习下载。除可直接运行的Matlab迷宫路径规划源码外,还提供了参数配置示例、环境规则自定义说明、运行日志与可视化图表,帮助直观理解Q值迭代与收敛过程;同时附有算法优化、多智能体融合及实际应用等拓展建议,对初学者深入掌握Q-Learning和研究者开展相关实验均有很高的参考价值。
1. 用一张奖励表教会机器走迷宫:Q-Learning 在 MATLAB 中的最小落地闭环
如果只用 30 行 MATLAB 代码,就能让一个智能体在网格迷宫里从起点走到终点,期间不写任何神经网络、不调用深度学习工具箱,你会不会觉得强化学习其实离日常开发没那么远?这就是 Q-Learning 的魅力:它用一张二维表格存放“状态—动作”的长期价值估计,靠反复试错更新这张表,最后收敛出一套从起点到终点的最优策略。相比深度强化学习动辄需要 GPU、经验回放和上千轮训练,Q-Learning 在状态空间可枚举的离散场景里反而更稳、更快、更好调试——迷宫路径规划正是它的经典主战场。
这篇文章会从 Q 表更新的数学原理讲起,然后直接用 MATLAB 手写一个不需要额外工具箱的完整迷宫训练脚本,给出奖励矩阵、衰减因子、探索率这几个关键参数的合理区间和调参依据,再对比 Reinforcement Learning Toolbox 里rlQAgent的标准做法,最后讲清训练曲线的判定精度和可视化方案。适合两类人:一是刚接触强化学习、想在 MATLAB 里看真实迭代过程的入门者;二是已经在做路径规划、想换个思路绕过 A* 或 RRT 的工程师。
2. Q-Learning 的原理与迷宫建模:先把 Q 表、奖励与状态转移说清楚
2.1 为什么迷宫路径规划适合用 Q-Learning
迷宫路径规划本质上是一个“有限状态、有限动作、带延迟奖励”的序贯决策问题。智能体每一步只做四个动作之一:上、下、左、右;它所在的位置就是状态;走到终点获得正奖励,撞墙或走入死胡同获得负奖励或零奖励。Q-Learning 的核心优势在于它不需要环境的动态模型——也就是说,智能体不需要提前知道哪条路通、哪条路堵,它只需要在每一步“试一下”,然后根据实际返回的奖励和新位置更新 Q 表。这与 A* 这类需要完整地图先验知识的规划算法有本质区别:A* 是“看全图再规划”,Q-Learning 是“走一步看一步再学习”。
对于网格规模在 10×10 到 30×30 之间的迷宫,状态空间大小是网格数乘动作数,即几百到几千的量级。Q 表在这个规模下完全可以放进内存,训练几百个 episode 就能收敛。如果迷宫更大或者状态变为连续空间,Q 表就会爆炸,那时才需要考虑 DQN 或策略梯度——但那是另一个话题了。
2.2 Q 值更新公式的直观理解
Q-Learning 的更新公式是:
Q(s,a) ← Q(s,a) + α * [r + γ * max_a' Q(s',a') - Q(s,a)]翻译成工程师语言:当前状态s下执行动作a,得到一个即时奖励r,并转移到新状态s'。新状态s'下能拿到的最大未来回报是max_a' Q(s',a')。把即时奖励和衰减后的未来回报加起来,就是这次尝试的“现实目标值”,减去当前 Q 表里存的“估计值”,得到时间差分误差,再乘以学习率α修正原来的估计。
四个参数的作用要记牢:α(学习率)控制新信息覆盖旧信息的速度,γ(折扣因子)控制远期回报的价值——γ越大智能体越有远见,γ越小则只顾眼前;ε(探索率)控制随机动作的比例,ε大则多探索、ε小则多利用已有经验。它们不是随便填的:α建议取 0.1 到 1 之间,迷宫问题常用 0.5 到 1 让收敛更快;γ建议取 0.9 到 0.99,因为终点奖励要传播回起点需要足够的衰减延迟;ε一般从 0.9 或 1 开始,随训练进程衰减到 0.01 左右。
2.3 MATLAB 中迷宫地图的两种表示方法
最常见的表示方法是数字矩阵:0表示可通行空地,1表示墙壁,2表示终点(也可以单独用终点坐标)。例如一个 5×5 的迷宫:
maze = [ 0 1 0 0 0 0 1 0 1 0 0 0 0 1 0 0 1 0 0 0 0 1 1 0 2 ];这种表示法直观且容易可视化,用imagesc或pcolor画出来就是一张格子图。另一种表示法是预计算状态转移表:把每个格子的四个方向写成next_state = transition(current_state, action),遇到边界或墙时next_state等于当前状态(原地不动),同时给一个惩罚奖励。第二种方法效率更高,因为训练循环里不需要反复判断坐标是否越界;缺点是迷宫改起来要重新生成转移表。
2.4 奖励函数的设计原则
奖励函数决定学习方向。常见的设计是三值奖励:空地行走给0(或一个小负值比如-0.1)防止绕路;撞墙给-1或不给奖励;到达终点给+10。-0.1这种“时间惩罚”在迷宫问题里非常有效,它会在 Q 表里制造梯度——智能体倾向于找到最短路径,而不是在空地上转圈。如果空地奖励是 0,所有非终点路径的价值会一样,策略可能收敛到一条绕远路但同样能到终点的路径,这在路径规划里是不能接受的。
2.4.1 MATLAB 里实现奖励计算的推荐写法
function [next_state, reward, done] = step(maze, state, action, end_idx) % 动作编码: 1=上, 2=下, 3=左, 4=右 [rows, cols] = size(maze); [r, c] = ind2sub([rows, cols], state); dr = [-1 1 0 0]; dc = [0 0 -1 1]; nr = r + dr(action); nc = c + dc(action); if nr < 1 || nr > rows || nc < 1 || nc > cols || maze(nr, nc) == 1 next_state = state; % 撞墙原地不动 reward = -1; done = false; else next_state = sub2ind([rows, cols], nr, nc); reward = -0.1; % 时间惩罚 done = false; if next_state == end_idx reward = 10; done = true; end end end这个函数的逻辑很直白:先把线性索引转成行列坐标ind2sub,根据动作查表得到方向增量,判断是否越界或撞墙。撞墙时状态不变、奖励为-1,不会让智能体通过反复撞墙刷奖励;走到终点返回+10并把done置为true,训练循环跳出当前 episode。函数拆出来而不是写在主循环里,是为了后面换rlQAgent时能直接复用环境逻辑。
3. 手写 MATLAB Q-Learning 训练循环:从零实现不依赖工具箱的迷宫逃脱
3.1 主框架:episode 循环与 step 调用的标准写法
不依赖任何工具箱的实现,核心就是一个双层循环:外层遍历 episode,内层遍历每一步。每一步选择动作(ε-greedy 策略)、执行step更新 Q 表、记录路径用于可视化。下面给出一个可以直接跑的完整脚本框架:
%% 参数配置 maze = [0 1 0 0 0; 0 1 0 1 0; 0 0 0 1 0; 0 1 0 0 0; 0 1 1 0 2]; [rows, cols] = size(maze); start_idx = 1; % 起点是左上角 end_idx = find(maze == 2); % 找到终点索引 n_states = rows * cols; n_actions = 4; Q = zeros(n_states, n_actions); alpha = 0.5; gamma = 0.9; epsilon = 0.9; epsilon_min = 0.01; epsilon_decay = 0.995; episodes = 500; max_steps = 100; %% 训练 for ep = 1:episodes state = start_idx; total_reward = 0; step_count = 0; while true % ε-greedy 动作选择 if rand < epsilon action = randi(n_actions); else [~, action] = max(Q(state, :)); end % 执行动作 [next_state, reward, done] = step(maze, state, action, end_idx); % Q 值更新 best_next = max(Q(next_state, :)); Q(state, action) = Q(state, action) + alpha * ... (reward + gamma * best_next - Q(state, action)); state = next_state; total_reward = total_reward + reward; step_count = step_count + 1; if done || step_count >= max_steps break; end end % 探索率衰减 epsilon = max(epsilon_min, epsilon * epsilon_decay); % 每隔 50 个 episode 打印一次进度 if mod(ep, 50) == 0 fprintf('Episode %d, total reward: %.2f, steps: %d, epsilon: %.3f\n', ... ep, total_reward, step_count, epsilon); end end这段代码的核心逻辑集中在while true循环里。动作选择用rand < epsilon判断要不要随机探索,否则从 Q 表的当前行取max对应的动作;Q 值更新严格按时间差分公式展开,先取next_state下的最大 Q 值作为best_next,再乘gamma、加reward,最后乘alpha修正回原值。代码末尾的探索率衰减很重要——如果不衰减,智能体永远在随机乱走,Q 表虽然也会收敛,但需要多 5 到 10 倍的时间步。
3.2 直接用max还是用randperm处理并列最大 Q 值
max(Q(state, :))返回第一个最大值对应的索引。如果 Q 表里多个动作的 Q 值恰好相等(初始时全部为 0),max会固定选择第一个动作,这会导致早期策略严重偏向一个方向。处理办法有两种:一是用find找出所有最大值的索引再随机挑一个;二是给 Q 表加一个小的随机扰动后再选。第二种做法在深度强化学习里叫 action noise,在网络初始化随机的情况下没必要,但在 Q 表场景下很有效。
best_actions = find(Q(state, :) == max(Q(state, :))); action = best_actions(randi(numel(best_actions)));建议在训练初期(前 50 个 episode)使用随机打破并列,后期直接用max加快决策速度。因为训练后期各动作 Q 值差距明显,并列的概率本身就很低。
3.3 训练过程的 3 个关键指标
训练是否正常,不能只看最终能不能到达终点,还要看中间过程。三个指标按重要性排序:
- 每 episode 总步数:理想曲线是整体下降趋势,前期波动大、后期稳定在最短路径长度附近。如果步数一直下不去,先看探索率衰减是否太慢。
- 每 episode 总奖励:因为每步都有
-0.1的时间惩罚,总奖励会随步数减少而上升。终点奖励 10、时间惩罚 -0.1,最短路径若为 8 步,则总奖励约10 - 0.8 = 9.2。这个数值可以作为收敛基准。 - 是否频繁重复访问同一格:这个需要在记录路径后手动分析,如果同一路径反复横跳,说明时间惩罚设置得太小,
-0.1可以改成-0.5再试。
3.4 表格:迷宫 Q-Learning 必调参数速查表
| 参数 | 推荐范围 | 迷宫问题常用值 | 调大后效果 | 调小后效果 |
|---|---|---|---|---|
| 学习率 α | 0.1 ~ 1 | 0.5 | 收敛快但震荡大 | 收敛慢但稳定 |
| 折扣因子 γ | 0.9 ~ 0.99 | 0.9 | 更重视远期回报 | 目光短浅 |
| 探索率 ε 初值 | 0.5 ~ 1 | 0.9 | 前期探索充分 | 过早利用 |
| ε 衰减系数 | 0.9 ~ 0.999 | 0.995 | 衰减慢,训练久 | 衰减快,易陷局部 |
| 时间惩罚 | -0.01 ~ -1 | -0.1 | 路径更短 | 路径可能绕路 |
这个表是具体调参时的起点。迷宫规模小(比如 5×5),ε 可以衰减快一些,0.98 就够;迷宫大(20×20 以上),ε 衰减要放慢到 0.999,否则智能体还在摸索阶段就过早进入利用模式,容易卡在局部最优。
3.5 可视化与路径提取:用 imagesc 画轨迹
训练完成后提取最优策略本身就很简单:从起点出发,每次查 Q 表取最大 Q 值对应的动作,执行到终点为止。这个过程中要把每一步经过的位置记录下来,然后用imagesc画出来:
%% 提取最优路径 state = start_idx; path = state; while state ~= end_idx [~, action] = max(Q(state, :)); [next_state, ~, done] = step(maze, state, action, end_idx); state = next_state; path = [path; state]; if done break; end end %% 可视化 figure; imagesc(maze); colormap(gray); hold on; [path_r, path_c] = ind2sub([rows, cols], path); plot(path_c, path_r, 'r-o', 'LineWidth', 2, 'MarkerSize', 6); title('Q-Learning 迷宫最优路径');可视化时的注意点:imagesc的坐标轴原点在左上角,而 MATLAB 的矩阵索引(i,j)也是从左上开始,因此行列坐标和图形坐标方向一致,不需要 reverse。plot里横坐标是列号、纵坐标是行号,和imagesc默认坐标一致。路径绘制时要确保起点和终点在图上有明显的标记,可以用scatter单独标一下。
4. 用 Reinforcement Learning Toolbox 复现并对比手写版
4.1 什么时候该换工具箱
手写版的优势是逻辑透明、无依赖、方便教学,缺点也很明显:没有内置的经验回放、没有自动超参搜索、没有训练进度可视化。如果你的目标是快速验证一版算法、或者作为正式项目的前期原型,手写版足够了。但如果后续要扩展到连续动作空间、要对比 DQN 或 PPO,或者要产出带训练曲线的标准报告,用 MathWorks 的 Reinforcement Learning Toolbox 更省事。rlQAgent是工具箱里的内置 Q-Learning 智能体,它可以直接吃rlMDPEnv或自定义环境,训练过程用train函数统一驱动。
4.2 基于 rlQAgent 的完整训练与评估代码
%% 定义迷宫环境 maze = [ 0 1 0 0 0 0 1 0 1 0 0 0 0 1 0 0 1 0 0 0 0 1 1 0 2 ]; [rows, cols] = size(maze); start_state = 1; end_state = find(maze == 2); getState = @(obs) obs(1); % 取观测中的状态分量 env = rlMDPEnv( ... 'ObservationInfo', rlFiniteSetSpec(1:rows*cols), ... 'ActionInfo', rlFiniteSetSpec([1 2 3 4])); % 直接使用上面手写的 step 函数作为环境转移函数 env.StepFunction = @(obs, action) stepWrapper(maze, obs, action, end_state); %% 定义 QAgent agentOpts = rlQAgentOptions( ... 'DiscountFactor', 0.9, ... 'EpsilonGreedyExploration', true, ... 'Epsilon', 0.9); agent = rlQAgent(agentOpts); %% 训练 trainOpts = rlTrainingOptions( ... 'MaxEpisodes', 300, ... 'MaxStepsPerEpisode', 100, ... 'StopTrainingCriteria', 'AverageReward', ... 'StopTrainingValue', 9.0, ... 'Verbose', true); trainStats = train(agent, env, trainOpts);这里的关键点有两个。第一,rlMDPEnv要求环境的StepFunction能接收观测(作为 obs)和动作,我们需要写一个stepWrapper把 obs 转成状态索引,再调用前面手写的step函数,这样环境逻辑完全复用,不需要重新定义转移矩阵。第二,rlQAgentOptions里的Epsilon在训练过程中会由工具箱内置逻辑自动衰减,衰减策略在官方文档里说明是随 episode 逐步降低,但实际衰减幅度不如手写版的epsilon_decay可定制。所以手写版适合需要精细控制训练过程的场景,工具箱版适合快速出结果。
4.3 rlMDPEnv 与手写循环的速度和灵活性对比
工具箱版本最大的成本在环境接口转换和内部训练流程的封装开销。在 5×5 小迷宫上,手写版 500 个 episode 大约 0.5 秒跑完,工具箱版可能需要 3 到 5 秒,差别来自每次 step 的对象传递和训练统计记录。但工具箱版免费赠送了训练曲线绘图、自动保存模型、多 episode 平均奖励统计等能力,手写版这些都要自己写。实际项目里没有绝对好坏,关键看你的交付物是什么:如果只是要一条最优路径和 Q 表,手写版;如果要给非技术同事汇报训练过程和收敛曲线,工具箱版。
5. 调参避坑与实际应用:从收敛判定到复杂迷宫泛化
5.1 收敛判定的正解:不是步数不变,而是策略稳定
很多初学者的误区是看到某一次 episode 的步数达到最短路径长度,就认为训练完成。实际上单次 episode 有偶然性,可能这次运气好、下次就绕路。正确的收敛判定是在连续 50 到 100 个 episode 内,提取出的最优路径完全一致,并且不再出现随机探索改道路径的情况。可以用下面的代码片段自动判定:
path_history = zeros(episodes, 1); % 训练结束后单独评估, epsilon 置 0 epsilon = 0; for ep = 1:episodes state = start_idx; count = 0; while state ~= end_idx && count < max_steps [~, action] = max(Q(state, :)); [state, ~, done] = step(maze, state, action, end_idx); count = count + 1; if done, break; end end path_history(ep) = count; end if all(path_history == path_history(1)) fprintf('策略已收敛到稳定路径\n'); end这里关键是把epsilon置 0,只用贪心策略评估,排除探索带来的随机步数变化。如果连续评估的步数序列稳定,说明 Q 表已经收敛到一个确定性的最优策略。如果步数始终在波动,优先怀疑时间惩罚与终点奖励的比例关系:终点奖励要大于时间惩罚乘以路径长度的上限,否则智能体可能认为永远不走到终点反而是高回报策略——这在训练曲线上表现为总奖励迟迟为负。
5.2 改进技巧:Q 表初始化、ε 衰减曲线与障碍物代价的变化
三个可以立刻试的改进方向:
Q 表初始化。初始值全 0 会让智能体的第一次成功探索产生较大幅度的更新,后续学习容易受早期随机路径影响。一个常见做法是把不可通行状态的 Q 值初始化为一个大负数(如-100),确保智能体从第一轮开始就会避开撞墙;可通行状态初始化为0。这个改造只需要一行代码:
Q = zeros(n_states, n_actions); for s = 1:n_states [r, c] = ind2sub([rows, cols], s); if maze(r, c) == 1 Q(s, :) = -100; end endε 衰减改成多段式。固定epsilon_decay在 500 episode 内的衰减曲线是平滑的,但不同训练阶段对探索的需求不同。我的习惯是分三段:前 30% 训练保持高探索(ε = 0.9 ~ 1),中间 40% 线性降到 0.2,后 30% 从 0.2 衰减到 0.01。这个方案比指数衰减更好控制训练节奏,代价是需要多写几行判断。
增加障碍物代价惩罚。如果迷宫里有些区域虽然可通行但明显是死胡同(通常与终点相距很远),可以给这些格子加一个额外的负奖励初始 Q 值,比如-5。这相当于把先验知识注入 Q 表,加速训练收敛。但注意这只是初始化技巧,不能替代奖励函数的设计——如果羊肠小道也能到达终点且路径更短,强加负值反而可能干扰最优策略的发现。
5.3 二维迷宫向更复杂场景的泛化方法
二维网格迷宫能跑通后,往三个方向扩展是常见的实践路径:
- 带动态障碍物:环境转移函数改为运行时动态计算,例如某堵墙每隔一段时间打开或关闭,奖励函数增加事件驱动逻辑。Q-Learning 在状态空间扩张后(把时间维加到状态里)仍然可以训练,但需要把“障碍物开关状态”编码进状态索引。
- 连续坐标场地:无法枚举离散状态时,Q 表失效,需要换 SARSA 或 DQN。MATLAB 里可以用
rlDQNAgent,网络用featureInputLayer定义。 - 三维或者多楼层迷宫:纯网格法会面临状态空间爆炸,20×20×5 的三维迷宫有 2000 个格子乘 6 个动作 = 12000 个状态,Q 表还能承受但训练时间明显变长。这时需要考虑分层方法:先在楼层维度用 Q-Learning,再在二维平面内规划路径。
5.4 调试口诀:训练异常时的三步定位法
遇到训练不收敛,我一般按固定的顺序排查:先看奖励函数——用一行脚本打印前 20 个 episode 每步的实际奖励,确认终点奖励是否真的被触发,有没有因为撞墙判定顺序写错导致永远到不了终点;再看探索率——打印当前 ε 值,确认是否衰减过快,在智能体还没有找到终点的时候 ε 已经低于 0.1,这时候后期 Q 表只会小幅更新,很难修正错误认知;最后看 Q 表分布——用histogram(Q(:))画一下 Q 值的分布,如果所有值都在-1和0之间,说明智能体几乎没到达过终点,奖励信号没有传播开,需要把终点奖励调大、时间惩罚调小,或者减小迷宫尺寸验证代码正确性。
这种“先奖励、再探索、后 Q 表”的顺序能覆盖 90% 以上的训练异常。迷宫路径规划在强化学习里算是难度最低的类型,但正是因为它简单,才能把 Q 表更新、参数影响和收敛判定这些底层机制看得明明白白——这些经验直接迁移到时序控制、资源调度这些更复杂的 Q-Learning 应用时,照样管用。
本文还有配套的精品资源,点击获取