尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

强化学习从Q-learning到DDPG:完整代码拆解与避坑指南

发布时间:2026/9/28 16:38:27

资讯中心
01
ARTICLE

强化学习从Q-learning到DDPG:完整代码拆解与避坑指南

强化学习从Q-learning到DDPG:完整代码拆解与避坑指南
简介一套强化学习从基础到进阶的全套学习与实践资源面向算法、人工智能及深度学习方向的学习者适合从零起步系统掌握强化学习核心概念。内容覆盖马尔可夫决策过程、表格型方法、策略梯度、DQN基础与进阶、PPO、演员-评论家、DDPG、TD3、SAC等主流算法并配套Q-learning悬崖寻路、DQN实现CartPole、Policy-Based方法实现Pendulum三个实战项目覆盖离散与连续动作场景。压缩包共223个文件以Python脚本56个、图片57个、npy数据55个、Markdown文档14个和Jupyter Notebook6个为主整体大小173.43MB目录按章节与项目组织便于对照学习。资源包含各算法的完整实现代码、训练图表、模型权重如TD3/SAC参数文件及说明文档代码注释清晰配合笔记可快速理解算法细节帮助读者从理论到实践提升技能。目前已有678人学习下载对希望结合代码深入理解强化学习的进阶读者有较高参考价值。1. 强化学习码源包拆解从 Q-learning 到 DDPG 的完整链路强化学习最不缺教材缺的是把理论公式和能跑的代码一一对应的那层胶水。这份《强化学习从基础到进阶-案例与实践》就是冲着这个痛点来的从 MDP 讲到 DDPG每一章都有可执行的 notebook 码源Q-learning 解决悬崖寻路、DQN 实现 CartPole-v0、策略梯度类算法跑 Pendulum-v0三个项目刚好覆盖表格型方法、离散动作、连续动作三条主线。适合两类人一类是刚学完贝尔曼方程、想动手但不知道从哪个算法下手的新手另一类是跑过不少 demo、想系统梳理 DQN 进阶技巧和 PPO/DDPG 边界的老手。下面我按实际拆包顺序把资源结构、复现步骤和踩过的坑从头捋一遍。2. 资源全貌与推进路径先看什么、后跑什么2.1 文件结构与内容定位解压之后是 6 个文件看起来不多但每个文件的定位很清楚。我在拆包第一遍时把它们按「热身、主干、说明」分成了三类文件定位对应章节task0.ipynb环境交互热身理解 observation/action/reward/done第一章基础task0_train.ipynb回合制训练循环骨架展示一个最小可用的训练流程第一、二章QLearning.ipynb表格型 Q-learning 完整实现第三章表格型方法 项目一DQN.ipynbDQN 基础与进阶技巧实现第六至八章 项目二README.md整体目录与算法对照说明全部racetrack_env.md自定义赛道环境的建模说明扩展练习task0.ipynb 和 task0_train.ipynb 单独拆成两份是有道理的。很多新手第一次跑强化学习代码其实还没搞清 gym 环境里 step 返回的到底是哪几个值就直接跳进 DQN 的训练循环结果 obs 的维度、done 的布尔语义全是懵的。task0 就是把「环境接口长什么样」这件事先趟一遍task0_train 再给出训练循环的骨架让你知道一个 episode 从 reset 到 done 是怎么串起来的。QLearning.ipynb 和 DQN.ipynb 是整套资源的主干。前者对应第三章表格型方法解决的是悬崖寻路这种状态空间有限的经典问题后者对应第六到八章 DQN 基础与进阶在 CartPole-v0 上做实验。README.md 建议先读里面应该写了算法和章节的对照关系racetrack_env.md 则是讲怎么自定义一个 gym 风格的环境属于进阶玩法。2.2 两套学习路径我按读者基础给两条推进路线。零基础路径建议三周走完第一周只碰 task0 和 QLearning.ipynb把 Q 表更新、epsilon-greedy 策略、回合制回报统计搞透第二周进入 DQN.ipynb重点理解经验回放池和目标网络这两块是 DQN 能稳定训练的核心第三周看策略梯度相关内容然后回到 Pendulum-v0 项目把连续动作问题串起来。有监督学习或深度学习基础的人可以直接从 DQN.ipynb 切入因为神经网络前向传播、反向传播这些你已经熟了缺的只是强化学习特有的训练范式。这种情况下我一般建议把 QLearning.ipynb 当作对照物快速扫一遍——不是要你实现一遍而是要你把「表格查值」和「网络拟合 Q 值」这两件事对应起来后面理解 DQN 的损失函数会顺畅很多。2.3 环境准备与运行顺序这套资源的主体是 Jupyter notebook运行前提是 Python 环境里有 gym、numpy、torch 和 matplotlib。我习惯用 Anaconda 单独建一个环境避免和手上的其他项目互相污染依赖conda create -n rl python3.8 conda activate rl pip install gym numpy matplotlib torch jupyter jupyter notebook这里的重点是 gym 的版本问题。gym 在 0.26 之后的 API 变动很大step 函数从返回 4 个值变成 5 个值obs, reward, terminated, truncated, info很多网上抄来的旧代码直接解包会报错。如果你跑的版本和资源作者不一致第一步不是改代码而是先看 README.md 里有没有写版本要求没写的话优先用pip install gym0.25.2这种旧版稳定版跑因为大多数经典强化学习教程都基于旧版 API 写。环境配好之后运行顺序按照 README 的目录来task0 - task0_train - QLearning - DQN。不要一上来就全选运行notebook 是交互式的每一步的输出都值得停下来看一遍。特别是 QLearning.ipynb 里应该有 Q 表可视化的部分把路径画出来的那一瞬间对「表格型方法」的理解会比读十遍公式都深刻。3. Q-learning 解决悬崖寻路Q 表迭代的三个关键参数3.1 悬崖寻路问题建模悬崖寻路Cliff Walking是 Sutton 书里的经典环境也是这套资源里第一个完整项目。环境是一个 4 行 12 列的网格起点在左下角终点在右下角起点和终点之间那一整排是悬崖。智能体每走一步得 -1掉下悬崖得 -100 并被拉回起点。目标是从起点走到终点让累计回报最大。选这个问题做第一个项目非常合适因为状态空间只有 48 个格子动作只有上下左右 4 种一张 Q 表直接装得下。你不需要考虑神经网络、特征提取、经验回放这些工程问题只需要盯住「Q 表怎么更新」这一件事。而且这个环境有一个特别直观的观察点Q-learning 学出来的最优路径是贴着悬崖边走的最短路径而 Sarsa 学出来的是绕开悬崖的保守路径。原因后面讲这个差异是理解 on-policy 和 off-policy 的绝佳素材。3.2 Q 表更新核心代码QLearning.ipynb 里的核心更新逻辑拆出来就是下面这段# Q-learning 单步更新 # Q(s,a) - Q(s,a) lr * (r gamma * max_a Q(s,a) - Q(s,a)) def q_learning_update(q_table, state, action, reward, next_state, lr0.1, gamma0.99): best_next np.max(q_table[next_state]) q_table[state, action] lr * ( reward gamma * best_next - q_table[state, action] ) return q_table # epsilon-greedy 选择动作 if np.random.rand() epsilon: action env.action_space.sample() # 探索 else: action np.argmax(q_table[state]) # 利用这段代码里最容易看漏的是best_next这一行。Q-learning 是 off-policy 算法更新目标用的是「下一个状态里所有动作中最大的 Q 值」不管当前策略接下来到底选哪个动作。这一行就是 Q-learning 和 Sarsa 的分水岭Sarsa 用q_table[next_state][next_action]也就是当前策略实际会选的那个动作的 Q 值。所以 Q-learning 更激进学出来的策略是理论最优Sarsa 更保守学出来的是「在探索噪声下还比较安全」的策略。参数设置上lr0.1表示每次更新最多移动 10% 的差距太大的学习率会让 Q 值来回震荡gamma0.99意味着智能体重视远期回报愿意为到达终点多走几步epsilon必须从 1.0 开始衰减否则前期探索不足Q 表里没被访问过的格子永远是 0后面就没法利用了。3.3 超参数与收敛判据训练循环里建议每跑完一个 episode 就统计一次总回报同时记录当前的 epsilon 值方便观察探索和利用的平衡过程。一个 episode 内步数超过 200 就强制截断避免智能体在悬崖边上反复横跳永不落地。关于 epsilon 衰减我一般会这样设置总训练 episode 数为 500前 250 个 episode 让 epsilon 从 1.0 线性降到 0.1后 250 个降到 0.01。衰减太快会导致前期很多状态没探索到衰减太慢则会让后期策略在最优路径附近随机抖动回报曲线抖得厉害。判断收敛不能只看单次回报要看最近 50 个 episode 的滑动平均——如果曲线稳定在 -13 左右说明智能体找到了从起点到终点的 13 步最短路径如果一直停在 -20 以下优先怀疑 epsilon 衰减太慢或学习率过大。4. DQN 实现 CartPole-v0经验回放、目标网络与曲线判读4.1 从 Q 表到神经网络悬崖寻路能靠 Q 表硬扛是因为状态空间只有 48 个格子。CartPole-v0 就完全不一样了它的状态是连续 4 维小车位置、小车速度、杆的角度、杆的角速度。你不可能把连续空间离散成一张表所以必须换思路——用一个神经网络去拟合 Q(s,a) 函数。这个环境的动作空间只有 2 个左推、右推reward 设计是每保持直立一步 1最高 200 分。网络结构不需要深一层 64 单元的 MLP 就够用输入是 4 维状态输出是 2 个 Q 值。很多新手上来就用 ResNet 结构纯属杀鸡用牛刀在 CartPole 上反而容易不收敛。项目值状态空间4 维连续位置、速度、角度、角速度动作空间2 维离散左/右单步奖励1保持直立终止条件角度超过 12 度 / 小车出界 / 达到 200 步4.2 经验回放池实现DQN 里最容易抄错、也最影响稳定性的模块就是对经验回放池的实现。回放池的本质是一个固定容量的双端队列把智能体和环境交互产生的(state, action, reward, next_state, done)存进去训练时随机采样一小批from collections import deque import random class ReplayBuffer: def __init__(self, capacity10000): self.buffer deque(maxlencapacity) def push(self, s, a, r, s_, done): self.buffer.append((s, a, r, s_, done)) def sample(self, batch_size): batch random.sample(self.buffer, batch_size) return zip(*batch)deque(maxlencapacity)这个写法是关键容量满了之后新数据会自动顶掉最旧的数据不需要你手动清理。容量设 10000 到 50000 都行太小的话样本多样性不够网络刚记住一个模式就被新样本覆盖太大则会让训练集里混入大量和当前策略不匹配的旧样本收敛变慢。采样用random.sample是为了打乱样本之间的时间相关性。强化学习的样本是序列数据相邻样本高度相关如果不打乱网络会因为梯度方向过于一致而陷入局部震荡。这就是回放池存在的根本原因——它把在线学习的序列数据变成了近似独立的离线数据集。4.3 目标网络与训练循环DQN 训练时最经典的问题是「自举导致的 Q 值发散」。如果用同一个网络去算预测值和目标值每次更新都会让目标也跟着动损失函数就不稳定。解决办法是冻结一个目标网络定期同步参数# 训练循环中计算 Q_targettarget_net 参数不参与梯度更新 with torch.no_grad(): q_next target_net(next_state_batch).max(1)[0] q_target reward_batch gamma * q_next * (1 - done_batch) loss criterion( q_net(state_batch).gather(1, action_batch), q_target.unsqueeze(1) ) optimizer.zero_grad() loss.backward() optimizer.step() # 每 C 步同步一次参数 if step % C 0: target_net.load_state_dict(q_net.state_dict())目标网络的值不参与梯度计算所以要用torch.no_grad()包起来这是新手最容易漏的。gather是取网络中对应动作的 Q 值因为动作是离散的我们需要的是智能体实际执行那个动作的预测值。(1 - done_batch)的作用是让终止状态的 Q_target 只等于即时奖励不再累加未来回报否则训练目标会被污染。同步频率 C 一般取 500 到 2000 步。同步太频繁目标网络和在线网络几乎同步变化自举问题没解决同步太慢目标网络和当前策略差距过大学习效率暴跌。如果你在 DQN.ipynb 里看到 Double DQN 的实现它的改动很小选动作用在线网络q_net评估 Q 值用目标网络target_net本质上是把「选择」和「评估」解耦能明显缓解 Q 值过估计。4.4 训练曲线的读法CartPole-v0 的回报上限是 200判断训练是否成功很简单看最近 100 个 episode 的平均回报是否稳定在 195 以上。但这里有个隐藏坑——200 分不代表你的策略真稳定因为环境在 200 步时会强制截断并判定成功可能智能体在第 201 步就会倒你根本看不到。要验证策略质量应该把训练好的模型单独跑 20 个 episode去掉截断限制看它到底能撑多少步。训练曲线震荡是很正常的特别是前 500 个 episode回报在 20 到 100 之间跳动都算健康。真正该担心的是两种形态一种是一直趴在底部不动说明回放池或学习率有问题另一种是已经到 180 分左右又突然崩回 50 分说明 epsilon 衰减太快或目标网络同步太频繁。读曲线的时候记住一个原则单次波动不看看滑动平均的走势。5. 强化学习训练避坑五个高频翻车场景与排查5.1 Q 表不收敛与回报停滞现象悬崖寻路训练了 500 个 episode滑动平均回报一直停留在 -80 左右的水平最优路径始终学不出来。原因最常见的是 epsilon 没有衰减或者学习率设置太大。epsilon 恒定为 1.0 意味着智能体永远在随机探索即使找到过终点后续也会因为随机动作偏离路径Q 表难以稳定学习率超过 0.5 则会让 Q 值在每次更新时过度跳动前后两次更新互相抵消。解决把 epsilon 改成随 episode 线性衰减从 1.0 降到 0.01学习率降到 0.1 并观察回报曲线是否变得更平滑。另外检查一下 Q 表初始化值全部初始化为 0 是常见做法如果初始化为很大的正数前期会严重抑制探索。5.2 DQN 损失下降但回报不涨现象loss 一直在下降看着训练很顺利但 episode 回报卡在 50 分左右怎么都上不去。原因loss 下降只能说明网络在拟合回放池里的数据不能说明策略变好了。很可能是回放池里大部分样本都来自训练初期的失败探索也就是 done1 的数据占比过高。网络在这种数据分布上学到的规律是「动一下就会死」于是策略变得极度保守不敢做任何尝试。解决打印回放池里 done1 样本的占比。如果超过 30%说明训练过程不稳定需要调低学习率、增大回放池容量。同时可以把网络最后几层改成 Xavier 初始化避免输出 Q 值在初始阶段就过大或过小影响了损失函数的量级。5.3 gym 版本不一致导致代码直接崩现象env.step(action)返回 5 个值代码按 4 个解包直接抛 ValueError或者gym.make(CartPole-v0)直接报错说环境不存在。原因gym 在 0.26 版本引入了 terminated 和 truncated 的拆分step 返回值从 4 个变成 5 个同时旧环境名 CartPole-v0 在新版里被改名或移除。这类问题不是代码逻辑错纯粹是环境版本和代码版本不匹配。解决先确认 README 里要求的 gym 版本没写的话直接pip install gym0.25.2把这个经典版本锁死。如果你必须用新版 gym就把解包代码改成obs, reward, terminated, truncated, info env.step(action)并把done换成terminated or truncated训练逻辑语义保持不变。5.4 相同代码复现不出相同结果现象跑三次训练三次曲线都不一样有时候收敛有时候发散无法判断算法到底有没有问题。原因没有设置随机种子。强化学习涉及环境随机性、网络初始化随机性和样本采样随机性三层随机叠加结果自然不可复现。另外 PyTorch 在 CUDA 上的操作本身就是不确定的即便设置了种子也不能完全锁定。解决在训练脚本开头统一设置三处随机种子random.seed(seed)、np.random.seed(seed)、torch.manual_seed(seed)同时调用env.seed(seed)。如果用了 CUDA再加torch.backends.cudnn.deterministic True。注意要在创建环境之前设置env.seed否则环境内部的状态已经初始化了种子无效。5.5 Pendulum 连续动作训练输出 NaN现象训练到某一步之后 Actor 网络输出直接变成 NaNreward 曲线从正常数值瞬间掉成空白整个训练作废。原因连续动作空间的 Q 值量级通常比离散动作大很多梯度过大导致参数爆炸是 NaN 的第一来源。另一个常见诱因是 OU 噪声的方差设得太大探索动作超出了环境允许的 action_space 边界环境报错或返回异常值。解决给网络参数加梯度裁剪torch.utils.clip_grad_norm_(actor.parameters(), max_norm1.0)同时在把动作喂给环境之前用np.clip(action, -2.0, 2.0)把输出限制在和环境 action_space 一致的范围。排查 NaN 时优先打印每一步的 Q 值和 Actor 输出看崩溃是从哪一步开始的而不是直接调参重跑。6. Pendulum-v0 上的策略梯度与 DDPGsoft update 的小参数大讲究6.1 从 REINFORCE 到 PPO方差问题策略梯度类算法解决的是「动作空间本身就连续」的问题——你没法用 max 操作去枚举所有动作。REINFORCE 是最朴素的策略梯度但它用完整回合的累计回报作为权重方差极大。PPO 的核心改进是用重要性采样配合 clip 目标函数把每次参数更新的步长限制在一个可信范围内这也是为什么 PPO 成了目前强化学习工程师手里的默认选项。6.2 DDPG 的 soft update 技巧DDPG 用确定性策略 actor-critic 架构解决连续控制问题比 PPO 多了两个额外的东西一个目标策略网络和一个软更新机制。软更新的代码很短却是训练成败的关键# soft update: 让目标网络缓慢逼近在线网络 tau 0.005 for target_param, param in zip(target_actor.parameters(), actor.parameters()): target_param.data.copy_(tau * param.data (1 - tau) * target_param.data)tau这个参数直接控制目标网络的跟随速度。tau0.005意味着每步更新只把目标网络往在线网络方向移动 0.5%一百步之后累计移动约 39%。如果像我第一次跑 Pendulum 那样顺手把 tau 设成 0.1目标网络几乎是在瞬间复制在线网络的参数自举发散问题立刻出现两三千个 episode 下来曲线还是毫无起色。换成 0.005 之后不到 800 个 episode 就看到了稳定的正弦跟踪曲线。从那以后我每跑一个新环境都会强制走一遍三件套锁死 gym 版本、设全随机种子、画 episode reward 曲线任何一步缺失就直接定位到具体环节不再盲目调参。这份资源的正确打开方式就是把它当成一个对照实验台——同一个环境把软更新开关、目标网络同步频率、epsilon 衰减速度换一换各跑一遍曲线差异摆在那里比读十遍公式都有用。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。