尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
深度强化学习 DQN 算法 Python 源码实战:从跑通到调参的完整指南
简介这份资源是深度强化学习DQN算法的Python实现源码面向计算机、电子信息工程、数学等专业的大学生以及正在准备课程设计、期末大作业或毕业设计的学习者。它解决的是强化学习入门阶段缺少可运行参考代码的问题帮助读者理解DQN的核心训练流程与智能体交互机制。压缩包内共2个文件均为py源码整体约5KB分别承担DQN算法主体逻辑与训练运行入口的职责结构精简便于直接阅读和调试。目前已有235人学习下载说明其在同类教学资源中具备一定参考价值。读者可获得一份可直接运行的DQN实现范例通过阅读源码掌握经验回放、目标网络、动作选择等关键模块的代码组织方式并在此基础上修改网络结构、超参数或环境接口快速迁移到自己的课题与实验场景中适合作为强化学习实践入门与二次开发的起点。1. DQN 算法 Python 源码从一份压缩包到能跑通的强化学习闭环很多人第一次拿到「深度强化学习 DQN 算法 Python 源码.zip」这类压缩包解压之后看到一堆.py文件第一反应是找main.py直接python main.py然后被ModuleNotFoundError、gym版本不兼容、env.seed报错轮番教育。DQN 本身不复杂复杂的是它把「神经网络」「经验回放」「目标网络」「环境交互」四件事揉在一个训练循环里任何一环参数没对齐奖励曲线就是一条躺平的直线。这篇笔记不讲论文推导只讲一件事拿到一份 DQN 的 Python 源码怎么判断它值不值得读、怎么把它跑起来、怎么改参数、怎么确认它真的在学而不是在玄学震荡。适合已经会写 Python、想用深度强化学习算法解决实际控制或决策问题的人也适合想从源码层面理解 DQN 算法到底在干什么的从业者。2. 先看懂 DQN 源码的骨架五个文件撑起一个训练闭环一份能跑的 DQN 源码不管作者怎么组织目录核心逻辑一定落在五个部分环境封装、Q 网络定义、经验回放池、训练主循环、参数配置。读源码的顺序不该从main开始而应该从「数据怎么流动」开始——状态从环境出来进网络算 Q 值选动作回环境拿奖励和下一状态存进回放池再从池里采样更新网络。这条链路理清了剩下的都是工程细节。2.1 环境封装gym接口和状态预处理是第一个分水岭绝大多数 DQN 源码用gym现在叫gymnasium做环境接口。你要先确认源码依赖的是哪一代老代码用gym.make(CartPole-v0)新代码用gymnasium.make(CartPole-v1)两者在reset()返回值上不一样——老版返回state新版返回(state, info)。这个差异会让训练循环直接崩不是算法问题是接口问题。import gymnasium as gym import numpy as np # 常见做法包一层预处理把状态转成 float32 并归一化 class EnvWrapper: def __init__(self, env_name): self.env gym.make(env_name) self.state_dim self.env.observation_space.shape[0] self.action_dim self.env.action_space.n def reset(self): state, _ self.env.reset() # 新版返回 tuple return np.array(state, dtypenp.float32) def step(self, action): next_state, reward, terminated, truncated, _ self.env.step(action) done terminated or truncated # 两个标志要合并 return np.array(next_state, dtypenp.float32), reward, done这段代码的关键在terminated和truncated的合并。老版gym只有一个done新版拆成「任务真正结束」和「时间上限截断」两个信号。DQN 在计算目标 Q 值时如果doneTrue就不加下一状态的折扣价值如果只是截断则仍要 bootstrap。很多源码在这里偷懒把truncated也当done导致智能体在接近时间上限时学到错误的终止信号表现为奖励曲线在后期突然塌陷。参数上state_dim和action_dim必须从环境对象动态取不要硬编码否则换环境就翻车。2.2 Q 网络三层全连接够用但输出层和损失函数要对齐DQN 的 Q 网络输入是状态维度输出是动作维度每个输出对应「在该状态下采取该动作的预期回报」。源码里常见两种写法输出所有动作的 Q 值适合离散动作少的场景或者只输出一个标量适合动作连续或动作空间极大。CartPole、LunarLander 这类离散小动作空间用前者。import torch import torch.nn as nn class QNet(nn.Module): def __init__(self, state_dim, action_dim, hidden128): super().__init__() self.net nn.Sequential( nn.Linear(state_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, action_dim) # 输出每个动作的 Q 值 ) def forward(self, x): return self.net(x)隐藏层宽度hidden是第一个要调的参数。太小比如 32在 LunarLander 上根本学不动太大比如 512在小任务上过拟合且训练慢。我一般从 128 起步观察 loss 是否稳定下降。损失函数用nn.MSELoss或nn.SmoothL1Loss后者对异常大的 TD 误差更鲁棒源码里如果用的是 MSE遇到奖励尺度大的环境容易梯度爆炸。注意输出层不要加激活函数Q 值可以是任意实数加了ReLU会把负 Q 值截断智能体就学不会「避开某些动作」。2.3 经验回放池容量和采样策略决定样本效率经验回放是 DQN 区别于普通 Q-learning 的核心。它把(state, action, reward, next_state, done)存进一个固定容量的缓冲区训练时随机采样一批打破时间相关性。源码里回放池的实现质量直接决定训练稳不稳。import random from collections import deque class ReplayBuffer: def __init__(self, capacity100000): self.buffer deque(maxlencapacity) def push(self, state, action, reward, next_state, done): self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size): batch random.sample(self.buffer, batch_size) state, action, reward, next_state, done zip(*batch) return (np.stack(state), np.array(action), np.array(reward, dtypenp.float32), np.stack(next_state), np.array(done, dtypenp.float32)) def __len__(self): return len(self.buffer)capacity设 10 万是常见起点。太小比如 1000样本重复率高容易过拟合最近的经验太大则早期经验长期占据缓冲区策略已经变了但旧数据还在拖后腿。batch_size一般 32 到 128太小梯度噪声大太大显存吃紧且更新次数少。采样用random.sample是无偏的有些源码用「优先经验回放」Prioritized Replay按 TD 误差加权采样效果更好但实现复杂第一次读源码不必强求。2.4 训练主循环目标网络更新频率是最容易设错的参数训练循环里有两个网络在线网络每步更新和目标网络定期同步。目标网络的作用是稳定 TD 目标如果两个网络完全同步Q 值会追着自己的尾巴跑直接发散。# 目标网络同步硬更新每 C 步复制一次参数 if step % target_update 0: target_net.load_state_dict(policy_net.state_dict()) # 计算 TD 目标 with torch.no_grad(): next_q target_net(next_state).max(dim1)[0] target_q reward gamma * next_q * (1 - done) # 当前 Q 值 current_q policy_net(state).gather(1, action.unsqueeze(1)).squeeze(1) loss nn.SmoothL1Loss()(current_q, target_q)target_update设多少CartPole 上 100 到 500 都行LunarLander 上我一般用 200。设太小比如 10目标网络频繁变等于没起到稳定作用设太大比如 5000目标网络太滞后学习速度慢。gamma折扣因子 0.99 是默认值任务越长期比如需要几十步才能拿到关键奖励越要接近 0.99 甚至 0.995。epsilon探索率从 1.0 线性衰减到 0.05 或 0.01衰减步数一般占总训练步数的 10% 到 30%衰减太快会陷入局部最优太慢则前期浪费大量随机动作。3. 把源码跑起来依赖、命令和第一次训练该看什么拿到源码后别急着改算法先让它在一个已知能收敛的环境上跑通。CartPole 是最低门槛的验证环境状态 4 维、动作 2 个正常 DQN 在几百个 episode 内就能稳定拿到 200 分以上。如果 CartPole 都跑不收敛问题一定在代码或参数不在算法本身。3.1 依赖安装gym和gymnasium不要混装# 常见做法新建虚拟环境避免和系统包冲突 python -m venv dqn_env source dqn_env/bin/activate # Windows 用 dqn_env\Scripts\activate # 如果源码用新版接口 pip install gymnasium torch numpy # 如果源码用老版接口 pip install gym0.21.0 torch numpy混装gym和gymnasium会导致import gym时拿到错误的包报错信息往往是AttributeError: module gym has no attribute make。判断源码用哪一代看reset()的调用方式state env.reset()是老版state, info env.reset()是新版。torch版本不用太纠结CPU 版就够跑 CartPole 和 LunarLander有 GPU 的话torch.cuda.is_available()确认一下源码里如果有.cuda()硬编码CPU 环境会直接报错。3.2 第一次训练先跑 50 个 episode 看奖励趋势python train.py --env CartPole-v1 --episodes 50 --render False不要一上来就跑 1000 个 episode先跑 50 个看奖励有没有上升趋势。正常情况前 20 个 episode 奖励在 10 到 30 之间波动随机策略水平30 到 50 个 episode 开始出现 50 以上的峰值。如果 50 个 episode 奖励始终在 20 以下且方差极小检查epsilon是不是没衰减或者回放池根本没存进去数据。训练日志里重点看三个量episode_reward、loss、epsilon。loss在早期会比较大TD 误差大随着训练逐渐下降但不会到 0如果loss变成nan多半是学习率太大或奖励没归一化。epsilon应该从 1.0 稳步下降如果一直停在 1.0说明衰减逻辑没触发。3.3 验证训练结果加载模型跑测试别只看训练曲线# 测试脚本关闭探索加载训练好的权重 policy_net.load_state_dict(torch.load(dqn_cartpole.pth)) policy_net.eval() state env.reset() total_reward 0 done False while not done: with torch.no_grad(): action policy_net(torch.tensor(state).unsqueeze(0)).argmax().item() state, reward, done env.step(action) total_reward reward print(fTest reward: {total_reward})训练曲线好看不代表模型真的好。有些源码在训练时用了render或环境本身的随机性导致训练奖励虚高。测试时必须policy_net.eval()关闭 dropout 和 batchnorm如果有并且用argmax选动作而不是epsilon-greedy。跑 10 次测试取平均CartPole 上稳定在 200 以上才算真正收敛。如果测试奖励远低于训练奖励说明过拟合了训练后期的经验需要增大回放池容量或降低学习率。4. 调参与排错DQN 不收敛时先查这五个地方DQN 的坑大多不在算法公式而在工程实现和参数配合。下面五条是我在复现不同源码时反复遇到的按「现象 → 原因 → 解决」整理遇到不收敛可以逐条对照。4.1 奖励曲线一直躺平loss也不下降现象训练 200 个 episode奖励始终在随机水平loss在某个值附近震荡不降。原因通常是学习率太大导致每次更新都把 Q 网络推离目标太远或者目标网络根本没更新。解决把学习率从1e-3降到1e-4或5e-4确认target_update的逻辑在训练循环里被真正执行。我见过一份源码把target_update写成了if step % target_update 0但step在内部循环里没累加目标网络永远停在初始化状态。4.2 奖励偶尔冲高然后突然崩掉现象训练到一半奖励冲到很高然后突然掉回随机水平再也起不来。原因是 Q 值过估计导致策略崩溃常见于gamma设得太大比如 0.999且奖励没有归一化。解决把gamma降到 0.99对奖励做缩放比如除以 10或者改用 Double DQN 的目标计算方式——用在线网络选动作、目标网络算价值能显著缓解过估计。4.3 训练速度极慢GPU 利用率低现象每个 episode 要跑好几秒nvidia-smi显示 GPU 利用率不到 20%。原因是回放池采样和网络更新之间的数据搬运太频繁或者batch_size太小导致 GPU 一直在等数据。解决把batch_size从 32 提到 64 或 128把state和next_state提前转成torch.tensor放在 GPU 上减少每步的 CPU-GPU 拷贝。如果环境本身是 CPU 模拟的瓶颈可能在环境步进考虑用向量化环境并行采样。4.4 换了环境就完全学不动现象CartPole 上跑得好好的换成 LunarLander 或自定义环境后奖励一直不涨。原因是状态维度变了但网络输入没改或者奖励尺度差异太大。解决确认state_dim是从env.observation_space动态取的检查奖励范围——LunarLander 的奖励在 -100 到 300 之间CartPole 每步固定 1如果不做归一化LunarLander 上的 TD 误差会大几个数量级学习率需要相应调小。4.5 测试时表现远差于训练现象训练日志里奖励稳定在 200测试脚本跑出来只有 50。原因是训练时用了epsilon-greedy但测试时忘了关或者模型保存的是训练中途的权重而不是最终权重。解决测试前调policy_net.eval()确认epsilon0加载的权重文件是训练结束时的state_dict。另外检查环境是否有随机种子训练和测试用不同的种子会导致表现差异固定种子后对比才公平。5. 从能跑到好用Double DQN 改造和训练日志的读法把基础 DQN 跑通只是起点真正投入使用时Q 值过估计和样本效率低是两个绕不开的问题。Double DQN 的改动很小但效果在多数环境上立竿见影值得作为源码改造的第一刀。5.1 Double DQN 的核心改动只改一行目标 Q 值计算普通 DQN 用目标网络同时选动作和算价值导致 Q 值系统性偏高。Double DQN 把这两件事拆开在线网络选动作目标网络算该动作的价值。# 普通 DQN next_q target_net(next_state).max(dim1)[0] # Double DQN在线网络选动作目标网络评估 with torch.no_grad(): best_action policy_net(next_state).argmax(dim1, keepdimTrue) next_q target_net(next_state).gather(1, best_action).squeeze(1) target_q reward gamma * next_q * (1 - done)改动只有两行但效果在 LunarLander 上很明显普通 DQN 经常在 -100 到 0 之间震荡Double DQN 能稳定爬到 200 以上。参数上不需要额外调整gamma和target_update保持原值即可。注意best_action要用policy_net算next_q要用target_net取顺序反了就退化成普通 DQN。5.2 训练日志该盯哪几个量奖励、loss、Q 值均值奖励曲线是最终指标但滞后。训练中我更关注三个量loss是否在缓慢下降、Q值均值是否在合理范围、epsilon是否按计划衰减。Q值均值可以用current_q.mean().item()打印CartPole 上收敛后 Q 值在 100 到 200 之间如果 Q 值冲到几千说明过估计严重需要上 Double DQN 或调小学习率。loss不用追求降到 0DQN 的 loss 反映的是 TD 误差策略改进过程中 loss 会反复波动只要不发散就行。指标正常范围CartPole异常信号处理episode_reward逐步上升到 200长期低于 50查 epsilon 衰减和回放池loss波动下降不发散持续上升或 nan降学习率检查奖励归一化Q 值均值100 到 200超过 1000上 Double DQNepsilon从 1.0 降到 0.05停在 1.0检查衰减步数逻辑5.3 一个具体技巧用torch.no_grad()包住目标计算这是血泪经验。目标 Q 值的计算不需要梯度如果忘了加torch.no_grad()PyTorch 会把目标网络的计算图也建起来显存占用翻倍训练几个 episode 就 OOM。更隐蔽的是有些源码在target_q计算时没加no_grad但因为在loss.backward()之前做了.detach()表面上能跑实际上计算图已经建了一半显存泄漏慢慢累积。我现在的习惯是只要不是要更新的网络前向一律with torch.no_grad():包住不给自己留后悔药。训练 DQN 最深的教训是不要相信「跑通一次」的结果。同一个源码换个随机种子、换个环境版本、换个 PyTorch 版本结果可能完全不同。我一般会固定种子跑三次取最差的那次作为基线如果最差的一次也能收敛才认为这个配置是稳的。DQN 的玄学部分大多来自工程细节没对齐把回放池、目标网络、奖励尺度这三件事盯死剩下的就是耐心调参。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

祝福前任的话各自安好最佳实践源码拆解

祝福前任的话各自安好最佳实践源码拆解

祝福前任的话各自安好最佳实践源码拆解 很多开发者刚学完 Python 或 Java 基础语法,脑子里全是 if-else 和循环,但真让你动手搭个完整项目,立马卡壳。这不是你笨,是缺乏 最佳实践…

📅 2026/9/23 4:16:38
基于CNN的驾驶员疲劳检测与预警系统:从模型到部署

基于CNN的驾驶员疲劳检测与预警系统:从模型到部署

简介:这份资源是面向高校计算机相关专业学生的Python毕业设计完整项目,主题为基于卷积神经网络的人脸识别驾驶员疲劳检测与预警系统,适合用作毕业设计、期末大作业或课程设计,也适合想入门深度学习与计算机视觉实战的初学者。压缩…

📅 2026/9/23 4:16:38
连锁门店信息孤岛怎么破?多门店管理系统打通数据全链路

连锁门店信息孤岛怎么破?多门店管理系统打通数据全链路

五六家店的时候,微信群加Excel勉强还能撑住;开到二十家店,店长在群里报销量、财务月底跪着对账、A店缺货B店堆着一批货卖不动、老板一拍桌子问“到底有多少库存”,结果没人能答上来。这不是哪一个人的管理能力问题,这是…

📅 2026/9/23 4:16:38
MORE NEWS

更多资讯

📰

解析编程中看似矛盾的比较表达式

1. 面试题解析&#xff1a;为什么i > j && i < j && i ! j可以成立&#xff1f;这个问题看似矛盾&#xff0c;但在编程语言中确实存在成立的场景。关键在于理解不同编程语言中变量比较的机制差异。让我们从Java的实现开始拆解。1.1 Java中的自动装箱与拆…

📰

3步跑通粒子动画源码解析,告别只会抄代码

3步跑通粒子动画源码解析,告别只会抄代码 你是不是也遇到过这种尴尬?Python语法背得滚瓜烂熟,前端框架文档翻了几遍,但一让你动手做个“会动的东西”,脑子就一片空白。特别是看到那些炫酷的粒子效果,心里痒痒的,但真上手时,除了复制粘贴别人的…

📰

无标题素材如何梳理?三问法+骨架反推,快速锁定内容主线

去年年底我接到一个需求&#xff0c;对方发来一个文件夹&#xff0c;里面塞了几十份资料——有截图、随手记的笔记、几篇别人写的文章、一张手绘草图&#xff0c;文件夹名字就叫“无标题文件夹”。我打开之后第一反应是“这活儿没法干”&#xff0c;因为这些东西之间看起来毫无…

📰

PDF怎么改字?三条高效编辑路线深度拆解

我微信里隔三差五就有人来问一句&#xff1a;PDF怎么改字&#xff1f;发出去的文件突然要改个日期&#xff0c;客户发来的合同想加一行补充条款&#xff0c;导师给的文献想在上面圈几笔&#xff0c;结果打开PDF发现里面的文字根本点不动。说实话&#xff0c;这几乎是每个办公党…

📰

微信长按8个隐藏技巧:聊天、语音转文字、提取文字一步搞定

微信我们天天都在用&#xff0c;但大多数人真的只把它当成了一个“聊天框”。实际上&#xff0c;微信很多高频动作都藏有一个统一的交互入口——长按。只要你按住聊天消息、图片、语音、会话列表、桌面图标&#xff0c;很多需要三步五步才能完成的操作&#xff0c;一步就能做到…

📰

GIMP 3.0实测:能否取代Photoshop与Affinity Photo?

GIMP 3.0等了七年才憋出来&#xff0c;这在开源圈里也算是拖延症晚期了。但2025年这个正式版放出来之后&#xff0c;我实实在在用了两个月&#xff0c;中间还顺手把工作流里的好几张商业插画、修图任务都拿它过了几遍。今天不吹不黑&#xff0c;就着"能不能取代Photoshop和…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬