MADDPG多智能体博弈对抗实战:毕设源码解析与调参避坑指南
简介这份资源围绕Python与MADDPG算法展开聚焦多智能体博弈对抗场景适合希望入门或进阶强化学习与多智能体协作对抗方向的学习者也可直接用于毕业设计、课程设计、大作业或工程实训等实践环节。压缩包共14个文件以10个Python脚本为核心涵盖DDPG、MADDPG、网络结构、经验回放缓冲区、强化学习工具函数以及测试环境等模块另附cfg配置、txt说明、gitignore与md文档整体约19KB结构紧凑、便于快速阅读与二次修改。资源已有266人学习下载说明其在同类多智能体博弈课题中具有一定参考价值。读者可借此理解MADDPG在多智能体环境下的训练流程、网络搭建与对抗策略实现掌握从环境交互到策略更新的完整链路并在此基础上替换环境或调整超参数完成自己的实验与项目复现。1. 从一份毕设源码说起MADDPG 多智能体博弈对抗到底能跑出什么如果你正在做多智能体强化学习方向的毕设、课程设计或者工程实训大概率会遇到一个尴尬局面单智能体的 DDPG、PPO 代码一抓一大把但一旦把环境改成两个及以上的智能体互相博弈代码立刻变得难以下手。这份graduate_design-main.zip就是冲着这个痛点来的——它用 Python 把 MADDPGMulti-Agent Deep Deterministic Policy Gradient在博弈对抗场景下完整实现了一遍包含MADDPG.py、DDPG.py、network.py、buffer.py、main.py、test.py、test_env.py、mag.py、rl_utils.py等模块是一套可以直接跑起来、也能拆开改的工程化代码。它解决的不是什么是多智能体这种概念问题而是我手上有一堆智能体怎么让它们各自学策略、还能在对抗中收敛的落地问题。适合两类人一类是刚接触多智能体强化学习、想找一个能跑通的最小闭环来理解 MADDPG 的新手另一类是做毕设或项目立项、需要一个可扩展骨架的进阶学习者。下面我按资源是什么 → 怎么用 → 坑在哪的顺序把这份代码拆开讲。2. MADDPG 的集中训练分散执行网络结构与代码模块怎么对应2.1 为什么博弈对抗场景必须用 MADDPG 而不是普通 DDPG普通 DDPG 的核心假设是环境稳定也就是状态转移概率不随其他智能体的策略变化。但在多智能体博弈里每个智能体都在学习、都在改策略从单个智能体的视角看环境是非平稳的——你昨天学到的策略今天对手变了就不管用了。这就是多智能体强化学习最经典的难点。MADDPG 的解法是集中训练、分散执行CTDE。训练时每个智能体的 Critic 网络能看到全局信息包括所有智能体的观测和动作执行时Actor 网络只依赖自己的局部观测。这样一来Critic 在评估动作价值时环境是相对稳定的Actor 又能保持分散决策的独立性。这份代码里MADDPG.py负责的就是这套集中训练的编排逻辑DDPG.py则是单个智能体的 Actor-Critic 实现两者是组合关系而不是继承关系。理解这一点很关键如果你把DDPG.py单独拿去跑多智能体环境会发现训练极不稳定loss 来回震荡这不是代码 bug而是算法选型错了。2.2 网络模块拆解network.py 里到底定义了什么network.py是整个工程的骨架通常包含 Actor 和 Critic 两个网络类。Actor 输入是单个智能体的局部观测维度输出是连续动作Critic 输入是全局状态加所有智能体的联合动作输出一个 Q 值。下面是我按这份代码结构还原的核心网络定义你可以对照自己的network.py看是否一致import torch import torch.nn as nn import torch.nn.functional as F class Actor(nn.Module): def __init__(self, obs_dim, act_dim, hidden_dim64): super(Actor, self).__init__() # 两层全连接ReLU 激活输出层用 tanh 把动作压到 [-1, 1] self.fc1 nn.Linear(obs_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.fc3 nn.Linear(hidden_dim, act_dim) def forward(self, obs): x F.relu(self.fc1(obs)) x F.relu(self.fc2(x)) # tanh 保证输出有界配合环境的 action_space 缩放 return torch.tanh(self.fc3(x)) class Critic(nn.Module): def __init__(self, global_obs_dim, global_act_dim, hidden_dim64): super(Critic, self).__init__() # 输入是全局观测 所有智能体动作拼接 self.fc1 nn.Linear(global_obs_dim global_act_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.fc3 nn.Linear(hidden_dim, 1) def forward(self, global_obs, global_act): x torch.cat([global_obs, global_act], dim-1) x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) return self.fc3(x)逻辑说明Actor 的obs_dim是单个智能体的观测维度不是全局维度这是分散执行的体现Critic 的输入维度是global_obs_dim global_act_dim其中global_act_dim act_dim * n_agents这是集中训练的体现。参数上hidden_dim默认 64 对小型博弈环境够用但如果你的环境观测维度超过 50建议加到 128 或 256否则容易欠拟合。tanh输出层是连续动作控制的标准做法如果你的动作空间有具体上下界记得在环境侧做缩放别直接改 tanh。2.3 经验回放与采样buffer.py 和 rl_utils.py 的分工buffer.py实现的是多智能体经验回放池。和单智能体不同它存的不是(s, a, r, s_, done)而是每个智能体各自的观测、动作加上全局状态和联合动作。采样时返回的是一个 batch 的字典或元组供MADDPG.py里的更新逻辑使用。rl_utils.py一般是工具函数集合常见的有软更新soft update、高斯噪声、折扣回报计算等。软更新是 DDPG 系列的关键目标网络不是硬拷贝而是按tau缓慢跟随def soft_update(target, source, tau0.01): # 目标网络参数按 tau 向在线网络靠近 for target_param, param in zip(target.parameters(), source.parameters()): target_param.data.copy_(target_param.data * (1.0 - tau) param.data * tau)tau取 0.01 是常见默认值太小收敛慢太大目标网络抖动厉害。我一般会在训练日志里盯 target Q 值的变化如果它和 online Q 值差距持续拉大说明tau偏大或者学习率不匹配。3. 把代码跑起来环境配置、训练入口与参数调优3.1 Python 环境与依赖安装的实操顺序这份代码是纯 Python 工程没有复杂的 C 扩展环境配置相对友好。但多智能体强化学习对版本敏感尤其是 PyTorch 和 Gym 的版本组合。我建议按下面的顺序来别跳步# 1. 确认 Python 版本建议 3.8 - 3.10太新的版本部分依赖轮子不全 python --version # 2. 创建独立虚拟环境避免污染全局 python -m venv venv # Windows 激活 venv\Scripts\activate # Linux / macOS 激活 source venv/bin/activate # 3. 安装核心依赖torch 按自己显卡情况选 CPU 或 CUDA 版本 pip install torch numpy gym matplotlib # 4. 如果环境用到多智能体粒子环境还需要额外装 pip install multiagent-particle-envs参数说明torch的安装命令不要照抄网上带cuXXX的先去 PyTorch 官网按你的 CUDA 版本生成对应命令。gym版本建议 0.21 以下因为 0.26 之后 API 改动很大step()返回值从 4 个变成 5 个这份代码大概率是按旧版 API 写的直接装新版会报解包错误。这是新手最容易翻车的地方。3.2 main.py 训练入口从参数到训练循环main.py是训练主入口通常包含超参数定义、环境初始化、智能体初始化、训练循环四部分。下面是我按这份工程结构还原的训练主循环骨架import numpy as np import torch from MADDPG import MADDPG from test_env import make_env # 超参数集中管理方便调参 EPISODES 3000 # 训练回合数 MAX_STEPS 25 # 每回合最大步数 LR_ACTOR 1e-3 # Actor 学习率 LR_CRITIC 1e-3 # Critic 学习率 GAMMA 0.95 # 折扣因子 TAU 0.01 # 软更新系数 BUFFER_SIZE 100000 # 回放池容量 BATCH_SIZE 1024 # 采样批量 env make_env() maddpg MADDPG(env, lr_actorLR_ACTOR, lr_criticLR_CRITIC, gammaGAMMA, tauTAU, buffer_sizeBUFFER_SIZE) for ep in range(EPISODES): obs env.reset() for step in range(MAX_STEPS): # 每个智能体根据自己的局部观测选动作 actions maddpg.select_action(obs) next_obs, rewards, dones, info env.step(actions) # 存入回放池注意存的是全局信息 maddpg.store_transition(obs, actions, rewards, next_obs, dones) obs next_obs # 回放池够一个 batch 才开始更新 if len(maddpg.buffer) BATCH_SIZE: maddpg.update() # 每若干回合打印一次平均回报观察收敛趋势 if ep % 100 0: print(fEpisode {ep}, reward: {np.mean(maddpg.reward_history[-100:])})逻辑说明select_action内部会加高斯噪声做探索训练初期噪声大、后期衰减这是 DDPG 系列的标配。store_transition存的是全局观测和联合动作不是单个智能体的这点和单智能体代码差别很大改代码时别搞混。update()里会遍历每个智能体各自更新 Actor 和 Critic。参数上GAMMA0.95比单智能体常用的 0.99 略小因为博弈对抗场景更看重近期收益BATCH_SIZE1024偏大是为了让 Critic 在非平稳环境下看到更多样本、降低方差。如果你的显存吃紧可以降到 256但训练会更抖。3.3 训练不收敛时先调哪几个参数多智能体训练不收敛是常态别一上来就怀疑代码。我一般按这个顺序排查先看奖励曲线是不是完全平的如果是多半是动作没生效或者奖励没传对再看是不是剧烈震荡震荡通常是学习率太大或者tau太大最后看是不是缓慢下降那可能是GAMMA或奖励尺度问题。具体调参建议LR_ACTOR和LR_CRITIC先都设 1e-3不收敛就各降一个数量级到 1e-4TAU从 0.01 降到 0.001 试试BATCH_SIZE加大通常有帮助但吃显存。还有一个容易被忽略的点噪声的衰减速度。如果噪声衰减太快智能体还没探索够就进入利用阶段策略会卡在局部最优。4. 避坑与排查多智能体代码最容易翻车的五个地方4.1 现象训练一开始就报维度不匹配原因MADDPG 里 Critic 的输入是全局观测加联合动作很多新手直接把单个智能体的obs_dim和act_dim传进去导致torch.cat时维度对不上。解决在初始化 Critic 前先算清楚global_obs_dim obs_dim * n_agents、global_act_dim act_dim * n_agents把这两个值传进去。如果环境里各智能体观测维度不同还要单独处理不能简单相乘。4.2 现象奖励曲线一直是一条直线完全不学原因最常见的是store_transition存错了东西比如存了单个智能体的观测而不是全局观测或者done标志没处理好导致 bootstrap 计算错误。解决在update()里打印一下采样出来的 batch 形状确认global_obs的维度是(batch, global_obs_dim)global_act是(batch, global_act_dim)。另外检查done为 True 时目标 Q 值是否正确地只用了即时奖励、没有加折扣的未来值。4.3 现象训练到一半突然 loss 爆炸变成 NaN原因多智能体环境下 Q 值容易发散尤其是 Critic 学习率偏大或者奖励尺度没归一化的时候。解决先做梯度裁剪在update()里对 Critic 加torch.nn.utils.clip_grad_norm_(critic.parameters(), 0.5)再检查奖励是不是动辄几百上千如果是把奖励缩放到 [-1, 1] 或 [0, 1] 区间。我一般还会把GAMMA从 0.95 降到 0.9 试试降低远期估计的方差。4.4 现象换了环境之后代码跑不通原因这份代码默认适配的环境观测和动作维度是固定的换环境后obs_dim、act_dim、n_agents都变了但超参数没跟着改。解决在main.py开头从env里动态读取维度别写死。比如obs_dim env.observation_space[0].shape[0]、act_dim env.action_space[0].shape[0]、n_agents env.n。这样换环境时只需要改环境初始化不用动网络结构。4.5 现象测试时表现远差于训练时原因训练时 Actor 加了探索噪声测试时如果忘了关噪声动作会一直抖或者训练时用了全局信息而测试时只给局部观测导致分布不一致。解决test.py里调用select_action时把噪声开关关掉通常代码里会有一个noiseFalse或exploreFalse的参数。另外确认测试用的观测和训练时同源别一个来自env.reset()一个来自手动构造。5. 进阶玩法从跑通到改出自己的一套对抗实验把代码跑通只是起点真正有价值的是能改。这份工程的结构其实留了不少扩展口子我分享几个我常用的改法。第一个是换对手策略做对抗强度实验。MADDPG 默认是所有智能体一起学但你可以把其中一个智能体的策略固定住比如用一个规则策略或者一个预训练好的模型当对手只训练另一个。这样能观察面对固定对手时策略如何演化比全学习场景更容易分析。改法是在MADDPG.py的select_action里对指定智能体跳过网络推理直接返回预设动作。第二个是加训练日志和可视化。rl_utils.py里可以加一个记录器把每个回合的奖励、每个智能体的 loss、平均 Q 值都存下来训练完用 matplotlib 画出来。多智能体实验不看曲线基本等于盲调我一般会画三张图总奖励曲线、各智能体奖励曲线、Critic loss 曲线。下面是一个简单的记录与绘图片段import matplotlib.pyplot as plt class RewardLogger: def __init__(self): self.episode_rewards [] self.critic_losses [] def log(self, reward, loss): self.episode_rewards.append(reward) self.critic_losses.append(loss) def plot(self, save_pathtraining_curve.png): fig, axes plt.subplots(1, 2, figsize(12, 4)) axes[0].plot(self.episode_rewards) axes[0].set_title(Episode Reward) axes[0].set_xlabel(Episode) axes[1].plot(self.critic_losses) axes[1].set_title(Critic Loss) axes[1].set_xlabel(Update Step) plt.tight_layout() plt.savefig(save_path) plt.close()参数说明episode_rewards建议存滑动平均后的值原始值抖动太大看不出趋势critic_losses存的是每次 update 的 loss如果它持续上升说明 Critic 在发散要回去调学习率或加梯度裁剪。第三个是验证方法。改完代码后怎么确认没改坏我的习惯是先用一个极小的环境跑 200 回合看奖励能不能从随机水平涨上去再用固定随机种子跑两次结果应该基本一致如果两次差异巨大说明代码里有随机性没控制住比如噪声没设种子或者环境 reset 没固定。这个小环境快跑 固定种子复现的习惯是我踩了无数次坑之后养成的每次改完 MADDPG 相关代码都强制走一遍能省掉大量排查时间。希望这份拆解能帮你把这份毕设源码真正用起来而不是只让它躺在压缩包里。本文还有配套的精品资源点击获取