Q-Learning 源代码及注释(matlab):从零实现强化学习入门
简介这是一份面向强化学习初学者与算法实践者的Q-Learning入门资料以Matlab源代码配合详细注释的形式帮助读者理解无模型决策算法的完整实现流程。资源包内共1个doc文档大小约33KB内容涵盖可移动矩阵TT、奖赏矩阵R、Q矩阵初始化、折扣因子gamma与epsilon-greedy策略等核心代码段并附有逐行中文注释。案例以王子寻找公主的16x1网格游戏为背景演示了障碍物规避、正负奖励设置、贝尔曼方程更新Q值以及训练结束后规划最优路径的全过程训练循环每100次迭代输出一次进度。目前已有1378人学习下载适合希望从零掌握强化学习基础、需要可运行代码对照练习的读者通过这份注释完整的脚本可快速理解状态动作定义、奖励机制与策略选择之间的配合关系。1. Q-Learning 源代码及注释(matlab).doc从一份老文档里挖出能跑通的强化学习骨架很多人第一次接触强化学习不是从 PyTorch 的 DQN 教程开始而是从一份流传多年的Q-Learning 源代码及注释(matlab).doc开始的。这份文档的典型结构是一段几十行的 MATLAB 主脚本加上逐行中文注释配一个简单的格子世界Grid World环境最后画出一条从起点到终点的路径图。它解决的问题非常具体——让一个智能体在不知道环境规则的前提下通过反复试错学会走迷宫。适合谁适合刚学完马尔可夫决策过程、想亲手看到 Q 表被填满的本科生和转行工程师也适合需要给学生演示「强化学习到底在算什么」的授课者。它不解决高维状态、连续动作、深度网络这些问题但它是理解 Bellman 方程最省事的一块敲门砖。下面我按「先立住理论、再复现代码、最后讲坑」的顺序把这份文档拆成能直接抄作业的实战笔记。2. Q-Learning 的数学骨架与 MATLAB 选型理由2.1 从 Bellman 最优方程到 Q 表更新公式Q-Learning 的核心只有一行更新式但这一行背后是完整的动态规划思想。定义状态s、动作a、即时奖励r、折扣因子γ、学习率αQ 值表示「在状态 s 下执行动作 a 之后按最优策略继续走能拿到的期望累计回报」。它的更新规则是Q(s,a) ← Q(s,a) α · [ r γ · max_a Q(s,a) − Q(s,a) ]方括号里那一项叫 TD 误差Temporal Difference Error。注意max_a Q(s,a)用的是下一状态的最大 Q 值而不是实际执行动作后的 Q 值——这正是 Q-Learning 属于 off-policy 的原因它学的是最优策略哪怕当前行为策略还在随机乱走。理解这一点后面调参时才知道为什么ε可以慢慢降、而α不能乱设。在 MATLAB 里Q 表就是一个二维矩阵行是状态编号列是动作编号。格子世界有 16 个格子就开16×4的矩阵动作是上下左右四个方向。这种「状态离散、动作离散」的设定让整份代码不需要任何工具箱纯基础语法就能跑这也是它当年被大量课程选作教学案例的原因。2.2 为什么用 MATLAB 而不是 Python 复现现在主流当然是用 Python但 MATLAB 在这类教学场景里有三个现实优势。第一矩阵操作是原生语法Q(state,:)直接取一行、max(Q(next_state,:))直接取最大值不需要 import numpy对没写过代码的学生门槛极低。第二绘图是内置的plot、scatter、rectangle几行就能把格子世界和路径画出来省掉 matplotlib 的配置成本。第三脚本即文件双击就能运行不需要配虚拟环境、装依赖这对实验室机房里装不了新软件的电脑很关键。当然代价也明显没有向量化环境并行、没有 GPU、跑大状态空间会慢到怀疑人生。所以我的建议是——用 MATLAB 这份代码理解算法理解完立刻用 Python 重写一遍做扩展。下面给出可复现的 MATLAB 版本逻辑和那份文档一致但把注释补全、把随机种子固定方便你对照结果。2.3 最小可复现的 MATLAB 实现先定义环境。用一个 4×4 的格子世界起点在左上角终点在右下角中间放一堵墙。状态编号按行优先从 1 到 16。% gridworld_qlearning.m % 4x4 格子世界起点(1,1)终点(4,4)障碍在(2,2)和(3,2) clear; clc; rng(42); % 固定随机种子保证结果可复现 nRows 4; nCols 4; nStates nRows * nCols; % 16 个状态 nActions 4; % 1上 2下 3左 4右 Q zeros(nStates, nActions); % Q 表初始化全 0 alpha 0.1; % 学习率 gamma 0.95; % 折扣因子 epsilon 0.9; % 初始探索率 epsilonMin 0.01; epsilonDecay 0.995; nEpisodes 500; % 训练回合数 % 障碍与终点 obstacles [6, 10]; % 状态编号(2,2)6, (3,2)10 goalState 16; reward -ones(nStates, 1); % 每走一步 -1鼓励最短路径 reward(goalState) 10; % 到达终点给正奖励这段初始化的关键在奖励设计。每步给-1而不是0是为了让智能体有动力尽快结束回合如果每步给 0、终点给 1在折扣因子小于 1 时绕远路和走直线的累计回报差距会被压缩收敛会变慢。这是很多人照抄代码后「怎么都学不会」的第一个隐藏原因。接下来是状态转移函数。给定当前状态和动作返回下一个状态和是否撞墙。function [nextState, hitWall] step(state, action, nRows, nCols, obstacles) hitWall false; r ceil(state / nCols); % 当前行 c mod(state - 1, nCols) 1;% 当前列 switch action case 1, r r - 1; % 上 case 2, r r 1; % 下 case 3, c c - 1; % 左 case 4, c c 1; % 右 end % 越界或撞障碍都视为原地不动 if r 1 || r nRows || c 1 || c nCols nextState state; hitWall true; return; end nextState (r - 1) * nCols c; if ismember(nextState, obstacles) nextState state; hitWall true; end end这里有个容易忽略的细节撞墙时nextState返回原状态而不是报错或跳到别处。这样智能体会在墙边反复拿负奖励Q 值自然被压低最终学会避开。如果你把撞墙处理成「回合终止」智能体会把撞墙当成一种逃避负奖励的手段反而主动撞墙这是血泪经验。主训练循环如下for ep 1:nEpisodes state 1; % 每回合从起点开始 steps 0; while state ~ goalState steps 200 % ε-贪婪策略选动作 if rand epsilon action randi(nActions); else [~, action] max(Q(state, :)); end [nextState, ~] step(state, action, nRows, nCols, obstacles); % Q-Learning 更新 tdTarget reward(nextState) gamma * max(Q(nextState, :)); Q(state, action) Q(state, action) alpha * (tdTarget - Q(state, action)); state nextState; steps steps 1; end epsilon max(epsilonMin, epsilon * epsilonDecay); endsteps 200是防止智能体在早期随机策略下无限打转属于工程上的保险丝。epsilon每回合衰减一次从 0.9 降到 0.01前期多探索、后期多利用。训练完把 Q 表按最大动作走一遍就能画出路径。2.4 参数怎么设α、γ、ε 的取值边界参数常用范围设太大设太小α 学习率0.05 ~ 0.3Q 值震荡不收敛收敛极慢几百回合没动静γ 折扣因子0.9 ~ 0.99远期奖励权重过高短路径学不出来只看眼前绕路ε 初始探索率0.8 ~ 1.0前期几乎纯随机浪费回合探索不足卡在局部最优ε 衰减0.99 ~ 0.999衰减太快没探索够就贪心长期随机Q 表学不精我的习惯是 α 取 0.1、γ 取 0.95、ε 从 0.9 按 0.995 衰减500 回合内基本能稳定收敛到最短路径。如果换成更大的格子比如 10×10回合数要提到 2000 以上或者把 α 调到 0.2 加速。3. 把文档里的代码跑起来环境搭建与逐段调试3.1 文件组织与运行方式那份文档通常把所有代码塞在一个脚本里函数定义放在文件末尾。MATLAB 从 R2016b 开始支持脚本内定义局部函数但如果你用的是更老的版本必须把step单独存成step.m。我一般这样组织qlearning_demo/ ├── gridworld_qlearning.m % 主脚本 ├── step.m % 状态转移函数老版本需要 └── draw_path.m % 路径可视化运行方式就是cd到目录命令行敲gridworld_qlearning或者直接点编辑器上方的运行按钮。如果报Undefined function step八成是版本问题把函数拆出去即可。3.2 用断点验证 Q 表是否真的在更新新手最容易犯的错是「跑完了但不知道对不对」。我的做法是在主循环里加一个断点观察 Q 表的变化。具体操作在Q(state, action) ...这一行左侧点一下设断点运行后按 F10 单步看工作区里Q矩阵的值。前几个回合应该大部分是负数或零几十回合后终点附近的状态比如 11、12、15会先出现正值然后像水波一样往起点扩散。如果跑了 100 回合 Q 表还是全零检查三件事奖励是不是全设成了 0、gamma是不是设成了 0、更新式里max是不是写成了min。3.3 可视化路径与收敛曲线训练完加一段绘图把最优路径和每回合步数画出来一眼就能判断学没学会。% 按贪心策略回溯路径 path 1; state 1; while state ~ goalState length(path) 50 [~, action] max(Q(state, :)); [state, ~] step(state, action, nRows, nCols, obstacles); path(end1) state; %#okAGROW end fprintf(最优路径长度%d 步\n, length(path) - 1); % 画格子与路径 figure; hold on; axis equal; axis([0 nCols 0 nRows]); for s 1:nStates r ceil(s / nCols); c mod(s - 1, nCols) 1; if ismember(s, obstacles) fill([c-1 c c c-1], [nRows-r nRows-r nRows-r1 nRows-r1], k); elseif s goalState fill([c-1 c c c-1], [nRows-r nRows-r nRows-r1 nRows-r1], g); end end [pr, pc] ind2sub([nRows nCols], path); plot(pc - 0.5, nRows - pr 0.5, r-o, LineWidth, 2); title(Q-Learning 学到的最优路径);ind2sub把状态编号还原成行列坐标pc - 0.5是为了让点落在格子中心。如果画出来的路径穿墙说明step函数里的障碍判断写错了重点检查ismember那行。3.4 收敛曲线判断训练是否到位光看路径不够还要看每回合步数是否稳定。在循环里记录steps训练完画出来stepsHistory zeros(nEpisodes, 1); % 在 while 循环结束后加stepsHistory(ep) steps; figure; plot(stepsHistory); xlabel(回合); ylabel(步数); title(每回合步数收敛曲线); grid on;健康的曲线是前期剧烈波动几十到上百步中后期快速下降并稳定在最短路径长度附近。如果曲线一直高位震荡不下降优先怀疑epsilon衰减太慢或alpha太小如果曲线早早掉到很低但路径不是最短多半是陷入了局部最优把epsilonMin调大一点、让它保留一点探索。4. 避坑与排查Q-Learning 在 MATLAB 里最容易翻车的五个地方4.1 现象Q 表数值爆炸到 Inf 或 NaN原因通常是奖励设得过大加上gamma接近 1TD 目标被反复放大。比如终点奖励设成 1000、gamma设 0.99几十步之后gamma^n虽然衰减但中间状态的 Q 值会先冲高。解决把奖励控制在[-1, 10]这种量级或者把alpha降到 0.05 以下。另外检查reward是不是被误设成了全局变量在别处被改。4.2 现象智能体学会绕圈而不去终点这是典型的「负奖励陷阱」。如果每步奖励是 0、只有终点是正奖励且gamma偏小智能体可能发现「原地不动」的累计回报和「走远路」差不多于是选择最省事的原地打转。解决每步给一个小的负奖励-1 或 -0.1让时间本身有成本。同时确认steps上限不要设得太大否则绕圈也能撑满回合。4.3 现象每次运行结果都不一样无法复现MATLAB 默认的随机数生成器每次启动都不同。解决脚本开头加rng(42)固定种子。注意randi、rand都受这个种子控制。如果你在并行环境或parfor里跑种子管理会更复杂教学场景直接串行就好。4.4 现象状态编号和行列坐标对不上格子世界的状态编号有行优先和列优先两种约定那份文档里如果没写清楚很容易在step和绘图之间用错。行优先是state (r-1)*nCols c列优先是state (c-1)*nRows r。解决全篇统一一种并在注释里写明。我一般用行优先因为和矩阵的sub2ind默认行为一致。4.5 现象训练几百回合后 Q 表还是全零先确认更新那行真的被执行了——在循环里加disp(Q(1,:))打印第一行。如果一直是零检查tdTarget的计算里reward(nextState)是不是取错了索引应该用下一状态的奖励不是当前状态。另一个隐蔽原因是max(Q(nextState, :))在nextState是终点时返回的是终点那一行的最大值如果终点行没被更新过就是 0这本身没错但要确保终点状态至少被访问过一次。5. 从 Q 表到深度 Q 网络这份代码还能怎么往下走把这份 MATLAB 代码吃透之后最自然的进阶是把它翻译成 Python 并用神经网络替换 Q 表。但在此之前我建议先在这份代码上做两个小实验成本极低、收益很大。第一个实验是改状态表示。把 4×4 扩到 10×10观察 Q 表大小从 16×4 涨到 100×4训练回合数需要多少才能收敛。你会直观感受到「表格法随状态数线性膨胀」这件事这正是深度 Q 网络存在的理由——用函数逼近代替查表。第二个实验是把epsilon的衰减策略从乘法改成线性对比收敛曲线理解探索调度对最终策略的影响。如果要做 Python 版本映射关系是这样的Q矩阵换成nn.Linear(state_dim, action_dim)max(Q(next_state,:))换成target_net(next_state).max()rng换成torch.manual_seed。但注意Python 版必须加经验回放Replay Buffer和目标网络Target Network否则在连续状态空间下训练极不稳定。这两个机制在表格法里不需要因为表格法每次更新只影响一个格子不会像神经网络那样「改一个参数影响所有状态」。验证方法上我习惯用两个指标卡住一是训练曲线是否收敛到理论最短步数二是把epsilon设成 0 跑 100 次测试看成功率是否 100%。如果测试成功率低于训练时的表现说明过拟合了训练路径需要增大探索或增加环境随机性。最后说个我自己的教训。早年我照着这类文档改代码为了让收敛快把alpha调到 0.5、gamma调到 0.99结果 Q 值在正负之间反复横跳画出来的路径每次都不一样我以为是随机种子的问题折腾了一整天才发现是学习率过大导致 TD 误差被过度放大。后来我养成一个习惯任何 Q-Learning 实现先把alpha固定在 0.1、gamma固定在 0.95跑通最短路径之后再动参数。这个顺序能省掉大量玄学调试。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

从跑工具到真正看懂漏洞:网络安全转行的进阶路线

从跑工具到真正看懂漏洞:网络安全转行的进阶路线

很多人转行网络安全,都是从“跑工具”开始的。拿到扫描器,对着目标跑一遍,看到高危漏洞的图标跳出来,截图、复制描述、粘贴到报告里,整套流程好像就结束了。但做了半年、一年之后,你大概率会遇到一种很具体…

2026/10/15 21:45:58 阅读更多 →
UDP局域网远程控制:关机、重启与音量调节实战

UDP局域网远程控制:关机、重启与音量调节实战

简介:这是一份面向局域网远程管理场景的实用工具资源,适合需要在同一网络内对多台电脑进行关机、重启及音量调节的运维人员或技术爱好者。资源围绕UDP无连接协议实现指令下发,通过构造包含目标IP、端口与操作命令的数据包,由目标机…

2026/10/15 21:45:58 阅读更多 →
树结构在实际项目中的应用场景与选型指南

树结构在实际项目中的应用场景与选型指南

1. 树结构到底能解决哪些实际问题很多人学数据结构的时候,学到树这一章就开始犯迷糊。数组和链表好歹能直观地看到数据排成一排,树这种一个节点分叉出好几个子节点的结构,到底在真实项目里能干什么?我刚开始写代码那几年也有这个困…

2026/10/15 21:45:58 阅读更多 →

最新新闻

LeetDown 完整指南:在 macOS 上把 A6/A7 设备降级到 OTA 签名固件(兼容性、使用流程与源码构建)

LeetDown 完整指南:在 macOS 上把 A6/A7 设备降级到 OTA 签名固件(兼容性、使用流程与源码构建)

桌面应用逆向工程固件 【免费下载链接】LeetDown a macOS app that downgrades A6 and A7 iDevices to OTA signed firmwares 项目地址: https://gitcode.com/gh_mirrors/le/LeetDown 点击查看 免费下载 LeetDown 是一款面向 macOS 的 GUI 工具,专门用于…

2026/10/16 3:38:06 阅读更多 →
OpenHarmony适配Flutter测试数据生成库:自动化测试与工程化实践

OpenHarmony适配Flutter测试数据生成库:自动化测试与工程化实践

1. 开场必须先把定位说清楚:flutter_data_generator 到底解决什么问题如果你所在团队把 Flutter 端的测试数据生产固定成了一套研发脚手架,然后某一天接到一个需求:让这套脚手架跑在 OpenHarmony 设备上,你的第一反应大概率不是写…

2026/10/16 3:38:06 阅读更多 →
从抖振到记忆加权:分数阶滑模控制中 α 阶次的机理与 Simulink 落地

从抖振到记忆加权:分数阶滑模控制中 α 阶次的机理与 Simulink 落地

简介:分数阶滑模控制(FOSMC)结合分数阶微积分与滑模控制理论,适用于非线性、时变及不确定性系统的鲁棒控制,是自动化与控制领域的重要研究方向。面向控制专业学生、科研人员及工程开发者,这份MATLAB/Simuli…

2026/10/16 3:38:06 阅读更多 →
LibreCAD入门指南:轻量级开源2D CAD核心原理与适用边界

LibreCAD入门指南:轻量级开源2D CAD核心原理与适用边界

简介:LibreCAD是一款面向设计师、工程师及CAD初学者的开源跨平台2D计算机辅助设计软件,解决轻量级专业绘图、DWG/DXF文件兼容编辑与多系统部署需求,特别适用于教学实训、个人项目制图及中小企业低成本CAD替代方案。资源包为官方源码或便携版安…

2026/10/16 3:38:06 阅读更多 →
用PyTorch从零实现SRCNN:图像超分入门实战与避坑指南

用PyTorch从零实现SRCNN:图像超分入门实战与避坑指南

简介:一套PyTorch实现的SRCNN图像超分资源,专为刚接触超分辨率或深度学习的初学者设计,目标是用一个可直接运行的工程讲清经典超分模型的完整流程。资源共28个文件,压缩包74.6MB,主体包含6个Python源码(模型…

2026/10/16 3:38:06 阅读更多 →
构建主动预警体系:网络安全检测方法、工具与落地路线图

构建主动预警体系:网络安全检测方法、工具与落地路线图

做安全的都懂,网络攻防从来不是比谁买的设备多,而是比谁能更快发现“已经发生但还没暴露”的入侵。我见过太多团队,防火墙、杀软、态势感知上了一整套,结果关键时刻还是靠运维偶然登录服务器才发现问题。根源在于他们把网络安全检…

2026/10/16 3:37:06 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →