尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
MADDPG多智能体博弈对抗源码解析与实战避坑指南
简介这份资源是面向计算机相关专业学生的多智能体强化学习项目源码基于MADDPG算法实现多智能体博弈对抗适合正在做课程设计、期末大作业或需要项目实战练习的学习者参考。项目为个人98分高分作业代码完整且经过严格调试下载后可直接运行能帮助读者快速理解多智能体环境下的策略学习与对抗训练流程。压缩包共13个文件以10个Python源码文件为主另含配置文件、说明文本与版本控制文件整体约15KB体量轻便便于阅读与二次修改。源码中涵盖经验回放、网络结构、DDPG与MADDPG核心逻辑、训练主流程及测试环境等模块并配有代码注释方便对照算法原理逐层梳理实现细节。目前已有423人学习下载适合希望掌握多智能体博弈对抗算法、完成课程项目或积累强化学习实战经验的同学参考使用。1. 从一份 MADDPG 源码说起多智能体博弈对抗到底在解决什么问题如果你手头正躺着一份「基于 MADDPG 的多智能体博弈对抗算法 python 实现源码代码注释」的压缩包打开后大概率会看到main.py、maddpg.py、replay_buffer.py、envs/这么几个文件注释写得挺全但真跑起来却不知道从哪下手改。这不是你一个人的困惑。MADDPG 这个算法本身不复杂复杂的是它背后那套「集中训练、分散执行」的博弈对抗思路以及多智能体环境里那些单智能体 RL 根本不会遇到的坑——非平稳性、信用分配、策略震荡。这份源码要解决的核心问题很明确让多个智能体在同一个环境里既竞争又协作每个智能体只能看到局部观测却要学出全局最优的联合策略。典型场景就是 predator-prey、多车协同、对抗博弈这类任务。适合谁看已经跑通过单智能体 DDPG 或 PPO、想往多智能体方向迈一步的工程师或者手里有这份源码、想把它改成自己业务场景比如多机器人调度、对抗仿真的开发者。下面我按「先讲清 MADDPG 为什么这么设计 → 再拆源码怎么跑通 → 最后说改哪里、坑在哪」的顺序把这份源码讲透。2. MADDPG 的集中训练分散执行为什么 critic 能看到所有人的动作2.1 从 DDPG 到 MADDPG多出来的那部分到底加在哪单智能体 DDPG 里actor 根据自身观测输出动作critic 根据自身观测和动作评估 Q 值。到了多智能体环境如果每个智能体都独立跑一套 DDPG会遇到一个致命问题环境对每个智能体来说都是非平稳的因为其他智能体的策略在同时变化。你今天学到的「在状态 s 下动作 a 好」明天别人策略一变这个结论就失效了。MADDPG 的解法是训练时让每个智能体的 critic 看到全局信息——所有智能体的观测拼在一起、所有智能体的动作拼在一起。这样 critic 评估 Q 值时环境对其他智能体的策略变化就被显式建模进去了。但执行时actor 仍然只用局部观测因为实际部署时你拿不到别人的观测。这就是「集中训练、分散执行」CTDE的核心。源码里对应的是maddpg.py中Critic类的forward方法输入维度是(num_agents * obs_dim num_agents * act_dim)而Actor的输入只有obs_dim。这个维度差异就是 CTDE 在代码层面的直接体现。2.2 源码目录结构与核心模块职责拿到源码先别急着跑花五分钟把目录结构看清楚后面改起来才不迷路。典型结构如下maddpg_project/ ├── main.py # 训练入口解析参数、建环境、跑训练循环 ├── maddpg.py # Actor/Critic 网络定义 MADDPG 智能体类 ├── replay_buffer.py # 经验回放池存 (obs, act, rew, next_obs, done) ├── envs/ │ └── predator_prey.py # 具体博弈对抗环境 ├── utils.py # 噪声、软更新、参数初始化等工具函数 └── arguments.py # 超参数集中管理main.py负责把环境、智能体、回放池串起来maddpg.py是算法核心Actor 和 Critic 的网络结构、目标网络软更新、动作噪声都在这里replay_buffer.py存的是全局的联合经验不是每个智能体各存一份envs/下是具体任务换成你自己的环境主要改这里。2.3 跑通训练的最小命令与关键参数假设你已经装好 Python 3.8 和 PyTorch依赖就 numpy、torch、gym 这几个。先确认环境能正常 reset 和 step# 进入项目根目录 cd maddpg_project # 安装依赖如果还没装 pip install numpy torch gym # 跑默认配置的训练先跑 1000 个 episode 看看曲线 python main.py --n_episodes 1000 --max_episode_len 25 --seed 42main.py里通常用 argparse 管理参数几个必须关注的参数典型值作用--n_episodes1000~5000总训练轮数太少学不出来--max_episode_len25每轮最大步数predator-prey 一般 25--lr_actor1e-4actor 学习率别设太大--lr_critic1e-3critic 学习率通常比 actor 大一个量级--gamma0.95折扣因子博弈对抗任务别设太接近 1--tau0.01目标网络软更新系数--batch_size1024回放采样批量多智能体场景要大一点跑起来后你会看到每个 episode 打印平均奖励。predator-prey 任务里如果 500 episode 后奖励还在 -20 以下震荡大概率是超参或噪声设置有问题先别怀疑算法。3. 把源码拆开看Actor、Critic、回放池三块怎么改3.1 Actor 网络局部观测进动作出Actor 的职责很简单输入自己的局部观测输出动作。源码里通常是一个三层 MLP中间用 ReLU最后一层用 tanh 把动作压到 [-1, 1]。关键点是最后一层的初始化——如果用默认初始化tanh 容易饱和动作一开始就卡在边界。常见做法是把最后一层权重和偏置初始化到很小的值比如nn.init.uniform_(last_layer.weight, -3e-3, 3e-3)。class Actor(nn.Module): def __init__(self, obs_dim, act_dim, hidden_dim64): super().__init__() self.net nn.Sequential( nn.Linear(obs_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, act_dim), nn.Tanh() # 动作范围 [-1, 1] ) # 最后一层小初始化避免 tanh 饱和 last self.net[-2] nn.init.uniform_(last.weight, -3e-3, 3e-3) nn.init.uniform_(last.bias, -3e-3, 3e-3) def forward(self, obs): return self.net(obs)obs_dim是单个智能体的观测维度act_dim是动作维度。hidden_dim 默认 64 对大多数博弈任务够用如果你的观测维度超过 100可以加到 128 或 256。注意 actor 的输入永远只有自己的观测不要手贱把别人的观测拼进来那就破坏 CTDE 了。3.2 Critic 网络全局观测全局动作进Q 值出Critic 是 MADDPG 的灵魂。它的输入是所有智能体的观测拼接和所有智能体的动作拼接输出一个标量 Q 值。源码里 Critic 的输入维度是(num_agents * obs_dim num_agents * act_dim)。注意动作是在第二层之后才拼进来的不是一开始就拼这样网络能先分别处理观测和动作的特征。class Critic(nn.Module): def __init__(self, total_obs_dim, total_act_dim, hidden_dim64): super().__init__() self.fc1 nn.Linear(total_obs_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim total_act_dim, hidden_dim) self.fc3 nn.Linear(hidden_dim, 1) def forward(self, obs_all, act_all): x F.relu(self.fc1(obs_all)) # 动作在第二层才拼进来 x torch.cat([x, act_all], dim-1) x F.relu(self.fc2(x)) return self.fc3(x)total_obs_dim num_agents * obs_dimtotal_act_dim num_agents * act_dim。这里有个容易翻车的地方如果你改环境后智能体数量变了这两个维度必须同步改否则 forward 时 cat 会直接报维度不匹配。我一般会在 arguments.py 里把 num_agents 单独拎出来所有维度都从它算。3.3 回放池存全局联合经验不是各存各的多智能体回放池和单智能体最大的区别是存的是全局的联合观测和联合动作。每个 transition 包含obs_all所有智能体的观测拼接、act_all所有智能体的动作拼接、rew每个智能体各自的奖励、next_obs_all、done。采样时一次采一个 batch 的联合经验然后每个智能体从里面取自己那部分。class ReplayBuffer: def __init__(self, capacity, num_agents, obs_dim, act_dim): self.capacity capacity self.ptr 0 self.size 0 # 预分配避免频繁扩容 self.obs np.zeros((capacity, num_agents, obs_dim), dtypenp.float32) self.act np.zeros((capacity, num_agents, act_dim), dtypenp.float32) self.rew np.zeros((capacity, num_agents), dtypenp.float32) self.next_obs np.zeros((capacity, num_agents, obs_dim), dtypenp.float32) self.done np.zeros((capacity, num_agents), dtypenp.float32) def push(self, obs, act, rew, next_obs, done): self.obs[self.ptr] obs self.act[self.ptr] act self.rew[self.ptr] rew self.next_obs[self.ptr] next_obs self.done[self.ptr] done self.ptr (self.ptr 1) % self.capacity self.size min(self.size 1, self.capacity) def sample(self, batch_size): idx np.random.randint(0, self.size, sizebatch_size) return (self.obs[idx], self.act[idx], self.rew[idx], self.next_obs[idx], self.done[idx])capacity 一般设 1e6predator-prey 这种小任务 1e5 也够。注意done是每个智能体一个因为某个智能体可能先结束而其他还在跑。采样用np.random.randint就够不用上 prioritized replay多智能体场景下优先回放反而容易不稳定。3.4 训练循环每个智能体各更新各的但 critic 用全局信息训练循环是main.py里最长的部分核心逻辑是每个 step 所有智能体根据局部观测选动作、加噪声、执行、存回放池每步结束后如果回放池够大每个智能体从池里采一个 batch用自己的 actor 算新动作用全局 critic 算 Q 值更新 actor 和 critic最后软更新目标网络。for agent_i in range(num_agents): # 从回放池采样 obs_b, act_b, rew_b, next_obs_b, done_b buffer.sample(batch_size) # 当前智能体视角的观测和动作 obs_i obs_b[:, agent_i] act_i act_b[:, agent_i] rew_i rew_b[:, agent_i] next_obs_i next_obs_b[:, agent_i] done_i done_b[:, agent_i] # 所有智能体的观测和动作拼接给 critic 用 obs_all obs_b.reshape(batch_size, -1) act_all act_b.reshape(batch_size, -1) next_obs_all next_obs_b.reshape(batch_size, -1) # 目标动作每个智能体的 target actor 根据 next_obs 算 with torch.no_grad(): next_act_all torch.cat( [target_actors[j](next_obs_b[:, j]) for j in range(num_agents)], dim-1 ) target_q target_critics[agent_i](next_obs_all, next_act_all) target_q rew_i gamma * (1 - done_i) * target_q # 当前 Q 值 current_q critics[agent_i](obs_all, act_all) critic_loss F.mse_loss(current_q, target_q.detach()) # 更新 critic critic_optims[agent_i].zero_grad() critic_loss.backward() critic_optims[agent_i].step() # 更新 actor只更新当前智能体的 actor其他智能体动作当常量 new_act_i actors[agent_i](obs_i) # 把当前智能体的动作替换成新动作其他不变 act_all_new act_all.clone() act_all_new[:, agent_i * act_dim:(agent_i 1) * act_dim] new_act_i actor_loss -critics[agent_i](obs_all, act_all_new).mean() actor_optims[agent_i].zero_grad() actor_loss.backward() actor_optims[agent_i].step() # 软更新 soft_update(target_actors[agent_i], actors[agent_i], tau) soft_update(target_critics[agent_i], critics[agent_i], tau)这段代码有几个细节值得说。第一更新 actor 时只把当前智能体的动作替换成新算的其他智能体的动作保持从回放池采出来的旧动作不变这是 MADDPG 的标准做法目的是让 critic 的梯度只反映当前智能体策略的变化。第二target_q计算时用的是所有智能体的 target actor不是当前 actor。第三软更新系数 tau 一般 0.01别设太大否则目标网络跟得太快训练震荡。4. 避坑与排查多智能体训练里那些让人怀疑人生的时刻4.1 奖励曲线一直不涨甚至越来越负现象跑了 1000 episode平均奖励从 -25 慢慢掉到 -50完全没有收敛迹象。原因最常见的是 critic 学得太快、actor 学得太慢导致 actor 的梯度方向一直是错的。多智能体场景下 critic 的输入维度大如果 lr_critic 设得比 lr_actor 大太多比如 1e-2 vs 1e-4critic 会过拟合当前策略actor 跟不上。解决把 lr_critic 降到 1e-3lr_actor 保持 1e-4两者差距不要超过一个量级。另外检查 gamma博弈对抗任务 gamma 设 0.99 会导致 Q 值累积过大改成 0.95 试试。如果还不行把 batch_size 从 256 加到 1024让 critic 看到更多样本再更新。4.2 某个智能体学会了其他智能体摆烂现象predator-prey 里一只 predator 追着 prey 跑另外两只原地转圈。原因信用分配问题。每个智能体只拿自己的奖励如果环境设计里只有抓到 prey 的那个 predator 拿正奖励其他两个拿不到有效信号就会摆烂。这是环境设计的问题不是算法的问题。解决改奖励函数给所有 predator 一个共享的团队奖励或者给「靠近 prey」这个行为一个塑形奖励。源码里envs/predator_prey.py的_reward方法就是改这个的地方。常见做法是reward team_reward * 0.5 individual_reward * 0.5让每个智能体既有个人动力又有团队压力。4.3 训练到一半突然崩掉Q 值变成 NaN现象前 300 episode 正常第 301 episode 开始 loss 打印 nan之后全崩。原因梯度爆炸。多智能体 critic 的输入维度大如果观测或动作没有归一化某些维度的值特别大几层 MLP 乘下来梯度就爆了。另一个可能是 target_q 计算时 done 没处理好导致 bootstrap 无限累积。解决第一在环境 step 返回观测前做归一化把观测压到 [-1, 1] 或标准正态。第二在 critic loss 反向传播前加torch.nn.utils.clip_grad_norm_(critic.parameters(), 0.5)。第三检查 done 的处理target_q rew gamma * (1 - done) * target_q里的 done 必须是 float 类型不能是 bool否则乘法会出问题。4.4 换了环境后维度对不上各种 shape mismatch现象把 predator-prey 换成自己的环境后报RuntimeError: shape [1024, 4, 2] is invalid for input of size ...。原因num_agents、obs_dim、act_dim 三个参数没有同步改。源码里这些值通常在 arguments.py 里定义但环境 reset 返回的观测维度可能和定义的不一致。解决在 main.py 开头加一段断言把环境实际返回的 obs 和 act 维度打印出来和 arguments 里的值对一遍。我一般会写obs env.reset() assert len(obs) args.num_agents, fnum_agents mismatch: {len(obs)} vs {args.num_agents} assert len(obs[0]) args.obs_dim, fobs_dim mismatch: {len(obs[0])} vs {args.obs_dim}这样一跑就能定位到是哪个维度错了比看报错栈快得多。4.5 训练速度慢得离谱一个 episode 要好几秒现象1000 episode 跑了三个小时还没完。原因回放池采样用了 Python 循环逐条取或者每次 forward 都在 CPU 上跑没转 tensor。解决回放池的 sample 方法用 numpy 花式索引一次取完别用 for 循环。网络 forward 前把 numpy 数组转成 torch tensor如果机器有 GPU 就.cuda()。另外torch.no_grad()在算 target_q 时一定要加否则会建计算图显存和速度都受影响。predator-prey 这种小任务GPU 上 1000 episode 应该在 10 分钟内跑完。5. 进阶技巧怎么验证 MADDPG 真的学到了博弈策略5.1 用固定随机种子做消融别只看一条曲线多智能体训练方差很大同一个配置跑三次可能两条收敛一条崩。验证算法是否有效至少跑三个 seed比如 42、123、2024把三条奖励曲线画在一起。如果三条曲线最终都收敛到相近水平说明算法稳定如果只有一条好另外两条崩那大概率是超参太敏感需要调 lr 或 batch_size。源码里通常用--seed参数控制但要注意 numpy、torch、环境各自的随机种子都要设import numpy as np import torch import random def set_seed(seed): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) # 环境如果有自己的随机种子也要设 env.seed(seed)只设 torch.manual_seed 是不够的numpy 的随机性在回放池采样和环境初始化里都会用到。5.2 看策略行为别只看奖励数字奖励收敛不代表策略合理。我习惯在训练结束后用--render或手动跑几个 episode把每个智能体的动作序列打出来看。比如 predator-prey 里如果 predator 的动作在追捕阶段应该是朝向 prey 的如果动作一直是 0 或边界值说明 actor 输出饱和了tanh 卡住了。另一个技巧是算「动作熵」把一个 episode 里每个智能体的动作分布统计一下如果某个智能体的动作方差接近 0说明它学成了一个常量策略这在博弈对抗里通常意味着它放弃了。这时候要回去检查它的奖励信号是不是太稀疏。5.3 改造成自己业务场景的三个切入点这份源码要落到实际业务通常从三个地方改环境、奖励、观测。环境改envs/下的文件把 reset 和 step 换成你的业务逻辑奖励改_reward方法这是最影响学习效果的观测改 reset 返回的 obs 维度同时同步改 arguments.py 里的 obs_dim。我一般会先写一个最简单的环境让智能体随机动作也能跑通确认维度、回放池、训练循环都没问题再逐步加复杂度。直接上复杂环境一旦报错很难定位是环境的问题还是算法的问题。这个习惯帮我省了很多后悔药——多智能体调试最怕的就是「不知道哪里错了」先把变量控制住再逐个放开。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

嵌入式MCU代码重定向到RAM防OTA升级变砖实战

嵌入式MCU代码重定向到RAM防OTA升级变砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/9 1:12:09
EffectCraft 与 After Effects 的真实差距:docs/gaps.md 中的诚实评估与 G1–G9 工作流

EffectCraft 与 After Effects 的真实差距:docs/gaps.md 中的诚实评估与 G1–G9 工作流

【免费下载链接】effectcraft 项目地址: https://gitcode.com/gh_mirrors/ef/effectcraft 点击查看 免费下载 EffectCraft 是一个从零用 Rust 编写的 After Effects 替代项目,其 docs/gaps.md 是目前仓库中唯一一份直面"离真正可用还有多远"的…

📅 2026/10/9 1:12:09
汇编语言实战指南:从核心概念到性能优化与逆向分析

汇编语言实战指南:从核心概念到性能优化与逆向分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/9 1:07:08
MORE NEWS

更多资讯

📰

停车场管理系统课程设计:用栈、队列、链表一题通关

简介:面向数据结构课程设计的学生,这是一份以C语言实现的停车场管理系统完整资料,源码基于链栈数据结构完成,覆盖车辆入场、出场、增删查改等核心逻辑,可自动计算停留时长与应收费用,命令行终端界面经过优化…

📰

仿微信聊天系统源码解析:WinForm桌面端IM开发实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

阿里 Qoder 智能体工作台实战:用 TaoToken 统一 Key 打通 Harness 与 Agent 工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

OpenClaw Talk 模式实战:TaoToken 统一 Key 打通 Voice Call 语音链路配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

工控数据类型与值范围详解:从PLC到Modbus的解析避坑指南

1. 从一次通讯调试翻车说起:为什么数据类型值得单独拎出来讲刚入行那会儿,我接手过一个改造项目:用上位机通过 Modbus RTU 读取一台老设备的温度值。协议文档上白纸黑字写着"温度寄存器地址 40001,单位 0.1℃"。我照着地…

📰

新电脑装机指南:从系统初始化到效率工具的科学配置思路

1. 装机思路的底层逻辑:为什么“必装清单”不能照抄每次帮朋友处理新电脑,我最怕听到的一句话就是“你直接把你的软件清单发我一份”。不是不愿意给,而是这份清单如果直接照搬,大概率会出问题。原因很简单:每个人的硬件…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬