
简介面向强化学习入门者与MATLAB初学者的一份Q-Learning教学示例文档以王子在16×1网格世界中寻找公主的游戏为背景演示无模型决策的完整实现思路。文档围绕可移动矩阵、奖赏矩阵与Q矩阵的构建展开详细说明障碍物与陷阱位置、正负奖励分配、折扣因子和epsilon-greedy探索策略的设置方式并给出训练循环中按贝尔曼方程更新Q值、每100次迭代输出训练进度、结束后从起点回溯最优路径的代码与注释。包内共1个doc文件约33KB篇幅紧凑适合对照代码理解状态、动作、奖励和策略更新之间的对应关系读者可据此掌握Q-Learning的基本流程并迁移到更复杂的格子世界或路径规划实验。已有1380人学习可作为强化学习课程实验与算法复现的参考材料。1. 十六个格子与一张 16×16 的全零矩阵Q-Learning 凭什么不建环境模型十六个格子、一张 16×16 的全零矩阵、900 轮循环就能把从任意起点到终点的路径学出来 —— 强化学习里最反直觉的一点是Q-Learning 全程不需要知道「世界的规则」它只要在每次转移后拿到一个数字。这份 MATLAB 实现把整件事压在几十行里可移动矩阵T描述图结构奖赏矩阵R描述转移的代价Q 表记录「在某个位置选择某个下一步值多少」。值得搞懂的不是这几行怎么写而是为什么「只记录、不建模」的表格法能收敛以及这套写法里藏着多少个改一个参数就会翻车的边界。读懂它等于把马尔可夫决策过程、贝尔曼方程、ε-greedy 这三样东西从公式变成了可以被 print 出来的数字。2. 环境建模T 与 R 两张矩阵怎样把「世界规则」压成查表在写第一行训练代码之前这份实现先用两个for和一个if把整个世界的物理规则写死了。这一步看起来最枯燥但它决定了后面所有 Q 值能不能被正确传播环境建模错了训练循环再标准也只会收敛到一个错误的不动点。2.1 列优先拉直为什么 ±1 判余数、±4 判区间摘要里把这个环境叫「16×1 的网格世界」从编号上看确实是一条 16 长的线性序列但相邻关系并不是简单的i±1。代码里同时用了i-1、i1、i-4、i4四种偏移这其实是一个 4×4 网格按列优先拉直后的结果编号 1、2、3、4 是第一列的四个行1、5、9、13 是第一行的四个列。clear % 定义可移动矩阵T(i,j)1 表示可以从 i 直接移动到 j TT zeros(16); % 注意这个变量后面从未被使用 T zeros(16); for i 1:16 % 行方向i-1 向上i1 向下 if ~ismember(i, [1,5,9,13]) % 不在第一行才能向上 T(i, i-1) 1; end if ~ismember(i, [4,8,12,16]) % 不在第四行才能向下 T(i, i1) 1; end % 列方向i-4 向左i4 向右 if i 4 % 不在第一列 T(i, i-4) 1; end if i 13 % 不在第四列 T(i, i4) 1; end end这段代码最值得琢磨的地方是两个方向的边界判断用了两套完全不同的写法。行边界写成ismember(i,[1,5,9,13])这样的离散集合因为按列优先拉直后同一行的编号模 4 同余边界就是一个 4 元素集合列边界写成i4和i13这样的区间判断因为列号等于floor((i-1)/4)1边界自然退化成区间。同一个 4×4 网格、同一套动作语义落到线性编号上却必须用两种表达 —— 这是「把二维结构压成一维索引」的必然代价。如果有人把这套编号改成行优先ismember里的四个数字和i4、i13三个区间会全部失效而 MATLAB 不会报错它只会安静地生成一张错误的邻接矩阵这就是后面所有问题的源头。2.2 T 矩阵是邻接表不是物理空间把障碍和跳跃两段加进来之后T的性质就彻底变了% 禁止进入 10、11、14 三个格子把这三列全部清零 T(:, [10,11,14]) 0; % 13 号格子清空所有出边只保留一条通往 15 的跳跃边 T(13, :) 0; T(13, 15) 1;T(:,[10,11,14])0清的是列语义是「任何状态都不允许转移到 10、11、14」它没有清行也就是说 10、11、14 这三个格子自己的出边仍然存在。这在逻辑上没问题因为进不去就出不来但如果初始状态采样漏掉了过滤agent 就会从一个「不存在的房间」里出发。T(13,:)0之后再T(13,15)1这是整份代码里最有意思的三行。13 号格子原本只有一条出边13→9清空后再单独指定 13→15等于把它改造成一个只能单向传送的门。这条边在几何上完全不成立 —— 13 和 15 在网格里隔着障碍但T从来不是几何结构它是马尔可夫决策过程里的转移关系。只要建模者认为「站在 13 号格可以跳到 15」这条边就合法Q-Learning 也照单全收。这正是表格法建模的自由度也是它容易被滥用的地方你可以任意添加非局部边代价是这张表从此无法用坐标解释。把T里所有 1 数一遍行方向 12 条无向边24 个 1列方向 12 条无向边24 个 1共 48 个清掉第 10、11、14 三列的入边减 11 个清掉 13 的行再减 1 个最后加上 13→15 补 1 个结果是37 条有效转移。16×16256 个格子里只有 37 个会被写入填充率 14.5%。这个数字后面还会用到。2.3 R 矩阵把奖励挂在转移上不挂在状态上R -1 * ones(16); % 默认每走一步付出 1 的代价 R(:, 6) 0; % 进入 6 号格不加不扣 R(:, 7) -5; % 进入 7 号格是陷阱 R(:, 16) 10; % 进入 16 号格拿到目标奖励 R(13, 15) 5; % 从 13 跳到 15 额外给 5形状是 16×16不是 16×4。这个尺寸暴露了整份实现最核心的设计选择动作被编码成了「下一个状态」。因为环境是确定性的 —— 在状态s选择动作a后继状态s唯一确定 —— 所以完全没必要维护一张「动作编号 → 后继状态」的映射表直接用s当动作的下标就行。R(state, action)读作「从state走到action这个格子立刻拿到多少奖励」Q(state, action)同理。这么做的收益是省掉一层间接寻址代码里每一步都是直接的矩阵下标代价是动作空间和状态空间被强行绑定成同一套编号Q必须是 16×16 而不是 16×4。一旦环境变成随机转移同一个动作可能落到不同后继这套编码立刻失效 —— 那时R就不再是矩阵而是一个三维张量Q(s,a)也不能再用后继状态当下标。理解这一点就理解了为什么这份代码只能处理确定性迷宫。奖励的挂法也值得说一句。R(:,16)10用的是整列赋值意思是「任何状态只要进入 16 都拿 10」这是把奖励挂在转移上而不是挂在状态上而R(:,7)-5让 7 号格成为全局陷阱无论你从哪来。默认的 -1 是步数惩罚本文还有配套的精品资源点击获取