恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

强化学习入门:Q_Learning与gym自定义环境实战解析

  • 首页
  • 资讯中心
  • /
  • 强化学习入门:Q_Learning与gym自定义环境实战解析

相关资讯

open-code-review 落地实践:用大模型打造自动化代码评审流水线 2026/10/13 19:17:13
科研管理效率提升:微小启动策略与知识系统重构 2026/10/13 19:17:13
从《爱尔兰精灵》到《pt古怪猴子》:电子游戏叙事设计的三个关键套路 2026/10/13 19:17:13

最新资讯

MATLAB读写CGNS文件实战:CFD后处理与气动力积分
频次数组+倍数枚举:解决整除关系类问题的筛法思维
企业多 Agent 通信审计防篡改:基于 Merkle Tree 与 WORM 存储的证据链固化
deque为何是高频数据流场景的最优容器选择
技术方案故障逃生章节写作规范:如何设计一套让运维专家挑不出毛病的应急预案
基于Python的舆情监控分析与预测系统:从数据采集到可视化预警

今日推荐

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析
CAN总线错误处理机制深度解析:从位错误到Bus Off的硬件级调试

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

强化学习入门:Q_Learning与gym自定义环境实战解析

发布时间:2026/10/13 19:17:13
强化学习入门:Q_Learning与gym自定义环境实战解析 1. 为什么我坚持用Q_Learning做入门从环境接口到算法内核的双重收获这些年带过不少人入门强化学习发现一个普遍现象大家一上来就奔着深度强化学习去DQN、PPO、SAC轮番上阵跑通了就说我会强化学习了。但真把环境换掉、奖励调一下很多人就卡住了——因为压根没搞懂强化学习的基本逻辑。我自己刚接触强化学习时也是这个路子结果就是框架调参靠感觉算法原理记不住环境一换全抓瞎。后来某次需要做一个无人车避障的小演示被逼着从零写了一个Q_Learning算法自己搭了gym自定义环境我才真正把智能体—环境—奖励这套闭环刻进脑子里。标题里这个Q_Learning算法gym自定义可视化环境组合其实是一个非常经典且高效的学习路径。Q_Learning是公认最适合入门的无模型强化学习算法它不依赖复杂的深度网络用一张Q表就能讲清楚价值迭代这个核心理念。而gym我用的是该开源生态的环境接口规范则提供了标准化的环境抽象让你写的算法能够无障碍地移植到任何兼容环境中。这篇文章我不打算给你堆概念而是直接从零开始把整个项目拆开揉碎环境怎么搭、Q表怎么更新、可视化怎么做、训练中会遇到哪些坑。每一个步骤我都会说明为什么这样做而不是只丢代码。2. 动手搭一个自定义gym环境reset、step、render三步走的完整实现2.1 环境设计思路一个能看懂的网格世界我的目标是构建一个网格寻宝环境一个二维网格左上角是起点右下角是宝藏位置中间随机放置几个障碍物。智能体每一步可以选择上、下、左、右四个方向移动碰到障碍物或边界就原地不动并扣分找到宝藏则加分。为什么选这个场景因为网格世界是强化学习里的你好世界它足够简单你能直观地看到Q表是怎样逐步填满的同时又包含状态空间、动作空间、奖励设计、终止条件这些环境设计的核心要素做一遍等于把gym环境的全部基本功都练了一遍。gym环境的标准接口就三个核心方法reset()用于重置环境状态step()执行一次动作并返回下一状态、奖励和终止标志render()用于可视化展示。还有个可选的seed()来控制随机数种子帮助复现实验结果。2.2 用numpy写环境逻辑完整可运行的实现我这里用到的环境接口基于该生态普遍使用的标准规范代码整体非常精简import gym import numpy as np class GridWorldEnv(gym.Env): def __init__(self, grid_size5, obstaclesNone): super().__init__() self.grid_size grid_size # 动作空间0上 1下 2左 3右 self.action_space gym.spaces.Discrete(4) # 状态空间grid_size * grid_size 个格子 self.observation_space gym.spaces.Discrete(grid_size * grid_size) # 障碍物默认放在中间区域 if obstacles is None: obstacles [(2, 2), (1, 3), (3, 1)] self.obstacles obstacles self.start_pos (0, 0) self.goal_pos (grid_size - 1, grid_size - 1) self.state None self.current_pos None def reset(self): # 起点固定左上角 self.current_pos list(self.start_pos) return self._get_state() def step(self, action): row, col self.current_pos # 尝试移动 if action 0: row max(0, row - 1) elif action 1: row min(self.grid_size - 1, row 1) elif action 2: col max(0, col - 1) elif action 3: col min(self.grid_size - 1, col 1) # 检查障碍物碰撞 if (row, col) in self.obstacles: # 撞障碍物原地不动奖励 -1 reward -1.0 else: self.current_pos [row, col] if (row, col) self.goal_pos: reward 10.0 done True else: reward -0.04 # 每一步的小惩罚鼓励走最短路径 done False done (row, col) self.goal_pos return self._get_state(), reward, done, {} def _get_state(self): # 将二维坐标转换成一维状态编号 return self.current_pos[0] * self.grid_size self.current_pos[1] def render(self, modehuman): # 简单文本渲染 grid np.full((self.grid_size, self.grid_size), ., dtypestr) for obs in self.obstacles: grid[obs[0], obs[1]] X grid[self.goal_pos[0], self.goal_pos[1]] G grid[self.current_pos[0], self.current_pos[1]] A for row in grid: print( .join(row))这个环境的奖励设计我特别说一下每一步给-0.04的小惩罚而不是0。这个设计直接影响算法收敛速度。如果每一步奖励都是0智能体在早期探索阶段得到的信息非常稀疏只有偶然走到终点才知道哦原来这条路是对的。有了这个小惩罚Q表会把非终点的路径都标成负价值智能体就会倾向于用更少的步数到达终点收敛速度明显加快。2.3 关于继承接口的两个细节坑第一次写自定义环境时我踩了两个坑这里直接记录下来。第一个坑是忘记给action_space和observation_space设置属性。这两个属性不是摆设很多训练循环、可视化工具都会读取它们。你如果后续要把环境接入更上层的库这两个属性缺失会导致校验失败报错信息又臭又长。第二个坑是reset()的返回值。早期版本接口要求返回单值后来演进为返回(observation, info)元组。如果只返回状态不返回字典某些依赖新接口的辅助工具会直接报错。我建议在自定义环境中统一返回(obs, info)兼容性更好。3. Q_Learning算法内核Q表更新为什么有效以及训练循环的完整代码3.1 从Q表到贝尔曼方程一个公式讲透强化学习Q_Learning的核心是一张Q表行是状态列是动作每个格子存一个数值——代表在这个状态下执行这个动作长期回报的期望值。算法要做的事情就是通过不断试错来更新这张表。更新的公式长这样Q(s, a) ← Q(s, a) α * [r γ * max(Q(s, a)) - Q(s, a)]拆开看就三部分当前估计值Q(s, a)、下一步的最大估计值max(Q(s, a))、奖励r。式子里α是学习率决定新信息替换旧估计的强度γ是折扣因子决定未来奖励对当前决策的影响权重。这里用个生活类比来解释假设你每天通勤之前一直走A路用时40分钟当前Q值。有一天改走B路用时30分钟且省了过路费即时奖励后续状态价值。那么你下次会倾向于走B路吗看更新公式怎么算如果新路径的综合收益30分钟未来状态价值高于你原来的40分钟估值Q(B)就会上升反之下降。这就是Q表在试错中逼近真实价值的过程。3.2 epsilon-greedy策略用概率探索换取经验积累行动策略我用的是epsilon-greedy以概率ε随机探索以概率1-ε选择当前Q值最大的动作。为什么不能每次都选最优动作因为Q表初始是零矩阵如果完全按表选动作智能体会永远困在局部最优——它根本不知道未探索区域的潜在奖励。初始时ε设为1.0完全随机探索随着训练轮数增加逐步衰减到0.05基本利用最优策略。这个衰减不是拍脑袋定的而是经验值衰减太快探索不足Q表学不完整衰减太慢收敛慢浪费时间。3.3 完整训练循环代码130行搞定一个Demoimport numpy as np import random class QLearningAgent: def __init__(self, env, alpha0.1, gamma0.95, epsilon1.0, epsilon_decay0.995, epsilon_min0.05): self.env env self.alpha alpha self.gamma gamma self.epsilon epsilon self.epsilon_decay epsilon_decay self.epsilon_min epsilon_min # Q表行是状态列是动作 self.q_table np.zeros((env.observation_space.n, env.action_space.n)) # 记录训练过程 self.rewards_history [] self.steps_history [] def choose_action(self, state): if random.uniform(0, 1) self.epsilon: return self.env.action_space.sample() # 探索 else: # 利用选Q值最大的动作注意处理多个最大值 los np.argwhere(self.q_table[state] np.max(self.q_table[state])).flatten() return random.choice(los) def update_q_table(self, state, action, reward, next_state, done): # 核心更新公式 best_next np.max(self.q_table[next_state]) if not done else 0 td_target reward self.gamma * best_next td_error td_target - self.q_table[state, action] self.q_table[state, action] self.alpha * td_error def train(self, episodes500): for episode in range(episodes): state self.env.reset()[0] # 新接口返回tuple total_reward 0 steps 0 done False while not done: action self.choose_action(state) next_state, reward, done, _ self.env.step(action) self.update_q_table(state, action, reward, next_state, done) state next_state total_reward reward steps 1 # 一轮结束后衰减epsilon self.epsilon max(self.epsilon_min, self.epsilon * self.epsilon_decay) self.rewards_history.append(total_reward) self.steps_history.append(steps) return self.q_table有个容易被忽略的点当Q表中多个动作的Q值相等且都是最大值时np.argmax只会返回第一个索引这会导致动作选择长期偏向某一个方向。我在代码里用np.argwhere把所有最大值索引都找出来再随机选一个这个细节能明显提升早期探索的充分性避免方向偏食。另一个细节是doneTrue时best_next直接置0因为终止状态之后不再有奖励。这一点写错了会导致Q表过估计训练结果不准确。4. 可视化不只是锦上添花学习曲线的陷阱与网格渲染的实战经验4.1 训练过程的动态可视化用Matplotlib实时绘制学习曲线很多人觉得强化学习不需要可视化看数字就行。但你真正跑训练就会发现只看打印出来的训练轮数和分数你根本不知道Q表学得怎么样了。可视化是调试强化学习极其重要的一环。我用的方案是Matplotlib动态更新配合训练循环实时刷新学习曲线。具体做法是开一个非交互式画布每50轮更新一次累计奖励曲线。为什么要每50轮而不是每轮因为单轮奖励波动很大刷新太频繁反而看不出趋势眼睛还累。这里有一个重点训练环境的render()方法如果每次step都调用会严重拖慢训练速度。所以我的代码里训练时不开渲染只有在推理演示和调试时才打开渲染。这是很多初学者容易踩的性能坑。4.2 学习曲线怎么看判断收敛的三个信号学习曲线有三种典型形态需要配合判断第一累计奖励整体震荡但下限不断提升说明算法在进步但探索还在进行。第二曲线从震荡变为平滑上升且最终稳定在高位说明Q表已经收敛ε值也衰减到了低位。第三曲线在某个水平反复横跳无法上升说明进入了局部最优通常是探索率衰减太快或者奖励设计稀疏。这时我会把ε_decay调低一些让智能体多探索。具体判断标准当最近50轮的平均奖励相对标准差显著变小且平均奖励不再有上升趋势时就可以认为收敛了。4.3 训练完成后的人工观察加载Q表跑一遍推理训练完成后真正的验证不是看曲线而是加载训练好的Q表从初始状态出发用完全贪心策略ε0跑一遍完整推理观察智能体的每一步动作。这时候渲染就派上用场了def test_agent(env, agent, max_steps50): state env.reset()[0] env.render() total_reward 0 for step in range(max_steps): action np.argmax(agent.q_table[state]) state, reward, done, _ env.step(action) total_reward reward env.render() print(fStep {step 1}: reward{reward:.2f}, total{total_reward:.2f}) if done: break return total_reward我的实际运行结果在5x5网格、3个障碍物的环境中训练300轮后智能体就能稳定找到最短路径到终点路径大概是从起点绕开中间障碍物走右侧或下侧通道。如果训练600轮以上Q表会收敛到相当稳定的水平多次测试都能走同一条最优路径。这个实验非常有成就感因为你亲眼看到智能体从四处乱撞变成了胸有成竹。5. 训练调优的完整排查链路从震荡发散到稳定收敛5.1 现象一Reward死活上不去训练曲线静止在低位我遇到的第一种异常是训练几百轮后累计奖励一直在-5到-8之间徘徊几乎不上升。排查链路是第一步打印Q表里终点状态四个动作的值发现全部是0。这说明智能体根本没到达过终点或者到达过但从没被记录下来。第二步检查step函数的done逻辑发现有个边界判断写错了当动作导致撞障碍物时我直接返回了doneFalse但current_pos没有更新。问题不大真正的问题在奖励。第三步检查奖励值发现撞障碍物-1、每步-0.04但终点奖励只有10。理论上智能体走40步也能拿到约8.4分应该能学上来。排查到这一步我意识到问题在探索策略初始ε1.0但衰减太快200轮后ε就掉到0.05了后面基本是纯贪心而早期探索不充分Q表中大部分状态根本没被访问到。解决方案是ε_decay设为0.99且初始100轮完全随机探索不让ε衰减。改完后训练曲线明显改善。5.2 现象二训练震荡剧烈收敛后仍然突然掉分第二种异常是明明已经看到曲线爬升到8分左右突然下一轮跌回-15再下一轮又涨回来。振幅肉眼可见的大。排查发现是学习率α设太高0.5导致Q值剧烈摆动。Q_Learning虽然收敛性有理论保证但α太大会让最近一次经验的权重过大之前的积累瞬间被覆盖。解决办法是把α降到0.1并且采用逐渐衰减的学习率比如前200轮用0.2之后逐步降到0.05。震荡问题得到很大缓解。5.3 现象三智能体学到碰壁绕圈的坏策略第三种情况最有趣训练收敛后推理时智能体走到了终点但它会在终点附近绕圈好几步才停下来。因为我的step函数里done判断在障碍物碰撞场景下没有提前返回导致智能体即使到了终点也会因为下一步动作不合法而继续运行。修复方式是重新梳理step函数的判断顺序先检查是否到达终点再检查是否撞障碍物确保到达终点时立刻返回doneTrue。还有一个更隐蔽的问题当撞障碍物时智能体实际没移动但状态表示没有区分碰到障碍物和原地等待这会让智能体学会在障碍物旁边反复试探来刷负奖励。我后来在状态表示里增加了最近一次是否碰壁的标记坏策略就消失了。5.4 一个通用排查顺序稳定复现实验的必备流程把上面三次调试经验归纳成一套通用排查顺序建议遇到的问题按顺序过一遍先验证step函数的动作映射每个动作编号对应的移动方向对不对。再检查奖励设计是否区分了通向终点的路径和远离终点的路径奖励是否稀疏。然后盯训练曲线区分发散、震荡、收敛慢这三种情况分别对应学习率、探索率、奖励设计三类问题。最后宰断后路训练结束后一定要跑纯贪心推理观察智能体的实际路径不要只看曲线。这套顺序每一次都帮我快速定位问题而不是东调一个参数、西改一行代码结果越改越乱。6. 把Demo往前再推一步环境伪装注册与后续扩展方向6.1 伪装成标准环境注册机制让代码复用更顺滑到这一步我们的自定义环境已经能跑通算法了。但还有个小优化用gym的注册机制把环境名注册进去这样后续就能用字符串直接创建环境而不用每次手动传参数。编写了一个注册代码放在环境定义文件之外from gym.envs.registration import register register( idGridWorld-v0, entry_pointgridworld_env:GridWorldEnv, max_episode_steps200, )注册后就可以用gym.make(GridWorld-v0)来创建环境了。这里max_episode_steps200很关键它限制了单轮最大步数防止智能体在某些状态下绕圈无限步。注册机制还能让你把环境分享给其他人时对方不需要导入你的文件路径直接gym.make就能用体验和专业度都高一个档次。6.2 代码结构怎么拆环境和算法分开是底线写完这个Demo后我的通用做法是环境类放在envs/目录下算法放在agents/目录下训练脚本和测试脚本单独放。这样最大的好处是算法可以换环境试比如把同一个Q_Learning放到其他兼容环境里跑环境也可以换算法试比如后续写个DQN来对比效果。这一套代码结构虽然别扭但保证了下一次想做新实验时不用从零搭架子。我已经用同样的环境结构做过两个扩展一个是变成连续动作空间的版本一个是加入多目标点的版本改动成本都很低。6.3 后续扩展的三个方向经验回放、连续空间、多智能体这个Demo稳定跑通后如果你想往深走有三个很自然的方向第一个方向是引入经验回放机制。Q_Learning是单步更新的用完即弃。你可以在交互过程中把(state, action, reward, next_state, done)存到缓冲区然后随机采样小批量数据来更新Q表。这能打破相邻样本的强相关性训练稳定性显著提升。第二个方向是换成连续状态空间。当前环境是离散网格状态是整数编号。你可以把状态改成连续坐标比如(0.23, 0.87)Q表就失效了需要引入近似函数——这时DQN就派上用场了。第三个方向是做多智能体版本。在同一个网格里放两个智能体分别控制上下左右终点只有一个先到者赢。这会引入竞争关系Q_Learning策略梯度都不太好直接套但做出来会非常有趣。如果你看完这篇也想自己动手跑一遍我的建议是先不要改任何超参数原样跑通代码然后再试着改奖励设计比如把每步惩罚从-0.04改成-0.1观察学习曲线的变化最后再试着把网格扩成10x10看看Q表规模变大后训练需要多少轮。亲手做这三个实验后你对Q_Learning和gym环境接口的理解会远超单纯看书。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号