☰
强化学习实例包:直接可跑的Q-learning与DQN实战
2026/10/13 4:23:58 网站建设 项目流程

简介:这是一份面向强化学习初学者的可直接运行实例,围绕自动打乒乓球模拟环境展开,帮助读者在无需复杂配置的前提下直观理解智能体如何通过与环境交互优化击球策略。资源包共10个文件,以6个pth模型权重、3个py脚本和1个pyc缓存文件为主,压缩包约35.78MB;其中py脚本承担环境封装、DQN算法与训练测试逻辑,pth文件保存不同训练阶段的网络参数,替换后即可对比智能体在各阶段的表现。已有4371人学习下载,说明其上手门槛与实用性得到较多验证。读者可借此掌握状态、动作、奖励与策略等核心概念,观察得分反馈如何驱动策略迭代,并在此基础上尝试替换算法或调整参数,探索Q-learning与DQN等方法的差异,适合作为强化学习入门练手与课程实验的参考素材。

1. 强化学习实例:为什么“直接能跑”比“讲得深”更稀缺

如果你搜过强化学习 直接能用的资源,大概率经历过这种落差:论文里的公式推导看懂了,开源仓库的 README 也读了,结果pip install完一跑就报错,环境版本对不上、依赖冲突、GPU 显存不够,折腾一下午连第一个 episode 都没跑起来。这份强化学习实例包解决的就是这个问题——它把环境依赖、训练循环、结果可视化全部打包好,拿到手直接运行就能看到智能体从随机乱走到稳定收敛的完整过程。

它适合三类人:刚学完 Q-learning 理论想动手验证的初学者、需要快速搭一个 baseline 做对比实验的算法工程师、以及想替换核心方法做二次开发的研究者。代码结构刻意保持扁平,没有过度封装,每个算法模块独立成文件,改起来不需要翻五层继承关系。下面我从实际拆包和运行的角度,把这份资源的结构、跑法、参数调优和常见坑讲清楚。

2. 拆开实例包:目录结构、算法模块与运行入口

2.1 目录布局与模块职责

拿到压缩包解压后,根目录下通常是这样的结构(不同版本可能略有差异,但核心模块一致):

rl-examples/ ├── envs/ # 环境封装 │ ├── gridworld.py # 网格世界环境 │ └── cartpole_wrapper.py ├── agents/ # 算法实现 │ ├── q_learning.py # 表格型 Q-learning │ ├── dqn.py # 深度 Q 网络 │ └── reinforce.py # 策略梯度 ├── configs/ # 超参数配置 │ ├── q_learning.yaml │ └── dqn.yaml ├── train.py # 统一训练入口 ├── evaluate.py # 模型评估与可视化 └── requirements.txt

这个布局的好处是职责清晰:envs/只管环境交互接口,agents/只管算法逻辑,configs/把超参数从代码里抽出来。你替换算法时只需要在agents/下新增文件,然后在train.py里注册一下即可,不用动环境代码。

train.py是统一入口,通过命令行参数指定用哪个算法、哪个环境、哪个配置文件。常见用法是:

python train.py --agent q_learning --env gridworld --config configs/q_learning.yaml

这行命令的含义是:用 Q-learning 算法在 GridWorld 环境上训练,超参数从configs/q_learning.yaml读取。--agent和--env是必填项,--config不指定时会加载同名默认配置。

2.2 环境接口:为什么统一成 Gym 风格

所有环境都遵循 Gym 风格的接口:reset()返回初始观测,step(action)返回(next_obs, reward, done, info)。这不是为了好看,而是为了算法代码能跨环境复用。Q-learning 的更新逻辑不关心状态是离散的网格坐标还是连续的 CartPole 四元组,它只关心step返回了什么。

以 GridWorld 为例,核心逻辑大概是这样:

class GridWorld: def __init__(self, size=5, goal=(4, 4), trap=[(1, 1), (2, 3)]): self.size = size # 网格边长 self.goal = goal # 目标位置 self.trap = trap # 陷阱位置列表 self.state = (0, 0) # 智能体初始位置 def reset(self): self.state = (0, 0) return self.state def step(self, action): # action: 0=上, 1=下, 2=左, 3=右 x, y = self.state if action == 0: x = max(0, x - 1) elif action == 1: x = min(self.size - 1, x + 1) elif action == 2: y = max(0, y - 1) elif action == 3: y = min(self.size - 1, y + 1) self.state = (x, y) if self.state == self.goal: return self.state, 1.0, True, {} # 到达目标,奖励 +1 if self.state in self.trap: return self.state, -1.0, True, {} # 踩到陷阱,奖励 -1 return self.state, -0.01, False, {} # 每步小惩罚,鼓励最短路径

这里有几个参数值得注意:size控制网格大小,默认 5×5 对初学者足够直观;trap是陷阱坐标列表,你可以改成任意位置来测试算法的避障能力;每步的-0.01惩罚是“步数惩罚”,目的是让智能体学会走最短路,而不是绕圈子。如果把步数惩罚设成 0,Q-learning 在 GridWorld 上可能学出绕远路但同样能到终点的策略——这不是 bug,是奖励设计的问题。

2.3 训练循环:episode、step 与探索率衰减

训练主循环的逻辑不复杂,但几个关键变量的含义必须搞清楚:

for episode in range(num_episodes): state = env.reset() total_reward = 0 while True: # epsilon-greedy 策略选择动作 if random.random() < epsilon: action = env.action_space.sample() # 随机探索 else: 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 if done: break # 探索率衰减 epsilon = max(epsilon_min, epsilon * epsilon_decay)

num_episodes是训练轮数,GridWorld 上一般 500~1000 轮就能收敛;epsilon是探索率,初始通常设 1.0(完全随机),随着训练衰减到epsilon_min(一般 0.01~0.05);epsilon_decay控制衰减速度,常见取值 0.995。这三个参数配合不好会出现两种典型问题:衰减太快,智能体还没探索完所有状态就开始“利用”,容易卡在局部最优;衰减太慢,训练后期还在大量随机动作,收敛曲线会一直震荡。

提示:如果你把epsilon_decay设成 0.99 以下,1000 轮训练可能不够收敛,需要同步增加num_episodes。

3. 跑通第一个实例:从安装依赖到看到收敛曲线

3.1 环境准备与依赖安装

先确认 Python 版本。这份实例包通常要求 Python 3.8 以上,推荐 3.9 或 3.10,太新的版本(3.12+)可能遇到部分依赖还没适配的问题。创建虚拟环境是标准操作:

python -m venv rl_env source rl_env/bin/activate # Linux / macOS # rl_env\Scripts\activate # Windows pip install -r requirements.txt

requirements.txt里一般包含numpy、gymnasium(或旧版gym)、matplotlib、pyyaml。如果安装过程中遇到gym和gymnasium冲突,说明你的环境里之前装过旧版,先pip uninstall gym再装。常见做法是锁定版本号,比如gymnasium==0.29.1,避免自动升级到不兼容的新版本。

安装完成后跑一个快速自检:

python -c "import gymnasium; print(gymnasium.__version__)"

能打印出版本号就说明基础环境没问题。如果报ModuleNotFoundError,检查虚拟环境是否激活、pip 是否指向正确的 Python 版本。

3.2 运行 Q-learning:观察奖励曲线的三个阶段

用默认配置跑 Q-learning:

python train.py --agent q_learning --env gridworld --config configs/q_learning.yaml

训练结束后会在logs/下生成奖励曲线图。一条典型的收敛曲线分三个阶段:前 100 轮左右奖励在 -1 到 -0.5 之间波动,这是随机探索阶段;100 到 300 轮奖励快速上升,说明 Q 表开始学到有效策略;300 轮之后曲线趋于平稳,接近每 episode 的最大奖励。如果你的曲线在第二阶段就平了但奖励值不高,大概率是陷入了局部最优,可以调大epsilon_min或增加num_episodes。

configs/q_learning.yaml里的关键参数:

learning_rate: 0.1 # Q 值更新步长 gamma: 0.99 # 折扣因子 epsilon: 1.0 # 初始探索率 epsilon_min: 0.05 # 最小探索率 epsilon_decay: 0.995 # 每 episode 衰减系数 num_episodes: 800 # 训练总轮数

learning_rate设 0.1 是表格型 Q-learning 的常见值,设太大(如 0.5)会导致 Q 值震荡不收敛,设太小(如 0.01)学习速度慢;gamma设 0.99 表示智能体比较“短视”,更看重近期奖励,GridWorld 这种短路径任务用 0.9~0.99 都合理。

3.3 切换到 DQN:验证方法可替换性

同一个环境换 DQN 算法,只需要改--agent参数:

python train.py --agent dqn --env gridworld --config configs/dqn.yaml

DQN 的配置文件多了几个神经网络相关的参数:

hidden_dim: 64 # 隐藏层维度 buffer_size: 10000 # 经验回放池容量 batch_size: 64 # 每次采样批次大小 target_update: 100 # 目标网络更新间隔(步数)

buffer_size设 10000 对 GridWorld 足够,但如果换到状态空间更大的环境(如 CartPole),可能需要 50000 以上;target_update控制目标网络同步频率,设太小会导致训练不稳定,设太大则学习滞后,常见范围 100~1000 步。DQN 在 GridWorld 上不一定比 Q-learning 收敛快,因为状态空间小,表格方法本身就够用。但换成 CartPole 这种连续状态环境,Q-learning 就无能为力了,必须用 DQN 做函数逼近。

注意:从 Q-learning 切到 DQN 时,如果报显存不足,把batch_size降到 32 或 16,GridWorld 的观测维度很低,不需要大 batch。

4. 避坑与排查:五个真实翻车记录

4.1 奖励曲线不收敛,一直震荡

现象:训练 500 轮后奖励仍在 -1 到 0 之间大幅波动,没有上升趋势。

原因:最常见的是epsilon_decay设得太大(如 0.999),探索率衰减太慢,训练后期仍在大量随机动作;其次是learning_rate设得过大,Q 值更新步长太大导致震荡。

解决:先把epsilon_decay调到 0.99~0.995 区间,观察 300 轮内是否有上升趋势;如果仍然震荡,把learning_rate从 0.1 降到 0.05 或 0.01。改完参数后建议固定随机种子跑三次,排除单次运行的偶然性。

4.2 训练速度异常慢,一个 episode 要好几秒

现象:GridWorld 这种小环境,一个 episode 理论上毫秒级完成,但实际跑起来每轮要等好几秒。

原因:大概率是train.py里每步都调用了env.render(),渲染操作会强制同步刷新界面,严重拖慢训练速度。另一个可能是日志写入太频繁,每个 step 都往磁盘写一条记录。

解决:检查train.py中是否有render调用,训练时关掉渲染,只在evaluate.py里开启。日志改成每 10 个 episode 记录一次,而不是每步记录。如果用的是 Jupyter Notebook,把训练循环放在.py文件里跑,Notebook 的交互式输出也会拖慢速度。

4.3 换了环境后算法完全学不动

现象:在 GridWorld 上跑得好好的 Q-learning,换到 CartPole 后奖励一直是 20 左右(随机水平),完全不学习。

原因:Q-learning 是表格型方法,它用字典或数组存储每个状态-动作对的 Q 值。CartPole 的状态是连续的四维浮点数,理论上无限多种状态,表格方法根本存不下。这不是参数问题,是方法选型错误。

解决:连续状态环境必须用函数逼近方法,把--agent换成dqn或reinforce。如果坚持用表格方法,需要先对环境做离散化(把连续状态分桶),但这会损失精度,不推荐。

4.4 依赖安装报错,提示版本冲突

现象:pip install -r requirements.txt时报Cannot install gymnasium and gym because these package versions have conflicting dependencies。

原因:系统里之前装过旧版gym,而实例包依赖gymnasium,两者在包名和 API 上有重叠但不兼容。

解决:先pip uninstall gym gymnasium -y清理干净,再重新pip install -r requirements.txt。如果仍然冲突,手动指定版本:pip install gymnasium==0.29.1 numpy==1.24.0。建议始终在虚拟环境里操作,避免污染系统 Python。

4.5 评估时表现远差于训练时的奖励

现象:训练曲线显示奖励已经收敛到 0.9 以上,但用evaluate.py跑评估时,智能体表现很差,经常撞陷阱或绕远路。

原因:评估时如果仍然开着探索(epsilon > 0),智能体会以一定概率随机选动作,表现自然下降。另一个可能是评估环境和训练环境的初始状态分布不一致。

解决:评估时把epsilon强制设为 0(完全利用策略),在evaluate.py里加一行agent.epsilon = 0。同时确认评估用的环境参数(如size、trap位置)和训练时一致。如果评估环境是随机初始状态,多跑几个 episode 取平均,单次评估的方差可能很大。

5. 进阶技巧:替换核心方法与验证收敛性

5.1 替换算法模块的标准化流程

这份实例包的设计意图就是让你改。替换算法的标准流程是三步:在agents/下新建算法文件,实现choose_action和update两个方法;在train.py的算法注册表里加一行映射;新建对应的 YAML 配置文件。以替换成 SARSA 为例:

# agents/sarsa.py class SARSA: def __init__(self, n_states, n_actions, lr=0.1, gamma=0.99): self.Q = np.zeros((n_states, n_actions)) self.lr = lr self.gamma = gamma def choose_action(self, state, epsilon=0.1): if random.random() < epsilon: return random.randint(0, self.Q.shape[1] - 1) return np.argmax(self.Q[state]) def update(self, state, action, reward, next_state, next_action, done): # SARSA 用实际选择的 next_action 更新,而非 max target = reward + self.gamma * self.Q[next_state][next_action] * (1 - done) self.Q[state][action] += self.lr * (target - self.Q[state][action])

注意 SARSA 的update比 Q-learning 多一个next_action参数,因为它是 on-policy 方法,用实际执行的下一个动作来更新,而不是用最大 Q 值。这个差异在悬崖行走(Cliff Walking)类环境里会体现得很明显:Q-learning 学出贴着悬崖走的最优路径,SARSA 学出绕远的保守路径。两者没有优劣之分,取决于你的任务是否允许冒险。

5.2 收敛性验证:不要只看一条曲线

单次训练的奖励曲线受随机种子影响很大,我一般会固定三个不同种子各跑一次,把三条曲线画在同一张图上。如果三条曲线都在相近的 episode 数收敛到相近的奖励水平,说明算法稳定;如果某条曲线明显偏离,检查那次运行的epsilon衰减轨迹和 Q 值分布。

另一个验证手段是看 Q 表的数值分布。收敛后,目标状态附近的 Q 值应该明显高于其他区域,且相邻状态的 Q 值差异平滑。如果 Q 值出现大面积相同或剧烈跳变,说明学习率或折扣因子需要调整。

# 可视化 Q 表(GridWorld 场景) import seaborn as sns q_table = agent.Q.reshape(env.size, env.size, -1) sns.heatmap(q_table.max(axis=2), annot=True, cmap='YlOrRd')

这张热力图能直观看出智能体是否学到了“越靠近目标 Q 值越高”的梯度。如果热力图一片均匀,说明训练不充分或奖励设计有问题。

5.3 从实例到项目的迁移习惯

这份实例包的价值不在于它本身能解决多复杂的问题,而在于它提供了一个可运行的最小闭环。我自己的习惯是:每学一个新算法,先在这个框架里跑通 GridWorld,确认理解无误后,再把envs/下的环境替换成目标任务的封装,算法代码基本不动。这样能把“算法逻辑”和“环境适配”两个问题解耦,出问题时定位更快。

从那以后我每次拿到新的强化学习代码,都强制先跑一遍它的默认配置,确认能复现 README 里的结果,再动手改。这个习惯帮我省掉了大量“以为是算法问题、其实是环境配置问题”的排查时间。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询