news 2026/10/26 17:26:05

MADDPG多智能体博弈算法Python源码解析与实战训练指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MADDPG多智能体博弈算法Python源码解析与实战训练指南

简介:基于Python的MADDPG多智能体博弈对抗算法实现,属于机器学习与强化学习领域,完整涵盖DDPG与MADDPG核心训练逻辑、神经网络结构构建、经验回放机制、探索策略及独立测试脚本,并集成ma-gym多智能体环境库与可视化演示模块,面向人工智能、自动化、通信工程、电子信息等专业的高校学生、科研工作者和行业从业者,适用于毕业设计、课程设计、项目初期演示及算法对比验证。压缩包内共91个文件,主体为76个Python源码,另有ipynb交互式笔记本、GIF实验录屏、配置文件与说明文档,整体仅3.26MB,轻量且目录清晰。目前已有92人学习或浏览,具备一定参考价值。资源除完整可运行的算法代码外,还提供多智能体对抗场景案例、环境封装接口及训练结果动画,可直观呈现智能体学习与策略演化过程;同时附带基础配置说明和调试指引,帮助使用者快速搭建实验环境,也便于二次开发与算法改进。

1. MADDPG 多智能体博弈对抗算法:这份 Python 源码能跑出什么

MADDPG(Multi-Agent Deep Deterministic Policy Gradient)是目前做多智能体博弈对抗绕不开的一版基线算法。很多第一次跑多智能体强化学习的人都会遇到一个反直觉的现象:同一套 DDPG 代码,在单智能体环境里收敛正常,改成双方对战之后,训练几千回合也不涨分。原因不是代码写错了,而是对手策略在变,环境对单个智能体来说始终是“非平稳”的。这份资源就是针对这个问题的 Python 实现:它把 MADDPG 算法源码和 ma-gym 环境库打包在一起,包含 combat、predator_prey、switch、checkers 等多个对抗场景,还带 main.py、test.py 和实验结果数据。适合正在做强化学习课设、多智能体博弈方向毕设的计算机相关专业学生,也适合想快速验证 MADDPG 基线效果的工程师和研究者。

2. 从 DDPG 到 MADDPG:为什么训练时要开全局视角

2.1 多智能体博弈为什么让单智能体算法失灵

单智能体 DDPG 的训练逻辑其实很简洁:actor 根据观测输出动作,critic 给这个动作打分,经验回放池把历史数据打散,然后随机采样更新。这套逻辑在单智能体环境里没问题,因为环境状态转移只取决于自己的动作,数据分布相对稳定。但到了多智能体博弈里,状态转移概率还取决于对手的动作:对手策略一变,你过去积累的旧经验就变成“过期数据”,用它们去更新 Q 值会把估计带偏,训练自然就卡住。

MADDPG 的解法是在训练时引入全局视角:每个智能体的 critic 输入所有智能体的观测和动作,相当于让评论员站在“上帝视角”做价值判断;而 actor 仍然只输入自己的局部观测,执行时不需要依赖别人信息。这就是常说的 CTDE(集中训练、分布执行)。这个设计改动很小,但正好解决了环境非平稳带来的 Q 值高估问题,因此在博弈对抗场景里比独立训练 DDPG 稳定得多。

2.2 源码组织:从 buffer.py 到 network.py,一条数据怎么流转

这份资源解压后的结构大致是这样:

MADDPG/ ├── MADDPG.py # 主算法:MADDPG 训练与更新逻辑 ├── DDPG.py # 单智能体 DDPG,用于对照实验 ├── network.py # Actor / Critic 网络定义 ├── buffer.py # 多智能体经验回放池 ├── rl_utils.py # 噪声、奖励处理等工具函数 ├── main.py # 训练主入口 ├── test.py # 加载模型跑验证 ├── test_env.py # 环境冒烟测试 └── ma-gym-master/ # 多智能体环境库 └── ma_gym/envs/ ├── combat/ ├── predator_prey/ ├── switch/ ├── checkers/ ├── traffic_junction/ ├── lumberjacks/ └── pong_duel/

先看 buffer.py。与单智能体最大的区别是,一条经验记录里存的是所有智能体的联合数据,而不是单智能体各自的独立数据:

# buffer.py 里一条经验记录的核心结构 experience = { "obs": obs_all, # shape: [n_agents, obs_dim] "act": act_all, # shape: [n_agents, act_dim] "rew": rew_all, # shape: [n_agents, 1] "next_obs": next_obs_all, # shape: [n_agents, obs_dim] "done": done # 全局终止标志 }

这里必须用联合数据,因为 MADDPG 的 critic 更新时要拿到全体观测和全体动作去计算 target Q。如果把经验拆成单智能体维度,训练时就拼不齐联合信息了。我在自己复现时最先看这个文件,因为只要这里存错维度,后面网络一跑就报 shape mismatch。

再看 network.py。Actor 和 Critic 的结构通常是这样的:

class Actor(nn.Module): def __init__(self, obs_dim, act_dim, hidden=64): super().__init__() self.fc1 = nn.Linear(obs_dim, hidden) self.fc2 = nn.Linear(hidden, hidden) self.out = nn.Linear(hidden, act_dim) def forward(self, x): x = torch.relu(self.fc1(x)) x = torch.relu(self.fc2(x)) return torch.tanh(self.out(x)) class Critic(nn.Module): def __init__(self, obs_dim, act_dim, n_agents, hidden=64): super().__init__() # 输入维度 = 所有智能体的观测 + 所有智能体的动作 self.fc1 = nn.Linear(n_agents * obs_dim + n_agents * act_dim, hidden) self.fc2 = nn.Linear(hidden, hidden) self.out = nn.Linear(hidden, 1) def forward(self, obs_all, act_all): x = torch.cat([obs_all, act_all], dim=-1) x = torch.relu(self.fc1(x)) return self.out(self.fc2(x))

注意 Actor 的输出层用了 tanh 激活,把动作压缩到 [-1, 1] 区间,这是 DDPG 系列处理连续动作的常见做法。但 ma-gym 里的环境大多是离散动作,所以外部还需要做一次动作映射,这个映射如果做错,训练会直接不收敛,后面避坑章节会专门讲。

2.3 三行核心更新公式:critic 和 actor 分别在学什么

MADDPG.py 里最核心的是更新代码,拆开看逻辑并不复杂:

# 1. 先算 target Q:使用 target 网络,输入所有智能体的下一步动作 with torch.no_grad(): next_actions = [] for i in range(n_agents): next_actions.append(target_actors[i](next_obs_all[i])) next_actions = torch.cat(next_actions, dim=-1) target_q = target_critics[i](next_obs_all, next_actions) y = reward_i + gamma * (1 - done) * target_q # 2. 更新 Critic:让当前 Q 值逼近 target Q critic_loss = F.mse_loss(critics[i](obs_all, torch.cat(actions, dim=-1)), y) # 3. 更新 Actor:最大化当前 Critic 对“自己当前策略”的打分 actor_loss = -critics[i](obs_all, torch.cat(actions_with_current_actor, dim=-1)).mean()

第一段代码里 target Q 的计算,关键点在于 next_actions 用的是 target actor 的输出,不是行为策略的动作。第二段更新 critic 时,输入是采样时的真实联合动作。第三段更新第 i 个智能体的 actor 时,联合动作里除了第 i 个智能体的动作来自当前 actor,其余智能体的动作保持采样时的动作不变。这个细节很隐蔽,如果图省事把全部动作都换成当前 actor 重新算,actor 的梯度方向就错了。

参数方面,我一般会按下面这套默认值起步:

参数常见默认值说明
gamma0.95折扣因子,稀疏奖励环境下可适当调高
tau0.01target 网络软更新系数
actor_lr1e-4actor 学习率,不宜过大
critic_lr1e-3critic 学习率,通常比 actor 大一档
batch_size128每次采样的经验条数
buffer_size1e6经验池容量,够用即可

这套参数在 ma-gym 的大多数环境里都能起步,等确认 baseline 跑通后再按场景调。

3. 跑通训练主流程:main.py 与 combat 场景的参数解读

3.1 运行前的环境准备:先用 test_env.py 做冒烟测试

我拿到这份资源后,第一件事不是急着跑 main.py,而是先确认 Python 环境和环境库本身可用。项目里带了 pyvenv.cfg 文件,说明作者是在虚拟环境里开发的,但这个虚拟环境在你机器上不一定能直接激活,所以最常见的做法是重建一个 venv:

cd MADDPG python -m venv venv source venv/bin/activate pip install numpy torch

如果你本机已经装好了 PyTorch 和 numpy,这一步可以跳过。然后先跑 test_env.py:

python test_env.py

test_env.py 做的是环境冒烟测试:创建环境实例、重置、用随机动作步进几步,确认 reset / step / render 接口都正常工作。如果这里报错,大概率是 gym 版本和 ma-gym 不兼容,或者环境里的依赖(比如 pygame)没装齐。这一步通过之前,不要碰 main.py,否则容易把环境问题误判成算法问题。

3.2 main.py 训练主循环:每步在做什么

main.py 的训练主循环,结构上大致是这样:

for episode in range(n_episodes): obs_all = env.reset() episode_rewards = [] for step in range(max_steps): actions = [] for i in range(n_agents): # 1. actor 输出连续动作 action_continuous = actors[i](obs_all[i]) # 2. 加探索噪声 action_noisy = action_continuous + noise[i]() # 3. 离散环境要做动作映射 action = map_to_discrete(action_noisy) actions.append(action) # 4. 环境步进,得到联合奖励和全局终止标志 next_obs_all, rew_all, done, _ = env.step(actions) # 5. 存入多智能体经验回放池 buffer.push(obs_all, actions, rew_all, next_obs_all, done) obs_all = next_obs_all episode_rewards.append(rew_all) # 6. 经验攒够后,每个 step 更新一次所有智能体 if buffer.size() > batch_size: update_all_agents(buffer, actors, critics, target_actors, target_critics) if episode % 100 == 0: avg_reward = np.mean(episode_rewards, axis=0) print(f"episode {episode}, avg reward: {avg_reward}")

这段代码里我特别想强调两个地方。第一个是噪声的加入方式,MADDPG 原论文和多数实现都是用 Ornstein-Uhlenbeck 噪声做时序相关探索,rl_utils.py 里应该就有实现;但 OU 噪声的方差如果不随时间退火,训练后期策略会一直在最优动作附近抖,reward 曲线就始终带着毛刺。第二个是 update_all_agents 的调用时机,常见做法是每步都更新一次,也有每 100 步更新一次的写法,这份代码默认是攒够 batch_size 后每步更新,理论上没毛病,但训练开销会明显偏大。

训练回合数方面,ma-gym 的 combat 场景我一般先跑 3000 到 5000 个 episode 看趋势。500 个 episode 内曲线不涨是正常的,因为这个环境里击杀事件本身是稀疏的,前期全靠随机探索碰运气。

3.3 训练结果怎么看:test.py 验证要关掉噪声

训练结束后,test.py 用来做模型验证:

python test.py --model-path ./results/model.pt

test.py 和 main.py 最大的区别是:验证时所有 actor 都不加噪声,纯确定性策略跑完若干 episode,统计平均奖励、击杀数等指标。这一步很容易踩坑的地方是忘记区分“训练模式”和“验证模式”——如果用带噪声的策略去验证,reward 会被拉低,你会误以为模型没训好。我一般会直接看每个 episode 里双方阵营的击杀差,而不是只看 reward,因为击败对手的稀疏事件最能反映策略是否真的在博弈。

4. 切换博弈对抗环境:ma-gym 里的七种场景怎么选

4.1 环境全景:每种环境适合验证什么

ma-gym 环境库整合在ma-gym-master/ma_gym/envs/目录下,每一种环境都是一个独立的博弈对抗或协作场景。它们的共同点是接口风格对标 OpenAIGym,都有 reset / step / render,但博弈结构和奖励设计差别很大:

环境博弈特征适合验证什么动作空间
combat两队士兵互相移动、开火团队对抗、稀疏奖励离散
predator_prey猎手围捕猎物协作追逐、分工围堵离散
switch智能体协作开关门时序决策、逃出房间离散
checkers简化跳棋对弈完全对抗、回合制博弈离散
traffic_junction车辆通过路口避碰、密集交通协作离散
lumberjacks收集木头的拼图任务协作规划离散
pong_duel双人乒乓球对打反应式对抗离散

如果你做的是“双方对战”这种对抗场景,combat 和 checkers 最合适;如果你想看协作,predator_prey 是经典选择。多数时候我建议先用 combat 起步,因为它的胜负判定直观,击杀事件明显,训练过程中肉眼能看出策略变化。

4.2 环境切换操作:主要改两个地方

切换环境不需要改算法代码,主要改 main.py 里的环境构建部分:

# 方式一:直接用 ma-gym 环境类(建议方式) from ma_gym.envs.combat.combat import Combat env = Combat(grid_size=15, n_agents=5, n_opponents=5) # 换成 predator_prey 只需要换环境类和参数 # from ma_gym.envs.predator_prey import PredatorPrey # env = PredatorPrey(grid_size=15, n_agents=2, n_preys=1)

切换时有两个容易翻车的点。第一个是构造函数参数名,ma-gym 不同环境接收的初始化参数不一样,比如 combat 是n_opponents,predator_prey 是n_preys,写错会直接 TypeError。第二个是观测维度会变,combat 里每个智能体看到的是周围一定范围内的敌我单位信息,predator_prey 里看到的可能是相对猎物的位置,obs_dim 不同时 network.py 里的输入维度必须同步改,否则 actor 网络直接报 shape 错误。

另外,ma-gym-master 里自带scripts/generate_env_markdown_table.py,可以生成一份环境清单文档,跑一下就能确认本机环境名和对应参数,省得翻源码。

4.3 离散动作映射:MADDPG 输出连续,环境要离散

这是切环境时最隐蔽的坑。原版 MADDPG 是为连续动作设计的,actor 输出的是 [-1, 1] 之间的连续向量,但 ma-gym 大多数环境的动作空间是 Discrete,比如 combat 里一个智能体的动作可能是“上下左右移动 + 开火”组成的离散索引。处理方式有两种:

# 方式一:直接取 argmax,把连续输出映射成离散索引 action = torch.argmax(action_continuous, dim=-1).item() # 方式二:把 actor 输出当作离散动作的 logits,按概率采样 action = torch.distributions.Categorical(logits=action_continuous).sample().item()

我建议先用方式一,简单稳定,训练初期探索靠噪声;等需要更细腻的策略再换成采样方式。换成离散动作后,每回合的回合制博弈(比如 checkers)还需要注意动作输入格式可能是坐标式的,不能简单套 argmax,这类环境最好先看它自带的 random_agent.py 是怎么调 step 的。

5. MADDPG 训练常见问题:五个必须记录的踩坑点

5.1 训练十几轮后 loss 直接变 NaN

现象:critic loss 前面还正常,跑了十几个 episode 突然输出 nan,之后 reward 曲线也全部变 nan。

原因:最常见是两个,一是观测或奖励数值没做归一化,量纲太大导致梯度爆炸;二是 critic 学习率设得过高,MADDPG 的 critic 输入是所有智能体拼接后的高维向量,比 DDPG 更容易梯度爆炸。

解决:先把 reward 做 clip,限制在 [-1, 1] 左右;再把 critic_lr 从 1e-3 降到 1e-4 试一轮;如果还炸,检查观测里有没有 inf 或极大值,通常 combat 环境里坐标归一化到 [0, 1] 之后就正常了。

5.2 报错 action shape mismatch,维度对不上

现象:环境 step 时报ValueError: action shape ... does not match action space ...,或者 actor 网络输出维度正常但 env.step 不接受。

原因:actor 输出是连续向量,但 ma-gym 的环境期望离散索引,或者期望每个智能体单独传一个整数,而代码里传了一个浮点数组。

解决:在 step 之前做动作映射,确认映射后的 action 类型是 int。判断方法很简单:打印env.action_space看一下是Discrete(n)还是MultiDiscrete,如果是 Discrete,每个智能体的动作就是一个整数,不是向量。

5.3 训练几百轮 reward 几乎不涨

现象:episode 平均 reward 一直在初始值附近抖动,偶尔高一次立刻掉回来,长时间没有上升趋势。

原因:探索噪声没有退火是头号嫌疑,OU 噪声方差恒定,导致策略一直在最优动作附近转圈;第二个原因是 reward 太稀疏,combat 里击杀事件是小概率事件,随机探索很难碰到正样本。

解决:先给噪声加一个随时间衰减的系数,比如每 1000 episode 乘以 0.9;同时把奖励设计改成“击中减血给小正奖励、被击中给小负奖励”,让智能体在稀疏的大奖励之外能学到梯度信号。这个方法比调网络结构有效得多。

5.4 test.py 加载模型时报 state_dict 大小不匹配

现象:加载.pt文件时 torch 报size mismatch for fc1.weight,或者干脆提示键名对不上。

原因:多数是因为训练时保存的是所有智能体的网络列表,而加载时只 load 了一个 actor,或者环境切换后 obs_dim / act_dim 变了,网络结构对不上。还有一种情况是保存了整个 model 对象,但用 load_state_dict 去读。

解决:加载前先打印torch.load(path).keys()看清楚里面是单网络还是网络列表;如果只是线性层的维度不匹配,说明是环境参数和训练时不一致,把 grid_size、n_agents 改回训练时的配置即可。

5.5 训练结束后策略只会原地打转

现象:test 时智能体站在原地不动,或者反复在同一格来回移动,双方几乎不发生接触。

原因:最典型的是“消极策略”被 reward 鼓励了——如果环境里不动不会受惩罚,而主动出击容易被反杀,策略网络就会学习到“不动最安全”;另一个原因是训练中期策略崩溃,actor 坍缩到一个固定输出,失去了探索能力。

解决:在 reward 里加一个小的每步惩罚,比如 -0.01,打破原地站立的收益优势;同时把噪声退火系数调慢一点,让策略后期仍有探索能力。这个问题的判定方法很直观:把 test 过程录成 gif,看一眼就知道智能体到底是在博弈还是在挂机。

6. 把实验结果变成验证闭环:记录曲线与录制对抗过程

6.1 把 episode 平均 reward 落地成 CSV

main.py 里打印的 reward 只在控制台一闪而过,不落地后面没法分析。我一般会在训练循环里加一小段:

import csv csv_file = open("rewards.csv", "a", newline="") writer = csv.writer(csv_file) # 每个 episode 结束后追加一行:回合数, 本轮平均奖励, 本轮胜方 writer.writerow([episode, avg_reward, winner]) csv_file.flush()

用 pandas 读出来画个 rolling mean 曲线,比肉眼看控制台可靠得多。画图时记得做平滑处理,原始曲线毛刺太重,rolling window 取 50 左右能看清趋势。

6.2 用 record_environment.py 录制 gif,验证策略是否在真博弈

ma-gym-master 里带了scripts/record_environment.py,可以把环境渲染过程录成 gif。常见的运行方式类似:

python scripts/record_environment.py -e Combat -n 5 --episodes 3 -o combat_demo.gif

具体参数名先跑一遍python scripts/record_environment.py --help确认,不同版本脚本的参数定义略有差异。录 gif 的价值不在于好看,而在于验证策略是不是在“真博弈”:两个队伍是真正在接近对手、开火、闪避,还是各自在原地随机抖动。reward 曲线可以骗人,策略可视化骗不了人,这是多智能体强化学习里最重要的验证手段。

我从那以后,每次拿到一个新环境做 MADDPG,都会先固定随机种子跑一遍 random_agent.py,记下随机策略的平均奖励作为最低基线;然后跑 MADDPG,把训练曲线和录制的对抗 gif 放在一起看,确认提升不是来自环境本身的偶然波动。这个流程帮我挡掉了不少“看起来收敛了、其实是环境太简单”的虚假成功,希望帮到你。

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

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

深入解析Deep Agents Code CLI启动流程与配置系统设计

1. 从入口文件看一个 CLI 工具的设计野心很多人读源码喜欢从核心算法模块开始啃,觉得那才是"有价值"的部分。但我在实际拆解过十几个开源 CLI 工具之后,越来越倾向于先读main.py。原因很直接:入口文件是一个项目设计哲学的浓缩版&a…

作者头像 李华
网站建设 2026/10/26 17:25:06

3D游戏提示词设计指南与高效应用技巧

1. 3D游戏提示词的价值与应用场景在游戏开发领域,3D游戏提示词(Prompts)已经成为提升开发效率的重要工具。这些精心设计的文本提示可以直接应用于各类3D游戏引擎和AI生成工具,帮助开发者快速实现特定场景、角色或效果的创建。不同…

作者头像 李华
网站建设 2026/10/26 17:22:26

科技服务机构数字化转型:痛点解析与效率提升方案

1. 科技服务机构面临的行业痛点与转型需求科技服务行业正经历着从粗放式增长向精细化运营的关键转型期。我接触过不少年营收在3000-5000万规模的科技服务企业,发现他们普遍面临着两个核心痛点:一是技术服务同质化严重,客户难以感知专业价值&a…

作者头像 李华
网站建设 2026/10/26 17:22:07

Muse开源SDK:AI Agent从屏幕囚笼走向ESP32硬件

1. 从榜单到开源:Muse 这步棋到底在下什么Muse 登顶 App Store 之后紧接着开源 SDK,这个节奏本身就值得琢磨。单纯看“登顶”这件事,在当下的应用生态里并不算特别稀奇——任何一个踩中情绪价值或者效率痛点的产品,配合一波运营&a…

作者头像 李华
网站建设 2026/10/26 17:20:01

生产线数智化转型中的质量可靠性管控实践

1. 生产线数智化转型中的质量可靠性管控实践在制造业数字化转型浪潮中,生产线数智化已成为提升企业核心竞争力的关键路径。我负责实施的某汽车零部件生产线改造项目,通过部署智能传感网络和边缘计算节点,将传统抽样检测升级为全流程实时质量监…

作者头像 李华
网站建设 2026/10/26 17:18:11

YOLO算法在农业杂草检测中的优化与应用

1. 田间杂草智能检测系统的核心价值 农业智能化转型中,杂草识别一直是制约精准施药的关键瓶颈。传统人工巡查方式每亩地平均需要3-5个工时,而基于无人机的视觉检测系统能在20秒内完成同等面积的扫描。我们团队实测数据显示,使用YOLO系列算法构…

作者头像 李华