ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

强化学习从基础到进阶:Q-learning、DQN、PPO、DDPG全码源实战

强化学习从基础到进阶:Q-learning、DQN、PPO、DDPG全码源实战 简介这是一份面向强化学习入门与进阶者的系统化学习资料适合AI算法工程师、深度学习研究者以及需要落地算法实验的高校学生。内容从强化学习基础、马尔可夫决策过程与表格型方法讲起逐步涵盖策略梯度、近端策略优化、DQN及其进阶技巧、演员-评论家、稀疏奖励、模仿学习、DDPG、TD3与SAC等主流算法并嵌入Q-learning悬崖寻路、DQN CartPole、Policy-Based Pendulum三个完整实战项目体系清晰可按章节循序渐进。资源共223个文件以56个Python源码、6个ipynb示例、57张示意图、55个npy数据文件、14份markdown笔记为主还包含pt/pth等训练好的模型权重压缩包约173MB便于对照算法流程和结果。目前已有678人学习目录结构按章节清晰组织每个实战项目均有场景图示和可运行代码既适合系统自学、巩固理论也可作为算法选型、代码复现与调参实验的参考工具。1. 强化学习从基础到进阶这套全系列码源能让你少走三个月弯路很多人入门强化学习是从看理论开始的以为把MDP、Bellman方程背下来就能写算法了。真到动手那天连个CartPole都跑不稳这几乎是每个RL新人的必经之地。这套《强化学习从基础到进阶-案例与实践含码源》把理论骨架和可运行的码源绑在一起从MDP定义到Q-learning在悬崖寻路里的完整实现再到DQN、PPO、DDPG这些主流算法在CartPole-v0和Pendulum-v0上的落地每一章都有能直接打开的notebook。适合已经有Python和PyTorch基础、想快速跨过理论懂但写不出代码这道坎的从业者也适合需要一套可靠基线代码做对照实验的研究生。2. 先立理论框架MDP与表格型方法Q-learning在悬崖寻路中的完整复现2.1 先把MDP五元组讲透再谈算法选型强化学习的问题定义几乎都落在马尔可夫决策过程MDP上。MDP用五元组描述状态集合S、动作集合A、状态转移概率P、奖励函数R以及折扣因子γ。智能体在某个状态s下执行动作a环境按P(s|s,a)跳到s同时返回奖励r目标就是最大化累积折扣奖励。所有后续算法都是这个目标的投影——DQN的replay buffer存的是(S, A, R, S)四元组PPO里的GAE计算依赖γDDPG里actor-critic两个网络的损失函数也从MDP目标推导。我一般会建议先别急着写代码拿一个具体环境把五元组对号入座状态是什么、动作空间是什么、转移概率是不是已知。这样分类的好处是能立刻判断该用哪类算法P已知且有模型动态规划最快P未知且状态空间小表格型方法P未知且状态连续神经网络拟合那条路就绕不开了。Cliff Walking环境就是做这件事的完美素材它的状态是网格坐标动作是上下左右四选一转移概率未知但Q-learning完全不需要知道具体概率它会在交互里把这个转移关系隐式学出来。很多教材在这里讲得太快导致读者把环境是什么和模型是什么搞混后面看PPO里old policy、new policy就彻底晕了。所以第一遍跑这个项目时我会把每一步的state、action、reward、next_state打出来盯着看十条轨迹比背十遍定义有用。2.2 为什么悬崖寻路适合拿来做第一个项目状态空间小到能看见Q表收敛悬崖寻路Cliff Walking是表格型方法最经典的案例。环境是4×12的网格起点在左下角终点在右下角中间靠近底部的一整排是悬崖。每走一步奖励-1掉进悬崖奖励-100并回到起点。这个环境的状态空间只有48个动作空间4个Q表就是一张48×4的矩阵训练时你能肉眼看见Q值在往合理方向走。这种确定性的反馈是大环境给不了的CartPole里网络是个黑匣子你很难看出内部逻辑但48×4的Q表可以直接打印出来看看右下角的Q值是不是比悬崖边的高一眼就知道学没学会。资源包解压出来之后别急着挨个点开notebook先看README.md里面写清了gym版本和每个notebook的作用。racetrack_env.md是赛车道环境的说明文档那个环境适合拿来做Q-learning的地图变体实验。码源里task0.ipynb和task0_train.ipynb承担的任务就在这里前者带你逐行看环境交互后者是一个可以直接改参数的训练脚本。我第一次跑的时候把epsilon从0.1改成0.5发现收敛明显变慢但探索更充分最后Q表里绕远路的路径也出现了。这种体感是后面调DQN、PPO的预演因为所有算法的探索-利用平衡换汤不换药。2.3 task0.ipynb核心更新逻辑Q-learning代码拆解与参数调法# task0_train.ipynb 核心更新逻辑 def q_learning(env, episodes500, alpha0.1, gamma0.99, epsilon0.1): q_table np.zeros((env.observation_space.n, env.action_space.n)) rewards_per_episode [] for ep in range(episodes): state env.reset() total_reward 0.0 done False while not done: if np.random.random() epsilon: action env.action_space.sample() # 探索随机动作 else: action np.argmax(q_table[state]) # 利用取当前最大Q值 next_state, reward, done, _ env.step(action) # Q-learning 使用 next_state 下最大的 Q 值做目标更新 q_table[state, action] alpha * ( reward gamma * np.max(q_table[next_state]) - q_table[state, action] ) state next_state total_reward reward rewards_per_episode.append(total_reward) return q_table, rewards_per_episode代码里三个参数直接决定训练行为。alpha是学习率控制每次更新的步长取0.1时更新平稳取0.5时学得快但容易震荡。gamma是折扣因子0.99表示更看重长期收益悬崖寻路里这个值不能太小否则智能体只顾眼前一步永远学不会绕开悬崖。epsilon是探索率每条轨迹里以epsilon概率随机动作它和alpha之间需要平衡——epsilon太高导致Q表难以稳定太低又容易掉进局部最优。task0_train.ipynb里episodes设500正常跑完reward曲线会从-100多爬升到-20左右这个数值和经典实现基本一致。如果你想更快看到收敛把alpha调到0.15、epsilon从0.1线性衰减到0.01大概300轮就能稳定。更新式里还有一个容易被忽略的细节目标用的是next_state下最大的Q值而不是实际执行动作的Q值。这是Q-learning和Sarsa最本质的差别也是它被称为off-policy的原因。在悬崖寻路里Q-learning学出来的策略比Sarsa更激进因为它总是假设下一步选择最优动作哪怕实际探索时会踩到悬崖。这个差别在工程上很重要off-policy方法可以复用历史数据on-policy方法每换一次策略就要重新采集这直接影响后面DQN和PPO的数据效率对比。注意如果你直接把np.random.random() epsilon改成 0.5去测试Q表大概率会在两个动作之间反复横跳先确认参数再改策略。2.4 表格型方法的边界从Cliff Walking到连续状态空间的失灵现场表格型方法能用的前提是Q表存得下。让状态变成连续量比如CartPole的四维观测或者Pendulum的角度加角速度Q表直接爆炸。很多新人在这儿翻车自己把连续状态离散化网格切细了维度爆炸切粗了又学不好。这不是算法问题是表达能力的硬边界。不过离散化在工程里没那么不堪工业控制里把角度、速度分几个档位Q-learning照样能用只是通用性差。这套码源的编排逻辑就在这儿先用Cliff Walking把Q-learning吃透再切到DQN.ipynb看神经网络是怎么替代Q表的。MDP、表格方法、连续状态这三个概念在同一章里打通后面理解DQN的经验回放就顺理成章了。如果你在task0里停留太久觉得自己没学透不敢往下走我劝你直接往前看——Q-learning的边界只有真正对比过DQN才会理解光靠想是想不出来的。3. DQN从入门到进阶CartPole-v0的经验回放与target network实战3.1 从Q表到DQN经验回放解决什么问题DQN相对Q-learning最大的变化是把Q表换成了神经网络同时引入两个工程上至关重要的部件经验回放replay buffer和target network。经验回放解决样本相关性问题——on-policy采集的相邻样本高度相关直接用它们的梯度更新网络参数会剧烈震荡把样本存进buffer里随机抽样打破了时序相关性样本还能复用训练效率也上来了。buffer大小一般设10万起步batch_size取64或128。这里有个常见误用有人直接把Q-learning的更新搬进DQN每步都更新网络结果reward曲线像过山车。原因不是算法错而是缺少经验回放这个稳定器。我自己的习惯是让buffer先存够5000条再开始训练前5000步纯随机探索效果比边采集边训练稳得多。target network解决的是目标漂移问题如果用同一个网络同时算Q值和目标值每一步更新都在移动靶子训练容易发散。DQN的做法是维护一个参数滞后拷贝的target_net每隔C步从当前q_net同步一次参数。这个C在CartPole上我习惯设1000步太大目标更新太慢太小又失去了稳定的意义。3.2 Double DQN与Dueling DQN进阶技巧的代码结构Double DQN的出现是因为基本DQN会系统性高估Q值。原因在于max操作天然偏向取大噪声环境下这个偏差会被放大。Double DQN的做法是把动作选择和价值评估分开用当前q_net选出最优动作再用target_net去评估这个动作的价值。代码改动很小就是把target计算里的target_net(next_states).max(dim1)[0]改成target_net(next_states).gather(1, q_net(next_states).argmax(dim1).unsqueeze(1))。这个改动在CartPole上体现不充分但在动作多、回报稀疏的环境里能明显缓解过估计。Dueling DQN则是改了网络结构把Q值拆成状态价值V和优势值A的和Q(s,a)V(s)A(s,a)。网络末端分成两支最后聚合。这个结构的好处是即使某些动作对当前状态没有明显区分度网络也能单独学到状态本身的优劣在奖励稀疏的场景更稳定。码源里第七章进阶技巧对这两个方向都给了实现我建议跑完基本DQN之后单独做一次对比实验用同一组随机种子对比reward曲线你会直观看到进阶技巧不是玄学而是有明确工程动机的改动。3.3 DQN.ipynb训练循环拆解可以直接改参数的骨架# DQN.ipynb 训练循环核心片段 for step in range(total_steps): if len(replay_buffer) batch_size: action env.action_space.sample() # 预热阶段完全随机 else: action epsilon_greedy(q_net, state, epsilon) next_state, reward, done, _ env.step(action) replay_buffer.push(state, action, reward, next_state, done) state next_state if len(replay_buffer) batch_size: batch replay_buffer.sample(batch_size) states, actions, rewards, next_states, dones batch with torch.no_grad(): # target_net 不参与梯度 targets rewards gamma * target_net(next_states).max(dim1)[0] * (1 - dones) q_values q_net(states).gather(1, actions.unsqueeze(1)).squeeze(1) loss F.smooth_l1_loss(q_values, targets) optimizer.zero_grad() loss.backward() optimizer.step()这段代码有几个细节值得停下来看。第一target_net(next_states).max(dim1)[0]取的是每个next_state下最大的Q值它和Q-learning更新式里的np.max(q_table[next_state])是同一个思想只是从查表变成了网络前向。第二(1 - dones)这个乘子很关键终止状态没有后续奖励目标就应该等于当前reward而不是reward加一个根本不存在的未来期望。很多新手在改写时把dones漏掉结果训练末期reward被系统性压低。第三loss用的是smooth_l1_loss而不是MSE它在误差大时梯度平缓误差小时梯度细致比MSE更抗离群点冲击。这三个细节如果对不上号训练发散时你很难定位问题。训练参数方面CartPole-v0默认用gamma0.99、learning_rate1e-3、batch_size64epsilon从1.0衰减到0.01衰减步数设50000。我用这套参数跑基本上2000步左右reward就能过200。如果你发现训练很久都过不了150优先查两件事一是target_net同步间隔C是不是设得太小二是epsilon衰减是不是太快导致探索没做够网络就锁死了。这两个参数一个管稳定性一个管探索是DQN调参里最先要动的旋钮。3.4 主流深度强化学习算法这么多为什么还要先把DQN吃透现在是个人都在谈TD3、SAC、RainbowDQN但我要说一句这些算法几乎都是DQN思路的延伸。TD3在DDPG基础上加了双Q函数和延迟更新DDPG本身是DQN和actor-critic的结合SAC用熵正则让策略更随机底子还是soft Q-learningRainbowDQN就是把Double、Dueling、优先经验回放、多步回报这些技巧叠在一起。所以码源第八章到第十二章的连续动作演员-评论家稀疏奖励模仿学习这些专题你追到根上都能找到DQN的骨架。先把CartPole-v0这个项目做透后面切Pendulum-v0时就不会被概念轰炸打懵。4. 从策略梯度到PPO再到DDPG连续动作空间的进阶之路4.1 策略梯度定理与REINFORCE的最简实现策略梯度方法和值函数方法的区别要放在台面上讲值函数方法先学Q值再选动作策略梯度方法直接参数化策略π(a|s;θ)用梯度上升最大化期望回报。REINFORCE是最简单的策略梯度实现它用一条完整轨迹的累计折扣回报作为动作好坏的信号让动作的log概率按回报方向拉伸。这个算法方差大但它是理解PPO的必经一站。码源第四章的策略梯度章节会推到这一步核心更新逻辑落在下面这段代码上。# REINFORCE 核心更新逻辑一条轨迹算一次梯度 for t in range(len(log_probs)): # G_t 是从 t 时刻往后的折扣累计回报 policy_loss policy_loss - log_probs[t] * G_t policy_loss policy_loss / len(log_probs) optimizer.zero_grad() policy_loss.backward() optimizer.step()这里有个关键点G_t的方差非常大一条轨迹的偶然性会被当作梯度信号放大。所以REINFORCE在Pendulum这种连续动作环境里几乎训练不动只能靠大量并行轨迹摊方差。这就是策略梯度章结束后要立刻切PPO的原因——PPO就是为了控制这个方差和步长而生的。很多人跳过REINFORCE直接看PPO的clip公式遇到为什么需要新旧策略比率就卡住。我建议还是花二十分钟把REINFORCE跑一遍你才会真正理解PPO的clip到底救了什么。4.2 PPO的clip机制工程上最稳的on-policy算法PPO在策略梯度基础上加了一个约束。它定义新旧策略的比率r_t(θ)π_θ(a|s)/π_θ_old(a|s)然后把这个比率clip在[1-ε,1ε]里。直观理解就是这次更新不许比旧策略偏离太多好处大于坏处时最多涨ε坏处大于好处时最多跌ε步长被硬性锁住。这个性质让PPO在超参数不敏感的情况下仍然稳定是工业界默认的on-policy首选。码源第五章的PPO章节围绕这个clip展开了完整推导和实现核心损失函数如下。# PPO 核心 clipped surrogate loss ratio torch.exp(log_prob_new - log_prob_old) surr1 ratio * advantage surr2 torch.clamp(ratio, 1.0 - clip_eps, 1.0 clip_eps) * advantage actor_loss -torch.min(surr1, surr2).mean()clip_eps通常取0.2。advantage用GAE计算GAE通过λ参数平衡偏差和方差λ→0时接近一步TD方差小偏差大λ→1时接近MC回报偏差小方差大。PPO里λ取0.95是最常见的起点。这里要提醒一句PPO是on-policy算法每次更新完策略后旧的replay buffer必须清空不能用DQN的经验回放复用数据。这个差别在码源里被反复强调因为很多从DQN切过来的新手会习惯性把buffer留着结果训练曲线诡异得一塌糊涂。4.3 DDPG与Pendulum-v0actor-critic在连续控制上的落地Pendulum-v0是连续动作空间的经典测试环境状态是摆杆角度和角速度动作是施加在关节上的连续力矩。Q表在这儿彻底失效REINFORCE方差爆炸PPO能用但样本效率一般这时候DDPG这类off-policy连续控制算法就派上用场。DDPG含四个网络actor和它的target、critic和它的target。actor输出确定性动作critic评估这个动作的Q值探索靠给动作加噪声完成。码源项目三使用Policy-Based方法实现Pendulum-v0就是围绕这套结构写的。# DDPG 更新 core在 batch 采样后 with torch.no_grad(): next_actions target_actor(next_states) target_q target_critic(next_states, next_actions) y rewards gamma * target_q * (1 - dones) # critic 回归目标 critic_loss F.mse_loss(critic(states, actions), y) critic_optimizer.zero_grad() critic_loss.backward() critic_optimizer.step() # actor 最大化 Q 值即最小化 -Q(s, actor(s)) actor_loss -critic(states, actor(states)).mean() actor_optimizer.zero_grad() actor_loss.backward() actor_optimizer.step()DDPG有四个网络更新顺序值得留意。critic要先更新因为actor的梯度依赖critic的评估actor更新完再软更新target网络软更新系数τ通常取0.005也就是每次把target参数向当前参数挪0.5%。这个τ也是DDPG翻车的重灾区取大了target跟随太快取小了训练又慢得像蜗牛。Pendulum-v0上reward的理论最优在0附近训练初期是负的几百看到reward爬升到-200以内就算正常起步不要期待它能跑到正数。另外如果你用的是新版gym环境名要改成Pendulum-v1观测和动作维度先打印核对一次再训练。DDPG对超参数敏感actor和critic的学习率我一般分开设actor用1e-4critic用1e-3同一套参数别硬套。噪声方面我习惯在actor输出的动作上加一个方差0.1到0.2的高斯噪声并且随着训练过程慢慢衰减完全去掉噪声后策略会失去探索能力这是连续动作空间里的探索-利用平衡。4.4 稀疏奖励与模仿学习这两个专题为什么值得单独看码源最后几个专题讲稀疏奖励和模仿学习这两个话题在工程上几乎是绑定的。稀疏奖励意味着智能体在绝大多数时间里拿不到任何指导信号随机探索基本无效。处理方法有三条线一是奖励塑形用领域知识补一个稠密奖励二是用HER这类算法改写目标但适用范围有限三是模仿学习让智能体先跟着专家轨迹学再进入强化学习阶段微调。码源里第十一章的模仿学习给了行为克隆的简单实现。我建议把行为克隆和PPO配合起来用先用专家数据把策略预热到能出有效动作的水平再交给PPO在环境中细调这样能省掉大量前期探索的时间。这个先模仿、再强化的组合拳比单纯堆算法工程经验有效得多也是我现在接手新任务时的默认起手式。这套基础打完之后如果你往离线强化学习方向走IQL这类算法的论文读起来也不会发怵因为行为克隆、off-policy、值函数估计这些概念你都亲手写过一遍了。5. 避坑/常见问题排查跑这套码源时最容易翻车的五个现场5.1 悬崖寻路reward一直徘徊在-100Q表没学起来现象跑完500轮每轮reward都在-100左右完全没收敛。原因epsilon设得过大或者alpha和epsilon的组合不当导致Q表始终在震荡。Q-learning更新式本身没问题是探索和利用的平衡没找对。解决先把epsilon降到0.1alpha调到0.1到0.15之间跑一轮确认能收敛到-20附近再把epsilon改到0.3以上观察探索的代价。如果你想验证epsilon过高会拖慢收敛可以单独跑一组epsilon0.5的对照对比两条reward曲线这个实验比看十页理论都直观。5.2 DQN在CartPole-v0上reward曲线震荡过不了150现象训练几千步之后reward始终在100上下震荡偶尔冲到180又掉下来。原因最常见的是target_net同步间隔C设得太短比如每100步就同步一次目标一直在变其次是epsilon衰减太快探索没做够。还有一个隐蔽因素gym版本不同会导致CartPole的终止条件有差异同样参数在旧版能跑到200新版可能只能跑到150。解决C从1000开始调epsilon衰减步数放宽到50000replay buffer预热5000步。版本方面固定gym0.21.0这类已知配置或者直接用CartPole-v1并适当调低对reward峰值的预期。排查顺序先参数后版本别一上来就改网络结构。5.3 Pendulum-v0训练时actor输出饱和到动作边界现象actor输出的动作长时间贴在-2或者2的边界上reward曲线一直很差。原因DDPG的探索噪声衰减太快或者actor学习率设得太大策略过早硬化。动作贴着边界说明actor已经放弃中间区域直接输出极端值这在连续控制里是最典型的训练失败信号。解决把探索噪声从N(0,0.1)调回N(0,0.2)τ从0.005调成0.001actor学习率降到1e-4。这个问题的本质和5.1是同一个病根都是探索-利用平衡没找对只不过发生在连续空间里症状更隐蔽。5.4 换台机器复现结果对不上现象同一份notebook昨天跑通今天跑不通或者换台电脑结果完全不同。原因没有固定随机种子PyTorch的CUDA运算有非确定性gym版本差异也会改变环境行为。很多人在自己机器上跑通一次就以为万事大吉换环境才发现结果完全不可复现。解决在代码开头固定torch.manual_seed(0)、np.random.seed(0)同时设置torch.backends.cudnn.deterministic True并在README里记录gym版本。我现在每份实验代码都会在开头写一个setup_seed()函数这是血泪经验换来的习惯。另外需要注意即便固定了种子GPU上某些算子仍然可能有微小浮动条件允许就在CPU上跑复现实验。提示固定随机种子时把torch.backends.cudnn.deterministic True也打开否则在GPU上每次前向结果仍可能不同。5.5 gym环境报错action维度或observation维度对不上现象DDPG在Pendulum上动作维度报错或者DQN在CartPole上observation维度不匹配。原因gym版本更新后环境接口有变化。Pendulum-v0在部分版本里已经改名为Pendulum-v1gym从0.21升级到0.26时环境注册方式也有调整连带着env.reset()的返回结构都可能不同。解决先看README里记录的环境版本用pip install gym0.21.0这类固定版本命令装齐。如果是新版gym把环境名改成Pendulum-v1并在训练前打印env.observation_space和env.action_space核对一次。这种问题最浪费时间的点在于报错信息会掩盖真实原因提前固定在requirements里算是你能给自己留的后悔药。6. 验证与进阶把能跑变成可信的关键动作跑通notebook只是第一步真正让你在面试和项目里站得住脚的是能证明这个算法在多次随机种子下都稳定。我的做法是用5条不同随机种子各跑一遍把每条种子的reward曲线存下来最后画均值±95%置信区间。用origin或者matplotlib都能画重点是横轴统一、纵轴统一不然对比没有意义。码源里的算法输出格式已经预留了保存reward历史的接口你只需要在外面套一层循环改种子就行。算法关键参数我常用的起点值Q-learningalpha / gamma / epsilon0.1 / 0.99 / 0.1DQNbatch_size / target同步步数C64 / 1000PPOclip_eps / GAE lambda0.2 / 0.95DDPGactor_lr / critic_lr / tau1e-4 / 1e-3 / 0.005改参数时每次只动一个其他保持不动记录实验结果。一次动三个旋钮翻车了都不知道赖谁。如果后面想往多智能体方向走这套基础打明白之后再看MAPPO、MADDPG的代码不会发怵核心的actor-critic骨架是一样的。验证还有一个被忽略的动作把训练好的策略导出做一次确定性回放关闭探索看它在环境里跑出来的轨迹。Q-learning的Q表直接argmax看路径DQN和DDPG把噪声置零看动作序列。这一步能发现很多训练曲线看不出来的问题比如策略绕远路、动作来回抖。我从那以后每次跑新算法都强制走一遍这套流程固定随机种子、5条种子跑实验、画置信区间、最后关闭探索做确定性回放。希望帮到你。本文还有配套的精品资源点击获取
返回列表