
简介基于Python实现的多智能体强化学习算法复现包集成了QMIX、VDN、QTRAN、MAVEN等主流MARL方法适用于毕业设计、课程设计与项目开发帮助学习者快速掌握多智能体协同决策的代码实现与调试思路。资源共10个文件以6个Python脚本为核心覆盖网络搭建、智能体定义、策略训练与主控流程等模块另含2个Markdown说明文档、1张结果展示图及gitignore配置压缩包体积仅47KB结构精简且便于阅读。目前已有276人学习并下载源码经过严格测试稳定性和可扩展性俱佳可作为二次开发的起点。各算法模块划分清晰统一接口便于对比不同方法的收敛表现文档与代码注释结合适合理解原理后独立复现实验。无论是课程作业、毕设课题还是科研探索这份轻量级代码都能提供扎实的参考。1. 一次 MARL 复现先想清楚这个最容易被忽略的问题多智能体强化学习里最常被问的问题不是网络结构选什么而是回头验收时拿什么证明我的代码是对的。只给出一张 reward 曲线说服力不够读者和答辩老师更关心的是你实现的算法与论文结果的差距是否落在合理的解释范围内。QMIX、VDN、QTRAN、MAVEN 这几个算法核心都在解决同一件事——多智能体环境里的 credit assignment信用分配问题也就是当团队只有一个全局奖励时怎么把这份奖励合理地归因到各个智能体的动作上。它们的实现难度递增但共享同一套 CTDE集中训练、分散执行范式因此复现的价值不只是跑通代码而是能纵向对比不同分解方式的优劣。本文按环境搭建 → 值分解基线VDN/QMIX→ 改进结构QTRAN/MAVEN→ 实验评估的顺序展开全程用 Python PyTorch不涉及框架层面的二次封装代码可以直接搬。如果你的目标是毕业设计或课设建议先把第 2 章的环境和评估脚本跑通再逐步替换算法这样能在最短时间内看到可复现的实验曲线。2. 搭建 MARL 复现环境SMAC 环境、Python 依赖与训练骨架2.1 为什么用 SMAC 而不是自己写环境复现 QMIX 和 VDN 那批论文社区默认用的是 SMACStarCraft Multi-Agent Challenge环境也就是星际争霸 2 内置的 mini-game 场景。SMAC 提供的部分可观察性每个智能体只能看到自己视野内的单位和离散动作空间恰好是这些算法论文的标准实验设置。如果只是做课程设计想快速展示效果可以用pettingzoo里的mpe或sisl环境它们的数据格式和 SMAC 类似但规模更小训练时间短。不过要注意QMIX 论文里报告的结果全部来自 SMAC若你拿简化环境的数据去对标论文表格数值一定对不上答辩时容易吃亏。环境安装时的 Python 版本敏感推荐用 Anaconda 创建独立环境。conda create -n marl python3.8 -y conda activate marl pip install torch1.13.1 --index-url https://download.pytorch.org/whl/cu117 pip install smac fancyimpute如果fancyimpute在 Python 3.8 以上版本安装失败可以用pip install smac后再单独处理缺失依赖。SMAC 依赖星际争霸 2 客户端Linux 服务器上可以用pylibsc2的离线包但更省事的方式是先用pettingzoo验证算法逻辑再切到 SMAC 做完整实验。Windows 用户安装 SMAC 时注意将 StarCraft II 放在默认位置否则会报SC2PATH未设置的错误。2.2 训练骨架里的三个公共组件在实现具体算法之前先把训练框架搭好。MARL 的main训练循环里有三个组件是所有算法共用的它们与具体算法无关但写不好会影响调试效率经验回放池ReplayBuffer用字典存储每个 transition字段包含obs, actions, rewards, next_obs, done, state。特别注意 SMAC 的state是所有智能体 obs 的拼接而中心化 critic 用的 state 在buffer中单独存。batch 采样每 step 从 buffer 里取一批episode而不是像 DQN 那样取单个 transition。多智能体场景里轨迹的自然单位是 episode所以缓冲区要按 episode 存储。梯度更新逻辑每learn_step或每 N 步更新一次目标网络用软更新tau0.005比硬替换更稳。核心采样代码从一个 episode 的维度组织数据class ReplayBuffer: def __init__(self, max_episodes1000): self.buffer deque(maxlenmax_episodes) def push(self, episode_data): # episode_data: list of (obs, actions, rewards, next_obs, dones, state) self.buffer.append(episode_data) def sample_batch(self, batch_size): episodes random.sample(self.buffer, batch_size) batch {} # 按 step 对齐生成 (batch, episode_len, n_agents, feat_dim) 的张量 for key in [obs, actions, rewards, next_obs, dones, state]: batch[key] torch.FloatTensor( [ [step[key] for step in ep] for ep in episodes ] ) return batch这里sample_batch返回的是 (batch_size, episode_len, n_agents, feat_dim) 四维张量。SMAC 中每个 step 没有额外 reward 灌入所以dones是布尔数组。很多初学者把 buffer 按 transition 存导致一个 episode 里多个 transition 的 TD 更新偏差很大所以我在代码里强制按 episode 压入再采样。具体实现时各算法的差异只在compute_loss阶段其余部分完全可以复用同一个Runner类。2.3 一个可以立刻跑起来的训练主循环这里给一个不依赖任何既有框架的最小训练主循环逻辑直接对应论文中伪代码的描述def train_one_episode(env, agents, buffer, args): obs env.reset() # (n_agents, obs_dim) episode_data [] state env.get_state() # 中心化 state done False while not done: actions [] for i in range(args.n_agents): action agents.choose_action(obs[i], epsilonargs.eps) actions.append(action) actions np.array(actions) nxt_obs, reward, done, info env.step(actions) nxt_state env.get_state() episode_data.append({ obs: obs, actions: actions, rewards: reward, next_obs: nxt_obs, dones: [done] * args.n_agents, state: state, next_state: nxt_state }) obs, state nxt_obs, nxt_state buffer.push(episode_data)逻辑上最需要注意的是 SMAC 的done是布尔量而当某支队伍全军覆没时reward可能是负值因此你在 TD loss 里要区分done是团队层面还是单个智能体层面的。MARL 里常见的错误是把 SMAC 的reward直接当作每个智能体的reward但它们是同一个标量广播到所有智能体所以训练时要考虑均摊或使用混合网络处理。数据流理顺之后下一步就开始写具体的算法模块。3. VDN 与 QMIX用值分解解决信用分配先理解单调性3.1 VDN 的加法分解和它的局限VDNValue Decomposition Networks是所有值分解算法里最直接的一个将联合动作价值函数分解为各智能体独立价值函数之和即Q_tot Q_1 Q_2 ... Q_n它假设智能体之间的贡献是线性叠加的在智能体规模大或异构性强的场景比如 SMAC 的3s5z里会退化明显。但 VDN 的价值在于实现简单适合作为入门基线。一次 forward 的核心代码就是把所有智能体的 Q 值沿 agent 维相加class VDNCritic(nn.Module): def __init__(self, obs_dim, n_actions, hidden_dim64): super().__init__() self.q_net nn.Sequential( nn.Linear(obs_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, n_actions) ) def forward(self, obs, actionsNone): # obs: (batch, n_agents, obs_dim) q_values self.q_net(obs) # (batch, n_agents, n_actions) return q_valuesVDN 的训练 loss 只是所有Q_i之和与r gamma * max_a Q_tot_target之间的 TD error不需要额外的混合网络。观察 VDN 的代码就会知道它的系数恒为 1没有可学习的权重。这也决定了 VDN 的泛化瓶颈——它无法表示某些协同动作带来的等值关系比如一个人按住门另一个人冲过去这类非线性协同。3.2 QMIX 混合网络的结构与关键参数QMIX 的出发点是VDN 的线性分解太强但完全自由的联合值函数又会破坏可分解性于是论文引入单调性约束——联合 Q 值对每个智能体的 Q 值单调递增。它可以写为∂Q_tot / ∂Q_i 0对应到实现就是让混合网络里每个智能体的权重非负。混合网络依然吃全局 state但权重由一个超网络产生通过取绝对值和 softplus 激活来保证正数。这个设计是全篇代码里最值得亲手写一遍的部分class QMIXMixer(nn.Module): def __init__(self, n_agents, state_dim, mixing_hidden_dim32, hyper_hidden_dim64): super().__init__() self.n_agents n_agents # 超网络生成第一层权重 W1: (n_agents, mixing_hidden_dim) self.hyper_w1 nn.Sequential( nn.Linear(state_dim, hyper_hidden_dim), nn.ReLU(), nn.Linear(hyper_hidden_dim, n_agents * mixing_hidden_dim) ) # 超网络生成第二层权重 W2: (mixing_hidden_dim, 1) self.hyper_w2 nn.Sequential( nn.Linear(state_dim, hyper_hidden_dim), nn.ReLU(), nn.Linear(hyper_hidden_dim, mixing_hidden_dim) ) self.hyper_b1 nn.Linear(state_dim, mixing_hidden_dim) self.hyper_b2 nn.Sequential( nn.Linear(state_dim, mixing_hidden_dim), nn.ReLU(), nn.Linear(mixing_hidden_dim, 1) ) def forward(self, agent_qs, states): # agent_qs: (batch, n_agents)已经取了所选动作的 Q 值 # states: (batch, state_dim) batch_size agent_qs.size(0) w1 torch.abs(self.hyper_w1(states)).view(batch_size, self.n_agents, -1) b1 self.hyper_b1(states).view(batch_size, 1, -1) hidden torch.bmm(agent_qs.unsqueeze(1), w1) b1 hidden F.elu(hidden) w2 torch.abs(self.hyper_w2(states)).view(batch_size, -1, 1) b2 self.hyper_b2(states).view(batch_size, 1, 1) q_tot torch.bmm(hidden, w2) b2 return q_tot.squeeze(1) # (batch, 1)以上代码与原论文的唯一差别是省去了abs引用时对偏置的约束因为偏置项不参与偏导单调性的证明。超网络每层输出的维度由n_agents * mixing_hidden_dim决定首层权重形状一定要对齐否则bmm会报维度错误。常见问题复现时把agent_qs传成了整个(batch, n_agents, n_actions)张量导致维度爆炸正确做法是提前用gather拿所选动作的 Q 值。3.3 QMIX 训练 loss 的完整写法与三个必调参数训练 loss 部分与 DQN 一样但有两个多智能体特有的坑一个是 target Q 必须用 target mixer 计算另一个是 loss 需要沿 batch 维度求平均而不是在所有智能体上求和。def compute_qmix_loss(batch, q_net, target_q_net, mixer, target_mixer, gamma0.99): obs batch[obs] # (batch, episode_len, n_agents, obs_dim) actions batch[actions].long() rewards batch[rewards] # (batch, episode_len, 1) dones batch[dones] q_values q_net(obs) # (batch, episode_len, n_agents, n_actions) actions_onehot F.one_hot(actions, num_classesq_values.size(-1)).float() q_selected (q_values * actions_onehot).sum(-1) # (batch, seq, n_agents) q_tot mixer(q_selected, batch[state]) with torch.no_grad(): next_q target_q_net(batch[next_obs]) next_q_tot target_mixer(next_q.max(-1).values, batch[next_state]) # 替换掉 episode 结束位置的目标值 target rewards gamma * (1 - dones) * next_q_tot td_loss F.mse_loss(q_tot, target) return td_loss一般会关注三个参数mixing_hidden_dim升级到 64 对复杂场景有微小的稳定收益hyper_hidden_dim保持 64 不要贪大gamma在 SMAC 上取 0.99 即可降到 0.95 会导致后期胜率上限明显下降。这两个超参数对 QMIX 来说比网络层数更敏感。如果你在训练中发现 loss 下降快但胜率不涨多半是epsilon衰减过快SMAC 中从 1.0 衰减到 0.05 通常需要 2000 个 episode 以上。4. QTRAN 与 MAVEN打破单调性约束修正 QMIX 的盲区4.1 QTRAN 的分解思路从单调退到可分解QMIX 成功的前提是联合 Q 值对每个智能体 Q 值单调这个约束在动作空间大或智能体数量多时会导致对最优联合 action 的拟合偏差。QTRAN 的目标是把可分解条件放宽为不改变最优动作即利用一个核对proof机制来保证分解等效。它的结构比 QMIX 多出一个联合动作价值函数并且损失函数里有额外约束项。直观说QTRAN 强制联合 Q 值在最优动作点等于各智能体 Q 值之和但允许在非最优动作处有松弛于是学习出的个体 Q 函数没有单调限制。实现时核心是那两个额外 loss 项def qtran_loss(batch, ...): # q_total_joint: 当前网络计算的最大化联合 Q # q_total_hat: sum(q_i) - 基线校正函数 V(s) # q_individuals: (batch, n_agents) q_total_joint joint_net(states) q_total_hat q_individuals.sum(-1, keepdimTrue) - v_values # 最优动作处的核对 loss loss_opt F.mse_loss(q_total_joint[selected_actions], q_total_hat) # 所有动作处的约束Q_tot Q_hat - margin diff F.relu(q_total_joint - q_total_hat - margin) loss_nopt diff.mean() td_loss mse_loss(q_total_joint[selected_actions], target_q) total_loss td_loss loss_opt loss_noptQTRAN 在对比实验中往往不如 QMIX 稳定原因在于margin的选择对结果影响很大。论文默认margin 0.1但在3m地图上我一般调到0.5。它的实现复杂度比 QMIX 高但收益不稳定所以建议在你的代码库里把 QTRAN 作为一个对照实验而不是主线方案。值得注意的是QTRAN 有 base 和 alt 两种变体alt版本里v_values由单独的网络计算不能与q_individuals共享网络这个细节经常被忽略。4.2 MAVEN 的记忆型层次结构用潜变量做探索MAVEN 的出发点同样是 QMIX 的单调性限制但它走的是另一条路径引入一个分层潜变量z让不同的z对应不同的联合行为模式。每个智能体的 Q 网络接收obs和z作为输入而z由一个上层策略生成。这样的结构让算法在探索阶段有更强的表达力因为z会把探索空间分割成多个子空间。MAVEN 的目标函数在 QMIX 的基础上多了一个互信息项用于鼓励不同z产生不同的行为分布def maven_loss(batch, z_samples, ...): # z_samples: (batch, z_dim)从上层策略采样或由 encoder 生成 # 1. 先计算 QMIX 的 TD loss其中 q_net 输入拼接了 z q_tot mixer(agent_qs, states) td_loss mse_loss(q_tot, target) # 2. 互信息正则项让 Q 值与 z 的相关性被 encoder 捕捉 z_pred z_encoder(states, q_tot.detach()) info_loss F.mse_loss(z_pred, z_samples) return td_loss beta * info_lossMAVEN 在复现时最大的工程坑在于z的采样时机z在一个 episode 开始时采样一次整个 episode 内保持不变这样智能体才能围绕某个策略模式展开连贯的行为。如果每个 step 都重新采样算法退化为随机噪声。4.3 QTRAN 与 MAVEN 的选型对比维度QTRANMAVEN分解方式核对约束松弛单调性潜变量分层分解探索能力弱依赖 epsilon 衰减强潜变量提供结构化探索复现难度中等loss 项多中等偏高要设计 z 采样逻辑适用场景价值函数接近可分解的任务需要多样策略探索的复杂场景常见失败模式margin 选择不当导致 loss 不收敛beta 太大导致 z 退化与 QMIX 无异从课程设计的角度建议实现顺序是 VDN → QMIX → MAVEN → QTRAN。QTRAN 的数学推导部分最容易在论文中解释但实际结果往往最不稳定要做好心理准备。MAVEN 的超参数调节要均匀beta从 0.1 起步如果互信息 loss 不下降可以适当增加z_dim到 8 或 16。5. 实验评估与毕设项目收尾的实操技巧5.1 胜率评估脚本的正确写法SMAC 官方的评估方式是让环境运行在evaluationTrue模式下用eval接口连续采样多个 episode 并统计胜率。单次评估至少要跑 32 个 episode方差才足够小。下面这段代码可以直接用于最后的实验报告绘图def evaluate(env, agents, num_episodes32): wins 0 for _ in range(num_episodes): obs env.reset() done False episode_reward 0 while not done: actions [agents.choose_action(o, epsilon0.0) for o in obs] obs, reward, done, info env.step(actions) episode_reward reward if reward 0: wins 1 return wins / num_episodes, episode_reward评估时所有智能体必须用epsilon0.0否则胜率会被探索噪声拉低。SMAC 中reward 0即判断胜利但要注意平局时 reward 为 0不算赢。在3m地图上 QMIX 一般需要训练 1-2 小时能看到明显上升而 VDN 大约在相同时间内达到稍低的胜率。5.2 一页纸讲清楚复现成功的验证方法毕业设计和课程设计答辩时最常被问到的问题是怎么证明你复现得对我建议准备三张图第 1 张是训练曲线对比图横轴为 episode 数第 2 张是不同跑次之间的方差阴影第 3 张是算法间的最终胜率柱状图。如果在3m上 VDN 与 QMIX 的最终胜率相差不超过 10%说明你的公共框架是可信的如果 QMIX 反而比 VDN 差优先检查混合网络的权重是否施加了非负约束。以3m地图为例常见的公开基准结果约在 VDN 80%~85%、QMIX 90%~95% 左右训练 200 万步左右。你的结果不必完全对齐但需要把差异归因到超参数或训练长度。一个有效的收尾技巧是在论文实验部分列出一个超参数表并额外记录运行环境如 GPU 型号、PyTorch 版本弱化数值差异本身。5.3 把模型文件与可视化结果作为交付物训练完成后除了保存model.pt建议把每个评估点生成的 replay 文件保存为.SC2Replay答辩时可以现场展示环境回放这比曲线直观得多。代码仓库中保留README.md其中写清环境安装步骤和每个算法的运行命令。MAVEN 的z可视化也可以做一个额外加分项把潜变量降维后用散点图展示直观对应不同策略模式。整个项目可以按环境搭建 → 基线实现 → 改进算法 → 对比分析来组织源码目录中算法模块与训练模块分离每个算法保留独立的run.py入口这比把所有代码塞进一个文件里更适合课程设计提交。本文还有配套的精品资源点击获取