MADDPG代码复现指南:从环境配置到稳定训练全流程

发布时间:2026/9/12 17:17:38
MADDPG代码复现指南:从环境配置到稳定训练全流程 简介MADDPGMulti-Agent Actor-Critic是面向混合合作-竞争环境下多智能体决策的经典算法本包为其PyTorch实现配套开源的Multi-Agent Particle Environment环境运行main.py即可快速启动实验适合强化学习入门者、论文复现及多智能体系统研究者使用。包内共99个文件、约1.8MB以32个Python源码文件为主涵盖模型定义、回放缓冲区、工具函数与训练入口另含42个pkl格式的模型或经验数据、22个pyc缓存文件及说明文档、结果预览图目录按maddpg、multiagent等模块清晰组织便于按需阅读和二次开发。截至目前已有882人学习下载。资源不仅包含可直接执行的完整工程还提供预训练数据与README说明可帮助读者快速理解MADDPG的Actor-Critic训练流程、环境交互逻辑以及混合竞争-合作场景下的策略演化过程节省自行搭建和调参的时间。1. 拿到 can_work_MADDPG.rar先想清楚它验证了什么从导师网盘或者论文作者个人主页拖下来一个can_work_MADDPG.rar这个命名说明发布者至少在自己机器上把代码完整跑通过一次全流程本身是自洽的。但你要立刻区分两件事他验证的是「代码能跑」不代表「在你机器上换一个 gym 版本、换一个显卡、甚至换一个 Python 小版本之后还能出同样的训练曲线」。MADDPG 是 DDPG 在多智能体环境上的直接扩展核心思想是 centralized training with decentralized execution也就是训练时每个智能体的 critic 能看到所有人的观测和动作执行时 actor 却只用自己的观测决定动作。而.rar才是这个标题里更容易被忽略的那半截——解压、校验、依赖安装这个链条里每一步都有版本陷阱。这篇就按解压、环境、训练、验证的顺序把「别人能 work」逐步变成「你也能重新 work」。2. 判断这个包是真 MADDPG 还是「每个智能体跑一个 DDPG」2.1 为什么 MADDPG 的核心区别在 critic而不在 actor很多初读代码的人会把 MADDPG 理解成多份独立 DDPG 并行训练这是一个会直接影响你改代码方向的误解。独立 DDPG 里每个智能体有自己的 actor 和 criticcritic 只输入自己的 observation 和 action训练时把其他智能体完全当作环境噪声的一部分。在simple_spread这种纯合作场景里它偶尔也能学出个大概但一旦碰到simple_adversary、simple_tag这类混合合作-竞争场景其他智能体策略在训练中持续变化每个智能体眼中的环境都在不断移动critic 的 Q 值估计会跟着剧烈震荡最终表现为 reward 曲线反复横跳。MADDPG 的解决办法是把训练和执行拆开。actor 保持局部a_i μ_i(o_i)只吃自己的观测输出连续动作向量这和 DDPG 完全相同。critic 换成全局Q_i(x, a_1, ..., a_N)输入是全部N个智能体的观测拼接x [o_1, ..., o_N]和动作拼接。这样 critic 在训练时看到了完整信息环境非平稳性被 part 观测补回来了智能体 i 的梯度就不再被其他智能体的策略变化直接干扰。你拿到 rar 包之后的第一件事就是确认所有 critic 类都接入了全部智能体数据只要是「每一个 agent 独立建一个 Critic(obs_i, act_i)」的写法不管它在自己的机器上能不能跑它都不是标准 MADDPG。2.2 用一条命令拆穿这是 TensorFlow 1 老仓库还是 PyTorch 复现can_work_MADDPG.rar这类包在复现圈里通常只有两个血统一个来自 OpenAI 早期的maddpg仓库基于 TensorFlow 1.x特征是tf.Session()、tf.get_variable、变量作用域训练代码最后用saver.save落盘.ckpt另一个是各种课程项目基于 PyTorch 的复刻特征是nn.Module子类、optim.Adam、torch.save。这两个血统对你的环境要求完全不同TF1 那版基本锁死tensorflow1.14和gym0.10.x在 Python 3.8 以上连 import 都过不去PyTorch 版则宽松得多。所以判断仓库类型不是好奇是决定你后面能不能省下两个小时。# 1) 列出压缩包内容看顶层目录和文件布局 unrar l can_work_MADDPG.rar | head -40 # 2) 解压到当前目录保留原目录结构 unrar x can_work_MADDPG.rar # 3) 进入解压后的目录通常是包名去掉扩展名的子目录 cd can_work_MADDPG # 4) 按框架特征搜索两条命中哪条就走哪条路线 grep -rn tf.Session\|tf.get_variable . --include*.py | head -5 grep -rn import torch . --include*.py | head -5第一行unrar l只列出文件不落盘用来快速判断有没有 README、multiagent/子目录、训练入口脚本再决定要不要完整解压。第三步的cd是假设发布者按包名建了顶层目录unrar x解压时默认保留压缩包内路径所以先ls看一层再进。第四步两条grep是互斥判断hit 到tf.Session就准备面对 TF1 的安装地狱hit 到import torch就按 PyTorch 依赖走。两条都没命中就要小心可能是把算法核心写成一个超大.py文件的「混沌仓库」这种包通常不值得继续投入。2.3 对照这段 PyTorch 骨架检查三件套是否完整判断血统之后下一步是确认网络结构没被精简掉。不管什么版本一个能 work 的 MADDPG 实现里每个智能体都必须有这四样actor、critic、target_actor、target_critic。critic 的输入尺寸是最容易露馅的地方你把它的第一层Linear的in_features和下面这段骨架比对一下就知道它是不是真做了全局拼接。import torch import torch.nn as nn def make_net(in_dim, out_dim, hidden64): # 两层 MLP中间用 ReLU输出层不加激活 return nn.Sequential( nn.Linear(in_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, out_dim), ) class DDPGAgent: def __init__(self, obs_dim, act_dim, n_agents, hidden64): # actor只看自己的观测输出自己的连续动作 self.actor make_net(obs_dim, act_dim, hidden) self.target_actor make_net(obs_dim, act_dim, hidden) # critic拼接所有智能体的观测和动作输出单个 Q 值 critic_in (obs_dim act_dim) * n_agents self.critic make_net(critic_in, 1, hidden) self.target_critic make_net(critic_in, 1, hidden) # 注意critic 的学习率通常比 actor 大 self.actor_optim torch.optim.Adam(self.actor.parameters(), lr1e-4) self.critic_optim torch.optim.Adam(self.critic.parameters(), lr1e-3)critic_in是重点如果包里的 critic 第一层输入只有obs_dim act_dim那这就是独立 DDPG 而不是 MADDPG你后面再怎么调参都在解决错误的问题。另一个重点在 target 网络更新方式标准做法是每次训练步做一步 soft updatetarget_param target_param * (1 - tau) param * tautau取0.01左右。很多「能跑但学不会」的实现这里直接load_state_dict硬拷贝每 N 步把 target 变成当前网络的一份复制这会让 target 的 Q 值剧烈跳变经验回放里的旧数据全部失效。拿到包先把这两个点确认掉比急着pip install更值钱。3. 从 rar 解压到跑通一个最小 MPE 环境3.1 解压前先测完整性密码问题别走歪rar这种格式和zip不一样解压工具不是系统自带unrar和rar是两个不同程序rar负责创建和管理压缩包unrar一般只负责解压和测试。Debian/Ubuntu 上需要apt install unrar装完先做完整性测试而不是直接解压因为网盘传输的压缩包经常有 CRC 错误直接unrar x会解到中途报错留下一半残缺文件你还要回头排查到底是传输问题还是磁盘问题。# 确认文件格式和大小防止下载到假的占位文件 file can_work_MADDPG.rar ls -lh can_work_MADDPG.rar # 测试压缩包完整性逐文件校验 CRC unrar t can_work_MADDPG.rar # 正式解压保留压缩包内的绝对目录结构 unrar x -o can_work_MADDPG.rarfile会输出RAR archive data, v5之类的格式信息如果输出的是ASCII text说明这是个网页错误页不用浪费后面的时间。unrar t逐个条目解压到临时区并校验输出末尾会有一行All OK不是这个结果就重新下载。unrar x保留完整路径unrar e才会把所有文件摊到当前目录-o表示遇到重名文件直接覆盖不询问适合第二次解压。如果unrar t直接报Incorrect password说明这个 rar 是加密的。正路只有一条往回找发件人要密码。网上那些「16 进制编辑器查看 rar 密码」「rar password cracker」工具对这个场景既慢又不可靠而且在学校和公司环境下解一个不是你的压缩包本身就涉及授权问题。文献代码包几乎不会加密真遇到加密版本发一封邮件要密码比花两小时折腾破解工具划算得多。3.2 按 README 重建环境多智能体粒子环境是最容易挂的依赖解压完成之后先做一件事读 README。这类代码包通常会把依赖写在一个requirements.txt里但版本往往写得不全因为发布者当初在自己的环境里手动装过一些包并没有全部沉淀进 requirements。你不需要把 requirements 里的版本当圣旨但要保证核心依赖的版本能被 MPE 接受。multiagent-particle-envs简称 MPE是这个领域的事实标准环境库奇葩的是它依赖的是老版gym接口如果你顺手装了个最新版gymnasiumenv.step()的返回结构和env.reset()的签名全变了训练脚本直接报错。# 建独立虚拟环境避免污染系统 Python python -m venv .venv source .venv/bin/activate # 先看 requirements 里写了什么再决定装不装 cat requirements.txt # 有 requirements 就装没有就手动装最保守的版本组合 pip install -r requirements.txt pip install gym0.10.5 matplotlib numpy # 如果代码仓库里带了 multiagent 子目录或者 MPE 源码用开发模式装 pip install -e ./multiagent-particle-envs/python -m venv是 Python 3.3 以后自带的功能比 conda 轻量而且不干扰你机器上其他项目。pip install gym0.10.5这个版本号是 MPE 生态里最常见的兼容点如果你的包要求的 gym 版本比这新以包内 README 为准。最后一行pip install -e是 MPE 被反复踩的坑很多下载的包把multiagent模块放在仓库子目录里但没安装直接跑训练脚本就会No module named multiagent。-e是 ediable 模式链接安装改环境里还能同步改源码。下面这张表是解压后最常见的依赖报错基本覆盖了 90% 的「can_work 到不了 work」情况报错信息原因处理方式No module named multiagentMPE 没有安装或装到了别的环境找到源码目录后pip install -eImportError: cannot import name rendering from gymgym 版本过新改成了 gymnasium降级到gym0.10.5附近TypeError: reset() takes 1 positional argument but 2 were given新 gym 支持reset(seed)旧代码没适配换旧 gym或改调用方式AttributeError: module numpy has no attribute floatnumpy 太新1.24 移除了 np.floatpip install numpy1.24最后一行 numpy 的坑在 TF1 血统的仓库里几乎必现因为老代码大量用了np.float、np.int在新 numpy 里这些别名被清理掉了。这四类报错全部和算法本身无关全是生态环境错位处理完依赖才轮到看算法。3.3 用 20 行脚本先跑通环境再碰训练脚本环境装好之后不要直接跑训练——训练脚本里堆了日志、可视化、保存路径一类的逻辑环境层面的问题会被这些噪音掩盖。正确做法是写一个最小脚本只加载simple_spread场景用随机策略跑几步确认观测维度、动作空间、reward 范围都对再让它和训练脚本对接。import gym from multiagent.environment import MultiAgentEnv import multiagent.scenarios as scenarios # simple_spread3 个智能体覆盖 3 个地标最平缓的入门场景 scenario scenarios.load(simple_spread.py).Scenario() world scenario.make_world() env MultiAgentEnv(world, scenario.reset_world, scenario.reward, scenario.observation) obs_n env.reset() for step in range(5): # 每个智能体在各自动作空间里随机采样构成联合动作 actions [env.action_space[i].sample() for i in range(env.n)] obs_n, reward_n, done_n, info env.step(actions) print(fstep {step}: obs_len{[len(o) for o in obs_n]}, reward{reward_n}) print(random policy mean reward:, sum(reward_n) / env.n)scenarios.load接收的是场景文件名Scenario类负责生成worldMultiAgentEnv把这个世界包装成 gym 风格接口。env.n是智能体数量env.action_space是列表每个元素对应一个智能体的动作空间。simple_spread的 reward 是智能体到地标的负距离之和所以随机策略的单步 reward 通常在-1.0到-2.5这个区间如果打印出来是0.0或者数值特别大说明环境构造参数有问题不是算法问题。这一步跑通之后训练脚本里所有环境部分你都心里有底了。4. 训练 MADDPG 的必调参数、reward 曲线判读和三个隐性故障4.1 训练入口的三个必调参数训练脚本一般叫main.py或者train.py具体以解压出来的文件为准。这类包十有八九把关键超参暴露出命令行参数但也有的是改config.py里的全局变量。不管是哪种形式三个参数你都必须亲手确认而不是用默认值batch-size、tau、noise相关设置。batch-size影响经验回放的方差MPE 这种短地平线任务里batch-size1024明显比128稳定原因是原始 transition 之间的相关性太大小 batch 采样出来经常是同一段轨迹上的连续样本梯度被带偏。python main.py \ --scenario simple_spread \ --episodes 20000 \ --batch-size 1024 \ --actor-lr 1e-4 \ --critic-lr 1e-3 \ --tau 0.01 \ --gamma 0.95 \ --noise-scale 0.1tau是 target 网络软更新的插值系数0.01意味着每一步 target 只朝当前网络移动 1%太小则 target 更新缓慢、训练前期反馈滞后太大则 target 跟着当前 Q 震荡、经验回放失去稳定作用。gamma取0.95而不是 DQN 常用的0.99因为 MPE 一个 episode 只有 25 个环境 step累计回报的视野本来就短0.99会让价值估计对远期信号敏感反而增加方差。noise-scale是探索噪声的初始幅度OU 噪声会在训练中衰减这个参数控制衰减起点设太大会让前面的曲线看起来像纯随机太小则前期探索不足。下表是四个最常改的参数和改错的典型症状你可以对号入座参数典型值设错的症状batch-size1024reward 曲线剧烈抖动单次 update 的 loss 跨度超过两个数量级tau0.01过大会让 target 网络 Q 值高频跳动reward 曲线呈锯齿状critic-lr1e-3超过 1e-2 几乎必发散某个 episode 的 reward 突然掉到初始值的 3 倍以下noise-scale0.1过小则前期不着陆曲线长时间保持随机策略水位不上升4.2 用 rolling mean 画 reward 曲线而不是看原始曲线MPE 里单个 episode 的 return 方差极大同一条策略相邻两个 episode 的 reward 能差出一倍直接看训练日志的原始数字只会得出「这玩意没在学」的错误结论。常见做法是拿全部 episode 的 return 做滑窗平均窗口取 100 到 200把曲线平滑掉高频噪声再判断趋势。import json import numpy as np import matplotlib.pyplot as plt # 训练脚本通常会定期保存每个 episode 的 return格式不一定是 json根据实际改读法 with open(results/returns.json) as f: returns np.array(json.load(f), dtypefloat) k 100 # 滑窗大小episode 总量越大可以取越大 smoothed np.convolve(returns, np.ones(k) / k, modevalid) plt.plot(smoothed) plt.xlabel(episode) plt.ylabel(smoothed return) plt.savefig(reward_smoothed.png) print(last 1000 episode mean:, returns[-1000:].mean())np.convolve在这里做的是滑动平均卷积核是np.ones(k)/kmodevalid避免了边界补零造成的前段虚线假象。判断学没学进去的标准很简单把returns[-1000:]的均值和前面 3.3 节随机策略的均值对比如果训练后比随机还差那不是没收敛是训练代码有 bug。如果曲线先下降再上升也是正常的MPE 里探索初期碰撞惩罚多reward 先走低再被策略拉回来所以要看到最后一段的均值不要看前 2000 episode 就关掉。4.3 「能跑但学不会」的三个隐性故障环境通了、依赖对了、曲线画了但 reward 就是上不去这时候问题大概率在以下三个地方。第一个是 target 网络的更新方式被实现成 hard copy。有些包为了省事在每个 episode 末尾让target_network.load_state_dict(network.state_dict())然后安慰自己「这样更稳定」。实际上 target 网络存在的意义就是让 Q 值估计有一个缓慢移动的目标hard copy 会让目标函数周期性跳变经验回放里积累的旧 transition 一瞬间全部过期。排查方法在训练代码里搜索load_state_dict如果它出现在 target 和主网络之间几乎可以断定是这个毛病改回target tau * target (1 - tau) * param即可。第二个是 replay buffer 的容量设定或者清空时机不对。MPE 的 episode 只有 25 步一个 episode 只产生 24 条 transition如果 buffer 容量只有几千训练 20000 episodes 后 buffer 里几乎全是最近几百 episode 的数据采样的多样性被破坏。自查方法看 buffer 容量是不是在1e5量级附近以及在训练循环里有没有「episode 结束就buffer.clear()」这种处理——有的话直接删掉除非你写的是 on-policy 算法。第三个是 critic 更新时误用当前策略的动作而不是 target 网络的动作。MADDPG 的 TD 目标写作r gamma * Q_target(x, a_1, ..., a_N)其中a_i必须由target_actor输出。有些实现图省事直接actor(next_obs)导致 TD 目标里混入一直在变的当前策略bootstrap 目标过度乐观reward 曲线表现为早期疯涨然后断崖下跌。排查方法在update()函数里找下一个动作是从哪个网络来的确认它属于target_actor并且被包在torch.no_grad()里。注意顺带检查梯度截断target 网络参与计算的部分如果没detach()梯度会从 target 反传回主网络造成一种「训着训着两个网络互相追尾」的隐性问题。5. 从 can_work 到可复现用三个随机种子固定 MADDPG 的随机源5.1 固定环境初始化、探索噪声、参数初始化三处随机源复现 MADDPG 实验要求你能用不同随机种子拿到均值和方差而不是只能导出一张「我这次跑得挺好看」的曲线。MADDPG 的随机源比单智能体 DDPG 多一处环境不是只有一个而是有多个智能体共享同一个numpy全局随机状态。MPE 的scenario.reset_world里生成初始位置时用的就是全局np.random所以种子设置必须放在make_env之前否则环境初始化和探索噪声会互相污染。import numpy as np import random import torch def seed_everything(seed: int): # 环境初始化、OU 噪声、replay 采样都吃 numpy 的全局状态 np.random.seed(seed) random.seed(seed) # PyTorch 的随机源网络初始化、dropout本场景无、CUDA 相关 torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) # 只有一个 GPU 时也得调用避免 CPU/GPU 不一致 torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark Falsetorch.backends.cudnn.deterministic True让 cuDNN 的卷积和矩阵乘选择确定算法benchmark False禁止它根据输入尺寸动态换算法——这两个设置不加即使手动固定了 torch 的种子同一份代码在不同跑次之间依然会有细微差异。注意seed_everything必须在env MultiAgentEnv(...)之前调用还要在加载数据集或生成任何随机样本之前。之后每次跑实验换一个 seed 参数即可。5.2 并行跑三个 seed把 reward 曲线变成均值±方差图单次跑的训练曲线说服力不够三个 seed 是复现报告的下限。因为每一步actor_optim.step()后 MAADDPG 的 critic 在共享缓冲区上做 feedforwardCPU 负载远低于深度视觉任务三个并行进程在普通台式机上完全跑得动。for seed in 0 1 2; do nohup python main.py \ --scenario simple_spread \ --seed $seed \ --episodes 20000 \ --outdir runs/seed_$seed \ logs/seed_$seed.log 21 done # 3 个测试完成后把每个 runs/seed_*/returns.json 汇总画图nohup让训练进程脱离终端继续执行 logs/seed_$seed.log 21把标准输出和错误报告分别定向到各自的日志文件丢到后台。命令执行后马上用jobs确认三个进程都起来了。训练结束之后把所有returns.json按 seed 分组逐 episode 计算均值、上分位、下分位最后一章画成三条曲线叠加的均值带图——这就是该模型复现实验里该出现的一张图。每个 seed 目录里同时保留一份seed_everything(seed)对应的超参快照才算把can_work_MADDPG.rar真正变成你自己的可复现 baseline。本文还有配套的精品资源点击获取