news 2026/10/31 5:54:13

MADDPG多智能体博弈对抗源码解析:从算法原理到训练避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MADDPG多智能体博弈对抗源码解析:从算法原理到训练避坑

简介:这份资源是面向毕业设计与强化学习入门者的MADDPG多智能体博弈对抗算法Python实现,适合具备一定机器学习、深度学习基础,希望理解多智能体连续动作空间协作与对抗机制的学生和研究人员。压缩包共13个文件,以10个py源码文件为主,辅以txt说明、md文档与cfg配置文件,整体约12KB,涵盖环境构建、智能体初始化、Actor-Critic网络搭建、训练流程与结果展示等模块,代码中配有详细注释,便于对照理解算法细节。目前已有122人学习下载。读者可据此复现MADDPG算法,掌握中心评论家与分散演员的协作机制,并在此基础上修改网络结构、调整参数或迁移到多车道路控制、多人游戏策略学习等场景,是开展多智能体博弈方向毕业设计与课题研究的实用起点。

1. 从一份 MADDPG 源码说起:多智能体博弈对抗到底在解决什么问题

如果你正在做毕业设计,选题落在「多智能体博弈对抗」上,大概率会遇到一个尴尬局面:单智能体的 DQN、PPO 代码一抓一大把,但一旦把环境里塞进两个以上会互相影响的智能体,训练曲线立刻变成玄学——奖励不升反降,loss 震荡,两个智能体要么一起摆烂,要么一个碾压另一个导致另一方学不到东西。这份基于 MADDPG 的多智能体博弈对抗算法 Python 源码,解决的正是这个核心痛点:让多个智能体在共享环境里各自决策、互相博弈,同时还能稳定收敛。

MADDPG(Multi-Agent Deep Deterministic Policy Gradient)是 UC Berkeley 团队在 2017 年提出的经典多智能体强化学习算法,属于 Actor-Critic 架构下的集中训练、分散执行范式。它最值钱的地方在于:训练时 Critic 能看到所有智能体的观测和动作,执行时每个 Actor 只依赖自己的局部观测。这个设计直接绕开了多智能体环境非平稳性的死结——当其他智能体策略在变,你的环境转移概率就在变,普通单智能体算法根本稳不住。

这份源码适合三类人:一是毕业设计需要完整可跑通的多智能体项目;二是想从单智能体 RL 进阶到多智能体 RL 的工程师;三是需要拿博弈对抗场景做算法对比实验的研究生。接下来我会把这份源码背后的算法逻辑、环境搭建、参数调优和踩坑记录拆开讲清楚,让你拿到手能跑、跑完能改、改完能写进论文。

2. MADDPG 算法拆解:集中训练分散执行到底怎么落地

2.1 Actor-Critic 在多智能体下的角色分工

单智能体的 DDPG 里,一个 Actor 负责输出动作,一个 Critic 负责评估动作价值。到了 MADDPG,每个智能体都有自己的 Actor 和 Critic,但 Critic 的输入维度变了——它接收的是所有智能体的联合观测和联合动作。这就是「集中训练」的含义:Critic 在训练阶段拥有全局信息,能准确评估当前联合动作的好坏。

执行阶段则完全不同。每个 Actor 只拿自己的局部观测obs_i,输出动作a_i。这意味着部署时不需要全局通信,每个智能体独立决策。这个「训练时开天眼、执行时闭眼跑」的设计,是 MADDPG 能在博弈对抗场景里 work 的根本原因。

源码里通常会有两个网络类:Actor和Critic。Actor 的输入维度是obs_dim,输出维度是action_dim;Critic 的输入维度是obs_dim * n_agents + action_dim * n_agents,输出是一个标量 Q 值。这个维度变化是新手最容易写错的地方,后面避坑章节会细说。

2.2 源码目录结构与核心模块定位

拿到一份 MADDPG 源码,先别急着跑,花五分钟把目录结构看清楚,能省掉后面两小时的瞎折腾。典型结构如下:

MADDPG/ ├── main.py # 训练入口,解析参数、创建环境、启动训练循环 ├── maddpg.py # MADDPG 核心类,管理多个 Agent ├── agent.py # 单个 Agent 的 Actor/Critic 定义与更新逻辑 ├── replay_buffer.py # 经验回放池,存储 (obs, actions, rewards, next_obs, done) ├── envs/ # 环境封装,常见有 simple_tag、simple_spread 等 │ └── multiagent_env.py ├── utils.py # 噪声生成、参数初始化、日志工具 └── arguments.py # 超参数配置

main.py是入口,负责把环境、MADDPG 类、回放池串起来。maddpg.py里通常有一个MADDPG类,内部维护一个agents列表,每个元素是一个DDPGAgent。agent.py是核心,Actor 和 Critic 的网络结构、前向传播、损失计算都在这里。

提示:不同版本的源码目录名可能略有差异,但核心文件一定包含 agent 定义、回放池和训练入口这三块。如果缺了回放池,说明这份源码可能不完整。

2.3 经验回放池的存储与采样逻辑

多智能体经验回放池和单智能体最大的区别在于:存的是联合信息,不是单个智能体的转移。每条经验通常包含:

  • obs:所有智能体当前观测,形状(n_agents, obs_dim)
  • actions:所有智能体当前动作,形状(n_agents, action_dim)
  • rewards:所有智能体奖励,形状(n_agents, 1)
  • next_obs:所有智能体下一时刻观测
  • done:是否终止

采样时,从池子里随机抽一批batch_size的数据,然后按智能体维度拆开,分别喂给每个智能体的 Critic。这里有个关键点:每个智能体的 Critic 更新时,用的都是同一批联合数据,但只优化自己的 Q 值。

class ReplayBuffer: def __init__(self, capacity, n_agents, obs_dim, action_dim): self.capacity = capacity self.n_agents = n_agents self.obs_buf = np.zeros((capacity, n_agents, obs_dim), dtype=np.float32) self.act_buf = np.zeros((capacity, n_agents, action_dim), dtype=np.float32) self.rew_buf = np.zeros((capacity, n_agents, 1), dtype=np.float32) self.next_obs_buf = np.zeros((capacity, n_agents, obs_dim), dtype=np.float32) self.done_buf = np.zeros((capacity, n_agents, 1), dtype=np.float32) self.ptr, self.size = 0, 0 def store(self, obs, act, rew, next_obs, done): # 按指针位置写入,满了就覆盖最旧的数据 self.obs_buf[self.ptr] = obs self.act_buf[self.ptr] = act self.rew_buf[self.ptr] = rew self.next_obs_buf[self.ptr] = next_obs self.done_buf[self.ptr] = done self.ptr = (self.ptr + 1) % self.capacity self.size = min(self.size + 1, self.capacity) def sample(self, batch_size): # 随机采样,返回的是按智能体维度组织好的联合数据 idxs = np.random.randint(0, self.size, size=batch_size) return (self.obs_buf[idxs], self.act_buf[idxs], self.rew_buf[idxs], self.next_obs_buf[idxs], self.done_buf[idxs])

这段代码里capacity一般设 100 万,batch_size设 1024 或 512。n_agents根据环境定,simple_tag 里通常是 2 到 4 个。注意done的维度是(n_agents, 1),因为每个智能体可能在不同时刻终止,不能只存一个全局 done。

2.4 训练主循环:从环境重置到参数更新

训练主循环的逻辑是:重置环境 → 每个智能体根据局部观测选动作 → 环境执行联合动作 → 存储经验 → 采样更新。伪代码如下:

for episode in range(max_episodes): obs = env.reset() # obs shape: (n_agents, obs_dim) for step in range(max_steps): actions = maddpg.select_actions(obs, explore=True) next_obs, rewards, dones, info = env.step(actions) buffer.store(obs, actions, rewards, next_obs, dones) obs = next_obs if buffer.size > batch_size: maddpg.update(buffer, batch_size) if np.any(dones): break

select_actions里每个智能体的 Actor 输出动作后,会加上高斯噪声或 OU 噪声来探索。update里先更新 Critic,再更新 Actor,最后软更新目标网络。这个顺序不能乱,Critic 没更新好就更新 Actor,策略梯度方向会偏。

注意:dones的判断用np.any还是np.all取决于环境设计。如果任一智能体终止就结束整个 episode,用np.any;如果所有智能体都终止才结束,用np.all。这个细节写错,训练数据分布会完全不对。

3. 环境搭建与源码运行:从零跑通第一个对抗场景

3.1 Python 环境与依赖安装的确定性步骤

这份源码依赖的库不多,但版本兼容性有坑。推荐用 Python 3.8 或 3.9,太新的版本某些 RL 库还没适配。安装步骤如下:

# 创建虚拟环境,避免污染全局包 python -m venv maddpg_env source maddpg_env/bin/activate # Linux/Mac # maddpg_env\Scripts\activate # Windows # 安装核心依赖 pip install numpy==1.21.6 pip install torch==1.10.0 pip install gym==0.21.0 pip install tensorboard

numpy别装太新的版本,1.24 以后某些旧代码里的np.float会报错。torch用 1.10 左右比较稳,CUDA 版本根据自己显卡选。gym0.21 是很多多智能体环境的依赖版本,0.26 以后 API 变了,step返回值从 4 个变成 5 个,直接跑会报解包错误。

如果源码里用了multiagent-particle-envs,还需要额外安装:

pip install git+https://github.com/openai/multiagent-particle-envs.git

这个包提供了 simple_tag、simple_spread 等经典博弈对抗场景。装完后用import multiagent测试一下,不报错就说明环境通了。

3.2 关键超参数配置表与含义说明

源码里arguments.py或main.py开头会有一堆超参数,新手最容易随便改然后训练崩掉。下表列出核心参数和建议范围:

参数名含义建议值调整影响
lr_actorActor 学习率1e-3 ~ 1e-4太大策略震荡,太小收敛慢
lr_criticCritic 学习率1e-3 ~ 1e-4通常比 Actor 略大
gamma折扣因子0.95 ~ 0.99越大越看重长期回报
tau软更新系数0.01太大目标网络不稳
batch_size采样批量512 ~ 1024太小梯度噪声大
buffer_size回放池容量1e6太小经验重复率高
noise_std探索噪声标准差0.1 ~ 0.3太大动作乱跳,太小探索不足

这些参数不是拍脑袋定的。gamma=0.95在博弈对抗里比较常用,因为对抗场景的回报周期通常不长。tau=0.01是 DDPG 系列的标准值,软更新目标网络时每次只靠近 1%。noise_std建议从 0.3 开始,训练稳定后逐步降到 0.1。

3.3 启动训练与日志观察

配置好参数后,直接运行入口脚本:

python main.py --env simple_tag --n_agents 4 --max_episodes 20000

训练启动后,终端会打印每个 episode 的奖励和 loss。同时 TensorBoard 日志会写到runs/目录下,用以下命令查看:

tensorboard --logdir runs/

浏览器打开localhost:6006,重点看三条曲线:agent_i/actor_loss、agent_i/critic_loss、agent_i/reward。Critic loss 应该整体下降然后在一个区间内波动,如果一直上升说明 Critic 过拟合或学习率太大。Reward 曲线在对抗场景里通常不是单调上升的,因为对手也在变强,能维持在一个水平不崩就是好现象。

提示:simple_tag 场景里,追捕者(adversary)和逃跑者(good agent)的奖励是零和的。如果你看到追捕者奖励上升、逃跑者奖励下降,说明对抗在正常进行。如果两者一起下降,大概率是探索噪声太大或者 Critic 没学好。

4. 避坑与排查:MADDPG 训练中最容易翻车的五个地方

4.1 现象:训练一开始奖励就爆炸或变成 NaN

原因:最常见的是 Critic 输入维度拼错了。MADDPG 的 Critic 输入应该是所有智能体的 obs 拼接 + 所有智能体的 action 拼接,维度是n_agents * obs_dim + n_agents * action_dim。如果只拼了当前智能体的 obs 和 action,Critic 看到的全局信息不完整,Q 值估计会严重偏差,梯度爆炸后直接 NaN。

解决:在agent.py里检查 Critic 的forward函数,确认输入拼接逻辑:

def forward(self, obs_all, act_all): # obs_all: (batch, n_agents * obs_dim) # act_all: (batch, n_agents * action_dim) x = torch.cat([obs_all, act_all], dim=-1) return self.net(x)

同时检查maddpg.py里传给 Critic 的数据是否已经按n_agents维度展平。展平用obs.reshape(batch_size, -1),不要用obs.view搞错维度顺序。

4.2 现象:Actor loss 一直不降,策略没有学到东西

原因:Actor 的更新依赖于 Critic 给出的 Q 值。如果 Critic 还没学好就频繁更新 Actor,策略梯度方向就是错的。源码里如果update函数先更新 Actor 再更新 Critic,或者两者更新频率一样但 Critic 学习率太低,都会导致这个问题。

解决:调整更新顺序和频率。常见做法是每个 step 更新一次 Critic,每两个 step 更新一次 Actor。或者把lr_critic设得比lr_actor大 2 到 3 倍,让 Critic 先收敛。另外检查 Actor 的损失函数是不是-critic_q.mean(),负号漏了会导致策略往反方向优化。

4.3 现象:多个智能体行为趋同,失去对抗性

原因:这是多智能体里典型的「策略同质化」。如果所有智能体共享同一个 Actor 网络,或者噪声设置一样、初始参数一样,它们会学到几乎相同的策略。在博弈对抗场景里,这意味着追捕者和逃跑者可能一起摆烂,或者一起往同一个方向跑。

解决:确保每个智能体有独立的 Actor 和 Critic 网络,不要共享参数。初始化时用不同的随机种子。噪声方面,不同智能体可以设不同的noise_std,比如追捕者 0.3、逃跑者 0.2,增加策略多样性。如果环境支持,给不同智能体设置不同的奖励函数权重,也能逼出差异化策略。

4.4 现象:训练后期奖励突然崩掉,之前学好的策略失效

原因:这是「灾难性遗忘」在多智能体里的表现。回放池满了以后,新经验覆盖旧经验,如果新经验分布和旧经验差异太大,网络会快速适应新数据而忘记旧策略。在对抗场景里,对手策略一变,你的最优响应也变,旧经验就过时了。

解决:把buffer_size设大一点,至少 1e6,让旧经验保留更久。或者用优先经验回放(Prioritized Experience Replay),给 TD 误差大的样本更高采样概率。源码里如果没实现优先回放,可以手动加一个简单的权重采样逻辑。另外,降低lr_actor和lr_critic在训练后期的值,让网络更新更保守。

4.5 现象:环境 step 返回值解包报错

原因:gym版本不匹配。0.21 及以前,env.step()返回(obs, reward, done, info)四个值;0.26 以后返回(obs, reward, terminated, truncated, info)五个值。源码如果是按旧版写的,在新版 gym 上跑就会报ValueError: too many values to unpack。

解决:要么降级 gym 到 0.21,要么改源码里的解包逻辑:

# 旧版写法 next_obs, rewards, dones, info = env.step(actions) # 新版兼容写法 result = env.step(actions) if len(result) == 5: next_obs, rewards, terminated, truncated, info = result dones = np.logical_or(terminated, truncated) else: next_obs, rewards, dones, info = result

改完后确认dones的维度和类型,多智能体环境里dones通常是列表或数组,不是单个布尔值。

5. 进阶技巧:用参数共享与课程学习把对抗效果再拉一档

5.1 参数共享的适用边界与实现方式

MADDPG 原版每个智能体独立网络,参数量随智能体数量线性增长。如果你有 10 个以上智能体,显存和训练时间都会吃不消。参数共享(Parameter Sharing)是常见的优化手段:所有同类型智能体共享一套 Actor 和 Critic 参数,输入里加上智能体 ID 的 one-hot 编码来区分。

实现上,把agent.py里的网络定义改成全局一份,每个智能体维护一个agent_id,前向传播时把 ID 拼到观测后面:

class SharedActor(nn.Module): def __init__(self, obs_dim, action_dim, n_agents): super().__init__() self.n_agents = n_agents self.net = nn.Sequential( nn.Linear(obs_dim + n_agents, 64), # 拼接 one-hot ID nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, action_dim), nn.Tanh() ) def forward(self, obs, agent_id): id_onehot = F.one_hot(agent_id, self.n_agents).float() x = torch.cat([obs, id_onehot], dim=-1) return self.net(x)

参数共享的边界是:同类型智能体才能共享。追捕者和逃跑者的观测空间和动作空间可能不同,强行共享会出问题。另外,共享后策略多样性会下降,需要靠噪声或奖励设计来弥补。

5.2 课程学习:从简单场景逐步过渡到复杂对抗

直接在一个复杂对抗场景里训练 MADDPG,收敛难度很大。课程学习(Curriculum Learning)的思路是:先让智能体在简单场景里学会基本动作,再逐步增加对手数量或环境复杂度。

具体操作上,可以分三个阶段:

阶段场景配置训练目标切换条件
第一阶段1 个追捕者 vs 1 个逃跑者学会追击/逃跑平均奖励稳定 50 episode
第二阶段2 个追捕者 vs 1 个逃跑者学会协作围堵胜率超过 60%
第三阶段3 个追捕者 vs 2 个逃跑者复杂博弈对抗训练到收敛

每个阶段训练时,加载上一阶段的模型参数作为初始化,而不是从头开始。这样能省掉大量重复探索时间。切换条件不要设得太死,如果第二阶段一直卡住,可以回退到第一阶段再训几千 episode。

5.3 验证训练效果:除了看奖励曲线还能看什么

奖励曲线只能反映整体趋势,要验证策略真的学到了东西,还得看行为层面的指标。我一般会记录三个额外指标:

  • 动作熵:统计每个智能体输出动作的分布熵。熵太低说明策略过早收敛到固定动作,探索不足;熵太高说明策略还在乱试。
  • 碰撞率/成功率:在 simple_tag 里,追捕者碰到逃跑者的次数、逃跑者成功躲避的次数,比奖励更直观。
  • 策略距离:计算不同智能体策略输出的 KL 散度或欧氏距离。距离太小说明策略同质化,对抗性不足。

这些指标可以在main.py的训练循环里加几行统计代码,写到 TensorBoard 里一起看。别只盯着 reward,reward 会骗人,行为指标不会。

5.4 一个我踩过的坑:软更新目标网络时忘了 detach

最后说一个血泪教训。MADDPG 更新 Critic 时,目标 Q 值是用目标网络算的,计算目标 Q 值的那部分计算图必须detach,否则梯度会回传到目标网络,导致目标网络和在线网络一起被更新,软更新就失去意义了。

# 正确写法 with torch.no_grad(): next_actions = target_actor(next_obs) target_q = target_critic(next_obs, next_actions) target_q = rewards + gamma * (1 - dones) * target_q # 错误写法:没有 no_grad 或 detach next_actions = target_actor(next_obs) # 梯度会回传! target_q = target_critic(next_obs, next_actions)

这个坑很隐蔽,因为代码能跑通,loss 也在降,但训练效果就是比预期差一截。我当初排查了两天才定位到。后来养成习惯:凡是目标网络相关的计算,一律包在torch.no_grad()里,不确定就加detach(),宁可多写一行也别省。

希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 5:54:00

ponytail插件怎么用?命令行信息聚合工具的实战指南

如果你最近在逛技术社区或者刷开发工具推荐时,频繁看到一个叫ponytail的词,大概率会和我一样先愣一下:这不是马尾辫吗?怎么就成了开发圈的热搜词。顺着热搜词里的 "ponytail skill"、"ponytail 插件"、"…

作者头像 李华
网站建设 2026/10/31 5:54:00

如何用航空安全标准DO-178C治理大模型AI幻觉?

过去一年我遇到的最扎心的线上事故,不是机器宕机,而是我们做的客服问答机器人一本正经地告诉用户"某款产品已经停产了",还煞有其事地编了个停产年份。实际上它是把两年前的一份内部公告和今年的一条促销信息缝在了一起。用户截图发…

作者头像 李华
网站建设 2026/10/25 8:21:00

从0到1搭建AI Skills技能包:让大模型输出稳定可复用

如果你最近在AI应用圈子里泡得足够久,肯定见惯了这种场面:同一条指令丢给同一个大模型,有人拿到的是80分的成品,有人拿到的是40分的大纲。差距不在模型版本,也不在提示词长短,而在于你有没有给AI一套可以反…

作者头像 李华
网站建设 2026/10/16 15:21:24

SSM+MySQL+JSP知识产权管理系统源码部署与避坑实战指南

简介:面向需要SSM项目实战或毕业设计的开发者,这套基于Spring、SpringMVC、MyBatis、MySQL与JSP的知识产权管理系统提供可直接运行的完整源码。系统包含管理员、员工、注册用户三类角色:管理员可管理员工、用户、公告、下载资料并公布申报结果…

作者头像 李华
网站建设 2026/10/27 22:40:35

游戏引擎基础架构拆解:从模块协作到ECS与资源管线

做游戏这么多年,有件事我一直觉得挺遗憾的:很多入行一两年的引擎使用者,能熟练调参、会改Shader、知道怎么烘焙光照,但被问到“你这套引擎的基础架构到底是怎么跑起来的”,往往一下子卡壳。引擎基础架构这个东西&#…

作者头像 李华
网站建设 2026/10/27 18:12:22

为Claude Code加装长期记忆:claude-mem实践指南

Claude Code用得好的人,最后几乎都会撞上同一个天花板:它没有长期记忆。新开一个会话,之前定的技术方案、写代码的偏好、项目的架构约定,全都得从头交代一遍。我一开始也觉得这是AI工具的正常状态,直到我试了claude-me…

作者头像 李华