ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Q-Learning入门实战:16格网格世界MATLAB源码详解

Q-Learning入门实战:16格网格世界MATLAB源码详解

简介:这份资源面向强化学习入门者与需要动手复现经典算法的学习者,以一份带详细注释的 MATLAB 源码,解决 Q-Learning 从理论到代码落地的问题。压缩包共 1 个 doc 文件,约 33KB,内容为可直接运行的脚本与逐行中文注释,涵盖可移动矩阵 TT、奖赏矩阵 R、Q 矩阵初始化、折扣因子 gamma 与 epsilon-greedy 策略等核心模块。案例以 16×1 网格世界中王子寻找公主为场景,设置障碍物、陷阱与终点奖励,训练循环中随机初始化状态、按策略选动作并依据贝尔曼方程更新 Q 值,每 100 次迭代输出训练进度,结束后回溯最优路径。已有 1380 人学习,适合希望理解环境建模、奖励机制与策略选择完整流程的读者对照研读与二次实验。

1. 从一张 16 格地图说起:这套 Q-Learning 源码要解决什么

强化学习入门最难受的地方,不是公式看不懂,而是看完贝尔曼方程之后,仍然不知道第一行代码该写在哪。这份 Q-Learning 源代码及注释(matlab)就是冲着这个痛点来的:它把「王子找公主」抽象成一个 16×1 的网格世界,用不到一百行 MATLAB 把环境建模、奖励设计、ε-greedy 策略、Q 值更新、路径回溯五件事一次讲透。适合两类人——正在做强化学习课程设计、需要一份能跑通能讲清的最小闭环的学生,以及要给团队做算法入门培训、想要一个可改参数的 demo 的工程师。它不追求工程完备,追求的是每一步都能对上公式,注释密度高到可以当伪代码读。

2. 环境建模:用两个矩阵把网格世界说清楚

这套方案最值得抄的地方,是它没有引入任何工具箱,纯靠两个 16×16 矩阵就把「世界长什么样」和「走一步值多少」定义完了。理解这两个矩阵,后面所有代码都是水到渠成。

2.1 可移动矩阵 T:状态转移的硬约束

T(i,j)=1表示可以从位置 i 走到位置 j,0 表示走不通。构造逻辑是先用四个方向铺一遍邻接关系,再用障碍物和跳跃规则覆盖掉。

clear % 定义可移动矩阵 T T = zeros(16); % T(i,j)=0 表示不可从 i 位置移动至 j 位置,1 则为可移动 for i = 1:16 % 上行动作 N:不在第一行才能往上走 if not(ismember(i,[1,5,9,13])) T(i,i-1) = 1; end % 下行动作 S:不在最后一行才能往下走 if not(ismember(i,[4,8,12,16])) T(i,i+1) = 1; end % 左行动作 W:编号大于 4 才有左侧邻居 if i > 4 T(i,i-4) = 1; end % 右行动作 E:编号小于 13 才有右侧邻居 if i < 13 T(i,i+4) = 1; end end % 定义不可到达区域(障碍物) T(:,[10,11,14]) = 0; % 定义跳跃:13 号位只能跳到 15 号位 T(13,:) = 0; T(13,15) = 1;

这段代码的关键在于「先铺后砍」的顺序。先把 16 个格子按 4×4 排布的四邻接关系全部连上,再统一把第 10、11、14 列清零——这三列对应的是障碍格,任何状态都不能转移到它们。最后单独处理 13 号位:先清空它整行,再只保留T(13,15)=1,等于给这个格子开了一条单向传送通道。

这里有个容易忽略的细节:T(:,[10,11,14])=0清的是「列」,也就是禁止任何状态转移到 10、11、14;但 10、11、14 作为出发点的行并没有被清。实际运行时因为初始状态会主动避开这三个格子,所以不会出问题,但如果你的改造版本允许从障碍格出发,就得补一行T([10,11,14],:)=0。我一般会在注释里显式写明这个假设,避免后面接手的人踩坑。

2.2 奖赏矩阵 R:把「想让它做什么」翻译成数字

% 定义奖赏矩阵 R R = -1 * ones(16); R(:,6) = 0; % 第 6 列:中性区域,不奖不罚 R(:,7) = -5; % 第 7 列:陷阱,重罚 R(:,16) = 10; % 第 16 列:公主所在,终局大奖 R(13,15) = 5; % 13 跳到 15 的传送奖励

R的默认值是 -1,意味着每走一步都要付出代价,这会逼着智能体去找最短路径,而不是在地图里绕圈。第 6 列给 0 是留了一块「喘气区」,第 7 列 -5 是明确的陷阱信号,第 16 列 +10 是唯一的正终局奖励。注意R(13,15)=5这一行写在R(:,16)=10之后,顺序不能反,否则会被列赋值覆盖——这种赋值顺序问题在 MATLAB 里非常隐蔽,血泪经验是:凡是按列批量赋值之后还要改单点的,单点赋值一定放最后。

提示:奖励量级直接决定收敛速度。+10 与 -5 的比值约 2:1,配合 γ=0.8,大约 300~500 轮就能稳定;如果把陷阱改成 -50,前期探索会被吓住,收敛反而变慢。

3. 训练循环:ε-greedy 与贝尔曼更新的最小实现

环境定义完,剩下的就是让智能体在里面反复试错。这一章是整个方案的核心,也是答辩时最容易被追问的部分——为什么用这个更新式、ε 取多少、训练多少轮够。

3.1 参数初始化与探索策略

% 将 Q 矩阵初始化为 0 Q = zeros(16); % 折扣因子(γ) gamma = 0.8; % 策略选择概率,也就是探索率 epsilon = 0.3;

Q全零初始化代表「一无所知」,这是最朴素也最稳妥的做法。gamma=0.8表示未来一步的奖励打八折,两步打 0.64 折,这个值偏保守,适合 16 格这种小地图;地图一大就该往 0.9 以上调。epsilon=0.3意味着 30% 的动作是随机探索、70% 走当前最优,这个比例在入门 demo 里够用,但要注意它是个固定值——真实项目里通常做成随训练轮次衰减,比如从 0.9 线性降到 0.05,前期多探索、后期多利用。

for episode = 1:900 % 随机化初始状态 state = unidrnd(16); % 避开障碍物,直到状态不在 10、11、14 上 while ismember(state,[10,11,14]) state = unidrnd(16); end % 如果不是最终状态 while state ~= 16 % 找出 state 下所有可能到达的位置 possible_actions = find(T(state,:) == 1); possible_Q = Q(state,possible_actions); if rand() < epsilon % 小于探索率,随机选一个动作 choose = unidrnd(length(possible_actions)); action = possible_actions(choose); else % 否则选 Q 值最大的动作 [~,index] = max(possible_Q); action = possible_actions(index); end % 贝尔曼更新 Q(state,action) = R(state,action) + gamma * max(Q(action,:)); % 进入下一状态 state = action; end if mod(episode,100) == 0 fprintf('训练的次数为:%d\n',episode) end end

逐行拆一下。unidrnd(16)是 MATLAB 统计工具箱里的均匀整数采样,等价于randi(16);如果你的环境没装统计工具箱,直接换成randi就行,这是最常见的报错来源。内层while是标准的回合制:只要没到 16 号位就一直走,每一步先算可选动作集,再按 ε-greedy 二选一,最后用Q(s,a) = R(s,a) + γ·max Q(s',·)更新。

这里有个理论上的简化值得说清楚:标准 Q-Learning 的更新式是Q(s,a) ← Q(s,a) + α[r + γ·max Q(s',a') - Q(s,a)],带学习率 α。这份代码把 α 直接取成了 1,也就是完全用新估计覆盖旧值。对确定性环境、小状态空间来说这没问题,收敛还更快;但一旦环境有随机性(比如同样的动作有时成功有时失败),α=1 会导致 Q 值剧烈震荡,这时候必须把 α 降到 0.1~0.3 并保留旧值加权。答辩时如果被问到「你的学习率在哪」,这就是标准答案。

3.2 路径回溯:训练完之后怎么把路线读出来

start_0 = 1; % 设置起始点 path = start_0; % 路径向量,从起点开始 while path(end) ~= 16 [~,next] = max(Q(start_0,:)); % 当前状态下 Q 值最大的动作 path = [path,next]; % 追加到路径 start_0 = next; % 移动到下一状态 end disp(path)

训练结束后,Q 矩阵就是一张「每个格子往哪走最划算」的查询表。回溯时不需要再探索,直接每步取max(Q(state,:))对应的列号作为下一站,直到命中 16。这段代码有个隐含前提:Q里不能出现「某状态下所有动作 Q 值都相等」的死锁,否则max会返回第一个索引,可能原地打转。实际跑的时候如果发现路径长度异常(比如超过 20 步),先检查是不是某个状态的 Q 行全零——那说明这个状态在 900 轮里从没被访问到,属于探索不充分,把episode加到 2000 或把epsilon提到 0.4 通常能解决。

注意:path用[path,next]动态扩容,在 MATLAB 里会触发反复内存分配。16 格无所谓,但如果把地图扩到几百格,建议预分配path = zeros(1,100)再按索引写入,这是 MATLAB 性能优化里最老生常谈的一条。

4. 参数调优与改造:让这份 demo 撑起你的课程设计

原版代码能跑通,但直接交作业会显得单薄。真正拉开差距的是你能不能讲清楚「改哪个参数、为什么改、改了之后现象怎么变」。这一章给三组可验证的改造方向。

4.1 关键参数对照表

参数原值可调范围调大后的现象适用场景
gamma(γ)0.80.5~0.99更看重远期奖励,路径更短但收敛慢地图大、目标远
epsilon(ε)0.30.05~0.9探索变多,前期路径乱,后期更可能找到全局最优障碍复杂、易陷局部最优
episode(训练轮数)900300~5000收敛更稳,但边际收益递减状态空间扩大时同步加
陷阱奖励-5-1~-20绝对值越大越早学会绕开,过大则不敢探索陷阱密集时适度加大

这张表建议直接放进你的设计文档「实验与分析」一节,每改一个参数跑一次,把路径长度和收敛轮次记下来做成折线图,就是现成的实验数据。

4.2 把固定 ε 改成衰减 ε

固定探索率的问题是后期还在瞎走,浪费训练轮次。改成随回合衰减,收敛通常能快 20%~30%:

epsilon_start = 0.9; epsilon_end = 0.05; decay_episodes = 600; % 前 600 轮线性衰减 for episode = 1:900 % 线性衰减:前期多探索,后期多利用 if episode <= decay_episodes epsilon = epsilon_start - (epsilon_start - epsilon_end) * (episode / decay_episodes); else epsilon = epsilon_end; end % ... 后续训练逻辑不变 end

改动的核心是把epsilon从循环外的常量挪进循环内,每轮重算。decay_episodes取总轮数的 2/3 左右比较稳,太短会导致探索不足、Q 表覆盖不全,太长则后期还在随机游走。这个改动在答辩时很好讲:它对应的是「探索-利用困境」这个强化学习的核心矛盾,比单纯说「我调了个参数」有说服力得多。

4.3 从 16 格扩到 N×N 网格

如果课程设计要求更大的地图,T的构造逻辑要泛化。核心是把「编号」换成「行列坐标」再映射回编号:

N = 6; % 6x6 网格 n_states = N * N; T = zeros(n_states); for r = 1:N for c = 1:N idx = (r-1)*N + c; % 行列转编号 if r > 1, T(idx, idx-N) = 1; end % 上 if r < N, T(idx, idx+N) = 1; end % 下 if c > 1, T(idx, idx-1) = 1; end % 左 if c < N, T(idx, idx+1) = 1; end % 右 end end % 之后同样用列清零的方式挖障碍、设终点

这段改造的价值在于它把「硬编码 16」变成了「参数化 N」,你的设计文档里就可以写「本方案支持任意 N×N 网格扩展,仅需修改 N 与障碍列索引」。注意编号规则必须和原版保持一致(行优先、从上到下、从左到右),否则路径回溯那段max(Q(start_0,:))的语义会错位。

5. 避坑与排查:这份代码最容易翻车的五个地方

跑不通的时候别急着怀疑算法,九成问题出在环境配置和矩阵语义上。下面五条按出现频率排序,每条都给出「现象 → 原因 → 解决」。

现象一:报错Undefined function 'unidrnd'。原因:unidrnd属于 Statistics and Machine Learning Toolbox,纯净版 MATLAB 或 Octave 里没有。解决:全局替换为randi,unidrnd(16)改成randi(16),unidrnd(length(possible_actions))改成randi(length(possible_actions)),语义完全等价。

现象二:训练跑完,回溯出的路径在原地打转或长度爆炸。原因:某个状态的 Q 行全零,max返回索引 1,导致来回跳。解决:训练后加一句sum(Q,2)检查哪些行全零,对全零状态说明探索没覆盖到,把episode提到 2000 以上,或把epsilon临时调到 0.5 重跑。

现象三:路径能到终点,但明显绕远,不走近路。原因:gamma偏小或训练轮数不足,远期奖励被折扣吃掉,智能体学成了「贪心短视」。解决:先把gamma从 0.8 提到 0.9 试一次,再观察 300 轮和 900 轮的路径差异;如果 900 轮还在变短,说明没收敛,加轮数。

现象四:改了奖励矩阵后,智能体宁愿踩陷阱也不去终点。原因:赋值顺序错了,R(:,16)=10被后面的列赋值覆盖,或者陷阱惩罚绝对值远大于终点奖励。解决:检查R的赋值顺序,单点赋值永远放最后;同时保证终点奖励绝对值大于单步惩罚,否则「活着扣分」会压过「到达得分」。

现象五:把代码贴进 Word 交作业,中文注释全变乱码。原因:MATLAB 默认编码与文档编码不一致。解决:.m文件统一存成 UTF-8,Word 里粘贴时选「只保留文本」再重新设字体;更稳的做法是代码用等宽字体单独排版,注释另起一行写,别混排。

提示:这五条里前两条占了实际求助的绝大多数。我一般会在代码开头加一段「运行前检查」注释,把工具箱依赖和编码要求写死,省得每次换机器重踩一遍。

6. 验收与答辩:怎么证明这套 Q-Learning 真的学到了东西

代码能跑只是及格线,验收要看的是「学习曲线」和「策略合理性」两个证据。我的习惯是准备三样东西:一张收敛曲线、一张路径对比图、一份参数敏感性表。

收敛曲线用每 100 轮记录一次「本回合步数」或「累计奖励」,画出来应该看到前期波动大、后期趋于平稳。如果曲线一直抖,说明epsilon太大或alpha该降;如果曲线早早躺平但路径不是最优,说明陷入了局部最优,得加探索。路径对比图更直观:把训练 100 轮、300 轮、900 轮各自回溯出的路径画在同一张 4×4 网格上,能清楚看到「从乱走到走最短」的演化过程,这张图放进答辩 PPT 比任何文字都有说服力。

参数敏感性表就按第 4 章那张表来填,每个参数取三档,记录收敛轮次和最终路径长度。答辩时被问「为什么 γ 取 0.8」,你可以直接指着表说「0.8 时 420 轮收敛、路径 6 步,0.95 时 780 轮收敛、路径 5 步,综合考虑选了 0.8」——有数据支撑的回答和「书上这么写的」是两种待遇。

还有一个容易被忽略的验收点:策略的确定性。训练好的 Q 表应该是确定的,同一个起点跑十次回溯,路径必须完全一致。如果每次不一样,说明你的回溯逻辑里混进了随机探索,检查是不是误把训练循环里的 ε-greedy 带进了回溯段。这个检查花不了一分钟,但能挡掉「结果不可复现」这种致命质疑。

最后说个我自己的教训。早年做第一个强化学习课设,我把所有精力花在调参上,结果答辩时被问「你的奖励函数为什么这么设计」,当场卡壳——因为我只是照抄了示例,从没想过 -1 和 -5 的比值意味着什么。从那以后我每次交方案前,都强制自己用一句话解释每个超参数的物理含义,解释不通的就回去重读代码。这套 16 格的 Q-Learning 源码正好适合练这个习惯:格子少、逻辑短,每个数字都能追到源头。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表