深度强化学习算法源码实战:PyTorch实现PPO、DQN、SAC与DDPG

发布时间:2026/9/8 8:27:23
深度强化学习算法源码实战:PyTorch实现PPO、DQN、SAC与DDPG 简介基于PyTorch深度强化学习算法实现合集面向需要入门强化学习并希望复现主流算法的开发者与学生。资源在Gym环境下编写涵盖PPO、DQN、SAC、DDPG、TD3等算法且针对论文复现了多种改进PPO侧包括dual-PPO、clip-PPO、RNN与attention变体DQN侧包含Rainbow DQN等离散与连续动作空间均有对应示例如CartPole和Pendulum。代码结构清晰共34个文件以23个Python脚本为主配有运行生成的Pyc缓存、示意图以及说明文档整体包体仅209KB便于快速下载与阅读。核心代码中加入TensorBoard支持可输出训练与评估指标方便对比算法收敛效果。已有613人学习下载适合希望结合Gym环境动手实践、理解深度强化学习算法原理与改进思路的读者。 很多朋友问我要过深度强化学习的源码尤其是想复现PPO、DQN、SAC、DDPG这类经典算法。理论看了一大堆真到动手的时候却发现卡在环境安装、代码理解、训练不收敛这些地方。最近整理了一套基于PyTorch实现的深度强化学习算法源码包把四种常用算法按统一风格组织在一起方便对照学习也方便直接拿去做实验对比。这套代码解决的核心问题是降低复现门槛。很多开源项目为了在论文里刷分代码写得又长又绕对新手极不友好。而我整理的这一套刻意保持了结构的统一和简单每个算法都有独立的目录公共组件抽出来复用超参数集中在配置文件里跑通一个算法之后切到另一个算法几乎不用改环境相关的内容。不管你是刚接触强化学习、想弄懂算法内部细节的初学者还是已经在做实验、需要快速验证某个改进点的研究者都可以用这套东西作为起点。1. 源码整体结构与模块化设计思路1.1 拿到压缩包先看什么解压之后不要急着跑训练脚本先把目录结构过一遍。通常这套源码包含四个部分核心算法实现、公共工具模块、训练入口脚本、配置文件目录。project_root/ ├── algorithms/ # 各算法实现 │ ├── ppo.py │ ├── dqn.py │ ├── sac.py │ └── ddpg.py ├── common/ # 公共组件 │ ├── replay_buffer.py │ ├── noise.py │ └── normalizer.py ├── scripts/ # 训练和评估入口 │ ├── train_ppo.py │ ├── train_dqn.py │ ├── train_sac.py │ └── train_ddpg.py ├── configs/ # 超参数配置 │ └── *.yaml └── requirements.txtalgorithms目录下每个文件对应一个算法common目录存的是所有算法都会用到的经验回放缓冲、噪声生成器、状态标准化工具。scripts目录是真正要执行的脚本里面读取配置文件里的超参数构建环境然后调用算法。configs文件夹里每一个yaml文件对应一个实验配置。我见过太多强化学习项目每个算法都单独写一套工具函数结果replay buffer拷贝了四份噪声生成逻辑也是各写各的。一旦要改某个公共逻辑就得同时改好几个文件极其痛苦。这套源码把公共部分抽出来目的就是减少重复代码让数据流动的方向更清晰。1.2 为什么用统一的通用模板深度强化学习代码最大的维护成本其实不在算法本身而在数据流。无论是DQN、DDPG还是SACoff-policy类算法都要依赖经验回放区别只在采样方式和存储内容不同。把它们统一成一个接口会省掉大量心智负担。class ReplayBuffer: def __init__(self, capacity, state_dim, action_dimNone): self.capacity capacity self.buffer [] self.position 0 def store_sample(self, state, action, reward, next_state, done): data (state, action, reward, next_state, done) if len(self.buffer) self.capacity: self.buffer.append(data) else: self.buffer[self.position] data self.position (self.position 1) % self.capacity def sample_batch(self, batch_size): batch random.sample(self.buffer, batch_size) state, action, reward, next_state, done map(np.stack, zip(*batch)) return state, action, reward, next_state, done这个通用接口好处很明显。DDPG和SAC这类连续控制算法共用一套实现完全没问题DQN也只需要在调用时把action从数组改成整数索引。对于新手来说从这一个类就能理清经验从哪里来、到哪里去的完整链路。代码里其他组件也一样优先保证逻辑可读而不是性能极致优化。比如噪声模块实现了高斯噪声和Ornstein-Uhlenbeck噪声两种在DDPG的配置文件里切换就行。状态标准化模块放在common里PPO和SAC都能调用因为这两个算法对输入状态的尺度非常敏感后面会详细讲。2. 四大经典算法的实现逻辑与踩坑点2.1 PPO稳定性和采样效率的权衡PPO是这四种算法里落地最广的也是我推荐新手最先去读的。它的核心思想是限制每次策略更新的幅度避免像传统策略梯度那样一步更新太猛导致训练崩掉。具体手段就是clipped surrogate objective。ratio torch.exp(new_logprob - old_logprob) surr1 ratio * advantage surr2 torch.clamp(ratio, 1.0 - clip_epsilon, 1.0 clip_epsilon) * advantage policy_loss -torch.min(surr1, surr2).mean()代码里实现得很直观。clip_epsilon通常设为0.2意思是如果新旧策略的差异过大就把优势函数的权重限制住。这样可以保证策略更新不会因为某个偶然的高回报样本就剧烈摆动。我踩过的坑是PPO对reward scaling极度敏感。同样的超参数在HalfCheetah上跑得好好的换到Walker2d上很可能就不收敛了。原因在于GAE计算优势函数时如果reward量级差异太大advantage的方差也会被放大策略更新步长实际上被撑大clipping起不到应有的作用。解决方式是在公共normalizer里对reward做标准化或者根据环境动态调整reward scale参数。2.2 DQN从表格到泛化的关键跳跃DQN在源码里的实现算是最清晰的网络就一个Q网络加一个target网络加上经验回放去掉探索噪声策略更新逻辑也不复杂。但要跑出稳定的效果有两个细节必须处理好。第一是target network的更新方式。很多初版实现是硬拷贝每隔K步直接把Q网络的权重复制过去。这套源码里默认采用了soft update也叫Polyak averagingtarget网络参数每次向Q网络移动一小步更新系数tau通常在0.005到0.01之间。for target_param, param in zip(target_net.parameters(), net.parameters()): target_param.data.copy_(tau * param.data (1.0 - tau) * target_param.data)为什么soft update更稳定因为硬拷贝会让目标值周期性跳变Q网络的回归目标本身不稳定训练过程容易震荡。soft update让目标值平滑变化类似给回归问题加了一个惯性项在不牺牲太多收敛速度的前提下大幅提升稳定性。第二是replay buffer与target network搭配的原因。如果没有经验回放网络会忘记之前见过的状态学到的Q值会偏向最新几条经验。经验回放打破了这个时间相关性让每次梯度下降用的batch在统计上更接近独立同分布这是DQN能稳定训练的重要前提。2.3 SAC最大熵到底解决了什么SAC是目前off-policy连续控制算法里最推荐用的一个。它和DDPG最大的不同是在目标函数里加了一个熵正则项让策略在多个近似最优的动作之间保持一定的随机性而不是逼成一个确定的动作。熵正则项的实际含义用大白话说就是假设有两条路都能到达目的地SAC的策略不会只挑其中一条而是在两条路之间分配概率保留探索的余地。这在真实机器人控制中意义很大因为模型对环境的估计一定有误差而保留随机性可以避免因为过度自信导致控制失败。实现熵正则的关键在温度系数自动调节。代码里不会把一个固定系数写死而是设一个target_entropy然后用梯度方法去自适应调整。多数连续控制环境的target_entropy设置为负的动作维度数比如动作维度为6就设为-6。alpha_loss -(alpha * (log_prob target_entropy).detach()).mean()这段代码让alpha在策略熵高于目标熵时增大在熵低于目标熵时减小永远保持策略的探索程度在合适区间。新手容易忽略的是alpha的学习率通常需要比policy和Q网络低一点点因为温度系数调整太快会导致探索节奏过于激进。2.4 DDPG连续控制的老牌选手DDPG是深度确定性策略梯度算法和SAC不同它输出的是一个确定性动作而不是动作分布的采样。为了让它在训练初期有探索能力代码里在动作上叠加了OU噪声或高斯噪声。DDPG真正难搞的地方在target network更新时的平滑正则。源码里实现了一种叫target policy smoothing的做法给target动作加上一个clip过的高斯噪声再把clip范围限制在动作边界内。这个做法防止Q函数对某个动作出现尖峰式的过估计避免Q值越学越偏。实际跑DDPG的实验结果经常给人感觉很玄学同一个任务换一个随机种子曲线可能差一大截。我在使用中明确感受到DDPG的调參比SAC更敏感batch size、噪声方差、tau值任何变化都可能带来完全不同的收敛结果。所以不建议在复杂环境上从DDPG开始调先把SAC跑通再回来对比会更省力。3. 环境配置与复现运行完整流程3.1 PyTorch与硬件环境准备这套源码基于PyTorch实现所以先要把PyTorch环境装好。我看很多朋友卡在环境搭建这一步其中绝大多数问题出在CUDA版本和PyTorch版本不匹配上。推荐直接用conda创建一个独立环境不要装在base环境里。创建命令很常规conda create -n rl python3.10 conda activate rl然后根据你的机器有没有NVIDIA显卡选择不同的安装方式。有GPU的话先用nvidia-smi确认驱动支持的CUDA版本再去PyTorch官网选对应的安装命令。比如驱动支持CUDA 12.1就装对应的版本pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121没有GPU就直接装CPU版本日常调试和跑小规模的实验完全够用。RNN和CNN在CPU上单进程跑会慢很多但经典强化学习实验如果不追求速度CPU版也能跑出结果。注意不要先装PyTorch再回头看CUDA版本一定要先确认显卡驱动支持哪个CUDA版本再选PyTorch版本。方向反了会陷入版本地狱。3.2 依赖安装与仓库跑通环境装好后进入项目根目录安装依赖。requirements.txt里通常包括以下包gymnasium0.29.1 numpy1.24.3 tensorboard2.14.0 pyyaml6.0这里我特别想提醒gym版本的问题。很多经典版本的gym接口和现在的gymnasium不太一样主要区别在env.reset()的返回值。老版本reset返回一个observation新版本返回(observation, info)元组。如果代码里直接写obs env.reset()在新版本下obs会被赋成一个tuple后续接状态维度就直接报错。我在这套源码里统一使用gymnasium接口所以跑环境前确认装的是gymnasium而不是旧版gym版本号锁定在0.29.1附近最稳。依赖装好后直接运行训练脚本python scripts/train_sac.py --config configs/sac_halfcheetah.yaml配置文件里把环境名、最大训练步数、学习率、buffer大小这些参数都列好了。如果想试试其他算法只需要换成对应的脚本和配置文件环境尽量保持一致这样对比出来的算法性能差异才有参考意义。3.3 用TensorBoard看训练曲线的正确姿势这套源码在训练脚本里接了TensorBoard会自动记录reward、loss、alpha这些指标。训练过程中另开一个终端tensorboard --logdir runs然后浏览器打开http://localhost:6006就能实时看到曲线。怎么看曲线才知道训练正常不正常我的经验是三件事第一看reward曲线是否有整体上升趋势不只是噪声抖动第二看critic loss是否收敛到一个相对稳定的区间如果loss一直乱跳甚至越来越大大概率是学习率太大或replay buffer里数据分布有问题第三看explained variance之类的辅助指标这个能反映优势估计的准确性。很多朋友只看reward曲线其他指标一概不看训练崩了也不知道原因在哪里。其实强化学习调试的核心就是看这些辅助指标它们比reward更能定位问题。4. 常见问题与排查技巧实录4.1 问题速查表实操过程中遇到的高频问题我整理成了一张表基本都是群里和朋友反复问过的现象可能原因解决方式训练开始后reward一直是负的reward scaling不合适或环境未标准化检查reward scale参数开启状态标准化loss变成NaN学习率过高或网络参数初始化不当降低学习率到3e-4以下检查是否有inf值传入PPO更新几次后策略崩溃clip_epsilon过大或GAE的gamma/lambda不匹配调低clip_epsilon到0.1~0.2检查lambda是否在0.95附近DQN在CartPole上稳定但在复杂环境发散target网络更新过频或replay buffer太小提高tau到0.01增大buffer到100万级SAC收敛很慢熵目标设置不当或alpha学习率过高检查target_entropy是否为负的动作维度数GPU显存占用低但训练很慢网络太浅或gym环境在CPU上计算瓶颈用并行环境采样或调大batch size4.2 训练不收敛时从哪里下手排查训练不收敛是强化学习新手最崩溃的时刻通常你会觉得代码逻辑没问题但曲线就是不动弹。我的排查顺序是固定的。第一步看reward量级。打印几条原始reward如果大到几千、小到零点零零几第一步先做reward scaling通常乘0.1或0.01就能解决很多问题。第二步看动作范围SAC和DDPG里动作要经过tanh压缩到[-1,1]如果环境本身动作范围不一样但没做映射策略会一直在边界饱和也学不出东西。第三步再看advantage估计PPO的话用debug模式把GAE计算中间步骤打出来确认done mask是否正确传递。还有一种极其隐蔽的坑是done信号的布尔类型。代码里如果错误地把done当成整数用该终止的地方没有及时截断优势估计就会把所有经验串成一个超长轨迹策略会被误导。排查时在buffer里多做一步断言看看terminal state后面是否接着出现了新episode的第一帧。4.3 超参调节的几个实测心得超参这块我直接给结论。学习率是个万能旋钮无论哪个算法只要不收敛先把所有网络的学习率降到3e-4以下试试八成能稳定下来。batch size在256到512之间是大多数环境的甜点区太小的batch会让策略更新噪音大太大的batch又可能导致更新太平滑、学得慢。buffer size和训练步数的关系也要留意。replay buffer如果太小DQN和SAC的经验多样性不足网络反复看几条旧数据很容易过拟合。但如果buffer过大且训练步数不够buffer里大部分都是早期随机探索的数据学到后面那些低质量经验会稀释新经验的信号。我习惯把buffer size设为总训练步数的五分之一到十分之一之间。随机种子问题值得多说一句。算法上了seed与不上seed效果可能天差地别这不算bug。但复现实验时必须固定环境种子、模型初始化种子、噪声生成种子不然两次结果没有任何可比性。这套源码里种子控制放在了配置里跑实验前检查一下。我个人在实际使用中的一个体会是这套代码最大的价值不是哪个算法跑出的分数有多高而是它提供了一个可以随意替换和扩展的骨架。你可以在common里加一个normalizer变体也可以在SAC里把Q网络换成dueling结构改动一处其他地方完全不需要动。建议先拿一个简单环境比如HalfCheetah把所有命令跑通再慢慢往里面加自己的想法。每次改完之后做一次小规模消融实验把改动前和改动后的曲线贴在一起训练效果好不好一眼就能看出来不需要等到整个实验跑完才发现改坏了。本文还有配套的精品资源点击获取