多智能体强化学习价值分解算法:VDN、QMIX、QTRAN与QPLEX实现解析

发布时间:2026/11/2 10:26:05
多智能体强化学习价值分解算法:VDN、QMIX、QTRAN与QPLEX实现解析 简介多智能体强化学习是当前人工智能研究的热点方向初学者常被 VDN、QMIX、QTRAN、QPLEX 等经典算法的实现细节困扰。这份源码包完整覆盖上述四种算法并配有对应训练好的模型文件专门面向毕业设计、期末大作业与课程设计等场景也适合正在入门多智能体强化学习的研究者对照学习。代码注释详实部署门槛低下载后简单配置即可运行。资源共 131 个文件压缩包约 9.05MB。其中 36 个 py 文件承载四种算法的核心实现29 个 npy 与 25 个 pkl 文件保存训练过程中的模型参数与中间结果18 个 png 图片直观呈现奖励变化与收敛曲线另有 TensorBoard 事件文件与 PDF 文档补充实验过程记录和配置说明。目录结构清晰按算法模块组织便于逐一研读。已有 482 人次学习下载系作者手打 98 分项目并获导师认可既可作为高分项目的重要参考也能帮助新手快速建立多智能体强化学习的完整实践认知。1. 价值分解不是黑匣子VDN、QMIX、QTRAN、QPLEX到底在解决什么单智能体强化学习调好了换到多智能体场景就全线崩盘这是很多刚接触多智能体强化学习的人的第一反应。你给每个agent独立跑DQN环境里有两个智能体时训练曲线的方差能大到让你怀疑人生三个以上基本等于随机游走。原因在于智能体之间互相影响单个agent的奖励和状态转移都依赖队友的动作独立学习器把环境当成了非平稳问题经验回放里存的数据很快全部过期。多智能体强化学习的经典解法之一就是VDN、QMIX、QTRAN、QPLEX这一条价值分解路线用联合动作价值函数指导训练再把联合价值分解回每个智能体自己的Q值用于决策。这套算法在合作式任务里效果稳定代码结构清晰是入门多智能体强化学习最值得先吃透的一批源码。这篇文章直接基于python实现把这四个算法放在同一套代码框架下对照着写附对应模型文件的保存、加载与复现验证。2. 跑通四个算法前先定环境PettingZoo与SMAC的选型与目录结构2.1 为什么这四个算法要放一起理解从加和分解到单调约束再到通用分解这四个算法的关系是一条演进线。VDN最早思路最简单联合动作价值Q_tot等于所有智能体个体Q值之和也就是Q_tot ΣQ_i。这种加法分解天然满足个体价值与联合价值的一致性但表达力有限无法刻画智能体之间的非线性协同。QMIX在VDN基础上引入单调性约束用 mixer网络把个体Q值组合成联合Q值要求∂Q_tot/∂Q_i ≥ 0既保留了VDN的可分解性又让联合价值能表达更复杂的非线性关系。问题在于单调性本身仍然是限制现实中很多任务的协同是“非单调”的——一个智能体的动作好坏取决于队友的动作QMIX表达不了这种情境。QTRAN试图打破单调性提出用联合动作价值Q_joint和个体Q值之间的差值约束来逼近任意分解函数。QPLEX走的是另一条路把dueling结构引入价值分解用价值函数部分保持单调约束、优势函数部分做线性分解理论上能表示任意Q_tot同时训练稳定性比QTRAN好得多。理解这条线很重要因为实际复现的时候你会发现VDN和QMIX代码几乎可以共用一套框架QTRAN和QPLEX则需要动更多核心结构。把四个算法放在同一个项目里对比实现比单独跑通某个算法更能看清多智能体强化学习的难点在哪。2.2 用PettingZoo在本地跑通VDN到QPLEX的最小配置环境选择直接影响你复现的难度。学术圈最常用的是SMACStarCraft Multi-Agent Challenge它基于星际争霸2的战术场景观测和状态空间设计得很规范QMIX系列论文的官方结果大多出自这套环境。但SMAC的缺点是依赖StarCraft II客户端安装时间久环境配置路径容易出问题。我一般建议先跑PettingZoo仓库里的MPEMulti-Agent Particle Environment环境它纯python实现pip装完就能跑适合验证算法逻辑是否正确。等你把四个算法的训练流程都调通再切到SMAC看数值结果。PettingZoo目前推荐的是v1.x版本安装命令如下pip install pettingzoo pip install torch pip install numpy注意PettingZoo的API在0.x到1.x之间有一次较大的接口调整。如果你搜到网上老教程里用的是from pettingzoo.mpe import simple_spread_v2这种写法说明教程对应的可能是0.x版本。1.x版本下的导入方式是from pettingzoo.mpe import simple_spread_v3_env只返回环境创建函数需要再调用env()得到环境实例。from pettingzoo.mpe import simple_spread_v3_env env simple_spread_v3_env.env( N3, local_ratio0.5, max_cycles25, continuous_actionsFalse, )参数说明N3表示3个智能体local_ratio0.5控制奖励中本地奖励和全局奖励的占比continuous_actionsFalse使用离散动作空间便于和DQN系列算法对接。MPE的simple_spread任务是让3个智能体去覆盖3个地标每个智能体只能看到自己的位置和地标位置属于典型的合作任务。这个配置下四个算法的训练曲线区分度很高VDN和QMIX能收敛QTRAN容易出现波动适合拿来比较。项目目录建议这样组织marl_vdn_qmix/ ├── agents/ # 每个agent的Q网络 ├── learners/ # VDN/QMIX/QTRAN/QPLEX各算法 ├── envs/ # 环境封装 ├── models/ # 训练好的模型文件 └── train.py # 统一训练入口models目录专门放模型文件每个算法单独建子目录训练时按算法名和时间戳保存。这样后面做对比实验、加载权重做决策验证时路径清晰不至于在乱七八糟的文件名里找半天。2.3 个体Q网络四个算法共用的智能体底座VDN、QMIX、QTRAN、QPLEX有个共同前提每个智能体有自己的动作价值函数Q_i(τ_i, a_i)其中τ_i是个体观测历史。由于环境部分可观测单步观测不够一般采用RNN对历史观测建模。以下是四个算法共用的个体Q网络import torch import torch.nn as nn class AgentQNetwork(nn.Module): def __init__(self, obs_dim, n_actions, hidden_dim64, use_rnnTrue): super().__init__() self.use_rnn use_rnn self.fc1 nn.Linear(obs_dim, hidden_dim) self.rnn nn.GRUCell(hidden_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, n_actions) def forward(self, obs, hidden_state): x torch.relu(self.fc1(obs)) h self.rnn(x, hidden_state) q self.fc2(h) return q, h逻辑说明fc1先把原始观测映射到隐藏维度中间接GRUCell建模时间维度上的观测历史最后输出每个动作的Q值。hidden_state需要由训练循环维护在episode结束时重置为零向量。如果用nn.GRU替代GRUCell输入维度要变成(seq_len, batch, feature)处理变长episode时反而更麻烦所以这里直接用GRUCell按时间步手动推进。参数说明obs_dim是单个智能体的观测维度n_actions是动作空间大小hidden_dim64在simple_spread这种小规模任务上足够。SMAC这类复杂场景建议提到128或256否则表示能力不够训练曲线会长期在低位徘徊。3. VDN与QMIX核心实现加和分解与单调约束的取舍3.1 VDN线性加和与它的局限VDN没有任何mixer网络联合Q值就是所有个体Q值直接相加。实现代码很简短class VDNCritic(nn.Module): def forward(self, q_individual): # q_individual: (batch, n_agents) return q_individual.sum(dim-1)逻辑说明训练时每个智能体用自己的Q网络输出Q_i加和得到Q_tot用Q_tot和目标值计算TD误差梯度通过加和操作回传到每个智能体的Q网络。执行时每个智能体只根据自己的Q_i做argmax选择动作。这套逻辑成立的充分条件是联合奖励等于个体奖励之和或者个体奖励的分配方式与合作目标一致。一旦任务里存在需要智能体协同才能获得的全局奖励而个体局部奖励又和全局奖励冲突VDN的分解方式就会失效。VDN的局限在实际运行中表现为训练曲线前期能正常上升后期卡在一个较低的水平不再变化。原因是加和分解无法表达“两个智能体同时做某件事才能获得额外奖励”这类非线性协同关系。这时候你需要的不是调学习率而是换QMIX。3.2 QMIXmonotonic mixer网络与hypernetwork权重生成QMIX的核心是mixer网络它接收所有个体Q值作为输入用全局状态state生成网络的权重。关键约束是mixer权重必须非负这样Q_tot对任意Q_i都是单调递增的。实现如下class QMixMixer(nn.Module): def __init__(self, n_agents, state_dim, hidden_dim32): super().__init__() self.n_agents n_agents self.hidden_dim hidden_dim self.hyper_w1 nn.Linear(state_dim, n_agents * hidden_dim) self.hyper_w2 nn.Linear(state_dim, hidden_dim) self.hyper_b1 nn.Linear(state_dim, hidden_dim) def forward(self, q_individual, states): # q_individual: (batch, n_agents) # states: (batch, state_dim) batch q_individual.shape[0] q_individual q_individual.reshape(batch, 1, self.n_agents) w1 torch.abs(self.hyper_w1(states)) w1 w1.reshape(batch, self.n_agents, self.hidden_dim) b1 torch.relu(self.hyper_b1(states)).reshape(batch, 1, self.hidden_dim) hidden torch.relu(torch.bmm(q_individual, w1) b1) w2 torch.abs(self.hyper_w2(states)).reshape(batch, self.hidden_dim, 1) q_tot torch.bmm(hidden, w2).reshape(batch, 1) return q_tot逻辑说明hyper_w1和hyper_w2接收全局状态生成第一层和第二层的权重矩阵。torch.abs保证权重非负这正是QMIX单调性约束的实现点。训练时Q_tot参与TD误差计算梯度同时流向mixer和个体Q网络。执行时每个智能体依然只依赖自己的局部观测做决策全局状态只参与训练这就是集中训练分布式执行CTDE的含义。参数说明hidden_dim32是mixer网络的隐藏层大小它控制联合价值函数的拟合能力。太大会让训练变慢且容易过拟合环境噪声太小则拟合不了复杂的协同关系。state_dim是全局状态维度在simple_spread里是所有智能体和地标位置的拼接。3.3 训练主循环经验回放、target网络与epsilon退火VDN和QMIX共用同一套训练循环区别只在mixer部分。以下是完整的训练循环骨架def train_step(batch, agents, mixer, target_mixer, optimizer, gamma0.99): obs, actions, rewards, next_obs, dones batch q_individual agents(obs) # (batch, n_agents, n_actions) q_actions q_individual.gather(2, actions) # 取出实际执行动作的Q值 q_tot mixer(q_actions) # 当前联合Q值 with torch.no_grad(): next_q_individual agents.target(next_obs) next_q_actions next_q_individual.max(dim-1)[0] next_q_tot target_mixer(next_q_actions) # target网络计算目标Q值 target rewards gamma * (1 - dones) * next_q_tot loss ((q_tot - target) ** 2).mean() optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(agents.parameters(), 10) optimizer.step()逻辑说明agents内部维护一份target网络每个train_step之前会间隔更新。gather(2, actions)按动作索引取出对应的Q值这是DQN系列的标准操作。目标值用target网络计算避免自举造成的不稳定。梯度裁剪放在backward之后防止RNN梯度爆炸。参数说明gamma0.99适用于中短episode的环境simple_spread的max_cycles25整个episode回报较小gamma取0.99比较合适。如果环境episode很长比如SMAC的某些场景能跑几百步建议gamma0.99不变但把经验回放容量加大到20000以上。epsilon退火曲线的设置直接影响训练成败我一般用如下策略epsilon max(0.05, 1.0 - episode / 50000)按episode数衰减而不是按step数衰减因为多智能体环境的单步奖励稀疏按step衰减会导致智能体在还没见过正奖励时就把探索率降没了。min_epsilon0.05保证后期仍有少量探索完全贪心会让训练曲线突然掉下去。4. QTRAN与QPLEX核心实现打破单调假设的两种路径4.1 QTRAN用联合动作空间约束替代单调性QTRAN不再要求单调而是要求联合Q值和个体Q值之和的差值与一个与动作无关的偏移量对齐。它同时训练三个网络个体Q网络、联合Q网络输入全局状态和所有智能体的动作、联合V网络输入全局状态。损失函数包含两部分一部分是常规TD误差另一部分是约束误差。class QTRANLearner: def __init__(self, agents, joint_q, joint_v, optimizer, gamma0.99): self.agents agents self.joint_q joint_q self.joint_v joint_v self.optimizer optimizer self.gamma gamma def loss(self, batch): obs, actions, rewards, next_obs, dones batch q_individual self.agents(obs) # 每个agent的Q值表 q_actions q_individual.gather(2, actions) # 实际动作的Q值 sum_q q_actions.sum(dim-1) # 个体Q值加和 q_joint self.joint_q(obs, actions) # 联合Q值函数 v_joint self.joint_v(obs) # 联合V值函数 # 约束1联合Q值与个体加和的差值要等于联合V值与个体V值加和的差值 # 约束2最优联合Q值要不低于任意动作组合的联合Q值 constraint_loss ((q_joint - sum_q) - (v_joint - self.agents.v_sum(obs))).pow(2).mean() td_loss ((q_joint - (rewards self.gamma * q_target))).pow(2).mean() return td_loss constraint_loss逻辑说明QTRAN的关键在constraint_loss。如果定义V_joint(s)为全局状态下所有agent最优价值的联合那么QTRAN要求在最优动作组合处Q_joint减去ΣQ_i等于V_joint减去ΣV_i成立。约束1强制这个差值对齐使得联合价值函数不依赖单调性也能被分解。实现上的难点是agents.v_sum(obs)需要每个agent网络额外输出一个状态价值V_i相当于网络头部同时产生Q值和V值两条输出分支。参数说明QTRAN比QMIX多了联合Q网络和联合V网络两个可训练模块学习率建议比QMIX低一个量级我通常用lr0.0005QMIX用lr0.001。这个算法对梯度裁剪的需求更强clip_grad_norm_的值从10降到5否则联合Q网络很容易训练爆炸。如果训练一段时间后发现loss曲线出现尖峰然后再也不降基本就是梯度爆炸后参数回不来了只能重开。4.2 QPLEXduplex dueling结构下的稳定分解QPLEX把dueling结构复制到价值分解上。联合Q值被拆成联合V值和联合优势函数A_totV部分用类似QMIX的单调结构拟合A部分用线性加和拟合。这样整个Q_tot的表示能力覆盖了单调和非单调两种情况同时避免了QTRAN约束项带来的训练不稳定。class QPLEXCritic(nn.Module): def __init__(self, n_agents, state_dim, hidden_dim32): super().__init__() self.v_mixer QMixMixer(n_agents, state_dim, hidden_dim) # 复用QMIX结构 self.adv_mixer nn.Linear(n_agents, 1, biasFalse) # 优势线性分解 def forward(self, q_individual, states): # q_individual: (batch, n_agents, n_actions) max_q, _ q_individual.max(dim-1) # V_i max_a Q_i # 每个agent的优势 A_i Q_i - V_i取实际动作为 A_i(a) q_actions q_individual.gather(2, actions) adv_individual q_actions - max_q.unsqueeze(-1) v_tot self.v_mixer(max_q, states) # 价值部分保持单调分解 adv_tot self.adv_mixer(adv_individual) # 优势部分线性加和 return v_tot adv_tot逻辑说明v_mixer复用QMIX的单调mixer结构拟合联合价值中“与动作选择无关”的部分。adv_mixer是简单的线性层把每个智能体的优势函数直接加权求和。dueling结构保证了即使优势部分不再满足单调性整体Q_tot依然能正确指导动作选择。QPLEX比QTRAN实现上更干净代码量也更少这是它能在QMIX之后成为主流选择的主要原因。参数说明adv_mixer用不带偏置的线性层因为优势函数本身的定义就是相对V值的偏离加偏置会导致分解不唯一影响训练稳定性。注意actions变量在这个函数里需要通过batch传入上面的代码片段为清晰起见省略了形参完整实现时需要把它作为forward的第三个参数。4.3 模型文件的保存与加载一个统一的做法四个算法训练结束后的产物是模型文件而这个环节最容易出问题。多智能体项目里每个算法可能包含多个网络模块QMIX有agent网络和mixer网络QTRAN有agent、joint_q、joint_v三个网络。如果只保存agent网络的state_dict加载时你会发现mixer权重丢了联合价值计算完全不对。我一般统一用下面这种方式import torch def save_model(path, algorithm, agents, mixerNone, joint_qNone, joint_vNone): payload { algorithm: algorithm, agents: agents.state_dict(), mixer: mixer.state_dict() if mixer else None, joint_q: joint_q.state_dict() if joint_q else None, joint_v: joint_v.state_dict() if joint_v else None, } torch.save(payload, path) def load_model(path, agents, mixerNone, joint_qNone, joint_vNone): payload torch.load(path, map_locationcpu) agents.load_state_dict(payload[agents]) if mixer and payload[mixer]: mixer.load_state_dict(payload[mixer]) if joint_q and payload[joint_q]: joint_q.load_state_dict(payload[joint_q]) if joint_v and payload[joint_v]: joint_v.load_state_dict(payload[joint_v]) return payload[algorithm]逻辑说明save_model把算法名、agent网络和所有辅助网络打包成一个字典一个模型文件对应一次完整实验。load_model在CPU上加载权重避免GPU环境下保存的模型换机器后因为device不匹配报错。map_locationcpu是强制加载到CPU再手动搬到GPU跨设备迁移时这一行能省掉很多奇怪的问题。参数说明训练过程中建议同时保存两种模型文件一种是每N个episode覆盖保存的checkpoint用于中断后续训另一种是训练结束后按当前参数保存的final模型。checkpoint还应该额外存optimizer的state_dict否则恢复训练时学习率调度器状态丢失训练曲线会跳变。5. 复现这四个算法最常翻车的5个点现象、原因与解决5.1 训练曲线完全不动loss一直是同一个数量级的常数现象跑了十万步训练loss几乎没有变化Q值输出也一直是同一组数智能体在环境里表现为原地绕圈。原因RNN的hidden state没有正确重置。多智能体环境里每个episode结束时有各自的done标志如果你的训练循环只在整个batch结束后才清零hidden state上一个episode的隐藏信息会污染下一个episode的前几步。对于GRU这类循环网络携带错误的历史状态会让梯度方向混乱loss维持在某个平台。解决每个step前把hidden state初始化为零向量在episode结束时显式detach并清零。代码里为每个环境实例单独维护hidden state不要用同一个tensor跨episode传递。5.2 QMIX的Q_tot输出全部变成负数训练震荡严重现象QMIX训练时loss一直在抖动Q_tot数值逐渐偏到负数区域最终整个训练崩溃。原因mixer网络的权重虽然用torch.abs保证了非负但偏置项b1用的是ReLU如果hyper_b1输出恰好落到0附近梯度会断流。另外个体Q网络输出如果整体偏负即使权重为正Q_tot也会持续走低。解决把偏置激活函数从ReLU换成ELU1保证偏置恒为正同时在个体Q网络的输出层后加一层LayerNorm稳定数值范围。这个改动不影响理论性质只影响数值稳定性实测对QMIX的训练收敛速度有明显改善。5.3 加载模型文件后报错size mismatch for agents.fc2.weight现象用训练好的模型文件加载到新初始化的网络时PyTorch直接抛size mismatch异常。原因模型文件保存时的观测维度或动作空间维度和你当前初始化的网络不一致。比如训练时用的是simple_spread_v3每个agent观测维度是18维加载时却用了一个新环境配置观测维度变成了22维state_dict自然对不上。解决在load_model函数里加一层维度校验对比payload[agents].keys()和你当前网络参数的shape不一致时直接抛出明确错误说明训练时用的维度和当前配置差在哪里。不要靠PyTorch默认的报错去猜那个提示信息对多智能体项目来说太粗了。5.4 epsilon衰减过快训练曲线走出一条陡峭的上升然后一路下降现象前期曲线上升很快看起来要收敛了但到某个点后开始掉头向下而且再也回不到前期的峰值。原因epsilon按step数线性衰减到0.05而多智能体环境的episode长度不固定。前期智能体还在探索阶段稀疏奖励让Q值更新很激进探索率降下来之后策略固化到早期学到的局部最优动作上无法跳出奖励反而下降。解决把epsilon衰减改为按episode数计算并且衰减到0.05之后保持住不要继续降到0。更稳妥的做法是分段衰减前20000个episode从1.0线性到0.2之后20000个episode从0.2到0.05最后固定。这样既保证前期充分探索又避免后期策略不稳。5.5 QTRAN训练爆炸loss值直接跳到NaN现象QTRAN的loss曲线在正常训练几千步后突然变成NaN所有参数开始向Inf方向发散。原因QTRAN的联合Q网络和联合V网络都在拟合全局状态下的价值这类网络的输出范围不受个体Q网络的约束容易在训练中产生极大值。加上QTRAN的约束损失包含两项的差值数值范围天然比QMIX大学习率稍微高一点就会爆炸。解决给所有损失项加梯度裁剪还不够我一般把QTRAN的联合网络单独用一个低学习率优化器比如lr0.0003个体Q网络用lr0.001。同时在联合Q网络的输出层后加torch.clamp限制最大绝对值到20防止数值失控。如果你在项目中同时实现了QTRAN和QPLEX遇到QTRAN训练爆炸时直接换QPLEX是更省时间的做法。6. 从零训练到加载模型文件做决策一条可复用的验证路径模型文件保存了好几个怎么确认它真的能复现训练时的表现我的做法分三步。第一步训练完成后重新初始化一套全新的网络加载模型文件关掉探索用贪心策略跑100个episode记录平均奖励。这个数字应该和训练日志里最后100个episode的平均奖励接近误差在10%以内说明模型文件完整、加载逻辑正确。差异过大说明你的训练过程里有随机因素没被控制住比如经验回放采样没有固定种子。第二步把四个算法的模型文件放在同一场景下比较。对simple_spread这个任务来说四个算法都能收敛但要比较的是收敛速度和最终奖励的稳定性。QMIX一般比VDN高10%到20%QPLEX在奖励方差上会比QTRAN小得多。如果你的结果不符合这个规律优先检查mixer的权重约束实现是否正确这是QMIX系列算法最容易写错的地方。第三步用加载的模型做可视化决策。把agent网络的Q值输出打印出来观察智能体在同一状态下是否稳定选择同一动作。如果Q值在两个接近的动作之间来回跳说明训练不充分或者epsilon降得太快模型的置信度不够。我自己的习惯是每次实验只跑5000步验证loss能正常下降再把训练放长跑过夜。否则直接长跑第二天起来看到NaN和训练曲线从头到尾都没变形的概率比你想象的高得多。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询