首页 / 资讯中心 / 文章详情

多智能体强化学习值分解算法:VDN、QMIX、QTRAN、QPLEX源码复现与选型指南

多智能体强化学习值分解算法:VDN、QMIX、QTRAN、QPLEX源码复现与选型指南 ★ FEATURED ARTICLE
简介本资源面向多智能体强化学习方向的课程大作业、期末设计与入门研究者提供基于Python实现的VDN、QMIX、QTRAN、QPLEX四种经典值分解算法源码及配套模型文件帮助读者理解从个体Q网络到混合网络的完整训练流程。包内共131个文件以36个py源码、29个npy数据、25个pkl模型、18张png训练曲线图为主另含xml环境配置、pdf说明文档与loss日志压缩包约9.05MB结构清晰便于按算法模块检索。代码围绕MultiAgentController、SMAC环境封装与ReplayBuffer三大组件展开涵盖个体Q值计算、动作生成、on-policy与off-policy数据存储差异等关键实现细节可直接对照复现实验并迁移到自定义环境。目前已有347人学习下载适合需要快速搭建多智能体训练框架、完成课程设计或对比算法性能的读者参考。1. 多智能体强化学习源码包从 VDN 到 QPLEX 的选型与复现路径如果你手头有一个基于 Python 的多智能体强化学习源码包里面包含 VDN、QMIX、QTRAN、QPLEX 四种算法的实现和对应模型文件第一反应大概率是「先跑起来看看效果」。但真正动手时你会发现这四个算法虽然都属于值分解Value Decomposition家族却在网络结构、单调性约束、参数共享策略上存在关键差异直接套用同一套训练脚本很容易翻车。这个源码包解决的核心问题是在 StarCraft II 的 SMAC 环境或类似的合作型多智能体任务中让多个智能体通过局部观测学会协同策略。它适合已经掌握 Python 基础、了解 PyTorch 或 TensorFlow 基本用法、想做多智能体算法对比实验的从业者。接下来我会按「先理解每个算法为什么这样设计再落到具体代码和参数最后说清楚哪些地方容易踩坑」的顺序把这条路径拆开讲透。2. VDN 与 QMIX值分解的起点与单调性约束2.1 VDN 的加性分解为什么是最小可用基线VDNValue Decomposition Networks的核心假设非常朴素联合 Q 值等于各智能体 Q 值的和。这个假设在代码里体现为一行torch.sum(agent_qs, dim1)但它的意义在于把信用分配问题简化成了可加性约束。源码包中 VDN 的实现通常包含三个部分每个智能体的局部 Q 网络通常是 GRU 或 MLP、一个求和模块、以及一个共享的经验回放缓冲区。我一般会先跑 VDN 作为基线因为它的训练最稳定超参数最少。关键参数只有三个hidden_dim局部网络隐藏层维度常用 64 或 128、lr学习率1e-3 到 5e-4 之间、batch_size32 或 64。在 SMAC 的 2s3z 地图上VDN 通常能在 200k 步以内收敛到 80% 以上的胜率。# VDN 的 Q 值聚合核心逻辑 # agent_qs: [batch_size, n_agents, 1] # 每个智能体的局部 Q 值直接求和得到联合 Q 值 joint_q torch.sum(agent_qs, dim1) # 加性分解无额外参数 # 后续用 joint_q 计算 TD 损失 td_loss F.mse_loss(joint_q, target_q)这段代码的逻辑是VDN 不引入任何额外的混合网络参数联合 Q 值就是局部 Q 值的线性加和。参数说明上agent_qs的维度必须是[batch, n_agents, 1]如果维度不对torch.sum会沿着错误的方向聚合导致训练完全失效。这是新手最容易犯的错误之一。2.2 QMIX 的单调混合网络与超参数设置QMIX 在 VDN 基础上引入了一个混合网络Mixing Network通过单调性约束让联合 Q 值对每个局部 Q 值的偏导数非负。代码实现上混合网络的权重通过一个超网络Hypernetwork生成并对权重取绝对值来保证单调性。class QMixer(nn.Module): def __init__(self, n_agents, state_dim, embed_dim32): super().__init__() # 超网络从全局状态生成混合网络的第一层权重 self.hyper_w1 nn.Linear(state_dim, n_agents * embed_dim) # 第二层权重生成输出维度为 embed_dim self.hyper_w2 nn.Linear(state_dim, embed_dim) # 偏置项 self.hyper_b1 nn.Linear(state_dim, embed_dim) self.hyper_b2 nn.Sequential( nn.Linear(state_dim, embed_dim), nn.ReLU(), nn.Linear(embed_dim, 1) ) self.embed_dim embed_dim self.n_agents n_agents def forward(self, agent_qs, states): bs agent_qs.size(0) # 生成第一层权重并取绝对值保证单调性 w1 torch.abs(self.hyper_w1(states)) # [bs, n_agents * embed_dim] w1 w1.view(bs, self.n_agents, self.embed_dim) b1 self.hyper_b1(states).view(bs, 1, self.embed_dim) # 第一层混合局部 Q 值加权求和后过 ELU hidden F.elu(torch.bmm(agent_qs.view(bs, 1, self.n_agents), w1) b1) # 第二层权重同样取绝对值 w2 torch.abs(self.hyper_w2(states)).view(bs, self.embed_dim, 1) b2 self.hyper_b2(states).view(bs, 1, 1) # 输出联合 Q 值 joint_q torch.bmm(hidden, w2) b2 return joint_q.view(bs, 1)这段代码的关键在于torch.abs的使用位置权重取绝对值保证单调性但偏置项不取绝对值。embed_dim控制混合网络的容量常用 32设得太大会导致过拟合太小则表达能力不足。state_dim是全局状态的维度在 SMAC 中通常等于所有智能体观测拼接后的长度。训练时 QMIX 的lr通常比 VDN 略小建议从 5e-4 开始调。2.3 从 VDN 迁移到 QMIX 时需要改哪些配置如果你已经跑通了 VDN想切换到 QMIX需要改动的配置项包括在config中把mixer_type从vdn改成qmix增加mixer_embed_dim参数默认 32并把agent_qs的聚合方式从直接求和替换为调用QMixer的forward。经验回放缓冲区需要额外存储全局状态state因为混合网络的权重生成依赖它。在 SMAC 中全局状态通常是所有智能体观测的拼接但要注意有些地图的state维度会随智能体数量变化需要在reset时动态获取。提示从 VDN 切到 QMIX 后如果训练曲线出现剧烈震荡先检查state是否正确传入混合网络再检查torch.abs是否只作用在权重上。3. QTRAN 与 QPLEX突破单调性约束的两条路线3.1 QTRAN 的松弛约束与实现代价QTRAN 试图解决 QMIX 单调性约束过强的问题。它的核心思想是不要求联合 Q 值对局部 Q 值单调而是通过一个松弛的线性约束来保证最优策略的一致性。代码实现上QTRAN 需要额外的网络分支来计算一个修正项训练时同时优化 TD 损失和约束损失。# QTRAN 的约束损失计算简化版 # joint_q: 混合网络输出的联合 Q 值 # agent_qs: 各智能体局部 Q 值 # state: 全局状态 # 计算最优局部动作对应的联合 Q 值 q_tot_optimal mixer(agent_qs_optimal, state) # 计算当前联合 Q 值与局部 Q 值之和的差 constraint_loss F.mse_loss( q_tot_optimal, agent_qs_optimal.sum(dim1) # 线性约束项 ) # 总损失 TD 损失 约束损失 total_loss td_loss lambda_constraint * constraint_losslambda_constraint是 QTRAN 最关键的参数通常设为 1.0 或 0.5。设得太小约束不起作用设得太大则 TD 损失无法收敛。QTRAN 的训练稳定性明显差于 QMIX在 SMAC 的复杂地图上经常需要多次随机种子才能跑出好结果。我一般只在需要对比「单调性约束是否必要」时才用 QTRAN日常实验还是以 QMIX 和 QPLEX 为主。3.2 QPLEX 的 dueling 结构与优势函数分解QPLEX 把 dueling 网络的思想引入多智能体值分解把联合 Q 值分解为状态价值项和优势项并通过一个变换网络把局部优势映射到联合优势。代码实现上QPLEX 包含三个核心模块局部 Q 网络、dueling 混合网络、以及变换矩阵生成网络。class QPLEXMixer(nn.Module): def __init__(self, n_agents, state_dim, embed_dim32): super().__init__() # 状态价值项只依赖全局状态 self.V nn.Sequential( nn.Linear(state_dim, embed_dim), nn.ReLU(), nn.Linear(embed_dim, 1) ) # 优势项变换网络生成变换矩阵 self.hyper_w nn.Linear(state_dim, n_agents * n_agents) # 局部优势的权重 self.hyper_adv nn.Linear(state_dim, n_agents) self.n_agents n_agents def forward(self, agent_qs, states): bs agent_qs.size(0) # 计算状态价值 v self.V(states) # [bs, 1] # 计算局部优势局部 Q 值减去局部 Q 值的均值 adv agent_qs - agent_qs.mean(dim1, keepdimTrue) # 生成变换矩阵并取绝对值 w torch.abs(self.hyper_w(states)).view(bs, self.n_agents, self.n_agents) # 生成优势权重 adv_weight self.hyper_adv(states).view(bs, 1, self.n_agents) # 变换后的联合优势 joint_adv torch.bmm(adv_weight, torch.bmm(adv, w)) # 联合 Q 值 状态价值 联合优势 joint_q v joint_adv.view(bs, 1) return joint_qQPLEX 的embed_dim通常设为 32 或 64lr建议从 5e-4 开始。相比 QMIXQPLEX 在智能体数量较多如 5 个以上的地图上优势更明显因为 dueling 结构能更好地处理不同智能体对联合 Q 值贡献不均衡的情况。3.3 四种算法的参数共享与网络结构对比算法混合网络结构单调性约束额外参数典型 lr适用场景VDN无直接求和隐式满足无1e-3小规模、快速基线QMIX超网络 绝对值权重显式强制中等5e-4中等规模、稳定训练QTRAN混合网络 约束损失松弛约束较多5e-4对比实验、研究单调性QPLEXdueling 变换矩阵无强制约束较多5e-4大规模、非均衡贡献参数共享方面四种算法通常都采用「所有智能体共享局部 Q 网络参数」的策略这在同构智能体场景下能大幅减少参数量。但如果智能体类型不同如不同兵种需要关闭参数共享改为每个智能体独立网络。4. 训练流程与模型文件管理从配置到保存4.1 配置文件的关键字段与修改方法源码包中通常有一个config字典或 YAML 文件控制算法选择、环境参数、训练超参数。关键字段包括algorithmvdn/qmix/qtran/qplex、n_agents、state_dim、obs_dim、hidden_dim、lr、batch_size、buffer_size、target_update_interval、epsilon_start、epsilon_end、epsilon_decay。# 典型的多智能体训练配置 algorithm: qmix n_agents: 3 obs_dim: 48 state_dim: 144 hidden_dim: 64 lr: 0.0005 batch_size: 32 buffer_size: 5000 target_update_interval: 200 epsilon_start: 1.0 epsilon_end: 0.05 epsilon_decay: 50000 mixer_embed_dim: 32修改配置时要注意state_dim必须等于n_agents * obs_dim在 SMAC 中通常成立否则混合网络输入维度会出错。buffer_size设得太小会导致样本相关性过高训练不稳定设得太大则内存占用高建议从 5000 开始。4.2 模型保存与加载的目录结构模型文件通常按「算法名/地图名/随机种子」的层级保存。每个检查点包含三个文件agent_net.pth局部 Q 网络参数、mixer_net.pth混合网络参数、optimizer.pth优化器状态。加载时先实例化网络结构再调用load_state_dict。# 保存检查点 torch.save({ agent_net: agent_net.state_dict(), mixer_net: mixer_net.state_dict(), optimizer: optimizer.state_dict(), train_step: train_step, epsilon: epsilon }, fcheckpoints/{algorithm}/{map_name}/seed_{seed}/step_{train_step}.pth) # 加载检查点 checkpoint torch.load(path, map_locationcpu) agent_net.load_state_dict(checkpoint[agent_net]) mixer_net.load_state_dict(checkpoint[mixer_net]) optimizer.load_state_dict(checkpoint[optimizer])保存频率建议每 10000 步一次同时保留最近 5 个检查点避免磁盘占满。加载时注意map_location参数如果在 CPU 上加载 GPU 训练的模型必须指定map_locationcpu。4.3 训练日志与评估指标训练日志至少记录train_step、td_loss、mean_episode_reward、win_rate、epsilon。评估时每 5000 步跑 10 个测试回合计算平均胜率和平均回报。如果td_loss持续上升而win_rate不涨通常是目标网络更新太慢或学习率太大。5. 避坑与排查多智能体训练中的五个血泪教训5.1 现象训练初期 loss 直接变成 NaN原因学习率过大或混合网络中出现了除零操作。QMIX 的绝对值权重在初始化时如果全为零梯度会爆炸。解决把lr降到 1e-4 试一次同时在混合网络初始化时给权重加一个小的偏置如 0.01避免全零初始化。5.2 现象胜率在 50% 附近震荡始终不涨原因epsilon衰减太快智能体过早陷入局部最优或者经验回放缓冲区太小样本相关性太高。解决把epsilon_decay从 50000 调到 200000buffer_size从 5000 调到 20000。如果还不行检查奖励函数是否过于稀疏考虑加入中间奖励。5.3 现象切换算法后模型加载报维度不匹配原因不同算法的混合网络输入维度不同。VDN 不需要stateQMIX 和 QPLEX 需要stateQTRAN 还需要额外的约束项。解决加载前先打印state_dict的 key 和 shape确认网络结构一致。如果从 VDN 切到 QMIX必须重新初始化混合网络不能复用 VDN 的检查点。5.4 现象GPU 显存溢出batch_size 调小后训练变慢原因混合网络中的torch.bmm操作在n_agents较大时显存占用急剧上升。解决把embed_dim从 64 降到 32或者使用梯度累积每 4 个 batch 更新一次参数。如果还不行把agent_qs和states转为float16进行混合网络计算。5.5 现象评估时胜率远低于训练时的胜率原因训练时使用了epsilon-greedy探索评估时如果没关掉探索动作会随机或者评估环境的地图种子和训练时不同。解决评估时设epsilon0并固定地图种子。另外检查agent_net是否处于eval()模式BatchNorm 和 Dropout 在训练和评估时的行为不同。6. 进阶技巧用参数共享与课程学习加速 QPLEX 收敛如果你已经把四种算法都跑通了下一步可以尝试两个进阶方向。第一个是参数共享的粒度控制默认所有智能体共享一套局部 Q 网络参数但在异构场景下可以按智能体类型分组共享。比如 SMAC 中如果同时有地面单位和空中单位可以建两个参数组每组内部共享。实现上只需把agent_net改成nn.ModuleList每个元素对应一个参数组。第二个是课程学习先在简单地图如 2s3z上训练到收敛再把学到的参数迁移到复杂地图如 5m_vs_6m作为初始化。迁移时只加载agent_net的参数混合网络重新初始化因为不同地图的state_dim和n_agents可能不同。我一般会把lr在迁移后调小一半让模型在复杂地图上微调而不是从头学。# 课程学习从简单地图迁移到复杂地图 # 加载简单地图的局部网络参数 simple_checkpoint torch.load(checkpoints/qplex/2s3z/seed_1/step_200000.pth) agent_net.load_state_dict(simple_checkpoint[agent_net]) # 混合网络重新初始化因为 state_dim 可能变化 mixer_net QPLEXMixer(n_agentsnew_n_agents, state_dimnew_state_dim) # 学习率减半微调 optimizer torch.optim.Adam( list(agent_net.parameters()) list(mixer_net.parameters()), lr2.5e-4 )验证迁移是否有效的方法对比「从头训练」和「迁移训练」在相同步数下的胜率曲线。如果迁移训练在 50k 步内就超过了从头训练 100k 步的胜率说明迁移有效。如果迁移后反而更差可能是简单地图和复杂地图的观测空间差异太大需要先做观测归一化。我自己的习惯是每次跑新地图前先花 10 分钟检查state_dim、obs_dim、n_agents三个维度是否匹配再确认epsilon_decay和buffer_size是否按地图复杂度调整过。这个习惯帮我省下了大量重跑实验的时间。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站