恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Python+MADDPG多智能体博弈对抗:从算法原理到训练避坑实战

  • 首页
  • 资讯中心
  • /
  • Python+MADDPG多智能体博弈对抗:从算法原理到训练避坑实战

相关资讯

Python知识图谱医疗问答系统源码解析与Neo4j实战 2026/10/3 3:11:34
哈希表与双指针经典题型:从两数之和到四数之和的解题思路 2026/10/3 3:11:34
Python实现文本转知识图谱:从本体建模到Neo4j写入全流程 2026/10/3 3:11:34

最新资讯

USACO银组真题解析:用图论连通分量建模,破解奶牛语言翻译问题
Python模型持久化选型:Joblib与pickle的边界及高效缓存实践
心血管疾病预测模型:临床可解释性与真实部署实践
3D高斯泼溅自采数据集全流程:COLMAP稀疏重建与训练避坑实战
PyTorch转ONNX算子不支持?3个实战方案搞定TensorRT部署
类型安全容器设计:告别Map<String,Object>与ClassCastException

今日推荐

SAP生产预留实战指南:MB21/MB23/MB25协同与MRP集成
编译原理实验:递归下降分析器消除左递归与避坑指南
Python协议级爬取Shopee商品数据实战

本周热门

从像素到笔画:srt-whiteboard-animation骨架笔迹追踪实现(Zhang-Suen细化+8邻接追踪)
网站建设的英语怎么说?别只背单词,看完这套安全完整流程才敢上线
新手入门看这篇:建设网站加盟避坑指南与SEO实操

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

Python+MADDPG多智能体博弈对抗:从算法原理到训练避坑实战

发布时间:2026/10/3 3:11:34
Python+MADDPG多智能体博弈对抗:从算法原理到训练避坑实战 简介这份资源围绕Python与MADDPG算法展开聚焦多智能体博弈对抗场景适合希望入门或进阶强化学习与多智能体协作对抗方向的学习者也可直接用于毕业设计、课程设计、大作业或工程实训等实践任务。压缩包共14个文件以10个py源码为主辅以cfg配置、md说明文档、txt文本与gitignore等辅助文件整体约19KB体量轻巧但结构完整。内容涵盖DDPG、MADDPG、网络结构、经验回放与训练环境等核心模块读者可据此理解多智能体策略学习与对抗博弈的实现思路并在此基础上修改环境、调整超参数或迁移到自己的课题中。目前已有266人学习下载可作为强化学习多智能体方向的入门参考与项目起点。1. 多智能体博弈对抗为什么单跑一个 MADDPG 总是学不出配合如果你已经用 Python 跑通过单智能体 DDPG第一次把两个智能体丢进同一个环境里对抗大概率会看到这样的曲线一个越来越强另一个直接躺平或者两边一起震荡到发散。这不是代码写错了而是多智能体强化学习里最经典的「非平稳性」问题——每个智能体眼里的环境都在被其他智能体的策略持续改写。MADDPGMulti-Agent Deep Deterministic Policy Gradient就是冲着这个痛点来的它用集中式训练、分散式执行CTDE的框架让每个 Critic 在训练时能看到所有智能体的观测和动作Actor 执行时只依赖自己的局部观测。这篇笔记不讲论文复述而是把「基于 Python MADDPG 的多智能体博弈对抗算法」从环境搭建、网络结构、参数设置到训练排查按我自己复现时踩过的顺序讲一遍。适合已经会写 PyTorch 训练循环、想把这套算法真正跑出对抗效果的读者新手可以跟着步骤走熟手可以直接看参数边界和避坑部分。2. MADDPG 的算法骨架集中式 Critic 与分散式 Actor 到底怎么接2.1 为什么不能直接把多个 DDPG 并排跑最朴素的做法是给每个智能体配一套独立的 DDPG各自维护自己的 Actor 和 Critic互不通信。问题出在 Critic 的更新目标上单智能体 DDPG 的 Critic 学的是 $Q(s,a)$它默认环境转移概率 $P(s|s,a)$ 是稳定的。但在多智能体环境里真实的状态转移是 $P(s|s,a_1,\dots,a_N)$其他智能体的策略一变同一个 $(s,a_i)$ 对应的回报分布就变了。Critic 追着一个移动的目标拟合自然学不稳。MADDPG 的解法是训练时让每个智能体的 Critic 输入所有智能体的观测和动作即 $Q_i(s, a_1, \dots, a_N)$。这样 Critic 看到的是全局信息环境对它来说重新变成平稳的。而 Actor 只拿自己的局部观测 $o_i$ 输出动作 $a_i$执行阶段不需要其他智能体的信息。这就是 CTDE 的核心权衡——用训练时的全局信息换取执行时的去中心化。2.2 网络结构与输入输出维度对照落地时最容易搞混的就是每个网络的输入维度。我一般会先画一张维度对照表再动手写代码否则调试时维度报错能查半天。网络输入输出说明Actor_i局部观测 $o_i$维度 obs_dim_i动作 $a_i$维度 act_dim_i执行时只用这个Critic_i全局状态拼接所有动作维度 sum(obs_dim) sum(act_dim)Q 值标量仅训练时使用Target Actor_i同 Actor_i同 Actor_i软更新延迟跟随Target Critic_i同 Critic_i同 Critic_i软更新计算 TD 目标关键点在于 Critic 的输入是「所有智能体的观测拼接 所有智能体的动作拼接」。假设两个智能体每个观测 8 维、动作 2 维那么 Critic 输入就是 882220 维。这个拼接顺序在所有智能体之间必须一致否则 Target Critic 算出来的 TD 目标就是错的。2.3 用 PyTorch 搭出可复用的 Actor 与 Critic下面这段是我常用的网络定义Actor 用 tanh 输出保证动作落在 [-1,1]Critic 用两层 256 的隐藏层。注意 Critic 的 forward 接收的是全局观测和全局动作两个参数拼接在内部完成。import torch import torch.nn as nn import torch.nn.functional as F class Actor(nn.Module): def __init__(self, obs_dim, act_dim, hidden256): super().__init__() self.fc1 nn.Linear(obs_dim, hidden) self.fc2 nn.Linear(hidden, hidden) self.out nn.Linear(hidden, act_dim) # 动作范围缩放环境动作若不在[-1,1]需在此调整 self.act_limit 1.0 def forward(self, obs): x F.relu(self.fc1(obs)) x F.relu(self.fc2(x)) # tanh 保证输出有界避免动作发散 return torch.tanh(self.out(x)) * self.act_limit class Critic(nn.Module): def __init__(self, total_obs_dim, total_act_dim, hidden256): super().__init__() # 输入是全局观测与全局动作的拼接 self.fc1 nn.Linear(total_obs_dim total_act_dim, hidden) self.fc2 nn.Linear(hidden, hidden) self.out nn.Linear(hidden, 1) def forward(self, all_obs, all_act): x torch.cat([all_obs, all_act], dim-1) x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) return self.out(x)逻辑说明Actor 只吃自己的 obs输出动作Critic 吃拼接后的全局 obs 和全局 act。参数上 hidden 取 256 是常见起点环境复杂可以加到 400/300 这种 DDPG 论文里的配置。act_limit 要根据实际环境的动作范围改比如某些连续控制环境动作是 [-2,2]这里就要设成 2.0否则 Actor 永远输出不到边界策略会被限制在次优解。2.4 经验回放池要存全局信息多智能体经验回放和单智能体最大的区别是每条经验里要存所有智能体的观测、动作、奖励以及下一个全局观测。因为 Critic 训练时需要拿到全部智能体的动作如果回放池只存单个智能体的数据Critic 就没法构造输入。import numpy as np import random class ReplayBuffer: def __init__(self, capacity, num_agents, obs_dims, act_dims): self.capacity capacity self.num_agents num_agents self.obs_dims obs_dims self.act_dims act_dims self.ptr 0 self.size 0 # 每个智能体单独存采样时再拼接 self.obs [np.zeros((capacity, d), dtypenp.float32) for d in obs_dims] self.act [np.zeros((capacity, d), dtypenp.float32) for d in act_dims] self.rew [np.zeros((capacity, 1), dtypenp.float32) for _ in range(num_agents)] self.next_obs [np.zeros((capacity, d), dtypenp.float32) for d in obs_dims] self.done np.zeros((capacity, 1), dtypenp.float32) def push(self, obs_list, act_list, rew_list, next_obs_list, done): for i in range(self.num_agents): self.obs[i][self.ptr] obs_list[i] self.act[i][self.ptr] act_list[i] self.rew[i][self.ptr] rew_list[i] self.next_obs[i][self.ptr] next_obs_list[i] 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) obs_batch [torch.as_tensor(self.obs[i][idx]) for i in range(self.num_agents)] act_batch [torch.as_tensor(self.act[i][idx]) for i in range(self.num_agents)] rew_batch [torch.as_tensor(self.rew[i][idx]) for i in range(self.num_agents)] next_obs_batch [torch.as_tensor(self.next_obs[i][idx]) for i in range(self.num_agents)] done_batch torch.as_tensor(self.done[idx]) return obs_batch, act_batch, rew_batch, next_obs_batch, done_batch参数说明capacity 一般取 1e6小环境 1e5 也够num_agents 要和环境里智能体数量严格一致obs_dims 和 act_dims 是列表每个元素对应一个智能体的维度。采样时返回的是列表训练循环里再按智能体索引取用。这里用 numpy 存、采样时转 tensor比全程 tensor 存更省显存也方便和环境返回的 numpy 观测对接。3. 从零跑通训练循环MADDPG 的更新顺序与参数设置3.1 训练循环的六个步骤MADDPG 的训练循环比单智能体 DDPG 多了一层「按智能体循环」的结构但核心顺序不变。我一般按下面六步走每一步都有明确的输入输出调试时哪一步出问题一目了然。第一步每个智能体用自己的 Actor 根据局部观测选动作加探索噪声。第二步环境执行联合动作返回每个智能体的奖励和下一个观测。第三步把这条联合经验存入回放池。第四步从回放池采样一个 batch。第五步对每个智能体用 Target Actor 算下一个联合动作用 Target Critic 算 TD 目标更新 Critic。第六步用 Critic 的梯度更新 Actor再软更新所有 Target 网络。def select_actions(agents, obs_list, noise_scale0.1): actions [] for i, agent in enumerate(agents): obs_t torch.as_tensor(obs_list[i], dtypetorch.float32).unsqueeze(0) with torch.no_grad(): act agent.actor(obs_t).squeeze(0).numpy() # 高斯噪声用于探索训练后期可衰减 act act np.random.normal(0, noise_scale, sizeact.shape) act np.clip(act, -agent.act_limit, agent.act_limit) actions.append(act) return actions def update(agents, buffer, batch_size, gamma0.95, tau0.01): obs_b, act_b, rew_b, next_obs_b, done_b buffer.sample(batch_size) for i, agent in enumerate(agents): # 用 Target Actor 算下一个联合动作 with torch.no_grad(): next_act_b [ agents[j].target_actor(next_obs_b[j]) for j in range(len(agents)) ] all_next_obs torch.cat(next_obs_b, dim-1) all_next_act torch.cat(next_act_b, dim-1) target_q agents[i].target_critic(all_next_obs, all_next_act) # done 为 1 时目标只剩即时奖励 y rew_b[i] gamma * (1 - done_b) * target_q all_obs torch.cat(obs_b, dim-1) all_act torch.cat(act_b, dim-1) q agents[i].critic(all_obs, all_act) critic_loss F.mse_loss(q, y) agents[i].critic_optim.zero_grad() critic_loss.backward() # 梯度裁剪防止 Critic 发散 torch.nn.utils.clip_grad_norm_(agents[i].critic.parameters(), 0.5) agents[i].critic_optim.step() # Actor 只用自己的 obs梯度来自 Critic 对动作的偏导 cur_act agent.actor(obs_b[i]) act_for_critic list(act_b) act_for_critic[i] cur_act all_act_for_critic torch.cat(act_for_critic, dim-1) actor_loss -agents[i].critic(all_obs, all_act_for_critic).mean() agents[i].actor_optim.zero_grad() actor_loss.backward() agents[i].actor_optim.step() # 软更新 Target 网络 soft_update(agent.target_actor, agent.actor, tau) soft_update(agent.target_critic, agent.critic, tau) def soft_update(target, source, tau): for tp, sp in zip(target.parameters(), source.parameters()): tp.data.copy_(tau * sp.data (1 - tau) * tp.data)逻辑说明Critic 的 TD 目标里下一个联合动作全部来自 Target Actor这是为了减少目标波动。Actor 更新时只替换当前智能体的动作为当前 Actor 的输出其他智能体的动作仍用回放池里的真实动作这样 Critic 对当前 Actor 的梯度才是准确的。参数上 gamma 取 0.95 是博弈对抗场景的常见值比单智能体的 0.99 略小因为对抗中远期回报不确定性更高tau 取 0.01 是标准软更新系数梯度裁剪 0.5 是我在对抗环境里必加的不加很容易在几千步后 Critic loss 爆掉。3.2 探索噪声与训练节奏多智能体对抗里探索噪声的设置比单智能体更敏感。如果所有智能体都用同样的噪声策略容易出现「一起乱动」或者「一起不动」的同步现象。我一般会给不同智能体设置不同的噪声衰减节奏或者至少让噪声尺度随训练步数线性衰减。class GaussianNoise: def __init__(self, act_dim, scale0.3, min_scale0.05, decay0.9995): self.scale scale self.min_scale min_scale self.decay decay def sample(self, act_dim): # 每步衰减但不低于 min_scale self.scale max(self.min_scale, self.scale * self.decay) return np.random.normal(0, self.scale, sizeact_dim)参数说明初始 scale 取 0.3 适合动作范围 [-1,1] 的环境如果动作范围更大要按比例放大min_scale 是噪声下限防止后期完全不探索导致策略僵化decay 取 0.9995 意味着大约 2000 步衰减到一半这个节奏在多数对抗环境里比较稳。注意噪声只加在执行阶段存回放池的应该是加噪后的动作因为 Critic 要评估的是实际执行的动作价值。3.3 奖励设计与对抗平衡博弈对抗环境里奖励设计直接决定算法能不能学出有意义的对抗。常见做法是零和奖励一方得分等于另一方失分。但纯零和容易导致训练不稳定因为任何一方的策略突变都会让另一方 Critic 的目标剧烈变化。我一般会在零和基础上加一个小的生存奖励或时间惩罚让智能体在学会对抗之前先学会不提前出局。def compute_rewards(env_result, agent_ids, survival_bonus0.01): rewards {} for aid in agent_ids: r env_result[aid][score] # 加生存奖励鼓励智能体存活更久 if env_result[aid][alive]: r survival_bonus rewards[aid] r return rewards参数说明survival_bonus 取 0.01 量级太大智能体会学会苟活而不对抗太小起不到稳定训练的作用。这个值要根据主奖励的量级调整如果主奖励是每步 ±10.01 大约是 1% 的权重比较合适。如果环境本身有明确的胜负终止条件也可以不加生存奖励改用胜负奖励的折扣累积。4. 避坑与排查MADDPG 训练中最容易翻车的五个地方4.1 现象Critic loss 先降后爆几千步后变成 NaN原因多智能体 Critic 的输入维度随智能体数量线性增长联合动作空间变大TD 目标的方差比单智能体高得多。如果不做梯度裁剪和奖励缩放Critic 的 Q 值很容易在早期估计出很大的值然后自我强化到发散。解决第一Critic 梯度裁剪设 0.5 或 1.0第二奖励做归一化用 running mean/std 把奖励缩放到 [-1,1] 附近第三Target Critic 的软更新 tau 不要大于 0.01太大目标网络跟得太快失去稳定作用。我自己的习惯是训练前先跑 100 步随机策略统计奖励的均值和方差用这个统计量做归一化。4.2 现象一个智能体越来越强另一个奖励长期不涨原因对抗环境里先学到策略的一方会压制另一方另一方因为探索不到有效动作Critic 学不到正样本形成马太效应。这不是算法 bug是博弈对抗的固有难点。解决第一给弱势方更大的探索噪声或更慢的噪声衰减第二用 self-play 的思路定期把强势方的策略快照作为对手而不是让两个在线策略一直互打第三检查奖励是否对称如果环境本身奖励不对称需要在算法层面做奖励重塑。我一般会在训练日志里分别记录每个智能体的 episode reward一旦发现差距持续扩大超过 3 倍就手动干预。4.3 现象Actor loss 震荡但不下降策略始终随机原因Actor 的梯度来自 Critic 对动作的偏导如果 Critic 本身没学好Actor 拿到的梯度方向就是噪声。常见触发条件是 Critic 更新次数不够或者 Actor 学习率相对 Critic 太高。解决第一确认每个训练步里 Critic 至少更新一次Actor 更新一次不要颠倒顺序第二Actor 学习率设成 Critic 的 1/10 到 1/5比如 Critic 用 1e-3Actor 用 1e-4第三检查 Critic 的输入是否真的包含了所有智能体的动作漏掉任何一个都会让 Actor 梯度失真。这个坑我在第一次复现时踩过Critic 只吃了自己的动作Actor 怎么训都不动。4.4 现象训练初期奖励就很高但策略明显是作弊原因环境奖励设计有漏洞比如智能体可以通过某种退化行为刷分而不是真正完成对抗任务。多智能体环境里这种漏洞比单智能体更常见因为智能体之间的交互会产生设计者没预料到的策略。解决第一训练前用随机策略跑几百步看奖励分布是否合理第二在奖励函数里加对抗性约束比如只有双方都参与交互时才给分第三定期保存策略回放视频或轨迹人工检查行为是否合理。我一般会在训练脚本里加一个每 100 episode 保存一次轨迹的钩子出问题时回看轨迹比看曲线有用得多。4.5 现象换一个随机种子结果完全不可复现原因多智能体训练的随机性来源比单智能体多——环境初始化、每个智能体的网络初始化、动作噪声、回放池采样任何一处没固定种子都会导致结果漂移。而且多智能体系统对初始条件更敏感混沌效应更明显。解决第一在训练脚本开头固定 numpy、torch、random 和环境的种子第二把环境 seed 和网络初始化 seed 分开设置方便做消融第三不要期望完全复现而是看多次运行的中位数表现。我一般会跑 3 到 5 个种子报告均值和标准差单次结果不作为结论。5. 进阶技巧用参数共享和优先回放把训练效率提上去5.1 同构智能体的参数共享如果环境里的智能体是同构的——观测维度、动作维度、任务目标都一样——那么可以让它们共享一套 Actor 和 Critic 参数。这样做的好处是每个智能体的经验都会更新同一套网络样本效率直接翻倍。实现上只需要把所有智能体的观测和动作在 batch 维度拼接Critic 的输入维度不变但 batch size 等效变大。def shared_update(shared_agent, agents, buffer, batch_size): obs_b, act_b, rew_b, next_obs_b, done_b buffer.sample(batch_size) n len(agents) # 把所有智能体的数据在 batch 维拼接 all_obs torch.cat(obs_b, dim0) all_next_obs torch.cat(next_obs_b, dim0) all_act torch.cat(act_b, dim0) all_rew torch.cat(rew_b, dim0) all_done done_b.repeat(n, 1) with torch.no_grad(): next_act shared_agent.target_actor(all_next_obs) # 这里需要构造全局 Critic 输入同构场景下可近似用自身观测代替 target_q shared_agent.target_critic(all_next_obs, next_act) y all_rew 0.95 * (1 - all_done) * target_q q shared_agent.critic(all_obs, all_act) critic_loss F.mse_loss(q, y) # 后续更新步骤与独立网络一致 return critic_loss参数说明参数共享的前提是智能体同构异构智能体强行共享会让 Critic 输入维度对不上。同构场景下Critic 的全局输入可以退化成「自身观测 自身动作」因为所有智能体共享同一套策略其他智能体的行为可以用自身策略近似。这个近似在智能体数量多的时候能大幅降低计算量但会损失一些理论上的精确性实践中效果通常可以接受。5.2 优先回放加速对抗关键样本的学习对抗环境里大部分经验是平淡的相持阶段真正决定胜负的是少数关键交互。优先回放Prioritized Experience Replay按 TD 误差给样本加权让 Critic 多学那些「意外」的样本。实现上需要在回放池里维护每个样本的优先级采样时按优先级概率抽。参数建议值作用alpha0.6优先级指数0 是均匀采样1 是完全按优先级beta0.4 起步线性升到 1重要性采样权重纠正优先级带来的偏差epsilon1e-5防止优先级为 0 的样本永远不被抽到优先回放对多智能体的收益比单智能体更明显因为对抗中的关键转折样本更稀疏。但要注意 beta 必须随训练升到 1否则重要性采样偏差会让 Critic 估计有偏。我一般会在训练前 10 万步把 beta 从 0.4 线性升到 1之后保持。5.3 验证训练是否真的在对抗最后说一个我自己的习惯不要只看奖励曲线判断训练好坏。奖励高可能是环境漏洞奖励低可能是探索不够。我会在训练中期固定一个对手策略比如早期快照让当前策略和它打 100 局统计胜率。如果胜率随训练上升说明策略确实在变强如果奖励涨了但胜率不动大概率是奖励设计有问题。这个验证方法比看曲线可靠得多也是我踩了多次「奖励虚高」的坑之后养成的习惯。希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号