拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MADDPG多智能体博弈对抗:原理、Python实现与避坑指南

MADDPG多智能体博弈对抗:原理、Python实现与避坑指南 简介面向多智能体博弈对抗研究该资源提供基于MADDPG算法的Python完整实现适用于计算机、人工智能、通信工程、自动化等专业的毕业设计、课程设计与期末大作业。项目代码包含算法核心模块、神经网络构建、经验回放缓冲区、训练主程序与测试脚本同时集成多智能体环境库覆盖捕食者-猎物、对战等典型博弈场景并配有运行演示动画便于直观理解集中训练与分布式执行机制。压缩包共91个文件以76个Python源码为主另含GIF演示、Jupyter示例、说明文档与环境配置等整体仅3.26MB轻量易用。目前已有1259人学习下载。代码经运行测试功能可靠注释详尽清晰可支撑算法复现、实验对比与二次扩展适合需要快速上手多智能体强化学习的中级学习者和项目开发者。1. 为什么用 MADDPG 做多智能体博弈对抗从独立 DDPG 的崩溃说起做多智能体对抗的人大概率都经历过同一个翻车现场给每个智能体单独套一个 DDPG训练到一半奖励曲线集体崩盘策略直接退化。原因不难想——环境里其他人的动作在变你的 Critic 却只盯着自己那一亩三分地对“对手变招”毫无感知梯度自然越算越偏。MADDPG 解决的就是这个问题用“集中训练、分散执行”的思路让 Critic 在训练时看到全局信息Actor 推理时只用自己的观测。这个设计让多智能体博弈对抗从“各玩各的”变成“配合与针对并存的博弈”也是多智能体强化学习里少数能直接落地的算法之一。这篇笔记面向两类人想用 Python 复现论文效果的算法学习者以及要把对抗博弈跑进仿真环境的一线工程师。前者能拿到可运行的代码骨架后者能找到参数边界和排错方向。2. MADDPG 核心机制与代码骨架集中训练、分散执行到底改了什么2.1 从 DDPG 到 MADDPGCritic 看见全局Actor 只看自己理解 MADDPG 的代码先得把 DDPG 的痛处说透。DDPG 里每个智能体有自己的 Actor 和 CriticActor 根据自身观测选动作Critic 根据自身观测加自身动作估 Q 值。问题在于当多个智能体同时与环境交互环境转移概率受所有智能体联合动作影响单智能体的 Q 函数根本不稳。你这边觉得“向左走收益高”实际上是因为对手恰好往右让了位等对手策略一变这个 Q 值立刻失真。MADDPG 的改动只有两处但这两处决定了整个算法性质。第一处Critic 的输入变成全局信息包括所有智能体的观测拼接obs_concat和所有智能体的动作拼接act_concat。每个智能体仍然只有自己的 Critic但这个 Critic 是“带全局视野的评估器”。第二处Actor 的输入保持局部观测输出也只用局部观测推理阶段不需要和其他智能体通信这就是“分散执行”的含义。“集中训练”意味着训练时 Critic 能看到别人Actor 专精自己的策略“分散执行”意味着部署时每个智能体只带自己的 Actor 和局部观测通信开销为零。这个特性让 MADDPG 特别适合做博弈对抗里的“智能体协同”与“对手建模”场景。比如三维追击问题里追击者之间通过 Critic 共享信息学会包抄而每个追击者实际决策时只需要自己的传感器数据。值得注意的一个细节是每个智能体有没有自己的 Critic 是可配置的。常见做法有两种一种是每个智能体一套独立的 Critic 网络另一种是所有智能体共享一个 Critic只在输入里区分 agent id。前者更稳适合异构智能体后者省参数适合同构群体。项目源码里如果要追求泛化能力建议按前者实现后面避坑章会提到共享 Critic 的隐患。2.2 项目源码目录结构与关键类划分一个标准 MADDPG 项目的文件组织通常围绕“环境、算法、训练、评估”四件事展开。我给你一个可参考的结构和源码包里的文件对应关系如下。maddpg/ ├── envs/ # 环境定义 │ ├── multi_agent_env.py │ └── simple_tag.py # 经典“追捕”对抗环境 ├── agents/ │ ├── actor.py # Actor 网络定义 │ ├── critic.py # Critic 网络定义 │ ├── maddpg_agent.py # 单个智能体的学习逻辑 │ └── replay_buffer.py # 经验回放 ├── train.py # 训练入口 ├── evaluate.py # 评估入口 └── config.py # 集中管理超参数maddpg_agent.py是最重要的文件它把上面说的机制翻译成代码。一个典型智能体类长这样class MADDPGAgent: def __init__(self, obs_dim, act_dim, num_agents, hidden_dim128, lr_actor1e-4, lr_critic1e-3): self.obs_dim obs_dim self.act_dim act_dim self.num_agents num_agents # Actor输入自身观测输出自身动作 self.actor Actor(obs_dim, act_dim, hidden_dim) self.actor_target Actor(obs_dim, act_dim, hidden_dim) self.actor_target.load_state_dict(self.actor.state_dict()) # Critic输入全体观测 全体动作输出本智能体的 Q 值 critic_in_dim num_agents * obs_dim num_agents * act_dim self.critic Critic(critic_in_dim, 1, hidden_dim) self.critic_target Critic(critic_in_dim, 1, hidden_dim) self.critic_target.load_state_dict(self.critic.state_dict()) self.actor_optimizer torch.optim.Adam(self.actor.parameters(), lrlr_actor) self.critic_optimizer torch.optim.Adam(self.critic.parameters(), lrlr_critic) def act(self, obs, noise_scale0.1): 推理时只用自己的观测输出动作加探索噪声 obs torch.FloatTensor(obs).unsqueeze(0) action self.actor(obs).squeeze(0).detach().numpy() if noise_scale 0: action np.random.normal(0, noise_scale, sizeself.act_dim) return np.clip(action, -1, 1)这里有一个容易忽略的设计点critic_in_dim用的是全量观测和全量动作的拼接而不是自己那部分。这个维度计算一旦写错训练初期就会报维度不匹配而且是所有智能体一起报。另一个细节是 Actor 的最终输出层必须用 Tanh 激活把动作约束在 [-1, 1]对应环境里的动作边界。如果环境动作范围不是 [-1, 1]需要在环境加载时做映射而不是改网络激活函数。常见错误是动作范围设成 [0, 1] 结果 Tanh 输出负值智能体直接原地踏步。经验回放缓冲区在 MADDPG 里也有特殊要求。存储的每一条经验必须是全体智能体的[obs_all, act_all, reward_each, obs_next_all, done]而不是单个智能体的独立经验。原因很直接Critic 训练时需要全局转移数据各存各的就没法拼全局。源码里通常每个智能体共享同一个回放缓冲区代码上就是传同一个 buffer 实例给所有 agent。class ReplayBuffer: def __init__(self, capacity1000000): self.capacity capacity self.buffer [] self.position 0 def push(self, obs_all, act_all, reward_each, obs_next_all, done): 一次性存入全体智能体的经验 if len(self.buffer) self.capacity: self.buffer.append(None) self.buffer[self.position] (obs_all, act_all, reward_each, obs_next_all, done) self.position (self.position 1) % self.capacity def sample(self, batch_size): batch random.sample(self.buffer, batch_size) obs_all torch.FloatTensor([item[0] for item in batch]) act_all torch.FloatTensor([item[1] for item in batch]) reward_each torch.FloatTensor([item[2] for item in batch]) obs_next_all torch.FloatTensor([item[3] for item in batch]) done torch.FloatTensor([item[4] for item in batch]) return obs_all, act_all, reward_each, obs_next_all, done这里的细节random.sample是均匀采样不是按时间序列优先采样。多智能体强化学习里目前大多数项目沿用均匀采样因为 TD 误差优先级采样PER在多智能体场景里还没有形成通用标准。如果你追求稳定复现用均匀采样就够了。缓冲区容量建议 1e5 到 1e6 之间容量太小的后果是经验多样性不足集中训练的 Critic 会产生偏差后面训练曲线会表现出“过拟合到近期经验”的特征。2.3 最小训练循环一次 update 里发生了什么训练主循环里每一轮 update 分为三步采样一个小批量、更新 Critic、更新 Actor。MADDPG 和 DDPG 的更新顺序一致但细节有差异。def update(self, replay_buffer, batch_size, gamma0.95): obs_all, act_all, reward_each, obs_next_all, done replay_buffer.sample(batch_size) # 计算 target Q 值用目标网络算下一个状态的全局 Q with torch.no_grad(): act_next_all [] for agent in self.agents: obs_next obs_next_all[:, agent.obs_index_start:agent.obs_index_end] act_next_all.append(agent.actor_target(obs_next)) act_next_all torch.cat(act_next_all, dim1) target_q self.critic_target(obs_next_all, act_next_all) target_q reward_each gamma * (1 - done) * target_q.squeeze(-1) # 更新 Critic让当前 Q 逼近 target Q current_q self.critic(obs_all, act_all).squeeze(-1) critic_loss F.mse_loss(current_q, target_q) self.critic_optimizer.zero_grad() critic_loss.backward() torch.nn.utils.clip_grad_norm_(self.critic.parameters(), max_norm0.5) self.critic_optimizer.step() # 更新 Actor最大化当前 Q 值 act_curr_all [] for i, agent in enumerate(self.agents): obs_i obs_all[:, agent.obs_index_start:agent.obs_index_end] act_curr_all.append(agent.actor(obs_i)) act_curr_all torch.cat(act_curr_all, dim1) actor_loss -self.critic(obs_all, act_curr_all).mean() for i, agent in enumerate(self.agents): actor_loss -self.critic(obs_all, act_curr_all).mean() agent.actor_optimizer.zero_grad() actor_loss.backward(retain_graphTrue) torch.nn.utils.clip_grad_norm_(agent.actor.parameters(), max_norm0.5) agent.actor_optimizer.step() # 软更新目标网络 with torch.no_grad(): for agent in self.agents: for param, target_param in zip(agent.actor.parameters(), agent.actor_target.parameters()): target_param.data.mul_(0.995).add_(param.data, alpha0.005) for param, target_param in zip(agent.critic.parameters(), agent.critic_target.parameters()): target_param.data.mul_(0.995).add_(param.data, alpha0.005)Actor 更新的细节值得多说几句。actor_loss -critic(obs_all, act_curr_all).mean()把所有智能体的动作都送进 Critic但反向传播时只更新当前智能体的 Actor 参数其他智能体的梯度被retain_graphTrue保留以便下一轮继续用。这样的写法在代码实现上是简单直接的但注意实际更新时每个智能体的 actor_loss 都要重新算一次critic(obs_all, act_curr_all)这会产生 N 次前向计算。工程上更高效的做法是算一次 Critic 输出、分别反传 N 次但为了保留图结构每轮清空梯度再算。gamma参数在多智能体对抗任务里建议设 0.9 到 0.98不要和单智能体任务一样拉到 0.99。原因是博弈环境的回合通常较短追击/逃脱场景单回合 25 步到 50 步折扣因子太高会让 Q 值收敛变慢训练步数需求暴涨。代码里(1 - done)的写法是防止回合终止后 Q 值继续向后传播污染下个回合。软更新的tau参数默认 0.01 或 0.005 都可以。如果训练震荡明显优先把 tau 降一档如果收敛太慢把 tau 升到 0.02 试试。不要动 Critic 的学习率来治震荡那是另一码事。3. 用 Python 从零跑通 MADDPG环境、配置与训练闭环3.1 环境搭建Python 版本与依赖选择MADDPG 的项目源码基本都是 Python 3.6 时代的产物但你现在新装环境别用 3.6。我的建议是Python 3.8 或 3.9PyTorch 用 1.10 到 2.x 都行但注意 2.x 下老代码里torch.autograd.Variable这类写法已经移除需要小改。最稳妥的环境组合如下Python 3.8兼容性最好OpenAI 的 gym 老接口不会报错PyTorch 1.13 或 2.0两者 API 基本一致2.0 编译更快gym 0.21 或 0.26老环境代码用 0.21用 0.26 需要改 reset 接口numpy 1.21 或 1.24装 gym 是这个项目里最容易踩的平台坑。老代码里env.reset()返回一个数组gym 0.26 之后返回(obs, info)元组很多训练脚本没适配就直接崩。我的建议是改用 0.21然后把环境代码里reset的逻辑统一改成新接口一劳永逸。这类环境适配问题在跑开源项目时非常常见项目源码里的环境文件通常依赖特定版本不要直接pip install gym最新版就开跑。3.2 核心配置参数表与含义MADDPG 项目的 config 文件通常集中管理所有超参数。下面表里是几组关键参数的经验值照着设一般不会出大问题参数建议范围作用与调参逻辑batch_size1024多智能体场景建议往大取小批量会导致 Critic 方差大replay_buffer_size1e5 - 1e6对抗任务回合短1e5 够用复杂环境再扩大actor_lr1e-4 - 3e-4比 Critic 低一个数量级防止策略更新过快critic_lr1e-3 - 3e-3Critic 需要更快拟合全局 Q 函数gamma0.9 - 0.98回合短场景用低折扣回合长用高折扣tau0.005 - 0.01软更新系数震荡调小过慢调大noise_scale0.1 - 0.3训练初期探索噪声后期按线性或指数衰减episode_max_steps25 - 50经典对抗环境的回合长度过长会拖慢训练target_update_interval1每步都做软更新不用硬更新noise_scale的衰减策略值得单独说。常见做法是每 5000 步把噪声乘 0.9510000 步后停止衰减。项目源码里一般会有一个noise_scale max(0.01, noise_scale * 0.95)的调度语句。噪声减太快会导致策略收敛到局部最优减太慢会让训练后期策略不稳定。调这个参数比调整学习率更容易立竿见影。3.3 命令行训练最小闭环跑通训练的最小命令我按最常见的训练入口来写。项目源码入口可能是train.py配置可能写死在文件里也可能用命令行参数覆盖。建议用 argparse 方式组织python train.py --env simple_tag --num_episodes 50000 --seed 42 --batch_size 1024对应的训练脚本骨架# train.py import argparse from maddpg import MADDPG from envs.simple_tag import SimpleTagEnv def main(): parser argparse.ArgumentParser() parser.add_argument(--env, typestr, defaultsimple_tag) parser.add_argument(--num_episodes, typeint, default50000) parser.add_argument(--seed, typeint, default42) parser.add_argument(--batch_size, typeint, default1024) args parser.parse_args() env SimpleTagEnv() agents MADDPG(num_agentsenv.num_agents, obs_dimenv.obs_dim, act_dimenv.act_dim) replay_buffer ReplayBuffer(capacity1000000) for episode in range(args.num_episodes): obs_all env.reset() episode_rewards np.zeros(env.num_agents) for step in range(env.max_steps): actions [agent.act(obs, noise_scalemax(0.01, 0.3 * (0.95 ** (episode // 100)))) for agent, obs in zip(agents, obs_all)] obs_next_all, reward_each, done env.step(actions) replay_buffer.push(obs_all, actions, reward_each, obs_next_all, done) if len(replay_buffer) args.batch_size: agents.update(replay_buffer, args.batch_size) obs_all obs_next_all episode_rewards reward_each if done: break if episode % 500 0: print(fEpisode {episode}, avg reward: {episode_rewards}) if __name__ __main__: main()这段代码里的关键逻辑每个 episode 开始先reset然后按最大步数循环。每个智能体的动作都带噪声噪声幅度随 episode 数指数衰减。整局结束后打印奖励用来观察训练趋势。注意replay_buffer.push的位置——必须在env.step(actions)之后、obs_all obs_next_all之前调用这样存的是“当前状态 动作 奖励 下一状态”如果调整顺序会存成错位数据训练直接没有意义。这种时序问题在代码里不报错但训练曲线会表现出“完全学不动”的特征排查起来很费劲。print输出的奖励是每个智能体的向量。追击者奖励通常是负数追到才有正收益逃脱者奖励是正数。这里不要拿单智能体平均奖励去判断训练好坏要看相对差距是否在缩小。4. MADDPG 博弈对抗的 4 个避坑点训练崩塌、奖励稀薄与复现偏差4.1 Critic 的 Loss 后期发散现象、原因与解决现象训练前 5000 个 episode 一切正常奖励曲线缓慢上升到 10000 个 episode 左右 Critic Loss 突然开始飙升从 0.01 涨到 100 以上同时策略表现直线下滑。原因这是 MADDPG 里最经典的训练崩塌模式。根子通常在两个地方。第一多个智能体的 Actor 同时更新让环境转移函数变得不稳定TD 目标本身在漂移。第二Critic 输入维度太高所有智能体观测加所有动作小批量下 Q 函数外推误差被放大。和单智能体 DDPG 不同多智能体场景里目标 Q 值的方差天然更大Critic 更容易过拟合到近期经验。解决三个手段按优先级尝试。第一步检查tau是否过大把软更新系数从 0.01 降到 0.005第二步把 Critic 的隐藏层从 128 扩到 256同时加一层 LayerNorm 稳定激活分布第三步给 Critic 的梯度加clip_grad_norm_(max_norm0.5)。如果还发散直接降critic_lr到 5e-4牺牲一点收敛速度换稳定性。这类问题没法靠加大训练量硬扛参数不调对跑再久也是飘的。4.2 动作维度不匹配现象、原因与解决现象训练脚本启动环境正常创建但第一次调用agent.act()就报维度错误提示矩阵乘法的维度对不上。原因simple_tag这类环境里每个智能体的观测不是单独的向量而是“自身状态 其他智能体相对位置 障碍物相对位置”的拼接。obs_dim 不是输入文件里写的那个数而是环境在运行中生成的实际长度。源码里如果直接写死obs_dim10而环境实际返回 14必然崩。另一个常见来源是 Critic 输入维度算错忘了乘num_agents或者观测拼接时把顺序搞反。解决在训练脚本里加一行断言比事后排查快得多。assert env.obs_dim agents.obs_dim, fobs_dim mismatch: env{env.obs_dim}, agent{agents.obs_dim}如果只有训练开始时报错大概率是 obs_dim 写死的问题如果是训练中途某个 episode 才报要检查环境是否在特定条件下返回了不同长度的观测某些仿真环境在障碍物数量变化时会改变观测长度。后一种情况建议在环境代码里固定障碍物数量前期先别引入可变数量的场景。4.3 策略梯度为 0现象、原因与解决现象训练正常跑loss 也正常下降但每个 episode 的智能体始终在原地打转动作输出几乎全为 0奖励曲线是平的。原因agent.act()里对动作做了np.clip(action, -1, 1)但网络输出如果长期落在饱和区比如全是 -0.99 或 0.99梯度回传会非常弱策略几乎冻结。这在多智能体对抗里很常见早期噪声太大智能体在边界上反复试探Actor 被推向饱和区后回不来。解决把动作采样改成先加噪声再 clip同时检查 Actor 最后一层是否用了 Tanh。如果用了 Tanh 还饱和说明输入观测的数值范围太大比如坐标值在 0 到 100 之间而网络初始化权重太小Tanh 输出直接被压到边界。建议对观测做归一化obs (obs - obs_mean) / obs_std这个变换在环境加载后计算一次训练过程中固定。用代码说就是obs_mean np.mean(all_collected_obs, axis0) obs_std np.std(all_collected_obs, axis0) 1e-8 obs_normalized (obs - obs_mean) / obs_std这种观测归一化在多智能体场景里比在单智能体里更重要多个智能体的坐标范围和相对距离量纲不同不归一化的话 Critic 的输入维度之间数值差异能到两个数量级梯度更新时较小量纲的特征会被直接淹没。4.4 复现老源码的 5 个兼容性问题现象、原因与解决现象拿到一份 MADDPG 的源码包按 README 装好依赖跑python train.py报各种错误。原因这类源码大多写在 2018-2020 年间主要问题集中在五个位置一是gym的env.reset()返回值格式变了二是 PyTorch 里torch.FloatTensor()和torch.Tensor()的默认 device 行为不同三是np.random.seed和torch.manual_seed需要同时设置四是env.step()返回值从(obs, reward, done, info)变为(obs, reward, terminated, truncated, info)五是部分老代码里用了torch.autograd.Variable在新版 PyTorch 里已经移除。解决按这个顺序排查。第一步打开env.py把所有env.reset()改成obs, _ env.reset()或把 gym 版本锁到 0.21。第二步全局搜索Variable统一替换成torch.tensor。第三步在train.py最前面加种子设置np.random.seed(args.seed) random.seed(args.seed) torch.manual_seed(args.seed)如果是 CUDA 环境还要加torch.cuda.manual_seed_all(args.seed)。老代码里往往只设了np.random.seed没设torch.manual_seed导致模型初始化参数每次都变前后两次实验对不上容易误判为模型问题。5. 训练完成之后评估、验证与改造方向的落地思路5.1 训练完怎么验证收敛评估脚本与胜负率统计训练阶段看到的奖励曲线只能说明“训练在推进”不能说明“策略真的学会了对抗”。要验证收敛质量需要关掉探索噪声让智能体完全按照 Actor 的确定性策略去跑一组评估回合然后统计指标。评估脚本的关键点在于必须使用act(obs, noise_scale0)。如果评估时还带着噪声你看到的战绩会被探索因素污染无法判断策略本身的水平。我习惯的做法是每训练 1000 个 episode 跑 100 局评估统计三个指标平均回合奖励、追击成功率、平均捕获时间。追击成功率对于simple_tag环境来说就是“在回合结束前是否发生过一次捕获”。def evaluate(agents, env, num_episodes100): agents.eval() # 切换到评估模式关闭噪声 success_count 0 episode_rewards [] for _ in range(num_episodes): obs_all env.reset() done False episode_reward np.zeros(env.num_agents) while not done: actions [agent.act(obs, noise_scale0) for agent, obs in zip(agents, obs_all)] obs_all, reward_each, done env.step(actions) episode_reward reward_each if episode_reward[0] 0: # 追击者有正收益视为捕获成功 success_count 1 episode_rewards.append(episode_reward) avg_reward np.mean(episode_rewards, axis0) success_rate success_count / num_episodes print(favg_reward: {avg_reward}, success_rate: {success_rate})这里判断“捕获成功”用的是追击者单步正收益这是simple_tag环境里比较粗糙的判断方式。如果你改了自己的对抗环境最好在环境层面加一个专门的成功标志而不是从奖励倒推。这也算一条经验多智能体对抗的评估指标应该由环境定义而不是由训练代码定义。还有一个容易被忽略的细节评估时各个智能体的初始位置要固定一组种子。如果不固定初始位置评估结果方差会很大两个策略之间 2-3% 的差距根本分辨不出来。固定随机种子后用 100 局取平均方差能压到 1% 以内。遇到这种情况我会固定评估用的env.seed(20240606)训练时保持随机这样评估结果真正反应策略水平而不被初始化运气干扰。5.2 改造方向一从同构到异构的博弈对抗如果你的对抗场景里有不同职责的智能体类型比如“速度快的侦察者”和“火力强的攻击者”MADDPG 原生结构并不直接支持“异构策略”。但你可以做一个小改造给不同角色分配不同的 Actor 网络再让 Critic 保持全局输入。项目源码如果只实现了共享 Actor你要改成按角色分网络。“集中训练、分散执行”的前提不变变的是 Actor 的 obs_dim 可能在不同角色间不同侦察者多了速度特征攻击者多了攻击范围特征。这个时候网络初始化更要小心。多组 Actor 中只要有一组初始化和 Critic 不匹配训练早期 Critic 的梯度就会被带偏。建议各角色的隐藏层独立初始化之外Critic 的输入层做 Xavier 初始化保证全局拼接后数值范围一致。5.3 改造方向二奖励稀疏化与奖励塑形的取舍经典环境的奖励都比较稠密每靠近一步给一个小负奖励捕获时给大正奖励。真实对抗场景里奖励往往是稀疏的比如只有“命中目标”才给奖励。这时候直接跑 MADDPG 大概率学不动因为 Critic 需要大量正样本才能学会估计高价值区域。我踩过的坑是用“距离差”作为塑形奖励结果智能体学会了绕着目标转圈刷距离奖励而不是真正捕获。原因是距离差奖励改变了最优策略的势函数塑形的权重没调好。如果一定要做奖励塑形建议用势函数法def potential_shaping(obs, next_obs, gamma0.95, phi_scale1.0): phi lambda o: -np.linalg.norm(o[:2] - env.target_pos) * phi_scale return gamma * phi(next_obs[:2]) - phi(obs[:2])势函数塑形保证不改变最优策略这在多智能体对抗里尤其重要因为额外奖励一旦打破了多个智能体之间的博弈平衡训练出来的策略会在对抗中暴露出明显的缺陷。5.4 改造方向三从仿真到部署Actor 参数压缩与推理优化训练好的 MADDPG 模型要部署到实际机器人或仿真平台时会遇到一个现实问题训练时我们用的是全局 Critic 指导策略但部署时只能用局部 Actor 推理。好消息是 MADDPG 的“分散执行”特性天然支持这种分离你只需要把 Actor 的权重导出而不需要导出 Critic。这是 MADDPG 相比其他多智能体算法比如 QMIX在实际部署时最大的工程优势QMIX 的单调性约束依赖全局状态推理时也需要全局信息。导出时建议用 TorchScript 做模型编译避免部署环境缺少 Python 依赖导致加载失败traced_actor torch.jit.trace(agent.actor, torch.FloatTensor([obs_dim])) traced_actor.save(actor_agent_0.pt)推理性能方面一个 128 维隐藏层的 Actor 在 CPU 上单次推理大约 0.5 毫秒完全满足仿真环境的实时要求。如果部署到嵌入式设备可以把隐藏层从 128 降到 64 再量化到 int8精度损失在 1% 以内但推理速度能快 3 倍。我一般会先量化再和原始模型做 100 局对抗对比量化模型胜率高过原始模型才算通过。多智能体博弈对抗项目的核心难点不是训练百步走通而是评估与部署阶段“看似能跑、实则没过阈值”的隐性坑。我自己做过一个追捕对抗的评估训练时平均奖励看起来收敛了但固定种子评估后发现成功率只有 20%原因是训练阶段没有关掉噪声探索反而成了主策略。后来我改成周期性评估、固定环境种子、统计 100 局成功率才真正看清模型的水平。如果你也是第一次上手 MADDPG建议先别改模型结构用经典simple_tag环境把训练闭环和评估体系跑通然后看失败案例——训练一个“追不上”的模型和训练一个“能抓住”的模型两者的经验价值完全不同。这套流程走完再决定要不要往复杂环境迁移希望帮到你。本文还有配套的精品资源点击获取
返回列表