简介基于Python与MADDPG的多智能体博弈对抗算法资源包面向系统学习多智能体强化学习的学生和开发者适用于毕业设计、课程设计、大作业、工程实训或初期项目立项能够帮助解决单智能体算法无法处理多智能体交互与对抗场景的问题。压缩包内共14个文件其中10个Python脚本构成算法核心另包含配置文件、说明文档、gitignore与环境配置信息整体压缩包约19KB代码体量小、模块边界清晰便于快速阅读与复用。核心模块涵盖DDPG与MADDPG算法主体、神经网络结构、经验回放缓存、强化学习工具函数、主流程入口与测试脚本并附有README说明文档方便读者直接运行观察对抗效果也可按文件调试学习完整调用链。通过研读和改写可掌握集中训练与分布式执行的核心思想、策略梯度更新流程以及奖励设计思路适合作为从入门到进阶的算法参考。目前已有267人学习下载值得需要的学习者实践参考。1. 基于PythonMADDPG的多智能体博弈对抗算法一个毕设代码包的完整拆解做过多智能体博弈项目的人应该都体会过那种尴尬论文里的MADDPG原理看懂了流程图也画得出可真要自己从零撸一套能跑的代码光是多智能体经验回放和Critic拼接就能卡上两三天。这份基于PythonMADDPG的多智能体博弈对抗算法毕业设计代码包解决的就是这件事——它把网络定义、单智能体DDPG底座、MADDPG主算法、经验回放缓冲区、对抗测试环境全部拆成了独立模块入口脚本一跑就能看到两个智能体在你来我往的对抗中逐步学到策略。适合拿来做毕设、课程设计或工程实训的起点也适合想弄懂「中心化训练、去中心化执行」到底怎么落地的进阶学习者。下面我按自己拆类似项目的习惯从原理、代码结构、复现步骤到常见坑一层层过一遍。2. 从DDPG到MADDPG为什么多智能体对抗不能直接套单智能体算法2.1 单智能体DDPG的局限性非平稳环境问题先看最基础的底座。DDPG是深度确定性策略梯度算法适合连续动作空间。它用Actor网络输出确定性动作用Critic网络评估状态-动作对的价值再靠经验回放打破样本相关性。单智能体场景下这套逻辑没问题但搬到多智能体对抗里就会翻车。原因在于环境非平稳。一个智能体在训练中不断更新策略它的行为模式一直在变对另一个智能体来说这个变化直接改变了它观察到的转移概率和奖励分布。也就是说每个智能体面对的环境里都藏着一个「会动的对手」。如果把对手当成静态环境的一部分Critic估算出来的Q值就会失真训练中期经常出现刚感觉策略在进步下一批样本又全盘崩掉的玄学现象。MADDPG解决这个问题的思路很直接给每个智能体的Critic开天眼让它能观测所有智能体的状态和动作。但Actor仍然只用自己的局部观测去决策。这就是所谓的「中心化训练、去中心化执行CTDE」。测试执行时每个智能体看到的还是自己的局部信息不依赖通信训练时Critic则把全部信息汇总起来价值估计一下子稳定很多。2.2 MADDPG的训练逻辑Critic拼接与逐个更新刚接触MADDPG时最容易绕晕的是更新流程。它不是把多个DDPG并行跑那么简单关键在于更新某个智能体时Critic要拼接所有智能体的状态和动作作为输入。常见的实现是先给每个智能体各自建一套Actor和Critic网络外加对应的Target网络。对局中把每个智能体的观测、动作、奖励、下一观测、是否结束全部存进一个共享的经验回放缓冲区。训练时从缓冲区采一批样本对每个智能体分别计算Critic损失和Actor策略梯度。用伪代码说某个智能体i的Critic更新目标大致长这样# 计算目标Q值target_q_i是智能体i的target网络 next_actions [target_actor_j(next_obs_j) for j in range(agent_num)] next_q target_critic_i(next_obs_concat, next_actions_concat) target_q reward_i gamma * (1 - done) * next_q这段代码里next_actions是把所有智能体target Actor对下一时刻观测的预测动作收集起来然后拼接成target Critic的输入。注意这里的拼接顺序在写代码时要保证后续所有智能体的拼接维度顺序一致否则训练过程会出现梯度计算错误但又不报错的情况这是后面排查的重点。参数上gamma衰减因子一般取0.95到0.99取值越接近1说明智能体越看重长期收益。做对抗博弈和简单协作场景有很大区别对抗中如果gamma太大会让智能体过度看重远期收益前期探索不足gamma太小又会让策略显得短视。用过几个项目后我一般取0.95起步。2.3 经验回放在多智能体场景下的设计差异单智能体经验回放存的是「状态、动作、奖励、下一状态」四元组。多智能体版本要存的是每个智能体各自的四元组但必须放在同一条样本记录里因为训练时Critic需要所有智能体同一步的观测和动作去拼接。这份代码包的buffer.py就是干这个事的。存储结构一般是这样的思路class ReplayBuffer: def __init__(self, capacity, agent_num, obs_dim, action_dim): self.buffer deque(maxlencapacity) # 每条样本包含: # obs: [agent_num, obs_dim] # action: [agent_num, action_dim] # reward: [agent_num, 1] # next_obs: [agent_num, obs_dim] # done: bool def sample(self, batch_size): batch random.sample(self.buffer, batch_size) # 把每条样本拆开按agent_num维度重组为批数据 return (np.array([b[0] for b in batch]), np.array([b[1] for b in batch]), ...)采样出来的每个字段第一维是batch_size第二维是agent_num后面才是观测维度。很多初学者在这容易踩坑把单个智能体的向量直接拼接成整体忽视agent_num那一维导致后面network.py里全连接层输入维度对不上报错还得回头查半天。设计经验回放时还要注意缓冲区容量。对抗场景里旧的策略行为样本对当前策略的参考价值衰减很快缓冲区太大反而会稀释近期有效样本但太小又会导致样本相关性高训练震荡。我一般设50000到100000具体看单局步数。3. 代码结构拆解这个压缩包里每个文件到底干什么3.1 文件清单与模块职责划分打开graduate_design-main.zip文件不算多但划分得很清晰。先把每个文件的角色捋一遍再决定从哪个文件入手读代码。文件职责阅读优先级main.py训练入口负责创建环境、初始化智能体、跑训练循环最高MADDPG.py多智能体算法主类管理多个DDPG智能体最高DDPG.py单个智能体的Actor-Critic实现含目标网络与软更新高network.py定义Actor和Critic的神经网络结构高buffer.py多智能体经验回放缓冲区中test_env.py对抗博弈测试环境定义了状态、动作、奖励中rl_utils.py工具函数比如Ornstein-Uhlenbeck噪声等中mag.py辅助模块做一些维度处理和转换低test.py / ceshi.py测试脚本加载训练好的模型跑对局回放低pyvenv.cfg / .gitignorePython虚拟环境与Git配置可忽略README.md项目说明低从阅读顺序建议是先看test_env.py搞懂任务定义再看network.py知道网络规模接着过DDPG.py理解单智能体更新细节最后读MADDPG.py看多智能体如何协同更新。README如果写得好可以先扫一遍但很多毕设项目的README只是跑通记录不必花太多时间。3.2 network.pyActor与Critic的定义方式network.py里的结构通常是两个类。Actor网络输入是某个智能体的局部观测输出是一个连续动作。Critic网络在MADDPG里的输入就不一样了它接收所有智能体的观测拼接和所有智能体的动作拼接输出一个Q值。写神经网络时几个细节值得注意。首先激活函数选择Actor最后一层输出的是动作对抗场景里动作范围一般在[-1,1]或[-2,2]所以最后一层用tanh把输出限幅。Critic最后一层不接激活函数直接输出线性Q值。中间隐藏层用ReLU居多也有项目用tanh效果差异不大。import torch.nn as nn import torch class Actor(nn.Module): def __init__(self, obs_dim, action_dim, hidden_dim64): super(Actor, self).__init__() self.fc1 nn.Linear(obs_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.fc3 nn.Linear(hidden_dim, action_dim) def forward(self, obs): x torch.relu(self.fc1(obs)) x torch.relu(self.fc2(x)) return torch.tanh(self.fc3(x)) class Critic(nn.Module): def __init__(self, obs_dim, action_dim, hidden_dim64): super(Critic, self).__init__() self.fc1 nn.Linear(obs_dim action_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.fc3 nn.Linear(hidden_dim, 1) def forward(self, obs_all, act_all): x torch.cat([obs_all, act_all], dim-1) x torch.relu(self.fc1(x)) x torch.relu(self.fc2(x)) return self.fc3(x)注意Critic的输入维度是所有智能体状态和动作拼接后的总和不是单个智能体的维度。这也是很多人改代码时最容易失手的地方环境里智能体数量一变这里的输入维度必须跟着变否则torch会直接报维度错误但有时候torch的广播机制会静默地把错误维度「消化」掉训练看不出毛病实际学的策略完全不对。hidden_dim用64还是128取决于任务的复杂程度。简单的追逐-躲避对抗64就够了遇到需要长时程推理的任务可以加到128或256。隐藏层加宽不一定总是好事小任务上过大的网络反而容易过拟合到经验池里的旧样本。3.3 DDPG.py与MADDPG.py目标网络和软更新的工程实现DDPG.py里定义的是单个智能体的Agent类每个Agent内部维护四份网络Actor、Critic以及各自对应的Target网络。这类实现里最关键的工程细节是target网络初始化时要和原网络保持一致之后靠软更新逐步逼近。软更新的公式是 θ_target τ·θ (1-τ)·θ_targetτ一般在0.01到0.001之间。τ太小target网络更新太慢价值估计收敛慢τ太大target网络跟得太紧起不到稳定作用。做对抗任务我习惯设0.01如果发现训练初期loss爆发式增长优先把τ降到0.005试试。MADDPG.py做的事情是管理一个Agent列表暴露一个update方法在训练循环里被main.py调用。它要做的事包括从buffer采样、为每个智能体计算target动作、拼接所有智能体的状态和动作给Critic、逐个更新。工程上要注意的是所有智能体的更新不能串行地共用一个梯度流每个智能体的Actor和Critic要独立更新否则会出现梯度耦合几个智能体学出来的策略趋同。4. 复现全流程从环境配置到训练观测4.1 环境准备与依赖安装这份代码依赖PyTorch和Numpy没有复杂的外部库。建议新建一个干净的虚拟环境再装不要直接放到全局Python环境里避免和系统里其他项目的包版本打架。python -m venv venv source venv/bin/activate # Windows下用 venv\Scripts\activate pip install torch numpy如果机器有NVIDIA显卡PyTorch会自动装CUDA版本。没有显卡纯CPU训练也能跑只是慢一些小规模的对抗环境CPU跑几百个episode是能接受的。装完可以先跑一个最小测试python -c import torch; print(torch.__version__)4.2 入口脚本main.py的训练循环main.py是典型的多智能体训练循环结构。初始化环境、初始化MADDPG、建buffer、循环episode跑对局、采集样本、更新网络。理论上直接执行python main.py就能看到训练输出。训练循环的核心框架大概长这样for episode in range(max_episodes): obs_n env.reset() # obs_n是一个列表每个元素是某个智能体的观测 episode_reward np.zeros(agent_num) while not done: actions [] for i in range(agent_num): obs torch.FloatTensor(obs_n[i]).unsqueeze(0) action agent.select_action(obs, noiseTrue) actions.append(action) next_obs_n, reward_n, done, _ env.step(actions) buffer.push(obs_n, actions, reward_n, next_obs_n, done) obs_n next_obs_n episode_reward reward_n if buffer.size() batch_size: maddpg.update(buffer, batch_size)重点解释一下其中几个逻辑。select_action里noise参数控制是否加探索噪声训练前期要开后期要慢慢关掉。reward_n是一个向量每个智能体拿到自己的奖励reward不一定是对称的对抗场景常见的是你1我就-1。buffer.push存的是这一整步里所有智能体的数据结构符合第2章说的样本格式。训练过程建议盯着两个指标每个episode的累计奖励曲线以及每个智能体的Critic loss。reward曲线整体向上说明策略在变好Critic loss持续降不下去大概率是学习率或网络规模有问题。在你确认奖励收敛之前不要中断训练多智能体对抗的收敛往往不是一条平滑上升直线中间会出现一段平台期甚至短暂下跌这是探索噪声和对手策略变化共同造成的属于正常现象。4.3 对局观测与模型保存训练结束后test.py或ceshi.py负责加载训练好的模型跑对局。这类测试脚本的常见逻辑是创建环境、加载每个智能体的Actor参数、关闭探索噪声、让智能体用纯确定性策略对局并打印每个智能体每步的动作和奖励。模型保存一般用PyTorch的torch.save保存的是state_dict。加载的时候要先构造相同结构的网络再load否则会报错。model Actor(obs_dim, action_dim) model.load_state_dict(torch.load(actor_0.pt)) model.eval()测试时把squeeze和unsqueeze处理好是一个高频翻车点。PyTorch的nn.Linear接收的输入是[batch, features]二维张量单步预测时观测是一维数组必须转成[batch, features]再进网络输出后再降维。忘记这一步模型有时能跑通但结果明显不对因为维度被广播处理了属于暗坑。测试对局跑完建议把每个智能体的累计奖励打印出来对比好判断双方是形成了一种均衡还是一方碾压另一方。如果出现碾压就要回到环境设计里去调奖励函数这就是第6章的内容了。5. 常见问题与避坑训练不稳定时的排查清单5.1 Critic输入维度对不上但又不报错现象训练能跑loss也在降但智能体策略表现非常差像是随机行为。原因PyTorch中nn.Linear对最后一维的匹配是严格校验的但对batch维度有广播容忍。当多智能体状态拼接出错时比如某个智能体只传入了自己的状态而不是全部智能体的状态拼接后的维度恰好和某个合法维度一致时矩阵乘法会静默地算出一个结果。这个结果数值是错的但不会让程序停下来。解决在MADDPG的update方法里加断言检查每个智能体Critic的输入维度是否等于agent_num * (obs_dim action_dim)。写一个便于快速定位维度问题的辅助函数把实际拼接的张量shape打印出来对照一下。这一步能省掉大量后期调试时间。5.2 训练中期reward突然全部暴跌现象前几百个episode奖励在稳定上升某次之后突然大幅下降且保持低位很久不恢复。原因多智能体对抗环境里双方都在学习某一方策略突变时另一方的经验池里积累了大量基于旧对手行为的样本价值估计瞬间失效。这在MADDPG里属于正常现象但要注意区分是正常的策略跳变还是网络bug。解决几种手段配合使用。噪声从0.3逐步衰减到0.02衰减周期拉长到训练总episode数的80%。降低单次更新步数比如每个episode结束后只更新一批减少旧样本被过度学习。如果暴跌后长时间不恢复检查Critic learning rate大于1e-3的调小到3e-4再重训。5.3 软更新τ值过大的震荡问题现象奖励曲线高频震荡像锯齿一样看不到整体上升趋势。原因τ设得太大target network追着原网络快速变价值估计失去稳定锚点。在对抗任务里对手策略也在变主网络本身就不稳定target再追得太紧Q值目标就跟着剧烈摆动训练自然稳不住。解决把τ从默认的0.01往0.001方向调。判断τ是否合适的办法是打印每个智能体的target Q值和在线Q值的差值差值持续波动剧烈就是τ偏大了平稳下降则说明设置合理。5.4 探索噪声几乎不衰减导致收敛性差现象reward曲线在某个水平附近来回摆动既不上升也不下降。原因Ornstein-Uhlenbeck噪声里的θ和σ参数在整个训练过程中是恒定不变的或者衰减策略过于保守。探索噪声太大即使策略学好也始终在做大幅偏离动作噪声太小动作过早确定就找不到更好的对抗策略。解决按episode进度线性衰减噪声乘子。训练前期用0.5倍最大噪声训练中期降到0.2倍训练后期直接关闭噪声用纯确定性策略跑几轮验证。这里的核心教训是把噪声当成超参数来管理不要放着不管。5.5 CPU和GPU版本混用导致的设备不匹配现象在GPU上训练保存的模型换到CPU机器上测试时报device mismatch。原因模型保存时参数在CUDA显存里加载时当前设备只有CPU自然对不上。解决保存时统一保存为CPU版本torch.save(model.state_dict(), actor.pt, _use_new_zipfile_serializationFalse)加载时代码里加一个map_location参数指定加载到当前设备。这类问题虽然简单但在毕设答辩前突然遇到还是很搞心态的提前处理掉比较安心。6. 进阶改造换环境、调奖励与验证收敛代码跑通只是开始真正拉开差距在于两步改造。第一步是修改test_env.py里的对抗场景定义目前代码包自带的应该是追逐-躲避或对称对抗这类经典设置你可以把它换成更贴合自己课题的任务比如双方争夺目标点或者攻防两阶段博弈。改环境的关键是奖励函数设计多智能体对抗的一个常见玄学是奖励给太密学习快但策略早熟奖励给太稀学习慢甚至不学习。我的习惯是先给稀疏基础奖励保证网络能收敛到「有策略」再逐步增加奖励项引导出更复杂的攻防行为。第二步是做一个直观的收敛验证手段。在main.py训练循环里每20个episode打印一次各智能体累计奖励均值同时把每个智能体单独跟一个随机策略的对手对局看是否稳定获胜。这个方法比单纯看训练曲线要可靠得多因为它能排除双方同时变差但差值不变带来的假收敛。多智能体对抗项目调试到后面最常见的挫败感来源是「调一个参数好了动一下别的地方又坏了」。把训练充分性、超参数、环境设计三者的变量尽量分开控制不要同时改两处。从那以后我每次拿到这类MADDPG代码包都强制自己先画一遍数据流图再动手改代码这能省下大量「看似能跑实际上策略是乱来」的排查时间。希望这份拆解对你复现和改造这个项目有点实际帮助。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?