尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MADDPG源码解析:中心化训练与多智能体博弈对抗实战

MADDPG源码解析:中心化训练与多智能体博弈对抗实战 简介基于MADDPG的多智能体博弈对抗算法Python实现项目源码面向计算机专业正在完成毕业设计、课程设计或期末大作业的学生也适合希望快速上手多智能体强化学习实战的开发者。整体遵循集中训练、分布执行的经典框架包含策略网络、价值网络、经验回放缓冲区、DDPG算法核心模块以及配套的ma-gym环境压缩包能够直接运行并观察智能体对抗训练过程。资源共14个文件以10个Python脚本为主另有环境压缩包、配置文件和说明文本压缩后仅1.6MB结构精炼便于阅读与二次开发。项目曾获导师认可、评审98分已有189人学习参考。借助这份源码读者可以理解多智能体博弈中的状态交互、奖励设计、探索策略与收敛调优思路并以此为基础扩展改进自己的算法实验。1. 博弈对抗场景为什么绕不开 MADDPG先看懂它在解决什么如果你做过多智能体对抗不管是无人机追逃、游戏 AI 还是机器人攻防大概率遇到过同一个问题把单智能体强化学习算法直接搬到多智能体环境里训练曲线像心电图一样乱跳明明每个智能体都在学整体却越来越差。MADDPGMulti-Agent Deep Deterministic Policy Gradient就是为这类问题设计的算法它用「中心化训练、去中心化执行」的思路把多智能体博弈里最棘手的非平稳性问题摁住了。这个项目源码适合正在做多智能体强化学习课题、毕设或对抗仿真的人也适合想从 DQN 往多智能体方向进阶的从业者。理解 MADDPG 不光是会调包跑 demo而是要搞清楚它的 Critic 结构为什么能兼容合作、竞争、混合三种博弈关系。2. 从 DDPG 到 MADDPG先弄懂中心化训练与去中心化执行2.1 非平稳性为什么单智能体算法到了多智能体环境就失灵先看一个具体的追逃场景一个追捕者去抓一个逃跑者两者都用 DQN 训练。追捕者的 Q 值更新依赖「当前状态 动作 - 奖励 下一个状态的最大 Q」。问题在于逃跑者也在更新自己的策略它在追捕者眼里就是环境的一部分而这个「环境」每几百步就变一次。也就是说追捕者学到的 Q 函数刚收敛一点逃跑者的策略一变Q 函数的目标值又全变了。这在强化学习里叫非平稳性。标准的 Q-learning 和策略梯度方法都假设环境转移概率是固定的多智能体博弈里每个 agent 的策略都在动态调整这个假设直接不成立。最常见的表现就是训练曲线震荡、奖励长期不涨甚至两个智能体同时退化成原地转圈。我见过不少同学把 DQN 调得非常好一上多智能体环境就翻车原因就在这里不是网络结构不对而是算法根本没有处理对手策略变化的能力。MADDPG 对这个问题给出的方案是不要去显式建模对手的策略而是在训练阶段给每个智能体一个「能看到全局信息」的 Critic。这样即使对手策略在变Critic 也能把其他智能体的动作作为输入学进去相当于把非平稳性从「环境噪声」降级成了「可观测输入」。2.2 中心化 Critic训练时开上帝视角执行时只留本地策略MADDPG 的核心结构可以拆成两层看。每个智能体 i 有自己的 Actor 网络输入是自己的观测 o_i输出连续动作 a_i。Actor 在训练和执行阶段保持不变永远只依赖局部观测。但每个智能体还有一个 Critic 网络训练时输入是所有智能体的观测拼接 x (o_1, o_2, ..., o_N) 和所有智能体的动作拼接 (a_1, a_2, ..., a_N)输出的是智能体 i 的 Q 值。这个设计的巧妙之处在于执行推理时只需要 ActorCritic 只在训练阶段存在。因为训练时可以把所有智能体的观测和动作收集起来传给中心化 Critic但部署时智能体之间往往没有可靠的全双工通信通道或者通信延迟大到不能容忍。这就是「中心化训练、去中心化执行」——CTDE 范式也是多智能体强化学习里最实用的工程架构之一。为什么不干脆让 Actor 也用全局信息因为训练和部署的条件不对称。训练时你可以用仿真环境里的全局状态部署到真实机器人时要额外建立通信链路、处理丢包和同步问题成本高且不可靠。MADDPG 把这个矛盾用 Critic 切开了训练阶段信息越全越好执行阶段模型越小越稳越好。2.3 经验回放、目标网络与软更新从 DDPG 继承的三个关键机制MADDPG 不是从零发明的算法它是在 DDPG 骨架上做的多智能体扩展因此 DDPG 的三个核心机制也要一起带上。第一个是经验回放。多智能体版本的回放缓冲区里存的不再是单条 (s, a, r, s)而是所有智能体的联合观测 x、联合动作 (a_1, ..., a_N)、每个智能体的奖励 (r_1, ..., r_N) 和下一步联合观测 x。采样时一次性取出所有智能体的数据否则 Critic 的输入就不完整。注意这里有个工程细节如果某个智能体的 rollout 长度和其他人不一致对齐会非常痛苦最好统一用同一个环境步来存经验。第二个是目标网络。每个智能体都有 Actor 和 Critic 的目标网络参数通过软更新同步。目标网络的作用是让 TD 学习的 target 在一段时间内保持稳定避免自举导致训练发散。在多智能体环境里目标网络还有额外意义对手的策略变化会传导到 Q target 里目标网络的滞后更新相当于给这个传导加了一层缓冲。第三个是软更新。DDPG 用的是 Polyak 平均θ_target τ * θ_current (1 - τ) * θ_targetτ 通常取 0.01。多智能体环境下 τ 的选择比单智能体更敏感后文的避坑章节会专门展开。下表整理了 DDPG 和 MADDPG 在结构上的差异。对比项DDPGMADDPGCritic 输入单个 agent 的 obs 和 action所有 agent 的 obs 和 actionActor 输入单个 agent 的 obs单个 agent 的 obs训练执行一致经验回放单元(s, a, r, s)(x, a_1...a_N, r_1...r_N, x)目标网络数量2 个每个 agent 各 2 个适用关系单智能体连续控制合作 / 竞争 / 混合博弈3. 环境构建多智能体环境如何配置跑通第一个追逃对抗3.1 多智能体环境如何配置MPE 的最小安装与版本约束做 MADDPG 最常见的仿真环境是 OpenAI 开源的 Multi-Agent Particle Environment简称 MPE。它用粒子模拟智能体2D 空间里做追逐、躲避、协作覆盖等任务渲染轻量、训练速度快非常适合验证多智能体强化学习算法。对 python 入门不久的同学来说这套环境比 MuJoCo 或 Unity ML-Agents 友好得多不需要装物理引擎纯 numpy 就能跑。MPE 的安装不建议直接pip install multiagent-particle-envs因为老版本依赖 gym 0.10.5 这类固定版本和新版 gym 接口冲突很多。我一般这样装git clone https://github.com/openai/multiagent-particle-envs.git cd multiagent-particle-envs conda create -n maddpg python3.8 -y conda activate maddpg pip install numpy python setup.py install逻辑说明先建一个独立的 conda 环境Python 版本锁在 3.8 到 3.10 之间太新的 Python 版本容易遇到 numpy 和 gym 的二进制兼容问题。setup.py install会把 multiagent 这个包装进当前环境之后在任意目录都能 import。参数说明python3.8 是我在 Windows 和 Linux 上都验证过的版本组合不指定 gym 版本是因为这个仓库会自动装一个兼容版本。如果你想用 pip 管理依赖也可以改成pip install -e .效果一样但 -e 模式会链接源码目录方便你直接改场景代码。3.2 跑通第一个追逃场景读 observation、action、rewardMPE 里最经典的竞争场景是 simple_tag里面有三个追捕者红色、蓝色、绿色和一个逃跑者黑色追捕者想抓住逃跑者逃跑者可以加速逃跑场景里还有两个障碍物。这个场景天然就是博弈对抗适合验证 MADDPG。先写一段最小代码把环境跑起来看看数据长什么样import multiagent.scenarios as scenarios from multiagent.environment import MultiAgentEnv scenario scenarios.load(simple_tag.py).Scenario() world scenario.make_world() env MultiAgentEnv(world, scenario.reset_world, scenario.reward, scenario.observation) obs_n env.reset() print(智能体数量:, env.n) print(每个智能体 obs 维度:, [obs.shape for obs in obs_n]) # 随机执行 10 步观察奖励变化 for step in range(10): actions_n [env.action_space[i].sample() for i in range(env.n)] obs_n, reward_n, done_n, info_n env.step(actions_n) if step 0: print(第一帧奖励:, reward_n) print(第一帧动作维度:, actions_n[0].shape)逻辑说明scenarios.load(simple_tag.py)按文件名加载场景类make_world()创建物理世界MultiAgentEnv把 world 包装成强化学习环境。这里不依赖 gym 的环境注册名所以新旧版本 gym 都能跑。参数说明env.n在这个场景里是 4对应 3 个追捕者加 1 个逃跑者。obs 维度通常是 [28] 或 [30] 左右具体取决于是否包含障碍物和边界信息动作维度是 [5]对应 4 个方向加一个停止。reward 的数值设计是每撞一次追捕者得 10 分逃跑者得 -10 分活着的时间越长逃跑者的负奖励越小。3.3 场景选择与奖励改造你的博弈问题适合哪个 reward 结构MPE 内置了多个场景分别对应合作、竞争和混合博弈。选场景前先想清楚你的任务是多个智能体共同完成一个目标还是要分阵营对抗场景智能体组成博弈类型适合验证什么simple_spread3 个合作智能体 3 个地标合作多智能体协作覆盖、避免重复占位simple_tag3 追捕者 1 逃跑者竞争追逃对抗、角色差异化策略simple_adversary1 目标 1 对抗者 2 帮手混合信息不对称下的欺骗与识别simple_push1 合作对 1 对抗者混合推球对抗、合作与竞争共存如果你要做的不是这几个内置任务而是自定义博弈最常见的做法是保留环境类只改 reward 函数。例如把 simple_tag 改成「追捕者抓到逃跑者得 1 分但逃跑者存活超过 500 步追捕者扣 1 分」def reward(self, agent, world): reward 0 if agent.adversary: # 逃跑者 reward 0.1 # 活着每步给一点正奖励鼓励逃跑 else: # 追捕者 for other in world.agents: if other.adversary: dist np.sqrt(np.sum(np.square(agent.state.p_pos - other.state.p_pos))) if dist 0.1: reward 1.0 else: reward 0.01 * dist # 距离越近奖励越高 return reward逻辑说明这里给逃跑者一个持续的存活奖励避免它原地发呆给追捕者一个距离 shaped reward让它即使没抓到也在学接近策略而不是等稀疏的碰撞奖励。参数说明0.1 是存活奖励系数调大容易让逃跑者只顾乱跑0.01 是距离奖励系数调太大会让追捕者忽略真正的碰撞目标。这两个系数就是博弈对抗调优的起点建议每次只动一个参数跑 5000 回合看趋势再改下一个。4. MADDPG 的 Python 项目源码结构核心模块拆解与关键参数4.1 项目源码目录一张图看懂训练代码怎么组织网上免费 python 源码大全里能搜到不少 MADDPG 项目但质量参差不齐很多是 TensorFlow 1.x 老代码根本跑不起来。如果是自己整理 PyTorch 版我建议按下面这个目录结构组织职责清晰也方便改maddpg_project/ ├── train.py # 训练入口环境、超参数、训练循环 ├── maddpg.py # MADDPG 类智能体调度、更新逻辑 ├── networks.py # Actor 和 Critic 网络定义 ├── buffer.py # 多智能体经验回放缓冲区 ├── config.py # 超参数集中管理 └── evaluate.py # 评估脚本加载模型、算胜率这个结构不是唯一答案但有个原则值得遵守训练循环、策略网络、经验回放三个模块一定要分开。博弈对抗调参频率非常高如果所有代码都堆在 train.py 里你改一个 reward 系数就要翻几百行效率很低。4.2 Actor-Critic 网络定义PyTorch 实现的关键细节MADDPG 的网络本身不复杂Actor 和 Critic 都是一两个隐藏层的 MLP。难点在 Critic 的输入拼接方式。下面这段代码是标准写法import torch import torch.nn as nn import torch.nn.functional as F class Actor(nn.Module): def __init__(self, obs_dim, action_dim, hidden64): super().__init__() self.fc1 nn.Linear(obs_dim, hidden) self.fc2 nn.Linear(hidden, hidden) self.fc3 nn.Linear(hidden, action_dim) def forward(self, obs): x F.relu(self.fc1(obs)) x F.relu(self.fc2(x)) return torch.tanh(self.fc3(x)) # tanh 把动作压到 [-1, 1] class Critic(nn.Module): def __init__(self, obs_dim_all, action_dim_all, hidden64): super().__init__() self.fc1 nn.Linear(obs_dim_all action_dim_all, hidden) self.fc2 nn.Linear(hidden, hidden) self.fc3 nn.Linear(hidden, 1) def forward(self, obs_all, action_all): x torch.cat([obs_all, action_all], dim1) x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) return self.fc3(x)逻辑说明Actor 输出层用tanh激活把动作限制在 [-1, 1]匹配 MPE 动作空间的数值范围。Critic 第一步就把所有智能体的观测和动作拼成一个长向量obs_dim_all是所有智能体 obs 维度之和action_dim_all是动作维度之和。拼完之后就是一普通 MLP 回归网络。参数说明hidden 取 64 还是 128取决于智能体数量和问题复杂度。simple_tag 这种 4 智能体任务 64 足够了hidden 太大反而容易让 Critic 过拟合到联合状态上训练速度也慢。obs_dim_all 的计算别偷懒用硬编码数字建议在训练脚本里动态拼接否则换场景就得改网络层。4.3 训练循环采样、更新 Critic、更新 Actor 的正确顺序MADDPG 的一个更新回合里有两个更新阶段顺序是固定的先更新 Critic再更新 Actor。Critic 更新用的是 TD 目标Actor 更新用的是 Critic 返回的动作梯度。核心代码段如下def update(self, replay_buffer, agent_id): # 从回放缓冲区采样一批联合经验 batch replay_buffer.sample(self.batch_size) obs_all batch[obs_all] # [batch, N, obs_dim] act_all batch[act_all] # [batch, N, act_dim] rew_i batch[rew][:, agent_id] # [batch] obs_all_next batch[obs_all_next] done batch[done] # 用目标 Actor 算下一步动作注意是 target 网络 with torch.no_grad(): act_next [ self.target_actors[a](obs_all_next[:, a]) for a in range(self.n_agents) ] act_next torch.stack(act_next, dim1) q_target self.target_critics[agent_id](obs_all_next, act_next) target rew_i self.gamma * q_target * (1 - done) # 更新 Critic最小化 TD 误差 q_pred self.critics[agent_id](obs_all, act_all) critic_loss F.mse_loss(q_pred, target) self.critic_optimizers[agent_id].zero_grad() critic_loss.backward() self.critic_optimizers[agent_id].step() # 更新 Actor最大化 Q 值 act_current [ self.actors[a](obs_all[:, a]) for a in range(self.n_agents) ] act_current[agent_id] self.actors[agent_id](obs_all[:, agent_id]) # 其他 agent 的动作 detach防止梯度串扰 act_current torch.stack(act_current, dim1) actor_loss -self.critics[agent_id](obs_all, act_current).mean() self.actor_optimizers[agent_id].zero_grad() actor_loss.backward() self.actor_optimizers[agent_id].step()逻辑说明target 里的下一步动作必须用 target actor 算这是 DDPG 系列的血脉规矩用当前 actor 算会导致目标值和当前值耦合训练不稳定。更新 Actor 时只有当前智能体的动作参与梯度回传其他智能体的动作要 detach 掉。如果不 detachCritic 的梯度会同时更新所有智能体的 Actor造成信用分配混乱这是多智能体训练里常见的一个隐性 bug。参数说明gamma是折扣因子batch_size是每次采样数量。MADDPG 更新频率不一定要每一步都做常见做法是每采集 100 步经验循环更新所有智能体各一次。如果你发现训练发散先把更新频率降下来试试。4.4 五个必调参数GAMMA、TAU、LR、BUFFER_SIZE、BATCH_SIZEMADDPG 对超参数比 DQN 敏感得多几个关键参数我整理成表后面避坑章节会展开讲现象参数推荐取值调低的影响调高的影响gamma0.95智能体变短视只看近几步收益0.99 在对抗场景容易不收敛tau0.01目标网络更新太慢学习偏保守目标网络振荡训练发散actor lr1e-4学得慢但稳定策略剧烈跳变critic lr1e-3收敛慢Q 值高估Loss 爆炸batch_size1024梯度噪声大内存压力大更新变慢单智能体里习惯用 gamma0.99但在博弈对抗里我反而常用 0.95。原因是对手策略在变太远的回报预测基本是猜不如让智能体更关注近期博弈结果。tau 取 0.01 时目标网络变化较慢如果 Critic Loss 震荡剧烈可以降到 0.005代价是训练变慢。5. 博弈对抗效果调优与避坑为什么训练了上万回合还在原地转圈5.1 通用排查顺序奖励、噪声、更新频率、网络维度多智能体训练出问题不要一上来就怀疑算法理论90% 的情况出在工程实现和参数搭配上。我一般按下面这个顺序排查第一步看奖励数值量级。如果 reward 在 0.001 到 0.01 这个量级网络初始化后 Q 值预测会完全压过真实奖励信号梯度直接被淹没。把奖励系数放大到 0.1 到 1 的量级再试。第二步看 exploration noise。MADDPG 在训练时会在动作上加高斯噪声噪声 sigma 太大策略永远在探索Q 值学不准sigma 太小策略过早收敛到次优。第三步看更新频率每步都更新容易造成样本相关性过高改成每 100 步更新一次。第四步核对状态动作维度拼接顺序obs 和 action 拼接错位不会报错但训练效果会莫名其妙地差。5.2 踩坑一追捕者学不会追人奖励被逃跑者「带偏」现象在 simple_tag 里训练 20000 回合追捕者还是满场乱跑逃跑者却学会了绕障碍物。训练曲线显示逃跑者奖励持续上升追捕者奖励接近 0。原因这是典型的 reward 结构失衡。MPE 默认给追捕者的惩罚是每次碰撞 -10但碰撞事件稀疏追捕者大部分时间拿不到任何反馈与此同时逃跑者每活一步都有负奖励惩罚追捕者等于追捕者一直在被罚却没学到怎么消除惩罚。解决给追捕者加距离 shaped reward离逃跑者越近奖励越高同时降低存活惩罚的绝对值。我在 3.3 节给过代码实际调的时候把距离奖励系数从 0.01 加到 0.05碰撞奖励从 1.0 加到 5.0追捕者的学习速度立刻上来了。这不算作弊reward shaping 只要不改变最优策略的序关系就是合法加速手段。5.3 踩坑二tau 设置不当目标网络变成「伪目标」现象训练前 1000 回合 Critic Loss 稳定下降之后突然飙升接着所有智能体策略崩溃连最简单的直线接近都做不到。原因tau 取 0.1目标网络几乎在跟当前网络同步更新等于没有目标网络TD target 每步都在变。单智能体 DDPG 里 tau0.1 可能还能跑但多智能体环境下联合动作空间更大Critic 目标值的方差本来就高再没有目标网络缓冲训练必然发散。解决tau 降到 0.01 或 0.005并且计算 target 时所有智能体的下一步动作都从 target actor 采样。还有个细节target network 的初始权重必须和当前网络一致否则第一个 target 就是错的训练从第一步就开始歪。5.4 踩坑三exploration noise 不衰减策略收敛后被噪声拖垮现象训练后期平均奖励不再上升画出来是一条平线但加载模型到评估环境里发现智能体在目标点附近来回抖动永远停不下来。原因训练时加的高斯噪声没有衰减机制。我在 4.4 节提到噪声 sigma 影响探索与利用的平衡如果 sigma 固定为 0.1即使策略已经收敛动作仍然被叠加 ±0.1 的随机扰动在 MPE 这种连续动作环境里扰动足以让智能体无法稳定停在目标位置。解决训练时做噪声衰减每 1000 回合 sigma 乘以 0.995或者用线性衰减到 0.01。评估阶段直接关掉噪声。下面是一种常用衰减写法sigma 0.1 for episode in range(total_episodes): if episode % 1000 0 and episode 0: sigma * 0.995 action actor(obs) sigma * np.random.randn(action_dim)逻辑说明sigma 从 0.1 开始每 1000 回合衰减到原来的 0.995 倍训练到 10000 回合时 sigma 约等于 0.06策略逐渐从探索过渡到利用。参数说明0.995 是衰减率衰减太慢噪声会拖累最终表现太快则前期探索不足。5.5 踩坑四CPU 上训练 6 个智能体经验回放缓冲区内存爆炸现象智能体数量增加到 6 个后训练速度骤降同时内存占用持续上升跑几个小时后程序被系统 kill 掉。原因经验回放缓冲区存的是联合状态和联合动作智能体数量翻倍每条经验的维度就翻倍。如果用 Python list 存 numpy 数组每条经验还带对象头开销10 万条经验就能吃掉几个 GB 内存。解决把回放缓冲区改成固定大小的 numpy 数组预先分配好内存避免 list 动态扩容。下面是一种紧凑存法class ReplayBuffer: def __init__(self, capacity, obs_dim, act_dim, n_agents): self.obs np.zeros((capacity, n_agents, obs_dim), dtypenp.float32) self.act np.zeros((capacity, n_agents, act_dim), dtypenp.float32) self.rew np.zeros((capacity, n_agents), dtypenp.float32) self.ptr, self.size 0, 0 def push(self, obs_n, act_n, rew_n): idx self.ptr self.obs[idx] obs_n self.act[idx] act_n self.rew[idx] rew_n self.ptr (self.ptr 1) % len(self.obs) self.size min(self.size 1, len(self.obs))逻辑说明缓冲区在初始化时就分配好固定大小的连续内存ptr做环形覆盖。capacity取 1e6 时6 智能体、obs 维度 30 的情况下内存占用约 1.5 GB可控。参数说明obs 和 act 的类型都用float32不要用float64否则内存翻倍对训练精度几乎没有影响。6. 评估博弈对抗效果胜率、自博弈与可视化回放6.1 两个硬指标平均回合奖励和胜率分开看多智能体博弈训练的评估不能只盯着平均回合奖励。奖励指标容易受环境随机性影响而且两个对抗阵营的奖励总和可能不是零和趋势判断容易被误导。更可靠的指标是胜率。以 simple_tag 为例冻结当前策略随机初始化逃跑者的初始位置跑 100 个回合统计追捕者抓到逃跑者的回合比例。def evaluate_win_rate(env, actors, n_episodes100): wins 0 for _ in range(n_episodes): obs_n env.reset() done False while not done: actions [] for i, actor in enumerate(actors): obs_t torch.FloatTensor(obs_n[i]).unsqueeze(0) action actor(obs_t).detach().numpy().squeeze(0) actions.append(action) obs_n, rew_n, done, _ env.step(actions) if rew_n[0] 0: # 追捕者最后一步拿到正奖励 wins 1 return wins / n_episodes逻辑说明评估时必须关掉 exploration noise否则评估结果会被噪声污染。胜率比平均奖励直观得多我习惯每 500 回合跑一次评估把胜率曲线和奖励曲线画在同一张图里胜率上升但奖励不涨说明策略在朝着赢的方向走只是 reward 数值设计有偏差。6.2 自博弈评估防止策略过拟合到固定对手MADDPG 训练时用的是当前所有智能体的实时策略这带来一个隐患策略可能过拟合到训练对手的特定打法上。评估时如果发现一个策略在训练对手面前胜率很高换一套随机参数对手立刻拉胯说明泛化能力差。自博弈评估的做法是周期性保存对手策略副本每训练 1000 回合就存一份 actor 权重评估时从历史副本里随机挑选对手。这相当于给智能体提供了多种「风格」的对抗样本比固定对手更能反映真实博弈能力。实践中我还会把训练对手和随机历史对手分为 50% 对 50% 混合评估兼顾稳定性和多样性。6.3 模型保存与回放用最小代码把训练结果变成可演示的对抗最后说一个实用习惯每训练 1000 回合保存一次模型权重不要只保留最终版本。博弈对抗训练经常出现「之前挺好的后面练崩了」的情况有阶段性 checkpoint 就等于有了后悔药可以回退到表现最好的版本继续调参。保存和回放的核心逻辑很简单# 保存把 actor 权重和训练参数一起打包 checkpoint { actor_weights: [actor.state_dict() for actor in actors], total_episodes: episode, } torch.save(checkpoint, fcheckpoint_{episode}.pt) # 回放加载权重进入评估模式 for i, actor in enumerate(actors): actor.load_state_dict(checkpoint[actor_weights][i]) actor.eval()逻辑说明主程序只设计 Actor 的权重Critic 不需要保存因为评估阶段用不到。actor.eval()会关闭 dropout 和 batch norm 的训练行为如果网络里没有这些层这一步可以省略但建议养成习惯。回放时记得把探索噪声关掉我多年前第一次做评估时忘了这步以为算法没收敛后来才发现是噪声把动作抖乱了。把保存的 checkpoints 按时间顺序加载回放你能直观看到智能体的博弈策略是怎么从「乱跑」进化到「围堵」的这个过程对排查环境设计问题和 debug 都很有帮助。我做多智能体项目这几年最深的体会是MADDPG 的训练效果好不好一半在环境设计和 reward 定义一半在超参数和噪声管理算法本身反而是最让人省心的部分。希望这些踩坑经验能帮你少走弯路如果你的项目也在做博弈对抗方向不妨从 simple_tag 起步先把胜率指标跑起来再做扩展。本文还有配套的精品资源点击获取
返回列表