简介这份资源是面向计算机相关专业学生与强化学习入门者的多智能体博弈对抗实战项目基于MADDPG算法用Python实现可直接用于课程设计、期末大作业或项目实战练习。压缩包共13个文件以10个py源码文件为主另含1个txt说明、1个cfg环境配置与1个gitignore整体约15KB体积轻便。源码按模块拆分涵盖DDPG网络、MADDPG核心逻辑、经验回放缓冲区、神经网络构建、强化学习工具函数以及多智能体环境与测试脚本并配有代码注释便于理解算法流程与调试思路。该资源为个人98分高分项目代码完整、经过严格调试下载即用能帮助读者快速跑通多智能体博弈对抗实验掌握集中训练分散执行的实现细节。目前已有423人学习下载适合需要完整参考方案与排错思路的学习者。1. 从一份 98 分课设说起MADDPG 多智能体博弈对抗到底能跑出什么如果你正在做多智能体强化学习的课程设计大概率会遇到一个尴尬局面单智能体的 DQN、DDPG 代码一搜一大把但一旦把环境里塞进两个以上会互相影响的智能体训练曲线立刻变成玄学——奖励不涨、loss 震荡、智能体学会摆烂。这份基于 MADDPG 的多智能体博弈对抗算法 Python 实现源码就是冲着这个痛点来的。它把多智能体博弈对抗算法拆成了可直接运行的工程MADDPG.py是算法主体DDPG.py是单智能体基座network.py定义 Actor/Critic 网络buffer.py管经验回放main.py串起训练循环rl_utils.py收口工具函数。适合两类人一是赶期末大作业、需要一份能跑通、有注释、结构清晰的参考实现二是想真正搞懂 MADDPG 里「集中训练、分散执行」到底怎么落到代码上的学习者。下面我按「这是什么 → 怎么跑起来 → 参数怎么调 → 坑在哪」的顺序把这份源码拆开讲。2. MADDPG 的算法骨架集中式 Critic 与分散式 Actor 怎么在代码里落地MADDPG 的核心思想一句话能说清每个智能体的 Actor 只看自己的局部观测但 Critic 在训练时能看到所有智能体的观测和动作。这个设计解决了多智能体环境非平稳的问题——因为每个智能体都在学习环境对单个智能体来说一直在变。源码里MADDPG.py和DDPG.py的分工正好对应这个思想。2.1 从 DDPG 到 MADDPG为什么不能直接复制单智能体代码DDPG 是确定性策略梯度算法适合连续动作空间。它的结构是 Actor 输出确定性动作Critic 评估这个动作的 Q 值。单智能体场景下Critic 只需要看自己的状态和动作就够了。但多智能体场景下如果每个智能体的 Critic 也只盯着自己就会出问题智能体 A 的策略一变智能体 B 面对的环境就变了B 的 Q 值估计立刻失准训练进入「你变我也变」的死循环。MADDPG 的解法是给 Critic 开天眼。训练时第 i 个智能体的 Critic 输入是全局状态所有智能体的观测拼接和全局动作所有智能体的动作拼接输出一个标量 Q 值。这样 Critic 的评估基准是稳定的全局信息不会因为某个智能体偷偷改策略就崩掉。而 Actor 依然只拿局部观测保证执行时不需要全局通信。这就是「集中训练、分散执行」的字面含义。源码里DDPG.py大概率是单智能体的基类实现包含 Actor 网络、Critic 网络、目标网络软更新、经验回放这些通用组件。MADDPG.py则在此基础上做扩展为每个智能体维护一套 Actor 和 CriticCritic 的输入维度从「局部观测局部动作」变成「全局观测全局动作」。network.py里应该定义了多层感知机结构buffer.py负责存储(obs, action, reward, next_obs, done)五元组其中 obs 和 next_obs 都是全局的。2.2 网络结构与经验回放network.py和buffer.py的关键参数network.py通常包含两个类Actor 和 Critic。Actor 的输入维度是单个智能体的观测维度输出维度是动作维度中间用两层全连接加 ReLU 激活最后一层用 tanh 把动作压到[-1, 1]。Critic 的输入维度是「全局观测维度 全局动作维度」输出是一个标量 Q 值中间同样两层全连接。这里有个容易翻车的点Critic 的输入拼接顺序必须和训练时保持一致否则 Q 值会莫名其妙地偏。buffer.py实现经验回放池。常见做法是用一个固定大小的队列或列表存满后按先进先出替换。采样时随机抽一批返回 numpy 数组或 tensor。关键参数是capacity太小会导致样本相关性高、训练不稳太大则占用内存且早期旧策略的样本会拖后腿。对于课设规模的环境capacity设在 100000 到 1000000 之间比较常见。# buffer.py 经验回放池的典型实现 import numpy as np class ReplayBuffer: def __init__(self, capacity): self.capacity capacity self.buffer [] self.position 0 def push(self, state, action, reward, next_state, done): # state/action 均为全局拼接后的数组 if len(self.buffer) self.capacity: self.buffer.append(None) self.buffer[self.position] (state, action, reward, next_state, done) self.position (self.position 1) % self.capacity def sample(self, batch_size): # 随机采样打破时间相关性 batch np.random.choice(len(self.buffer), batch_size, replaceFalse) state, action, reward, next_state, done zip(*[self.buffer[i] for i in batch]) return (np.array(state), np.array(action), np.array(reward), np.array(next_state), np.array(done)) def __len__(self): return len(self.buffer)这段代码的逻辑很直白push负责存sample负责随机取。参数capacity控制池子上限batch_size控制每次训练抽多少条。注意done标志位在计算目标 Q 值时用来决定是否截断如果环境是分幕的done 为 True 时目标 Q 值只取即时奖励不再加折扣后的下一状态价值。2.3 训练主循环main.py里每个 episode 到底在做什么main.py是整份源码的入口。典型流程是初始化环境、初始化 MADDPG 智能体集合、初始化回放池然后进入双层循环——外层是 episode内层是 timestep。每个 timestep 里每个智能体根据自己的 Actor 选动作环境执行联合动作后返回各自的奖励和下一个全局观测把这些数据塞进回放池。当回放池样本足够时每个智能体从池里采样一批计算 Critic 损失和 Actor 损失反向传播更新网络最后软更新目标网络。# main.py 训练循环骨架 for episode in range(num_episodes): obs env.reset() for t in range(max_steps): actions [agent.select_action(o) for agent, o in zip(agents, obs)] next_obs, rewards, dones, _ env.step(actions) # 拼接全局状态和全局动作 global_obs np.concatenate(obs) global_next_obs np.concatenate(next_obs) global_actions np.concatenate(actions) buffer.push(global_obs, global_actions, rewards, global_next_obs, dones) if len(buffer) batch_size: for agent in agents: agent.update(buffer, batch_size) obs next_obs if all(dones): break这里有几个参数值得盯住max_steps控制单幕最大步数太小会导致智能体来不及学到有效策略batch_size影响梯度估计的方差软更新系数tau通常设 0.01 左右控制目标网络跟随当前网络的速度。gamma是折扣因子博弈对抗场景下一般设 0.95 到 0.99。3. 把源码跑起来环境依赖、入口选择与训练曲线观察拿到一份源码最怕的是「下载即用」变成「下载即报错」。这一章按实际复现顺序走一遍从 Python 环境到跑通训练再到怎么看结果。3.1 环境准备Python 版本、依赖库与pyvenv.cfg的暗示源码包里有个pyvenv.cfg说明作者用的是 Python 虚拟环境。常见做法是 Python 3.8 或 3.9太新的版本可能和某些老库不兼容。依赖库大概率包括numpy、torch、gym或gymnasium可能还有matplotlib用于画训练曲线。安装命令如下# 创建虚拟环境并安装依赖 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install numpy torch gym matplotlib如果gym版本较新注意env.step()的返回值从 4 个变成了 5 个多了truncated源码里如果按 4 个解包会报错。解决办法是降级gym到 0.21 左右或者手动改解包逻辑。这是多智能体代码里最常见的翻车点之一。3.2 入口文件辨析main.py、test.py、ceshi.py分别干什么源码里有三个疑似入口main.py、test.py、ceshi.py。按命名习惯main.py是训练主入口test.py是测试或评估入口ceshi.py是「测试」的拼音可能是作者调试时留下的脚本。test_env.py大概率是自定义环境或环境测试文件。mag.py可能是某个具体博弈场景的实现比如追逃、对抗或合作任务。建议的复现顺序是先跑test_env.py确认环境能正常 reset 和 step再跑main.py看训练是否启动最后用test.py加载训练好的模型做评估。如果main.py里直接调用了mag.py里的环境类那mag.py就是核心场景文件值得重点读。3.3 训练过程监控奖励曲线、loss 与「不涨」的排查思路训练启动后最直观的监控指标是每个 episode 的累计奖励。多智能体博弈对抗场景下奖励曲线通常不会像单智能体那样平滑上升而是呈现对抗性震荡——一方涨另一方跌或者双方在某个水平附近拉锯。这是正常的因为博弈的本质就是策略互相压制。如果奖励完全不涨按以下顺序排查第一检查动作是否真的作用到了环境有些环境对动作范围有要求Actor 输出的[-1,1]可能需要缩放第二检查 Critic 的输入是否真的拼接了全局信息如果只拼了局部那就退化成独立 DDPG非平稳问题会直接压垮训练第三检查回放池是否在样本足够前就开始训练早期样本太少会导致过拟合第四看 loss 是否爆炸如果 Critic loss 冲到几百多半是学习率太大或奖励尺度没归一化。# 训练曲线绘制的常见做法 import matplotlib.pyplot as plt rewards_history [] # 每个 episode 的累计奖励 plt.plot(rewards_history) plt.xlabel(Episode) plt.ylabel(Cumulative Reward) plt.title(MADDPG Training Curve) plt.savefig(training_curve.png)这段代码不复杂但关键在rewards_history怎么记。常见做法是记录所有智能体奖励之和或者分别记录每个智能体的奖励画多条线。博弈对抗场景下分开画更能看出谁占优。4. 参数调优与避坑学习率、软更新与那些让训练崩掉的细节这一章集中讲踩坑。MADDPG 的论文看起来清晰但落到代码里参数之间的耦合比单智能体复杂得多。以下五条是我在复现这类源码时反复遇到的。4.1 学习率与网络更新频率Actor 和 Critic 不能一刀切现象训练初期奖励短暂上升后迅速崩塌loss 剧烈震荡。原因Actor 和 Critic 用了同一个学习率且 Critic 更新太频繁。Critic 需要先学到一个相对准确的 Q 值估计Actor 才能根据它更新策略。如果两者同步猛更新Critic 还没站稳Actor 就被带偏了。解决Critic 的学习率通常比 Actor 大一个量级比如 Critic 用1e-3Actor 用1e-4。另外可以延迟 Actor 的更新比如 Critic 每更新两次Actor 更新一次。源码里如果没做这个区分可以手动在update函数里加一个计数器。4.2 软更新系数tau的取值太大目标网络形同虚设现象训练不稳定Q 值估计和目标 Q 值差距始终很大。原因tau设得太大比如 0.1 甚至 0.5目标网络几乎在复制当前网络失去了「稳定靶子」的作用。MADDPG 原文建议tau取 0.01。解决把tau降到 0.01 或 0.005观察 Q 值是否逐渐收敛。如果环境特别简单可以适当放大到 0.02但不要超过 0.05。软更新公式是target_param tau * param (1 - tau) * target_paramtau越小目标网络越稳。4.3 奖励尺度与归一化博弈对抗里奖励不对称会怎样现象一方智能体奖励始终为正另一方始终为负训练后期弱势方直接摆烂。原因博弈环境的奖励设计本身可能不对称比如追逃任务里追捕者每步都有惩罚逃跑者每步都有奖励。如果不做归一化Critic 的 Q 值范围会差出几个量级。解决常见做法是对奖励做 running mean 标准化或者手动把奖励缩放到[-1, 1]。另外可以给每个智能体单独维护一个奖励缩放系数避免强势方主导梯度。源码里如果没做这一步可以在buffer.push之前加一层奖励处理。4.4 观测拼接顺序与维度对齐一个下标错位就白训练现象代码能跑loss 也降但智能体学不到任何有效策略。原因全局观测拼接时智能体的顺序和 Critic 输入的顺序不一致或者next_obs拼接时漏了某个智能体。这种 bug 最隐蔽因为不会报错只是静默地让 Critic 学到错误关联。解决在拼接全局观测和全局动作时固定智能体顺序并在代码里加断言检查维度。比如assert global_obs.shape[0] sum(obs_dims)。每次改完环境或智能体数量都重新跑一遍维度检查。4.5 环境重置与 done 标志分幕任务里最容易忽略的截断现象训练到后期智能体在某一幕里表现很好但下一幕立刻归零。原因done标志没有正确传递或者环境 reset 后没有重新初始化隐藏状态。有些环境在 done 为 True 时还会返回一个无效的next_obs如果直接塞进回放池会污染训练数据。解决在push之前判断done如果为 True把next_obs置为零向量或直接跳过存储。另外如果环境有状态延续比如 RNN 类策略reset 时要同步清空隐藏状态。源码里test_env.py可以用来验证 reset 和 step 的返回值是否符合预期。5. 从跑通到改出花换环境、加智能体与验证训练是否真的收敛跑通源码只是第一步。课设拿高分的关键往往在于你能不能在这个基础上做一点自己的东西。这一章讲两个进阶方向和一个验证技巧。5.1 替换自定义环境mag.py的接口约定与最小改动mag.py大概率是作者自定义的博弈环境。如果你想换成自己的场景需要对齐几个接口reset()返回所有智能体的初始观测列表step(actions)接收联合动作列表返回next_obs列表、rewards列表、dones列表和额外信息observation_space和action_space要能被智能体读取以确定网络输入输出维度。最小改动路径是复制mag.py改名为你的环境文件保留类名和方法签名只改内部逻辑。然后在main.py里把环境实例化那一行换成你的类。注意观测维度和动作维度变了之后network.py里的输入输出维度要同步改否则第一层全连接就会报维度不匹配。5.2 增加智能体数量Critic 输入维度膨胀后的内存与显存问题从 2 个智能体加到 4 个Critic 的输入维度会翻倍。如果每个智能体观测维度是 10动作维度是 22 个智能体时 Critic 输入是(10*2 2*2) 244 个智能体时变成(10*4 2*4) 48。网络参数变多回放池里每条样本也变大。常见做法是适当减小capacity或者把 batch_size 从 256 降到 128避免显存爆掉。另外智能体数量增加后非平稳性更强可能需要把tau再调小一点或者增加 Critic 的更新频率。如果训练实在不收敛可以考虑给每个智能体单独设奖励缩放避免某个智能体的梯度主导全局。5.3 验证收敛固定随机种子跑三次看奖励方差判断训练是否真的收敛不能只看一次运行的曲线。常见做法是固定随机种子跑三次比较最终奖励的均值和方差。如果三次的最终奖励差距在 10% 以内说明策略相对稳定如果差距很大说明训练对初始化敏感需要调参或增加训练轮数。# 固定随机种子的做法 import torch import numpy as np import random def set_seed(seed): torch.manual_seed(seed) np.random.seed(seed) random.seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) # 在 main.py 开头调用 set_seed(42)这段代码放在训练脚本最前面保证每次运行的初始网络参数、环境随机性和采样顺序一致。注意有些环境内部有自己的随机数生成器可能需要单独设种子。如果源码里没有这个函数加上去再跑三次对比结果会清晰很多。5.4 一个具体技巧用test.py加载模型做确定性评估训练时的动作通常带探索噪声评估时要关掉噪声用 Actor 的确定性输出。test.py大概率就是干这个的。常见做法是加载训练好的模型参数把select_action里的噪声项去掉跑几个 episode 看平均奖励。如果评估奖励远低于训练奖励说明策略过拟合了训练时的噪声或者 Critic 过估计了 Q 值。我一般会在test.py里加一个render开关把环境可视化出来肉眼确认智能体行为是否符合预期。有一次训练曲线很漂亮但渲染出来发现两个智能体在原地转圈奖励是靠环境 bug 刷出来的。从那以后我每次评估都强制走一遍渲染确认行为合理再去看数字。希望这份拆解能帮你少走点弯路把这份源码真正用起来。本文还有配套的精品资源点击获取 SEO 优化官网定制响应式建站教育培训建站