资讯动态

Q Learning最小实现:300行代码带你入门强化学习

发布时间:2026/10/21 5:17:11 来源:尧图企业网站定制
简介压缩包提供了一套基于Python的Q Learning强化学习完整实现适合正在学习强化学习、需要参考可运行代码的开发者。资源以Python脚本作为核心代码配合README说明、PDF报告与许可证文件共5个文件整体仅852KB轻量易部署。代码演示了Q表初始化、epsilon-greedy动作选择、基于学习率与折扣因子的Q值更新以及多episode训练收敛过程借助gym环境如CartPole-v1可直接观察智能体策略优化效果。PDF报告对算法原理、更新公式和实验流程做了梳理可与代码逐段对照帮助理解时序差分思想与探索-利用平衡机制。压缩包内还包含状态表示相关的辅助说明便于调试时理清数据结构与训练循环。已有1110人学习下载适合作为课程作业、算法入门或二次开发的基础参考。1. 项目定位一套能直接跑的Q Learning最小实现很多朋友第一次接触Q Learning最大的感觉是“看得懂公式写不出代码”。网上动不动就是几千行的大项目装了OpenAI Gym、PyTorch、TensorFlow还没跑起来环境先崩三次。这套源码不一样它只依赖Python标准库加上NumPy和Matplotlib核心代码不超过300行却把Q Learning的完整学习闭环——环境交互、Q表更新、策略改进、结果可视化——全部串起来了。你拿到手之后不需要理解贝尔曼方程只要会跑脚本就能看到智能体在网格世界里从随机乱走变成找到最优路径。这套代码最适合两类人一是刚入门强化学习、想亲眼确认Q Learning确实能学会走迷宫的学生党二是想在现有项目里快速加一个“基于表格的决策模块”来做验证的开发者改改环境参数就能套到自己的模拟场景里。2. 技术选型与目录结构不装框架也能上手的极简设计2.1 依赖栈Python、NumPy、Matplotlib这份代码的依赖极精简运行环境只需要Python 3.6以上第三方库只有NumPy和Matplotlib。作者刻意没有采用OpenAI Gym而是自己写了一个网格世界环境类好处是你能直接看到环境如何与智能体交互不会被Gym的接口封装遮住视线。对想二次开发的人来说替换环境逻辑反而比在Gym的wrapper里绕来绕去更直接。整份源码的物理文件结构我拉了一下大概是这样qlearning_demo/ ├── environment.py # 网格世界环境定义 ├── agent.py # Q Learning智能体核心逻辑 ├── train.py # 训练主脚本含可视化 └── requirements.txt # 依赖声明requirements.txt里只有两行numpy和matplotlib。environment.py负责“世界规则”agent.py负责“决策大脑”train.py把两者拼起来。这种划分很教科书但很清晰——你改环境不用碰智能体改智能体不用碰界面。2.2 文件清单每个文件在做什么environment.py定义状态空间、动作空间、状态转移规则、奖励函数。默认是一个4x4的网格左上角起点右下角终点中间没有障碍物。你也可以手动在网格里添加“陷阱”格子踩上去会有惩罚。agent.py实现Q表的初始化、epsilon贪心策略选择动作、Q值更新。所有强化学习的“大脑”都浓缩在这里大约80行。train.py设置超参数学习率、折扣因子、epsilon衰减系数初始化环境与智能体跑N个episode并实时绘制每个episode的总奖励曲线和当前策略路径。requirements.txt环境依赖声明装包用的。从工程角度看这份代码麻雀虽小、五脏俱全。你甚至可以直接把agent.py里的Q学习类复制到另一个项目里只要环境提供step(action)和reset()两个方法就能用接口约定非常轻量。对于只想快速验证“Q Learning能不能解决我的问题”的人来说这种设计比动不动就抽象出BaseEnv基类好得多。3. 核心模块拆解Q值更新与动作选择代码逐段说透3.1 环境类构建一个简易网格世界打开environment.py全网核心是一个GridWorld类。构造方法里定义网格大小、起点终点坐标、障碍物位置。关键代码长这样class GridWorld: def __init__(self, size4, start(0,0), goal(3,3), obstacles()): self.size size self.start start self.goal goal self.obstacles set(obstacles) self.state start def reset(self): self.state self.start return self.state def step(self, action): # action: 0up, 1down, 2left, 3right row, col self.state if action 0: row max(0, row - 1) elif action 1: row min(self.size - 1, row 1) elif action 2: col max(0, col - 1) elif action 3: col min(self.size - 1, col 1) self.state (row, col) if self.state self.goal: return self.state, 1.0, True # 到达终点奖励1 elif self.state in self.obstacles: return self.state, -1.0, True # 踩到陷阱惩罚并终止 else: return self.state, 0.0, False # 平常走一步没奖励逻辑说明step方法接收动作索引只修改当前坐标。边界用max/min钳制防止越界。奖励设计很简单——到达终点给1踩陷阱给-1普通移动是0。done标志决定是否结束本episode。参数方面size控制网格边长start/gool/obstacles都能直接改。这种设计不加任何花哨功能但做实验够用。你可以把obstacles改成比如((1,1),(2,2))立刻多出一个迷宫玩法。3.2 智能体类Q表初始化与epsilon贪心策略agent.py里的QLearningAgent类才是整个项目的灵魂。构造函数里初始化Q表为一个全零数组维度是“状态数 × 动作数”。对于4x4网格状态数是16动作数是4所以Q表就是16行4列的矩阵。代码import numpy as np class QLearningAgent: def __init__(self, state_size, action_size, alpha0.1, gamma0.9, epsilon1.0, epsilon_decay0.995, epsilon_min0.01): self.state_size state_size self.action_size action_size self.alpha alpha # 学习率 self.gamma gamma # 折扣因子 self.epsilon epsilon # 初始探索率 self.epsilon_decay epsilon_decay self.epsilon_min epsilon_min self.q_table np.zeros((state_size, action_size)) def choose_action(self, state): if np.random.uniform(0, 1) self.epsilon: return np.random.choice(self.action_size) # 随机探索 else: return np.argmax(self.q_table[state]) # 利用当前最优 def update(self, state, action, reward, next_state, done): current_q self.q_table[state, action] if done: target reward # 游戏结束没有未来奖励 else: target reward self.gamma * np.max(self.q_table[next_state]) self.q_table[state, action] self.alpha * (target - current_q)逻辑说明choose_action实现了epsilon贪心策略。当随机数小于当前epsilon时随机试动作否则选Q值最大的动作。update方法是Q Learning的核心公式先用下一个状态的最大Q值估算未来奖励再乘以折扣因子gamma加上本次奖励得到目标值。然后用学习率alpha乘上“目标值减去当前Q值”的差更新当前(state, action)的Q值。done为真时不需要再考虑下一个状态目标就是当前奖励本身。当时我第一次看这段代码也有点懵为什么更新不直接q_table[state][action] target因为那样会让Q值震荡而且无法平滑收敛。alpha小于1的含义是“吸收一部分差异”跑起来之后你会发现学习率设成0.1或者0.2收敛曲线远比0.5以上平滑得多。3.3 训练主循环奖励累积与Q值更新train.py把环境与智能体整合起来跑指定轮数的episode。核心循环如下for episode in range(episodes): state env.reset() total_reward 0 done False while not done: action agent.choose_action(state) next_state, reward, done env.step(action) agent.update(state, action, reward, next_state, done) state next_state total_reward reward agent.epsilon max(agent.epsilon_min, agent.epsilon * agent.epsilon_decay) print(fEpisode {episode}: reward{total_reward}, epsilon{agent.epsilon:.3f})逻辑说明每次episode开始时重置环境拿到初始状态。进入内层循环基于当前状态选动作环境返回next_state、reward和是否结束的done然后调用update更新Q表。每一步都把state指向next_state直到done为真。外层循环结束后把epsilon乘上epsilon_decay让探索率逐步降低帖使智能体逐渐从“乱试”过渡到“利用已知最优”。参数方面episodes默认是500你可以调到1000观察更充分的收敛过程。这段代码也间接回答了“为什么Q Learning不记忆历史经验”它每步都在实时更新所有信息都沉淀到Q表里。3.4 策略可视化从Q表到行动轨迹训练完毕代码里还有一个可视化函数。它根据当前Q表从起点开始模拟走一遍每步选最优动作把路径画到网格上。代码大致是这样def render_episode(env, agent): state env.reset() route [state] while True: action np.argmax(agent.q_table[state]) next_state, _, done env.step(action) route.append(next_state) state next_state if done: break return route逻辑说明直接把Q表里每个状态的最大值动作当作当前动作一步一步走完整条轨迹。render_episode返回一个坐标列表可以直接用matplotlib画折线。这种可视化价值很大你能肉眼看到智能体策略是否收敛到最短路径——比如4x4网格里从(0,0)到(3,3)的最短步数是6步如果可视化里能看到6步走完说明Q Learning真的学到了东西。有人会问为什么不从Q表里直接反推每步方向因为可视化应该和真实环境一致每一步都参考Q表选动作最终表现才是策略的落地效果。4. 运行方法与关键参数以调参视角复现学习曲线4.1 启动命令与输出含义拿到这份代码先不要改任何东西直接跑通用基准版本。我一般习惯先建一个干净的虚拟环境python -m venv qlearn_env source qlearn_env/bin/activate # Windows下用 activate.bat pip install -r requirements.txt python train.py跑起来之后控制台会每回合打印一行包含回合序号、该回合总奖励和当前探索率。前几个回合epsilon接近1智能体基本在瞎走总奖励很低几十回合后epsilon降下来Q表有了有效信息总奖励会稳定在接近1的水平因为一般几步就能到达终点并拿到1的奖励。训练结束还会弹出matplotlib窗口画出两条曲线一条是“每回合总奖励”另一条是“每回合步数”。注意奖励曲线不应该是严格单调上升的中间有波动非常正常这正是探索-利用权衡的表现。如果看到曲线的整体趋势在上升、震荡区间逐步收窄那就是收敛信号。4.2 可调参数一览与实验对比核心参数都集中在train.py头部一行注释都没有含糊做实验时你可以照着改参数名默认值含义改大效果改小效果alpha0.1学习率收敛快但震荡大收敛慢但更稳gamma0.9折扣因子更看重长期奖励更看重眼前奖励epsilon1.0初始探索率前期试得多前期利用多epsilon_decay0.995探索衰减率衰减快早收敛衰减慢持续探索episodes500总训练轮数可能过拟合可能不收敛我建议你用同样的网格分别跑alpha0.1和alpha0.5的两组实验对比奖励曲线的震荡幅度。你会发现alpha0.5前期冲得快但后期偶尔会突然掉回低分这就是更新步长太大导致Q值来回跳。反过来alpha0.05可能要跑两三百回合才看到明显上升趋势但曲线更平滑。折扣因子gamma在网格任务里差异不大如果任务改成“陷阱给出-10惩罚终点给50奖励”gamma就非常敏感了此时gamma0.99和gamma0.9学习到的最优路径会不同。4.3 修改环境把4x4网格改成10x10障碍场最刺激的改法是改environment.py。比如把起点改到(0,0)终点改到(9,9)中间加一面墙跑起来看看Q Learning能不能绕过墙。改动很小env GridWorld(size10, start(0,0), goal(9,9), obstacles((2,2),(3,2),(4,2),(5,2)))就这么一句话原本简单迷宫立刻变成绕行任务。此时要注意Q表维度也会跟着变但不用你手动改因为agent.py里的state_size是从环境里动态取出来的。这个设计很贴心换环境不用动智能体代码。如果你更狠一点把obstacles设为随机生成的一排墙甚至可以用同一个代码包跑出类似“障碍穿越”的模拟。跑这种10x10网格episodes建议调到2000以上因为状态空间从16变成了100需要的探索次数显著变多。5. 避坑与排查我调这个Demo时的血泪经验5.1 学习率设成0.9训练反而崩溃现象我把alpha设成0.9跑了300回合奖励曲线不仅没上升反而越跑越差最后智能体一直原地打转。原因学习率太大导致Q值更新时严重震荡误差被不断放大策略根本不收敛。解决把alpha降回0.10.2。我一般先像作者一样用0.1跑通再逐步调高。如果你追求更快收敛速度正确做法是增加episodes而不是放大步长。5.2 epsilon减得太快智能体永远在走老路现象epsilon_decay设成0.9结果前30回合探索率就从1掉到不足0.04智能体从此只选择当前Q表里“看似最优”的动作但那个动作是早期乱试时留下的坏结果于是永远卡在原地。原因探索不足策略固化太早。解决把epsilon_decay设成0.9950.999或者改用更保守的衰减方式比如前一半训练回合强制探索后一半再让epsilon线性衰减。我自己的习惯是先用大衰减率跑一遍看趋势再根据曲线转折点重新设。5.3 Q表索引报错状态数对不上现象报IndexError: index 10 is out of bounds。原因环境网格变大但Q表初始化的state_size还是旧的16。这份代码里作者已经把state_size从env.state_size动态读取但如果你自己复制代码时就容易写死。解决检查train.py里创建智能体的那一行确保state_size来源于env对象。踩了这个坑的人很多因为直觉告诉你“状态数不就是一个常数吗”但网格一变就翻车。5.4 奖励写得太稀疏跑了2万步没反应现象我自定义环境把普通步数奖励改成-0.1到达终点奖励只给1结果训练几千回合总奖励一直在-20左右波动怎么调学习率都没用。原因稀疏奖励本是强化学习的经典难点指标任务里走一步扣0.1完成才给1累计要扣很多步智能体很难从稀疏信号中学到正确方向。解决要么把奖励改成“每走一步都加一点正向奖励”要么缩小网格缩小任务复杂度先把基本功能跑通再追求严格奖励设计。Q Learning本身就是一个奖罚分明的系统奖励设计才是真正的调参核心。5.5 可视化卡死matplotlib刷新频率踩坑现象训练跑到中途弹出来的图形窗口无响应电脑风扇狂转。原因可视化代码每回合画一次图500个回合就画500次每次还调plt.show()必然卡死。解决要么缩短episodes到50要么改成每10回合刷新一次图像。改法很简单在循环里加个判断if episode % 10 0: render(...)。这种问题完全是工程习惯问题不是算法问题。6. 二次开发延伸从表格Q Learning走向可扩展的强化学习框架6.1 把策略从表格抽象成函数这份代码的Q表本质是一个二维数组状态和动作都是离散整数。真实业务场景里状态往往是连续值比如机器人的角度、速度、坐标。常见做法是先把连续状态离散化比如把角度按5度一档切分。我在二次开发时会先写一个简单的抽象接口def state_to_index(state): return (int(state[0] * 10), int(state[1] * 10))然后把choose_action和update方法改成接收这个索引。好处是环境层可以随便换智能体只管和索引打交道。当状态空间大到表格装不下比如连续三维空间分成1000档就要1000*4的Q表这时就该考虑DQN了。但这份代码给你提供了绝佳的改造起点因为Q表的更新公式和DQN的损失函数在数学上同根同源把q_table[state, action]换成神经网络的输出即可。6.2 加入经验回放与目标网络顺着这个思路再进一步可以加入经验缓冲区。在原有代码里环境每步返回的(state, action, reward, next_state, done)本身就是一条经验把这几条塞进一个列表每训练几步随机抽样一批更新Q值。经验回放能打破数据相关性让表格版Q Learning在复杂任务中稳定很多。代码改动不大核心是把train.py里的内层循环改成“推经验→抽样→更新目标”。目标网络则更进阶需要单独copy一份Q表每隔几十步同步一次这能减少自举偏差。我把这两样都加上之后同一套网格环境收敛速度肉眼可见更快。6.3 扩展环境接口如果你想把这个Demo接入自己的模拟器只需要模仿environment.py实现三个方法reset()返回初始状态step(action)返回(next_state, reward, done)。这三个方法就是你和这份代码之间的契约。我最近在做一个排队系统的模拟就是直接把GridWorld换成事件驱动的客户流矩阵action改成选取服务窗口其他训练逻辑一行没改。这个代码包让我最舒服的地方是它没有框架绑架哪怕你完全不懂强化学习框架也能用自己的业务环境验证Q Learning是不是有效。从那次开始我每次拿到新的强化学习Demo都会强制自己先跑一遍默认参数再看Q表可视化最后再谈改环境或上神经网络——这个顺序比直接换模型重要一百倍希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑