多智能体强化学习算法源码包:VDN、QMIX、QTRAN、QPLEX一次跑通

发布时间:2026/11/2 14:42:49
多智能体强化学习算法源码包:VDN、QMIX、QTRAN、QPLEX一次跑通 简介一套基于 Python 的多智能体强化学习算法实现合集覆盖 VDN、QMIX、QTRAN、QPLEX 四种经典价值分解方法并配有对应模型文件与详细代码注释。适合毕业设计、期末大作业或课程设计参考也适合希望从代码层面入门多智能体强化学习的读者。包内共 131 个文件以 36 个 py 源代码、29 个 npy 与 25 个 pkl 模型数据为主另含训练过程图片、事件日志及说明文档可辅助理解算法收敛趋势与网络结构。压缩包约 9.05MB轻量易部署下载后稍作环境配置即可运行。已有 482 人学习使用。作者自述为个人手打高分项目并获导师认可代码注释清晰新手也能跟随阅读目录按算法与训练结果组织除可直接运行的算法实现外还包含可视化图表与训练日志便于对照分析不同值分解方式在合作环境中的表现。1. 多智能体强化学习算法源码包VDN、QMIX、QTRAN、QPLEX 一次跑通多智能体强化学习入门和高分毕设之间往往就差一份能同时跑通 VDN、QMIX、QTRAN、QPLEX 的 Python 源码。这套基于 Python 实现的多智能体强化学习算法源码包把四个经典值分解算法放进同一个工程代码带注释模型文件和 TensorBoard 事件日志一并附上解压后装好依赖就能直接训练和对比不用再对着论文公式从零实现也不必担心算法写错了自己还看不出来。对毕业设计、期末大作业来说最大的成本不是看懂公式而是把环境、网络结构和训练循环打磨到能稳定产出曲线。这套资源把整条链路备齐新手能顺着注释理解每个文件熟手能直接拿它当四算法对比的基准框架改环境、加算法都顺手。我拆这个包最直观的感受是它是真正训练过多轮的完整工程不是半成品。后面就按“原理 → 跑通 → 验证”的顺序讲每一步都落到具体文件和参数上。2. 值分解算法原理VDN、QMIX、QTRAN、QPLEX 的边界在哪里2.1 从联合动作空间爆炸到 IGM 原理多智能体强化学习第一个绕不开的问题是动作空间爆炸。假设有 N 个智能体每个智能体有 m 个离散动作联合动作空间就是 m 的 N 次方。像 SMAC 这种 5v5 战斗场景每个智能体几十个动作联合起来就是天文数字。直接对联合动作求 Q 值训练量和个人电脑的显存都扛不住。于是有了一个折中不显式建立联合动作表而是保留每个智能体自己的 Q 网络再想办法把个体 Q 值组合成联合 Q 值。这个组合过程有讲究不是随便加一加就行。为了保证“最优联合动作 最优个体动作的集合”需要满足 IGM 条件Individual-Global-Max每个智能体各自取 argmax 得到的动作组合恰好是整体取 argmax 的最优联合动作。VDN、QMIX、QTRAN、QPLEX 四个算法的本质区别就在于用不同方式逼近甚至部分放松这个条件。VDN 和 QMIX 严格保证 IGM代价是对联合动作值的表达能力有限制QTRAN 和 QPLEX 则在理论和实现上把约束重构换来更强的拟合能力。这部分概念对应到代码里就是每个算法文件里总能看到的那行注释joint Q f(q1, q2, ..., qN)。理解了这个约束再看训练循环里的 loss 公式就会清楚为什么有的算法损失函数多一项、有的多两项。2.2 VDN 与 QMIX加性分解和单调网络约束VDNValue Decomposition Networks的思想最简单联合 Q 值等于所有个体 Q 值直接相加Q_tot Σ Q_i。个体网络各自输出自己的 Q 值最后过一个求和层。结构上只是一个加和操作实现就是十几行代码的事。QMIX 的改进在于不再用线性求和而是用一个混合网络Mixing Network去拟合联合 Q 值和个体 Q 值的关系。关键在于限制混合网络的权重全部非负这样联合 Q 值对任意一个个体 Q 值都是单调递增的从而满足 IGM 条件。混合网络还能把全局状态 s 作为额外输入让系统感知到个体观测不到的信息。对比维度VDNQMIX分解方式线性求和非线性混合网络权重约束无非负权重约束表达能力强弱弱中等受单调性限制实现难度很低低到中典型适用场景简单协作任务大多数典型 MARL 环境代码实现上两者的区别主要集中在混合网络那一层。VDN 就是一个求和操作QMIX 需要单独定义一个 Mixer 网络里面用取绝对值的方式约束权重符号或者在初始化后固定权重符号不变。我帮人调代码时发现QMIX 最常见的问题是忘了把混合网络权重约束成非负导致训练中后期曲线忽高忽低。这个坑很隐蔽因为前期训练曲线看着正常到后期单调性约束一失效协作行为就开始崩。如果你只是想让毕业设计快速出结果VDN 是最省心的选择但答辩时容易被导师问一句“为什么不用更 advanced 的方法”。所以大多数合理的选择是直接上 QMIX它既有理论可讲又在大多数地图上表现稳定。2.3 QTRAN 与 QPLEX约束放松与优势函数显式建模QTRAN 的出现是为了打破 QMIX 的单调性限制。QMIX 的单调约束意味着智能体间的协作只能同向加强但真实场景里“一个智能体更激进、另一个智能体就得更保守”这种负相关协作QMIX 拟合不了。QTRAN 引入了辅助联合值函数的可加性约束通过额外的约束项来逼近任意联合值函数理论上放松了 IGM 限制。QTRAN 的问题在于理论完善但训练敏感。它需要同时优化多个约束项于是有三个损失函数叠加。我在实际使用中看到的现象是收敛速度比 QMIX 慢超参数稍微动一下就出现奖励回退。尤其那个正则系数设大了训练被约束项带着走设小了约束就形同虚设。QPLEX 换了一个思路把联合 Q 值拆成联合值函数 V 和联合优势函数 A优势函数再按个体优势分解。这种方式既能表达复杂的负相关协作又有比 QTRAN 更强的理论保证。实现上会出现一个明显的 Advantage Module把联合优势映射成个体优势的加权组合。四个算法的实现复杂度排序按我拆代码的经验是 VDN QMIX QTRAN QPLEX参数量和训练耗时也基本是这个递增顺序。选型建议我给得很直接环境交互简单、希望快速看效果就 QMIX任务存在明显负相关协作且你有时间调 QTRAN 的正则系数可以试 QTRAN如果想让毕设显得有深度QPLEX 是四个算法里最值得展开分析的一个代码结构里可讲的内容也最多。3. 把源码跑起来MARL.iml、事件文件和训练入口逐个拆开3.1 项目文件解构MARL.iml、.gitignore 和 tfevents 各自的作用刚拿到压缩包根目录里除了算法文件夹外有几个东西要特别注意。MARL.iml 是 IntelliJ IDEA 的模块描述文件记录着这个工程在 IDE 里的模块名称和源码路径。用 PyCharm 打开时IDE 识别到 iml 文件会按既定模块结构加载省去手动标记源码目录的步骤。.gitignore 定义版本管理时忽略的目录比如模型输出目录、运行缓存这些不该提交的文件。events.out.tfevents.* 这串文件是训练产出的 TensorBoard 事件日志文件名里那串数字是 Unix 时间戳。这个工程里的事件文件时间戳集中在 1659774917 到 1659853150换算过来大约是一天内多次训练留下的。同一批事件文件放在根目录说明这个工程在不同时间点重新跑过多轮。正因为有这些事件文件你下载后打开 TensorBoard 就能看到已训练好的奖励曲线这对判断“项目到底通没通”非常关键。需要说明的是事件文件不分框架无论是 TensorBoard 官方写的还是 PyTorch 的 torch.utils.tensorboard 写的格式一致读取方式完全一样。所以拿到项目后第一步别急着看代码先用 TensorBoard 把已有曲线打开能读出来就说明环境问题解决了一半。3.2 环境准备Python 版本与依赖安装这类多智能体项目的依赖组合常见有三种纯 TensorFlow、PyTorch 全家桶、或者 PyTorch 写算法加 TensorBoard 写日志。从事件文件格式看你无法直接判断底层框架所以安装依赖前先打开源码里的 requirements.txt 或环境说明看一眼。我在多个 MARL 项目里最稳的一套组合是 python 3.9 torch 1.13.1 tensorboard配合 CUDA 11.7conda create -n marl python3.9 -y conda activate marl pip install torch1.13.1 --index-url https://download.pytorch.org/whl/cu117 pip install numpy1.23.5 tensorboard2.12.0逻辑说明第一步创建独立 conda 虚拟环境避免和系统 Python 混用导致依赖冲突第二步按 CUDA 版本装对应 PyTorch 轮子第三步装经过验证的 numpy 和 tensorboard。参数说明里值得注意的只有 cu117它对应 CUDA 11.7。如果你的显卡驱动只支持更老版本把 cu117 改成 cu116 或 cu113 即可。tensorboard 2.12 能正常读取 1.x 时代写出来的 tfevents 文件兼容性没问题。装完验证一下是否可用python -c import torch, tensorboard; print(torch.__version__, tensorboard.__version__)能正常输出版本号环境基本就绪。接下来要确认事件文件能被读取这一步放到第五章专门讲。注意如果项目里的事件文件在根目录而不是 runs 子目录启动 TensorBoard 时要把 --logdir 指到事件文件所在层直接指到项目根目录会解析失败。3.3 训练脚本组织以 QMIX 为例的核心循环与参数清单工程里四个算法的入口一般对应四个可执行脚本比如 train_qmix.py、train_qplex.py。以 QMIX 为例训练脚本的核心循环是下面这种组织方式。这并非这份资源的原脚本而是我在大多数 MARL 源码里都会见到的通用结构实际文件内容以你下载的源码为准# train_qmix.py 核心训练循环结构示例 import torch from torch.utils.tensorboard import SummaryWriter from algo.qmix import QMIX def train_qmix(config): env create_env(config[map_name]) # 环境工厂函数 agent QMIX( obs_dimenv.obs_dim, # 单智能体观测维度 n_actionsenv.n_actions, # 离散动作数 n_agentsenv.n_agents, # 智能体数量 state_dimenv.state_dim, # 全局状态维度 hidden_dimconfig[hidden_dim], lrconfig[lr], gammaconfig[gamma] ) buffer ReplayBuffer(capacityconfig[buffer_capacity]) writer SummaryWriter(log_dirconfig[log_dir]) for episode in range(config[max_episodes]): obs env.reset() ep_reward 0.0 while not env.done: actions agent.choose_actions(obs, epsilonconfig[epsilon]) obs_, rewards, done, _ env.step(actions) buffer.push(obs, actions, rewards, obs_, done) obs obs_ ep_reward sum(rewards) if len(buffer) config[batch_size]: loss agent.learn(buffer.sample(config[batch_size])) writer.add_scalar(train_loss, loss, agent.learn_step) if episode % 50 0: eval_reward evaluate(agent, env, episodes3) writer.add_scalar(eval_reward, eval_reward, episode)逻辑说明第 17 到 24 行是单 episode 的执行循环多个智能体同时出动作环境返回下一观测和一组奖励每一步的 s、a、r、s 都会被推进 replay buffer。第 25 到 28 行只有 buffer 里样本足够多时才开始学习这是绝大多数强化学习项目避免“样本不足导致 Q 值震荡”的常规做法。第 30 行每 50 个 episode 评估一次评估用的是确定性策略不受 epsilon 干扰。参数说明几个关键值。epsilon 通常从 1.0 线性衰减到 0.1决定探索和利用的平衡gamma 一般取 0.99如果任务本身很短0.95 也能接受hidden_dim 在 64 到 128 之间选SMAC 这类中等复杂环境用 64 就够换成 attention 结构的个体网络再考虑 128。下面是一份可用的参考配置参数名推荐值说明lr1e-4 ~ 5e-4超过 1e-3 极易震荡batch_size3264 需要更大的 bufferbuffer_capacity50000再大会明显吃内存hidden_dim64中等地图足够max_episodes5000太少看不出收敛趋势epsilon 衰减终点0.1训练后期保留最低探索最容易翻车的一步是直接把 batch_size 拉到 128然后发现 GPU 显存撑不住。后面第四章专门讲这些坑。4. 多智能体训练避坑事件文件混乱、不收敛与模型加载失败4.1 一堆 tfevents 分不清哪次训练先改 log_dir 结构现象项目根目录下躺着十几个 events.out.tfevents 文件打开 TensorBoard 后所有曲线重叠在一起根本分不清哪条是 VDN、哪条是 QPLEX。原因训练脚本里的 log_dir 是固定值比如直接写死成 logs。每次重新训练都在同一个目录里新写事件文件算法名和训练时间没有体现在目录结构里。这批事件文件之所以这么乱就是因为连续多轮训练用了同一个输出目录。解决从一开始就把 log_dir 设计成按“算法名 时间戳”分层。我一般会这样写import time log_dir fruns/{config[algo]}_{time.strftime(%Y%m%d_%H%M%S)}逻辑说明算法名区分不同算法时间戳区分同算法的不同轮训练。参数说明里%Y%m%d_%H%M%S 生成的是形如 20220806_153000 的字符串精确到秒同一秒内启动两次训练的概率可以忽略。启动 TensorBoard 时用tensorboard --logdirruns指向上层目录左侧就能按子目录折叠浏览。对于下载下来的这个资源你也可以在本地把已有事件文件按算法名分到子目录下再启动 TensorBoard不必重跑训练。4.2 奖励曲线一直不涨按这四个方向排查现象训练跑了 2000 个 episodeTensorBoard 上的平均奖励始终在初始值附近loss 降得极慢甚至不降。原因不是所有“不收敛”都是算法写错了。常见四种epsilon 没衰减导致一直随机探索reward 数值范围过大导致 loss 震荡episode 长度过长导致单条轨迹里有效信号占比太低环境本身奖励稀疏几百个 episode 根本积累不出有价值的样本。解决把排查顺序固定下来。先看 epsilon 衰减计划确认训练后期确实掉到 0.1 以下再打印 reward 的均值和方差方差太大就做奖励缩放最简单的做法是reward reward / 20.0然后把 episode 长度从 200 减到 50观察曲线是否有明显反应最后拉长总训练步数到 3000 个 episode 再下结论。实际操作中前两步做完就能解决七成问题。提示奖励缩放属于“后悔药”式修复能让 loss 波动快速变小但别指望它在奖励定义本身有 bug 时兜底。这里头真正称得上玄学的是 epsilon 衰减曲线。衰减太快智能体还没探索够就进入利用阶段衰减太慢训练后期还在大量随机瞎逛曲线平台期提前出现。线性衰减在中型地图上够用但如果你换了一张奖励更稀疏的地图建议改成指数衰减。4.3 checkpoint 加载错位先分清 online 和 target现象模型文件目录里按 episode 保存了多份权重加载最新那份后评估分数反而不如加载早一点的版本。原因保存策略是“每 N 个 episode 保存一次”但文件名里的编号和实际训练进度对不上或者存的是 target network 的权重而不是 online network。target network 是缓慢更新的旧网络直接用它做推理效果自然比 online 网络差。解决加载 checkpoint 时先打印一下内部字段确认存的是 online 还是 target# 加载检查脚本片段 import torch ckpt torch.load(results/model_5000.pth, map_locationcpu) print(list(ckpt.keys())) # 看含 online 还是 target print(saved step:, ckpt.get(step))逻辑说明ckpt.keys() 能列出字典里所有字段一般会有 q_net、target_q_net、mixer、optimizer 等。参数说明saved step 是训练到第多少步存的权重可以用它判断这份 checkpoint 靠前还是靠后。如果确实只有 target 网络权重也不必慌大多数实现里 target 每隔固定步数会从 online 拷贝加载后多跑几十步就能追上来。关键是别拿错的权重去评估并写进毕设结论。4.4 显存内存爆掉batch size 与序列截断的取舍现象训练一两个小时后系统内存从 4GB 一路涨到 20GB或者在 GPU 上直接报 CUDA out of memory。原因replay buffer 容量没设上限一直在塞数据或者 batch size 设成 128网络输入又包含循环层展开的时间步显存峰值直接翻倍。多智能体项目的 obs 通常是多个智能体的观测堆叠一个样本的维度比单智能体大 N 倍同样的 batch size 显存占用完全不是一个量级。解决先看 replay buffer 实现确认是固定容量还是无边界 list。固定容量压在 50000 以下是稳妥的。如果 batch size 已经降到 32 还爆大概率是 episode 长度太长把单条轨迹切成固定长度片段再入库# 截断示例把单条轨迹切成固定长度片段再入库 truncate_len config[truncate_len] # 常见取 10~30 for t in range(0, len(trajectory) - truncate_len 1, truncate_len): buffer.push(trajectory[t:ttruncate_len])逻辑说明把长度为 L 的轨迹切成若干长度为 truncate_len 的片段每段独立入 buffer后续训练采样以片段为单位。参数说明truncate_len 取 10 到 30 之间。这个操作会损失长程依赖信息但多智能体协作通常在几步之内就能看出动作好坏截断到 20 步左右对效果的影响比预期小得多。如果项目用的是循环神经网络截断后的序列依然保留时间顺序训练时按时间步展开即可。5. 横向验证四个算法TensorBoard 事件文件读取与对比实验设计5.1 读 TensorBoard 事件文件的三种姿势拿到这份资源后第一个值得做的验证就是把事件文件读出来确认训练确实发生过而且四个算法的曲线能分开看。三种方式里我最常用第二种它可以直接跟实验代码衔接。第一种启动 TensorBoard Web 界面tensorboard --logdirruns --port6006浏览器打开 http://localhost:6006进 scalar 面板就能看到 loss、reward 曲线。下拉列表里所有 tag 都可以展开包括 train_loss、eval_reward 这类指标。第二种用 event_accumulator 在 Python 里读from tensorboard.backend.event_processing.event_accumulator import EventAccumulator ea EventAccumulator(runs/qmix/events.out.tfevents.1659774917) ea.Reload() print(ea.Tags()) # 列出所有可用的 tag train_reward ea.Scalars(eval_reward) steps [x.step for x in train_reward] values [x.value for x in train_reward]逻辑说明EventAccumulator 是 TensorBoard 的底层读盘模块加载目录或单个事件文件后Tags() 返回所有被记录的指标名Scalars() 返回的每个 x 对象拥有 step、value、wall_time 三个属性。参数说明路径要写到事件文件那一层如果写到 runs 目录下面一层可能解析不出来。这个方式的优点是拿到数据后可以直接用 matplotlib 重画曲线也可以计算收敛步数和均值方差不用从浏览器截图。第三种直接从事件文件名的数字读时间戳判断哪次训练在后。文件名里的数字是 Unix 秒比如 1659774917 和 1659853150差值约 7.8 万秒也就是相差大约 22 小时说明这两批训练隔了将近一天。这个方法适合快速确认训练历史不适合做精细分析。5.2 对比实验必须锁死的变量用这份源码做四算法对比最忌讳的是不控制变量。如果让 QMIX 跑 1000 个 episode、QPLEX 跑 3000 个最后说 QPLEX 更强这个结论站不住脚。我通常的控制矩阵是这样的控制维度设定值说明随机种子seed ∈ {1, 2, 3}每个算法跑 3 个 seed结果取均值加减方差训练总步数统一按 5000 个 episode 或 2e5 环境步网络隐藏层统一hidden_dim 固定 64环境配置统一同一张地图、同一智能体数量评估方式统一训练结束后各跑 10 个 episode 取平均奖励每个算法的每个 seed 单独开一个 log_dir例如 runs/seed1/qmix、runs/seed1/qplex。这样 TensorBoard 上能用正则过滤出同一 seed 下的四条曲线生成的对比图也干净。血泪经验是训练中途不要手动改任何超参数包括学习率。一旦改了就相当于一个新的实验得重新起一个 log_dir。所有对比实验跑完后统一汇总均值别边跑边改边看这样你在论文里写的“QPLEX 比 QMIX 快了多少”才有可复现性。5.3 曲线形状怎么读四种典型结果与结论边界曲线读出来后常见四种形态。VDN 在简单环境里率先冲到不错的奖励但后期平台期明显因为线性分解撑不住复杂协作。QMIX 前期爬升比 VDN 慢但中后期能超过 VDN在单调性任务里最稳定。如果环境中存在非单调协作QTRAN 的曲线可能出现“先高后低”的情况约束正则项在训练初期帮它找到不错策略后期约束项和 Q 值更新的平衡没稳住奖励开始回退。QPLEX 通常是四条曲线里后期最稳的前期预热慢但中后期保持上升趋势。另一种值得警惕的情况是四条曲线几乎完全重合。这通常意味着环境任务太简单四个算法的表达力差异没有被激活。这时候别急着换算法先把环境难度调大比如增加智能体数量或加大动作空间让差异显现出来。这套源码里不同算法在同一环境下的模型文件差异正是用来做这种环境灵敏度检验的。对比实验的结论边界也要讲清楚在一个地图上 QPLEX 优于 QMIX只能说明在这个地图上成立。多智能体算法的表现高度依赖环境特性你在论文里至少要跑两张以上难度不同的地图并且报告标准差。导师真正看的不是哪个算法第一而是你清不清楚算法适用的边界。6. 加载预训练模型做迁移验证自定义任务中的低成本启动技巧拿到模型文件后常见想法是直接在自己定义的任务环境里评估算法。但这会撞上一个现实问题自定义环境的观测维度、动作空间定义和项目原环境大概率不一致强行加载会报 shape mismatch或者静默加载了一堆无用的权重。我的做法是先在源码自带的环境里把模型文件跑出一个基线分数再改环境每次只改一个维度比如只改奖励函数不动观测和动作维度。这样做的理由是模型在单一维度变化下的表现差异能隔离出那个维度对算法的影响。如果同时改观测和奖励出了问题根本分不清是环境配置错了还是算法不适合。具体加载方式参考下面的脚本# 在加载模型前先验证 shape 是否匹配 import torch def load_pretrained(agent, model_path): ckpt torch.load(model_path, map_locationcpu) # 只加载能匹配的键避免因维度不同直接崩掉 incompatible agent.load_state_dict(ckpt[model], strictFalse) print(missing keys:, incompatible.missing_keys) print(unexpected keys:, incompatible.unexpected_keys)逻辑说明strictFalse 会把网络结构差异打印出来而不是直接抛异常终止脚本。missing_keys 是原模型没有的层unexpected_keys 是原模型有但新网络没有的层。这两个输出能准确告诉你改动范围。如果项目是 TensorFlow 版本对应操作是 tf.train.latest_checkpoint 加 tf.train.load_checkpoint思路一样。检查完差异之后如果只是新增了网络层可以考虑冻结预训练参数只训练新增层。在迁移场景里我把预训练权重当作初始化手段而不是当作已经收敛的解。每次换新环境我都会先跑一个 50 个 episode 的短训练确认奖励曲线的起点比随机策略高才开始完整训练。起点高说明预训练权重确实提供了有效信息起点和随机差不多说明环境改动太大权重的迁移价值有限。从那以后我每次拿到一份多智能体源码都会强制走一遍“环境自检 → 短训练 → 看曲线 → 对比随机策略”四步流程确认真的有学习信号之后再考虑往里面加自己的模块。这个流程帮我挡住了至少三次因为环境配置错误导致的无效实验。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询