简介:这份资源围绕Python与MADDPG算法展开,聚焦多智能体博弈对抗场景,适合希望入门或进阶强化学习与多智能体协作对抗方向的学习者,也可直接用于毕业设计、课程设计、大作业或工程实训等实践环节。压缩包共14个文件,以10个Python脚本为核心,涵盖DDPG、MADDPG、网络结构、经验回放缓冲区、强化学习工具函数以及测试环境等模块,另附cfg配置、txt说明、gitignore与md文档,整体约19KB,结构紧凑、便于快速阅读与二次修改。资源已有266人学习下载,说明其在同类多智能体博弈课题中具有一定参考价值。读者可借此理解MADDPG在多智能体环境下的训练流程、网络搭建与对抗策略实现,掌握从环境交互到策略更新的完整链路,并在此基础上替换环境或调整超参数,完成自己的实验与项目复现。
1. 从一份毕设源码说起:MADDPG 多智能体博弈对抗到底能跑出什么
如果你正在做多智能体强化学习方向的毕设、课程设计或者工程实训,大概率会遇到一个尴尬局面:单智能体的 DDPG、PPO 代码一抓一大把,但一旦把环境改成两个及以上的智能体互相博弈,代码立刻变得难以下手。这份graduate_design-main.zip就是冲着这个痛点来的——它用 Python 把 MADDPG(Multi-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_dim=64): 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_dim=64): 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, tau=0.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_actor=LR_ACTOR, lr_critic=LR_CRITIC, gamma=GAMMA, tau=TAU, buffer_size=BUFFER_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(f"Episode {ep}, reward: {np.mean(maddpg.reward_history[-100:])}")逻辑说明:select_action内部会加高斯噪声做探索,训练初期噪声大、后期衰减,这是 DDPG 系列的标配。store_transition存的是全局观测和联合动作,不是单个智能体的,这点和单智能体代码差别很大,改代码时别搞混。update()里会遍历每个智能体,各自更新 Actor 和 Critic。
参数上,GAMMA=0.95比单智能体常用的 0.99 略小,因为博弈对抗场景更看重近期收益;BATCH_SIZE=1024偏大,是为了让 Critic 在非平稳环境下看到更多样本、降低方差。如果你的显存吃紧,可以降到 256,但训练会更抖。
3.3 训练不收敛时先调哪几个参数
多智能体训练不收敛是常态,别一上来就怀疑代码。我一般按这个顺序排查:先看奖励曲线是不是完全平的,如果是,多半是动作没生效或者奖励没传对;再看是不是剧烈震荡,震荡通常是学习率太大或者tau太大;最后看是不是缓慢下降,那可能是GAMMA或奖励尺度问题。
具体调参建议:LR_ACTOR和LR_CRITIC先都设 1e-3,不收敛就各降一个数量级到 1e-4;TAU从 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时把噪声开关关掉,通常代码里会有一个noise=False或explore=False的参数。另外确认测试用的观测和训练时同源,别一个来自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_path="training_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 相关代码都强制走一遍,能省掉大量排查时间。
希望这份拆解能帮你把这份毕设源码真正用起来,而不是只让它躺在压缩包里。
本文还有配套的精品资源,点击获取