
简介面向高校多智能体强化学习课程设计与期末大作业这份压缩包提供了一套完整的Python算法实现合集涵盖VDN、QMIX、QTRAN、QPLEX四种主流价值分解方法并附带训练好的模型权重文件。项目以SMAC星际争霸环境为默认场景代码模块划分清晰MultiAgentController用于构建智能体网络、生成动作并计算个体Q值ReplayBuffer同时支持transition与episode两种存储方式训练时可直观对比on-policy和off-policy的差异也能替换自定义环境进行扩展实验。压缩包共含131个文件整体大小约9.05MB包括36个Python源码、29个npy中间数据、25个pkl模型文件、18张训练曲线图和PDF说明文档另有TensorBoard事件文件与损失记录便于查看训练细节和继续调参。已有347人浏览学习适合作为期末项目或课程设计的多智能体强化学习参考实现能帮助读者对照原论文理解算法设计思路并在SMAC场景下直接复现、横向比较不同算法的收敛表现。1. 多智能体强化学习课程设计一套 python 源码跑通 VDN、QMIX、QTRAN、QPLEX做过多智能体强化学习课程设计的人大概都有过这种经历论文里的 VDN、QMIX、QTRAN、QPLEX 递进关系能讲清楚但真到交期末大作业时被环境安装、依赖冲突和代码实现卡得死死的最后只能拿一份理论报告凑数。这份基于 python 实现的多智能体强化学习源码包把四个价值分解算法和对应模型文件一次配齐核心模块划分得很清楚MultiAgentController 管智能体网络和动作生成SMAC 管环境交互ReplayBuffer 管训练数据存取。装上依赖就能跑解决的是「课程设计必须拿出对比实验曲线」这件事。适合正在赶期末大作业的学生也适合想快速复现 QMIX 做基线对比的从业者。2. 从价值分解到四大算法这套源码包里各改了什么多智能体强化学习和单智能体最大的差别不在网络结构而在「怎么给每个智能体分功劳」。在 SMAC 这类场景下你看到的是 8 个单位在打架每个单位只能看到自己视野内的局部信息但训练时你又希望它们能完成协同配合。联合动作空间是每个智能体动作数的乘积直接学一个全局 Q(s,a) 在智能体数量稍微上去之后就彻底不可行。这套源码包的四个算法走的是同一条路线中心化训练、分布式执行CTDE训练时用全局状态做指导执行时每个智能体只用自己的局部观测决定动作。2.1 价值分解的动机单智能体 DQN 为什么不能直接搬到多智能体直接搬 DQN 会遇到两个绕不开的问题。首先是环境非稳态其他智能体的策略也在更新导致当前智能体看到的转移概率时刻在变Q 网络要去拟合一个不停晃动的目标非常容易震荡。其次是信度分配一场团战打赢了到底是哪个单位的关键操作如果不做分解所有智能体共享同一个团队奖励梯度回传时「一锅端」每个 agent 都拿不到真正属于自己的那份信号。价值分解的思路就是学一个联合动作价值 Q_tot把它拆成每个智能体个体价值 Q_i 的函数每个智能体有自己的 Q 网络一个混合结构把个体 Q 合成全局 Q训练时只优化 Q_tot执行时每个智能体直接用 Q_i 选动作。VDN、QMIX、QTRAN、QPLEX 这四个算法本质上是「Q_tot 怎么从 Q_i 合成」的四种方案合成方式的表达能力决定了这个算法能覆盖什么样的协作模式。2.2 四条技术路线的递进VDN 求和、QMIX 单调、QTRAN 松弛、QPLEX 优势分解四个算法的差别用一张表能看得很清楚算法核心约束Q_tot 合成方式表达能力VDN无条件Q_tot sum Q_i只能表达线性可加的简单协作QMIX单调性约束非负权重混合网络单调函数类适合大多数 SMAC 同构场景QTRAN松弛约束引入全局状态值函数逼近 Q_tot理论上可表示任意联合价值函数训练偏难QPLEXadvantage 分解dueling 结构 优势函数逐项分解理论完备难图上更稳但网络更重VDN 是 2017 年的工作做法最直接把联合 Q 写成所有个体 Q 的求和。这里隐含一个强假设——智能体之间的贡献是线性可加的不存在交互项。好处是实现简单、训练稳定坏处是很多需要协同的场景它学不出来。QMIX 在 VDN 基础上引入 mixing network用一组非负权重把个体 Q 混合成联合 Q。非负权重的约束保证了「个体 Q 变大联合 Q 不会变小」这就是单调性。在 SMAC 的 8m、3m 这类同构地图上QMIX 的收敛速度和最终胜率都非常稳是这套源码包里最适合当默认基线的算法。QTRAN 是对 QMIX 表达能力的批判性改进QMIX 只能表示单调函数但真实问题里经常出现非单调的协作模式——两个智能体都去执行某动作才是最优只有一个去反而更差这种「拐点」是单调函数表达不了的。QTRAN 引入一个全局状态相关的值函数做松弛约束理论上覆盖任意 Q_tot。代价是多了一个要同时优化的目标实际训练里超参稍微不合适就掉点表现容易波动。QPLEX 走的是另一条路把联合 Q 写成优势函数分解的形式同时保留 dueling 网络结构让每个智能体分别分解出自己的优势项。理论上它能表示任意联合价值函数在 SMAC 的难图上往往比 QMIX 更稳。代价是网络结构最复杂显存占用和调参成本都高。在动手跑之前建议先把这四个算法的关系在脑子里排一遍VDN 是最朴素的求和QMIX 用单调混合扩大表达QTRAN 想突破单调限制但引入更多优化目标QPLEX 把优势分解直接做进网络结构。后面调参翻车的时候回头看这一步能省很多时间很多「训练不收敛」本质上是算法表达能力不适合当前地图而不是代码写错了。2.3 三个核心模块的代码分工MultiAgentController、SMAC、ReplayBuffer这套源码的结构属于典型的「课程设计友好型」三个核心类各管一段。MultiAgentController() 包含多智能体的网络和所需操作value-based 算法下它生成 q net如果是 AC 类算法则生成 actor net它不包含 mixing net 或 critic net。对外暴露的操作是计算 individual Q 值、按 epsilon 选动作。class MultiAgentController: 生成 agent 模型并计算 individual q 值。 value-based 算法用 q netAC 算法用 actor net。 def __init__(self, model_config): self.q_net QNet(model_config) self.target_net QNet(model_config) def compute_q(self, obs_batch, agents_mask): # obs_batch: 一批观测张量 # agents_mask: 屏蔽已经阵亡的智能体 return self.q_net(obs_batch) * agents_mask def choose_action(self, q_values, epsilon): # epsilon-greedy 策略训练初期探索后期趋于利用 if random.random() epsilon: return random_action(q_values) return q_values.argmax(dim-1)这里我一般会特别提醒一个点agents_mask 不能省。SMAC 里智能体可能阵亡死亡单位的 obs 还在 batch 里不屏蔽的话梯度会把死单位的 Q 也算进去训练会莫名震荡。这是复现 QMIX 的人最常见的隐藏 bug。ReplayBuffer 的接口同样直接它的核心设计是两种存储模式class ReplayBuffer: 支持 transition 和 episode 两种存储模式的回放缓冲区。 modetransition 存单步样本适合 off-policy 单步更新 modeepisode 存完整轨迹适合需要整条序列的算法。 def __init__(self, modeepisode, capacity10000): self.mode mode self.buffer deque(maxlencapacity) def sample(self, batch_size): # 按 mode 决定返回单步还是整条 episode内部做形状处理 ...模式选择直接对应 on-policy/off-policy 的数据需求这个点后面第 4 章会专门展开。3. 把源码跑起来环境安装、地图摆放与训练启动的逐行拆解拿到源码包之后第一步不是读代码而是先让一个能跑的实验跑起来。课程设计时间紧你得先确认环境通再回头去看每个模块怎么实现。下面这套流程我在多个版本的 SMAC 工程上都用过照着走能省掉大半天的排错时间。3.1 python 环境与依赖别在系统环境里硬装我建议先建一个独立的 conda 环境python 版本选 3.8 或 3.9。这套源码的核心依赖是 torch、numpy、tensorboardSMAC 环境本体也是一个 pip 包。卡住最多的地方是 torch 与 python 版本的搭配千万别在系统 python 里硬装装坏了连日常脚本都用不了。conda create -n marl python3.8 conda activate marl pip install torch numpy tensorboard pip install smac参数说明conda create -n marl 创建独立环境避免污染系统 pythonpython3.8 是这套源码兼容面最广的版本选择tensorboard 用来读源码包里那批 events.out.tfevents.* 事件日志smac 是星际争霸多智能体环境的 python 封装但装完 smac 不等于环境就绪它还需要游戏本体和地图文件。如果机器没有 GPUtorch 的 CPU 版本也能把 8m 这种小地图跑出结果只是速度慢。QMIX 的 mixing network 和 QPLEX 的 dueling 结构都偏重有 GPU 能省很多等待时间。我一般会先看任务期限再决定要不要上 GPU如果只是交课程设计CPU 跑 2000 个 episode 完全在可接受范围内。3.2 SMAC 环境与星际争霸地图的落地姿势SMAC 依赖星际争霸 II 的游戏本体。常见做法是下载 SC2 后把目录位置配到环境变量 SC2PATH再把 SMAC 官方仓库提供的 maps 解压到游戏目录的 Maps 下。地图文件体积不小官方没有放进 pip 包这也是环境配置里最容易卡住的一步。配好之后我会先写一段极小的验证脚本确认环境是真的通了from smac.env import StarCraft2Env env StarCraft2Env(map_name8m) env_info env.get_env_info() print(agents:, env_info[n_agents], actions:, env_info[n_actions]) obs env.reset() # 触发一次 reset验证环境闭环能走通这段代码的作用是检查环境创建和 reset 两个最基本的环节。env_info[n_agents] 是该地图的智能体数量env_info[n_actions] 是每个智能体的离散动作数如果你看到类似 (8, 14) 的维度组合说明 SC2 本体和地图文件都就位了。map_name 值得特别注意改地图名就能切换场景难度后面做对照实验时它是最重要的自变量。提示如果 StarCraft2Env 创建时报找不到地图优先检查 SC2PATH 环境变量是否指对了包含 StarCraft II 可执行文件的目录。3.3 训练启动命令与关键参数解读环境通了之后就可以启动训练。这份源码的入口是一个 main.py通过命令行参数切换算法和地图这是课程设计最舒服的形态——不用改代码就能换算法python main.py --algo qmix --map 8m --n_episodes 5000 --buffer_mode episode几个关键参数的含义--algo 指定算法取值 vdn、qmix、qtran、qplex对应第 2 章说的四条路线--map 指定 SMAC 地图名8m 是最常用的入门地图8 个 marine 对阵 8 个 marine智能体同构最适合做算法对比--n_episodes 控制训练总 episode 数课程设计拿 2000 到 5000 都能出可看的收敛曲线--buffer_mode 是这份源码特有的参数episode 模式存整条轨迹transition 模式存单步on-policy 的算法必须用 episode 模式并且在训练阶段结束后清空缓冲区。我一般会先跑 100 个 episode 验证流程不报错确认模型输出形状、reward 计算、模型保存这些环节都正常再放开跑完整实验。这一步能救回很多无效训练时间代价只是几分钟。3.4 模型文件与事件日志的复盘用法训练结束之后日志目录下会多出两类东西模型权重文件和 events.out.tfevents.* 事件日志。后者就是 TensorBoard 的事件源源码包里自带的那些 events.out.tfevents.1659* 文件就是之前跑训练留下的历史日志可以直接用来恢复当时的训练曲线。tensorboard --logdir ./logs --port 6006浏览器打开 http://localhost:6006可以看到 reward、win rate 等标量随训练步数的变化曲线。模型文件则有两个用途一是加载权重继续训练相当于断点续跑二是加载训练好的模型做纯评估关闭探索噪声epsilon0看稳定胜率。复盘时有个经验只看 reward 曲线会被骗。SMAC 里 reward 的尺度会随战斗进程出现很大方差win rate 才是最终标准。注意观察 win rate 曲线的拐点位置和方差拐点出现得越早说明算法在这个场景上学得越快。4. 做课程对比实验四算法的统一接口、超参与公平对照组设计课程设计的核心不是跑通一个算法而是拿出一个让老师信服的对比。这套源码包最大的价值恰好在这里四个算法共用同一套训练接口实验设计得好答辩时一张图就能把工作量讲清楚。4.1 一个参数切换算法统一接口的直接收益源码里算法注册的逻辑一般是这样的ALGO_MAP { vdn: VDN, qmix: QMIX, qtran: QTRAN, qplex: QPLEX, } def build_algorithm(algo_name, config): algo_cls ALGO_MAP[algo_name] return algo_cls(config)ALGO_MAP 把字符串映射到算法类build_algorithm 只负责按名字实例化。MultiAgentController 不关心上层是哪个算法它只提供 q net 和动作选择混合网络和特殊值函数由各算法子类自己实现。这意味着你切换算法时只需要改一个参数底层的数据流、训练循环、日志记录全部复用。但要注意四个算法的超参并不完全一致。QMIX 的 mixing network 有独立的 learning rateQPLEX 的 advantage 模块还需要额外的分解系数。直接用 VDN 的默认超参跑 QPLEX效果差是正常的不代表代码有问题。切换算法之后至少要把学习率和 batch size 重新看一遍。4.2 公平对照实验的三要素答辩时老师最爱问的一句话是「你这几个算法的超参是不是一样的」。如果完全一样对某些算法不公平如果完全不一样又失去了对比意义。我的习惯是固定三样东西随机种子、地图、训练总 episode 数超参则每个算法各自调到可接受范围最后把实际使用的超参列在报告附录里。一个常见的课程设计结果表会长这样算法地图seed最终 win rate达到 80% win rate 的 episodeVDN8m0约 78%未达到QMIX8m0约 95%约 1800QTRAN8m0约 85%约 2500QPLEX8m0约 96%约 1600具体数字由超参决定但规律是稳定的同构简单地图上 QMIX 和 QPLEX 明显领先VDN 受限于线性加和假设容易到瓶颈QTRAN 的曲线方差偏大。还有一个最容易翻车的细节训练代码里必须显式固定随机种子并让 cuDNN 走确定性算法。# 固定随机种子并关闭 cuDNN 的自动调优保证可复现 seed_everything(42) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark False这三行代码我每次跑对照实验前都会加上。少了它们同一个 seed 在 GPU 上跑两次结果都可能不一样答辩时如果被要求现场重跑结果对不上报告会非常尴尬。提示seed_everything 要同时设置 random、numpy、torch 三套随机数生成器缺一个都不算真正固定。4.3 on-policy 与 off-policy 的 buffer 清空逻辑资源正文里反复提过 ReplayBuffer 的两种存储方式根子就在 on-policy 和 off-policy 的数据哲学差异上。on-policy 要求训练数据必须来自当前正在被改进的策略所以策略更新之后旧策略采集的数据就不能再用了必须清空 buffer 重新采样。off-policy比如 QMIX 这类 value-based 算法可以用行为策略采集的数据反复训练buffer 不清空但某些带重要性采样的变体会在采样时额外计算权重。对应到代码里就是这个逻辑# on-policy每个训练阶段结束后清空防止旧轨迹污染新策略 if config[on_policy]: replay_buffer.clear() # off-policy旧数据可复用不清空只按优先级或均匀采样两类的差异可以总结成一张表项目on-policyoff-policy数据来源当前策略实时采集行为策略历史采集buffer 处理阶段结束后清空长期复用不清空更新频率每个 episode 结束后整体更新每个 batch 都可以更新典型代表策略梯度类变体VDN、QMIX在这份源码里VDN 和 QMIX 按 off-policy 跑即可QTRAN、QPLEX 要看具体变体的实现方式。你只需要记住一个排查方向训练曲线在某一步突然断崖式下跌先检查是不是 buffer 清空逻辑没生效。5. 常见问题与避坑跑这四个算法最容易翻车的 5 个场景前面把流程说通了下面这 5 个坑是按课程设计作业里出现频率排的每一条都值得在最终提交前对一遍。前两个是环境问题中间两个是训练问题最后一个是答辩思路问题。5.1 SMAC 环境创建失败SC2PATH 没指对地图就绪位置现象StarCraft2Env(map_name8m) 直接抛异常报错信息类似找不到地图初始化参数、找不到 SC2 可执行文件。原因pip install smac 只安装了环境的 python 封装游戏本体的地图数据需要单独放置。SMAC 默认从环境变量 SC2PATH 找游戏目录这个变量很多教程不会显式设置。解决先确认 SC2PATH 指向包含 StarCraftII.exe或 .app的目录再把 maps 解压到游戏目录下的 Maps 文件夹最后重新执行 3.2 节的验证脚本。如果是 Mac 用户注意路径里的版本号目录不能嵌套错位。5.2 训练 loss 不降、win rate 长期为 0现象reward 一直徘徊在 0 附近epsilon 都衰减到底了win rate 还是 0。原因最常见的有两种。一是 on-policy 算法 buffer 没清空旧轨迹持续拖累新策略的梯度更新二是地图难度超出算法能力比如拿最朴素的 VDN 直接跑异构地图它根本学不会协同。解决先在训练循环里加一行打印确认 buffer 长度在每个训练阶段结束后都被重置。然后换回 8m 这种同构简单地图验证算法本身。如果简单地图能收敛说明代码没问题是场景选难了如果简单地图也不收敛再回头查 buffer 和 reward 归一化。5.3 显存溢出或训练慢到怀疑人生现象跑一段时间后进程 OOM或者 QPLEX 一个 episode 要好几秒实验周期完全不可接受。原因SMAC 的 episode 长度往往很长QMIX 和 QPLEX 需要同时保存完整轨迹并做序列前向显存和时间的消耗集中在 batch_size 与轨迹长度的乘积上。QPLEX 的 dueling 结构还额外多了一组优势函数分支。解决把 batch_size 从 32 调到 16 或 8配合梯度累积来补偿训练稳定性。如果还吃紧就把 buffer_mode 从 episode 换成 transition用单步更新替代整条轨迹更新牺牲部分样本效率换取可训练性。5.4 同一个 seed 两次结果不一致复现翻车现象答辩前重跑实验曲线和提交报告里的差出一截明明代码一行没改。原因GPU 上的非确定性操作cuDNN 的 benchmark 模式会在不同运行环境下选择不同的卷积算法另外多进程采样时子进程的随机种子没有正确传递也会导致采样序列漂移。解决设置 torch.backends.cudnn.deterministic True 和 benchmark False在创建 DataLoader 或采样 worker 时用 worker_init_fn 给每个进程单独固定种子。这个坑在跑 QPLEX 时尤其明显因为它的优势分支对网络初始化和采样顺序更敏感。5.5 QTRAN 跑得比 VDN 还差别急着改代码先看地图特性现象QTRAN 在 8m 上 win rate 连 VDN 都打不过看起来像「算法越高级越不行」。原因QTRAN 的松弛约束引入了额外的全局状态值函数多了个要同时优化的目标。在简单同构地图上VDN 的线性加和假设已经够用QTRAN 的优势发挥不出来反而容易被超参拖累。这不是代码 bug是算法特性与场景不匹配。解决换 3s_vs_5z、5m_vs_6m 这类异构或需要精细配合的地图再跑QTRAN 理论上的表达优势才体现得出来。答辩时主动说清楚这一点比藏着不说更加分——这正是课程设计要的训练理解算法的适用边界。6. 把这套源码改成自己的自定义环境、buffer 切换与对比图导出课程设计交完不是终点绝大多数人会把这份源码拿来扩展成自己的项目。这一章只讲三个最值钱的改造点不碰架构重构。6.1 自定义环境照着 SMAC 的 API 封一层写自己的环境时最省事的做法不是改 MultiAgentController而是把自己的环境封装成 SMAC 同款接口。reset 返回观测、get_env_info 返回环境元信息、step 返回 (obs, reward, done, info)三种方法对齐之后整份源码的训练循环可以原封不动地跑你的环境。血泪经验是封装环境永远比重构训练循环便宜。6.2 不同算法选不同 buffer 模式VDN 和 QMIX 用 transition 模式跑单步更新速度快、显存省QTRAN 和 QPLEX 用 episode 模式保整条轨迹训练更稳定。这个选择直接写在 config 里改一个字段就能切。我自己跑实验时习惯先验证算法本身再在最终对比实验里统一用 episode 模式保证数据格式一致减少答辩时的解释成本。6.3 导出一张四个算法叠一起的对比图import matplotlib.pyplot as plt for algo_name in [vdn, qmix, qtran, qplex]: data load_results(flogs/{algo_name}_8m.csv) plt.plot(data[episode], data[win_rate], labelalgo_name) plt.xlabel(episode) plt.ylabel(win rate) plt.legend() plt.savefig(comparison.png, dpi150)这段脚本的逻辑很简单读每个算法在同一地图上的训练日志把 win rate 曲线叠加到同一个坐标系。关键是横轴必须用 episode 数不能用时间戳。四个算法单步耗时不同时间戳对齐的曲线起点天然错位答辩时老师一眼就能看出来。我自己做这个系列实验时吃过一次亏当时画图用了时间戳当横轴QMIX 和 QPLEX 因为训练速度快曲线整体偏右答辩老师直接问「你横轴是什么」。从那以后我每次做算法对比都会强制走一遍同样的流程——固定种子、统一 episode 数、确认 buffer 模式匹配算法性质、最后用 episode 对齐曲线。这个源码包让我少走了很多弯路也希望帮到你。本文还有配套的精品资源点击获取