
简介这是一份带逐行中文注释的Q-Learning MATLAB源代码文档聚焦强化学习中的经典无模型算法适合人工智能课程学生、竞赛选手或算法入门者阅读资源以“王子找公主”的网格寻路游戏为案例演示智能体如何利用状态转移矩阵、奖赏矩阵以及Q值迭代逐步学会避开障碍物和陷阱并找到最短路径。压缩包共1个doc文件大小约33KB包含完整训练脚本、关键参数说明和路径回溯代码目前已有1378人学习下载。文档从可移动矩阵与奖赏矩阵的设计出发解释折扣因子gamma和选择概率epsilon的意义并展示epsilon-greedy策略下随机探索与利用最优动作的平衡训练循环部分清晰给出了贝尔曼方程更新Q值的实现还通过每100次迭代输出状态便于观察收敛。读者不仅能复现该案例的最优路径规划还可将同样的代码结构迁移到其他栅格环境或决策问题中是快速上手Q-Learning的理想参考资料。1. 拿到这份《Q-Learning 源代码及注释(matlab).doc》先搞清三件事第一次拿到《Q-Learning 源代码及注释(matlab).doc》这类文档时多数人做的事一模一样打开MATLAB把代码一段段敲进去点运行然后看着一个小方块在网格上乱撞撞着撞着某一次莫名其妙走到了终点。这个doc确实帮很多人入了强化学习的门但也坑过不少人——它附带的注释只告诉你“这一行在算什么”不说“为什么这么写”于是你改一个参数就可能全盘翻车。下面把这类MATLAB源码拆开讲透Q表怎么初始化、epsilon-greedy为什么是两行 if-else、Bellman 更新那一行为什么非要取下一状态的最大值以及doc里最容易抄错的几个细节。适合刚接触强化学习、要交课程作业或做小型路径规划验证的读者如果你已经在跑深度强化学习也可以用这套表格型 Q-Learning 当基准对照。2. 建模先行把 Q-Learning 的数学公式落成 MATLAB 能跑的数据结构2.1 从 MDP 到网格世界状态、动作、回报怎么映射成变量Q-Learning 虽然号称 model-free但你要在 MATLAB 里跑起来第一件事不是写公式而是把状态空间、动作空间、回报函数翻译成数组和矩阵。以这类 doc 里最常见的 5×5 网格世界为例左上角是起点右下角是终点智能体每一步可以上、下、左、右移动撞墙原地不动并吃一个负回报到达终点获得正回报。状态用 1 到 25 的编号表示而不是直接存坐标。这样 Q 表天然就是一个 25 行 4 列的矩阵Q(state, action)第 i 行第 j 列存储“在状态 i 执行动作 j 的长期价值估计”。动作 1/2/3/4 分别对应上/下/左/右这是这套源码里最常见的约定也是零基础入门最容易忽略的地方MATLAB 下标从 1 开始你没有办法像 Python 那样让状态从 0 编号写zeros(25, 4)还是zeros(24, 4)决定了后面所有坐标转换的边界逻辑。% q_learning_gridworld.m % 网格尺寸与状态编码: 第 r 行第 c 列的格子编号为 (r-1)*cols c rows 5; cols 5; S rows * cols; % 状态总数 25 A 4; % 动作数: 1上, 2下, 3左, 4右 % Q 表: 状态数 × 动作数, 全部初始化为 0 Q zeros(S, A); % 起点编号 1, 终点编号 25 startState 1; goalState 25;这段代码背后的选型逻辑要讲清楚为什么用“编号”而不是直接用(r, c)坐标因为 Q 表的下标必须是整数Q(3,2)表示状态 3 执行动作 2如果你存的是坐标就得自己维护一套“坐标→行号”的映射复杂度完全不必要。MATLAB 自带的sub2ind和ind2sub就是干这个的后面状态转移时会大量用到。另外S rows*cols这种写法比直接写25强得多——一旦你想改成 10×10 或 8×6 的网格只改rows和cols两个变量后面所有边界判断和索引都自动适配。回报函数不建议像一些老代码那样先铺一张 25×25 的R矩阵因为当动作有 4 个时回报本质上是“状态 动作”的函数即R(s,a)而不是简单的“状态到状态”的转移回报。写成嵌套函数或 switch-case 会直观得多这点在 2.2 展开。2.2 回报函数怎么设计稀疏、步数惩罚与危险规避三张牌回报设计直接决定算法学成什么样但恰恰是 doc 注释里写得最少的部分。同一个 5×5 网格回报函数不同收敛速度和路径形态天差地别。业内常说的“Reward Shaping”说的就是这件事。下面三种设计风格我都在工程里见过放在一起对比设计风格普通步回报到达终点撞墙行为效果稀疏回报01000收敛慢前期几乎学不到东西步数惩罚-1100-10自动倾向最短路径收敛快危险规避0100-100特别怕墙但可能在开阔区域徘徊稀疏回报理论上是 Q-Learning 的“标准设定”但表格型 Q-Learning 在完全零奖励的环境里只能靠随机游走碰运气碰到终点5×5 网格还好网格一大基本就学不动了。所以我给学生和同事的建议是先上“步数惩罚”方案把每一步的非终止回报设为 -1。这么做有一个数学上的妙处智能体从起点到终点路径每多一步就多扣 1 分最终学习到的最优策略天然就是最短路径不需要你额外写任何“找最短路径”的逻辑。在 MATLAB 里我一般不把回报写成一个提前算好的矩阵而是放在状态转移函数里即时计算原因很简单回报往往依赖“目标状态”和“是否撞墙”这两者在运行时才确定。具体代码留在第 3 章讲状态转移时给出这里先强调一个设计原则回报必须是状态转移结果s, a, s的函数不是当前状态 s 的静态属性。这也是新手最容易犯的错——把“终点格子”的回报直接写进了 R 矩阵导致智能体在终点附近反复横跳也能吃奖励。2.3 先拆 Bellman 更新公式三段各自负责什么doc 里通常会把核心更新公式写成一行类似Q(s,a) Q(s,a) alpha * (r gamma * max(Q(s2,:)) - Q(s,a))。这一行看着简单实则包含三个职责完全不同的部分目标值target、时间差误差TD error、以及旧估计的保留比例。把公式拆开看r gamma * max(Q(s2,:))是“现实”与“未来”的合成。r是环境给的即时回报它告诉智能体这一步走得值不值max(Q(s2,:))是“下一个状态能带来的最大未来价值”注意这里必须取最大因为 Q-Learning 是 off-policy 算法它在估计价值时假设后续每一步都采用最优策略不管当前实际策略是什么。gamma是折扣因子负责把未来价值折算到现在——未来 10 步的奖励折算到现在如果 gamma0.9只剩大约 0.35。Q(s,a)是旧估计r gamma * max(Q(s2,:))是带噪声的新观测两者之差就是 TD error。智能体每走一步只把旧估计往目标方向挪一小步挪动的幅度由alpha控制。理解了这三段你才能看懂后面所有调参建议alpha管的是“新信息顶掉旧信息的力度”gamma管的是“智能体看得多远”回报设计管的是“什么叫做好行为”。这三者互相牵制不存在“某个参数万能最优”这种好事。3. 源码主线把主循环、动作选择、Q 表更新逐行讲透3.1 程序骨架episode 与 step 两层循环怎么搭几乎所有表格型 Q-Learning 的 MATLAB 实现都是两层循环外层是episode代表“完整走完一轮从起点到终点的尝试”内层是step代表一轮尝试中的单步。episode 结束的条件有两个一是到达终点二是步数超过maxSteps上限。后者是保命条款没有它一旦智能体陷入死循环程序就卡死了。% 主循环: 训练 maxEpisodes 轮 maxEpisodes 800; maxSteps 200; % 预分配数组, 记录每轮到达终点用了多少步 stepsPerEpisode zeros(maxEpisodes, 1); for episode 1:maxEpisodes s startState; % 每轮初始状态都是起点 totalReward 0; % 可选: 用来观察每轮累计回报 % 内层循环: 走一步算一步, 直到终点或步数耗尽 for step 1:maxSteps % 动作选择、状态转移、Q更新三件事都发生在这里 % 具体实现见 3.2 - 3.4 if s goalState break; % 到达终点, 本轮结束 end end % 记录本轮实际步数 stepsPerEpisode(episode) step; % 若提前到达终点, 这里记录的 step 不是 maxSteps end这段骨架里有几个细节老手可能觉得啰嗦新手照着抄却容易抄错。第一for step 1:maxSteps和while s ~ goalState都能写但 for 版本自带步数上限少写一个循环判断条件直觉上更安全。第二stepsPerEpisode必须在主循环外预分配否则 MATLAB 会在循环里动态增长数组网格一大、episode 一多性能拖累非常明显——这一点在第 5 章还会再提。第三记录步数时要放在“判断是否到达终点”之后否则break之前的值和实际步数会差 1画出来的学习曲线会系统性偏移一格。3.2 动作选择epsilon-greedy 的两行代码与随机种子Q-Learning 的动作选择策略一般是 epsilon-greedy大部分时候选择当前 Q 表里价值最高的动作利用小部分时候随机选一个动作探索。没有探索智能体永远发现不了新路径没有利用学到的经验全被随机抖动稀释。这个平衡是整套算法的灵魂。% epsilon-greedy 动作选择 % rand() 返回 (0,1) 均匀分布随机数 if rand() epsilon act randi(A); % 探索: 随机选一个动作 else [~, act] max(Q(s, :)); % 利用: 取 Q 表当前最大值对应的动作 end参数说明epsilon初始值常见取 0.1 到 0.3。如果你是做迷宫、寻路这类单目标任务建议用“衰减版”而不是固定值公式写在 4.3如果你只是想把 doc 里的示例跑通固定 0.1 也能出结果。randi(A)返回 1 到 4 的均匀随机整数等价于ceil(rand() * A)但randi可读性更好。max(Q(s,:))返回两个值第一个是最大值第二个是最大值出现的位置——这里用~丢掉第一个值第 5 章会讲这个~在旧版 MATLAB 里的兼容性坑。一个容易被忽略的工程细节是随机种子。MATLAB 默认每次启动随机数流都不同所以同一份代码两次运行学习曲线的形状完全不一样有人会误以为“算法不稳定”。做实验前建议在脚本开头加一行rng(0)固定随机种子这样至少能让每次训练结果可复现调参才能对比出真实差异。3.3 状态转移用 ind2sub 处理网格边界和撞墙状态转移是源码里最“无聊”却最容易写崩的部分。常见的错误写法是直接用s±1和s±5表示上下左右——在 5×5 网格里碰巧是对的但一旦改成非矩形地图就全乱套。正确做法是先把当前编号转成行列坐标移动后再检查边界最后转回编号。% 由当前状态编号求行列坐标 [r, c] ind2sub([rows, cols], s); % 根据动作计算目标行列 switch act case 1, r2 r - 1; c2 c; % 上 case 2, r2 r 1; c2 c; % 下 case 3, r2 r; c2 c - 1; % 左 case 4, r2 r; c2 c 1; % 右 end % 边界检查: 出界则原地不动, 并给予负回报 if r2 1 || r2 rows || c2 1 || c2 cols s2 s; % 撞墙, 状态不变 reward_ -10; % 撞墙惩罚 else s2 sub2ind([rows, cols], r2, c2); if s2 goalState reward_ 100; % 到达终点 else reward_ -1; % 普通步惩罚 end end逻辑说明ind2sub把 25 拆成 (5,5) 这样的行列对sub2ind再把行列对拼回 25这两个函数是 MATLAB 处理网格索引的标准工具比手写取模运算直观也不容易错。撞墙时状态s2必须等于s不能是某个空洞的非法状态——如果这里写错Q 表会出现很多“从未访问过”的行行为异常且难以排查。回报统一放在状态转移分支里计算避免了单独维护 R 矩阵的不一致风险。这段代码是后续所有魔改的锚点。你想加障碍物就在边界检查里多一个obstacle(r2,c2)1的判断你想让终点不只有一个就把if s2 goalState换成ismember(s2, goalStates)你想把网格改成八方向移动只需扩充 switch-case 分支。后面第 6 章就是基于这个结构做扩展的。3.4 核心一步Q(s,a) 更新的 target 与 error状态转移算完终于到了最核心的一步更新 Q 表。这一步在 doc 里通常只占一行注释“Q值更新”但这里藏着 Q-Learning 和 SARSA 的本质区别也是源码阅读者最容易混淆的地方。% 取出下一状态的最优估计价值 % max 返回两个输出: 第一个是最大值, 第二个是对应动作编号 [vmax, ~] max(Q(s2, :)); % 计算 TD target 即时回报 折扣 × 未来价值 tdTarget reward_ gamma * vmax; % 计算 TD error 目标 - 当前估计 tdError tdTarget - Q(s, act); % 更新: 沿着误差方向走一步, 步长由 alpha 控制 Q(s, act) Q(s, act) alpha * tdError;这里的max(Q(s2,:))是 Q-Learning 的身份证。它评估“下一个状态能带来的最好情况”认为未来会采取最优动作。如果你把这一行替换成Q(s2, act2)——也就是用“实际执行的下一步动作”的价值来更新——算法就变成了 SARSA。SARSA 是 on-policyQ-Learning 是 off-policy两者的区别在新手阶段可能感觉不大但在随机环境或带惩罚的环境里学出来的策略保守程度会明显不同。之前有人把 doc 里的代码抄串行把max的取法抄成了实际动作估值训练出来的智能体总是绕远路就是因为这个细节。参数说明gamma在 5×5 网格里取 0.9 就够了alpha取 0.5 时新旧信息各占一半收敛较快但最终会在最优值附近抖动取 0.1 时更平滑代价是收敛慢。这些数字不是越接近 1 越好第 4 章单独展开。另外注意这里用s2计算未来价值时如果s2恰好是终点max(Q(s2,:))取的是 Q 表那一行里最大的数。训练初期这一行还是 0所以到达终点的学习信号主要靠即时回报reward_撑起来这就是为什么回报设计太稀疏时环境“教”得太慢。3.5 观察收敛记每轮步数而不是盯着 Q 表发呆训练跑起来了怎么判断有没有学对很多刚接触的人习惯盯着 Q 表看数字变化这不是好习惯。Q 表里的数值会一直变但它变大变小都不代表策略变好——真正该看的是“每轮走到终点用了多少步”。% 训练结束后画学习曲线: 每轮到达终点的步数 figure; plot(1:maxEpisodes, stepsPerEpisode, LineWidth, 1.2); xlabel(Episode); ylabel(Steps to Goal); title(学习曲线: 步数应随训练下降); grid on; % 平滑视图: 看最近50轮均值, 忽略单轮波动 window 50; smoothed movmean(stepsPerEpisode, window); hold on; plot(1:maxEpisodes, smoothed, r, LineWidth, 2); legend(原始步数, 50轮移动平均, Location, northeast);如果每轮步数在前 100 轮里快速下降然后进入平台期说明智能体真的在变聪明。如果 500 轮之后步数依然在 200 左右振荡也就是每次都没能到达终点问题大概率出在回报设计或 epsilon 没衰减而不是随机种子。movmean是 MATLAB 自带的移动平均函数比手写滑动窗口方便得多画出来的曲线能把单次回合并发的噪音抹掉适合判断“趋势是否已经平台化”。4. 参数不调好算法不收敛四个必填超参4.1 alpha 学习率固定值还是随 episode 衰减alpha 控制每一轮更新时“新信息顶掉旧估计”的力度取值在 0 到 1 之间。alpha 太大Q 值会被最近的几次高回报或低回报带着剧烈抖动训练曲线像锯齿alpha 太小收敛慢网格稍大就浪费大量算力。在 doc 的示例代码里固定 alpha 是最常见的一般取 0.1 到 0.5。我的建议是如果你只是在固定网格、固定回报的静态环境里跑直接固定 0.5 没毛病如果你的环境带有随机性比如对手行为在变、回报有噪声最好让 alpha 随训练递减。一个不复杂的经验公式% alpha 衰减: 前期大步学, 后期小步微调 alpha 0.5 / (1 episode / 50);episode / 50是衰减速率50 表示大约 50 轮之后 alpha 降到 0.25500 轮之后降到 0.05 左右。这个值要跟maxEpisodes匹配训练轮数越多分母该调大否则 alpha 过早衰减到接近 0后半段训练基本停止。4.2 gamma 折扣因子任务越长它越要接近 1gamma 决定智能体“看多远”。gamma0 时智能体只看即时回报永远学不会为远期目标做出局部牺牲gamma1 时远期奖励和即时奖励等权但在某些非确定性环境下可能出现价值高估不收敛。多数任务取 0.9 到 0.99 之间。有一个快速估算方法如果你希望“10 步之后的奖励对当前决策仍有约 50% 的影响力”那么 gamma 的取值应满足gamma^10 ≈ 0.5即 gamma ≈ 0.93。网格世界任务路径一般不超过几十步0.9 起步即可如果你的自定义地图是一条长走廊终点在 50 步开外gamma 至少要推到 0.98否则终点奖励折算到起点附近已经接近于零智能体完全没有出发的动力。表格型 Q-Learning 的一个常见失误是把 gamma 和 alpha 混在一起调看到不收敛就同时乱改。正确的排查顺序是先固定 alpha0.5、epsilon 衰减只动 gamma观察学习曲线的下降速度是否有变化。gamma 对“是否走最短路径”的影响远大于 alpha而 alpha 影响的是收敛平顺度而非策略质量。4.3 epsilon 探索率线性衰减公式与终值epsilon 是最直观但最容易被写成“固定值”的参数。固定 epsilon0.1 时训练后期每 10 步就有 1 步在随机乱走学到的策略再好也会被随机动作干扰得不像样。成熟的工程做法是让 epsilon 从较大的初始值线性衰减到很小的终值。% epsilon 线性衰减: 从 1.0 衰减到 0.01 epsilon max(0.01, 1.0 - episode / maxEpisodes * 0.99);这段公式的意思第 1 轮 epsilon≈1几乎全随机探索第 500 轮左右降到 0.5最后一轮约 0.01只剩 1% 的随机动作兜底。max(0.01, ...)是保底条款保证训练后期仍然留一点点探索防止环境变化时策略彻底僵死。对 5×5 网格这个衰减速度已足够网格更大、路径更曲折时建议把衰减终值保持 0.05探索保留得多一点否则容易困在局部最优。4.4 episode 次数与网格规模的匹配经验episode 数量没有万能答案但它跟状态空间大小强相关。一个很粗略的经验值episode 数至少是状态数的 20 倍。25 个状态的网格500 轮起步100 个状态的网格2000 轮才算合理。下面是几个常用规模的经验配置表可以直接照抄起步值网格规模状态数建议 episode 数每轮最大步数起步 alpha3×39200500.55×525500 - 8002000.510×101001500 - 30004000.320×2040050008000.3这里有个容易起冲突的细节每轮最大步数设太大死循环未命中时单轮耗时会很高设太小合法路径被截断终点永远走不到。经验做法是把最大步数设为“网格对角线步数的 3 倍”比如 5×5 网格对角线 8 步上限 200 已经留足了余量。如果发现训练后段步数曲线贴着上限走别急着加步数上限先检查回报函数里是否有某个状态在“原地打转吃惩罚”也优于往前走的逻辑漏洞。5. 排错笔记Q-Learning MATLAB 代码的经典翻车现场5.1 翻车现场Q 表训练完仍是全零现象训练几百轮结束disp(Q)一看全是 0智能体完全没学习。原因最常见的不是算法错了而是“更新代码根本没有被执行”。我见过好几次都是把 Q 更新写在了if s2 goalState的分支里——只有到达终点那一轮才更新平时步则跳过Q 表里能学到东西的行只有终点附近那几格。另一个高频原因是Q(s, act)里act是空的比如max的第二个返回值被写成了~而旧版 MATLAB 根本不支持~占位直接报错你以为是语法问题其实是对应关系没对上。解决在 Q 更新那一行打一个条件断点条件是episode 10 step 100跑一次看s、act、s2三个变量形状对不对。确认更新每步都执行再确认act在 1 到 4 之间。强烈建议把 Q 更新代码单独抽成一个函数传(Q, s, act, s2, reward_, alpha, gamma)这样既能单测又能避免主循环里变量覆盖导致“改了没生效”。5.2 翻车现场几千轮还在原地乱撞现象学习曲线在 10 步上下振荡永远不下降几百轮过去还是这个德行。原因epsilon 固定在 0.9智能体 90% 的动作都在随机走相当于随机策略或者是回报设计得太稀疏智能体从起点出发一百多步都碰不到一次终点Q 表里只有“撞墙”和“普通步”两种负反馈没有任何正向信号引导它朝终点移动。解决先把 epsilon 改成衰减版用 4.3 的公式再把普通步回报从 0 改成 -1稀疏回报改成步数惩罚。如果还不行做一个“人工演示”手写一段固定动作序列让智能体从起点走到终点把每一站的(s, a, s2, reward)喂到 Q 更新公式里相当于预置了一组有正回报的经验。这不是作弊在真实工程里叫 “expert demonstration”很多机器人强化学习项目就是这么冷启动的。5.3 翻车现场doc 里复制的中文注释变成乱码现象从《Q-Learning 源代码及注释(matlab).doc》里把代码复制到 MATLAB 编辑器中文注释变成“锟斤拷”或者一堆 Ã©ï¼ 的乱码字符串里的引号还自动变成了中文全角引号报错“字符串未结束”。原因doc 文件里的中文注释通常按 Word 的本地编码中文系统里通常是 GBK/GB2312保存而新版 MATLAB 编辑器默认按 UTF-8 解析文本编码不匹配就乱码。加上 Word 的自动排版会把替换成中文引号“”MATLAB 不认识这俩全角字符直接把字符串语法炸了。解决不要直接复制 doc 里的代码块先粘到 Windows 自带的记事本另存为 UTF-8 编码再用 MATLAB 打开。更稳的做法是只从 doc 里复制纯代码部分字母、数字、运算符中文注释全部手敲因为注释不重要复制乱码得不偿失。如果你用的是学校机房的老版 MATLAB还要检查编辑器的“预设 编辑器/调试器 语言 文件编码”把编码设成 UTF-8这是一次踩坑后我能给的最实际建议。5.4 翻车现场智能体总是“绕远路”到达终点现象策略学会了每次都能到终点但路径明显绕弯从 5 步能走到 12 步怎么看怎么别扭。原因三个可能性按概率排序。第一回报里普通步是 0 不是 -1所有路径的长期回报一样算法随机选了一条不一定选最短的第二gamma 太小远一点的终点奖励折算到起点几乎为零智能体更看重眼前少撞墙而不是尽早到达第三epsilon 后期没有衰减策略里混入了随机的冗余动作造成视觉上的“绕路”。解决把普通步回报改成 -1把 gamma 从 0.9 试到 0.95观察步数曲线有没有进一步下降。还有一个检查技巧训练结束后用纯贪心策略回放一遍路径也就是永远取max(Q(s,:))对应的动作如果贪心路径还是绕问题在回报和 Q 表如果贪心路径正常问题就在 epsilon 没降下来。5.5 翻车现场训练极慢一个 5×5 网格都要跑几十秒现象800 个 episode 跑下来要等半天风扇狂转进度却走得很慢。原因十有八九是内层循环没有步数上限。while s ~ goalState死循环在某几个状态之间反复横跳永远停不下来每个 episode 都空转到天荒地老或者是在循环里写了disp(s)、clc清屏这类输出语句MATLAB 在循环里刷新控制台的代价比你想象的高得多。解决内层循环一律换成for step 1:maxSteps配上if s goalState, break; end从根本上杜绝死循环。把一切调试输出移出主循环只保留stepsPerEpisode这种必要记录。另外Q 表更新的 if-else 链里不要每次都调用disp或者重新计算rows*cols把常量提到循环外。5×5 网格 800 轮正常 MATLAB 应该在几秒内跑完如果超过 20 秒按照这条逐项检查基本一抓一个准。6. 把它改造成自己的场景地图替换、路径回放与可复现实验6.1 改地图的最小改动清单前面这套代码最大的价值是“可替换性”。你想把它从 5×5 网格改成自己的地图只需要动四个地方rows、cols、起点编号、终点编号。但改成真实场景后通常还会遇到一个实际问题地图里有障碍物。障碍物不能简单用“撞墙惩罚”表达因为障碍物在网格内部不在边界上智能体可能穿过去。常见做法是维护一个障碍物矩阵和网格同尺寸有障碍物的格子为 1否则为 0。状态转移时把“出界原地不动”扩展为“出界或落入障碍物都原地不动并给惩罚”。改动量不大但价值很大——它把玩具示例变成了能用在仓储、游戏寻路里的微型路径规划器。6.2 训练结束后做一次贪心路径回放学习曲线下降只代表“数值上学到了”不代表智能体规划的路径真的合理。训练结束后做一个纯贪心回放把路径画出来一眼就能看出策略质量。回放代码和训练时的状态转移逻辑基本一样只是动作选择不再用 epsilon-greedy而是每次都取 Q 表最大值% 用训练好的 Q 表做纯贪心策略回放 s startState; path s; % 记录路径上的状态编号 while s ~ goalState [~, act] max(Q(s, :)); % 只利用, 不探索 [r, c] ind2sub([rows, cols], s); switch act case 1, r2 r - 1; c2 c; case 2, r2 r 1; c2 c; case 3, r2 r; c2 c - 1; case 4, r2 r; c2 c 1; end if r2 1 || r2 rows || c2 1 || c2 cols s2 s; % 出界, 原地不动 else s2 sub2ind([rows, cols], r2, c2); end path(end1) s2; % 记录新状态 s s2; if numel(path) rows * cols * 4 error(回放未收敛, 检查Q表或回报设计); end end % 状态编号转坐标并画路径 [rPts, cPts] ind2sub([rows, cols], path); figure; plot(cPts, rPts, b-o, LineWidth, 1.5); xlabel(列); ylabel(行); title(训练后贪心策略路径回放);这段回放代码里有一个我特意留下的保护逻辑numel(path)超过状态数四倍就报错防止回放也陷入死循环。路径回放是验证训练效果的最后一关——曲线漂亮但路径盘绕大概率是回报函数鼓励了坏行为曲线一般但路径直接说明算法本身没问题只是超参没调透。6.3 我的习惯固定随机种子 commit 注释写清参数最后分享一个这几年养成的习惯它多次帮我避免了“自己坑自己”。第一每次跑实验前在脚本开头写rng(0)固定随机种子确保同一份代码两次运行结果一致。没有这一步你调参前后的差异可能是随机噪声造成的怎么调都觉得“没效果”。第二把每次实验用的 alpha、gamma、epsilon、episode 数记下来作为代码文件头部的注释块。第三把这个.m文件纳入源代码管理每一次提交到仓库时commit 注释里写明“这次改了 Q 表更新逻辑”还是“只调整了 gamma 从 0.9 到 0.95”——这点在机器学习代码里特别重要因为算法代码的每一次微调只有配合当时的参数和随机种子才能复现commit 注释是你给自己留的后悔药。我早年跑这份 doc 时因为偷懒没记录参数一周后想在原来的基础上把网格改大怎么都想不起来当时 0.8 的收敛效果是用 alpha0.1 还是 0.3 跑出来的。后来被迫用 git 重看历史版本翻了半天才对齐。现在每做一次实验我都会顺手在 commit 注释里写全超参哪怕只是改了一个数字。这套表格型 Q-Learning 跑通之后你再看 DQN、PPO 的 MATLAB 实现会发现很多结构都能对号入座——主循环、回放缓冲、策略更新无非是把 Q 表换成了神经网络核心思路一脉相承。希望帮到你。本文还有配套的精品资源点击获取