MADDPG多智能体强化学习:集中训练分布执行与Keras实战解析 简介MADDPG算法是强化学习领域处理多智能体协同与竞争问题的经典方法这一实现基于Keras编写适合研究多智能体交互的Python开发者、算法学习者及需要连续动作控制方案的工程师。压缩包共21个文件包括12个已训练完成的h5模型权重文件、7个Python脚本、1个Markdown说明文档及1个License文件总大小7.86MB。脚本覆盖训练、预测、环境定义、模型构建、经验回放缓冲和动作噪声等核心模块结合权重文件可快速复现或继续训练。资源已有813人学习下载对于希望掌握MADDPG原理与工程实现的读者提供了可直接运行的代码框架与参考权重可在此基础上进行二次开发应用于机器人协作、博弈策略或交通控制等多智能体场景。1. 多智能体强化学习为什么单智能体DDPG在多智能体场景会集体失效两个智能体在同一片地图上做协作任务各自用已经调好的 DDPG 单独训练结果谁都没学会配合。这通常不是超参问题而是单智能体强化学习范式在多智能体环境中先天失效——当每个智能体都在不断改变自己的策略环境对任何一方来说都变成了非平稳的经验回放池里保存的旧经验下一秒可能就是过期数据。MADDPGMulti-Agent Deep Deterministic Policy Gradient是解决这一问题的经典范式让每个智能体在训练时共享其他智能体的观测与动作来评估动作价值执行时却只依赖自己的观测做决策。这一层「集中训练、分布执行」的设计背后是对非平稳性、扩展性与训练稳定性三者的系统性权衡。搞懂 MADDPG也就掌握了多智能体强化学习最基础的一条范式脉络后续大量改进算法都能从这里推演出来。2. MADDPG的机制拆解集中批评家为什么能稳住非平稳环境MADDPG 的算法思路来自把一个单智能体连续动作控制算法扩展到多智能体场景所必须的那几步改造。单智能体版本负责提供确定性策略梯度与 Actor-Critic 骨架MADDPG 则把 Critic 换成集中视角同时保留 Actor 的独立决策接口。这一章的三节分别回应一个问题为什么必须有集中批评家为什么目标网络和经验回放在这里如此关键以及策略集成补上的最后一块短板。2.1 非平稳环境多智能体强化学习的第一性难题标准强化学习默认环境是平稳的马尔可夫决策过程。状态转移概率 p(s|s,a) 固定不随时间和策略变化DDPG、DQN、PPO 全都建立在这个假设上。多智能体环境打破了它。双智能体博弈里两个智能体同时选动作状态转移由双方动作共同决定。智能体 2 每更新一次策略智能体 1 观察到的转移规律就变一次。表面上你训练的是单智能体实际你在和一个持续进化的对手博弈。这直接毁掉经验回放。经验回放池能够重复利用旧经验的前提是旧经验依然能反映当前环境动力学对手策略更新后旧经验对应的 TD 目标已经失真拿它训练只会把 Q 函数拉回旧世界造成训练震荡甚至发散。多智能体训练里那种「前期很正常、跑到一半奖励曲线突然崩掉」的现象多半就是这个原因。我一般会这么理解单智能体 RL 默认环境是「客观的」多智能体环境是「主观的」——对每个智能体而言其他智能体是环境的一部分而这部分环境还在学。MADDPG 不去试图消除这个问题而是引入一个能看到全局的批评家来稳定价值评估的基准。2.2 集中批评家为什么看到全局的 Critic 能稳定梯度MADDPG 把网络分成 Actor 和 Critic 两类。每个智能体 i 有一个 Actor输入自己的观测 o_i 输出连续动作 a_i一个 Critic输入所有智能体的观测 (o_1,...,o_n) 和所有智能体的动作 (a_1,...,a_n)输出对智能体 i 动作的 Q 值 Q_i(x, a_1,...,a_n)其中 x 是全部观测的拼接。训练时 Critic 能看到全局Actor 只用自己的观测这就是「集中训练、分布执行」的完整含义。为什么集中批评家能稳住梯度当 Critic 的输入包含其他智能体的动作时环境对智能体 i 而言相当于一个给定其他动作 a_{-i} 条件下的状态转移过程。其他智能体的策略变化可以被进入 Critic 的动作特征所吸收不再需要预测他们的未来策略于是非平稳性被重新拉回近似平稳的框架。梯度质量也明显变好Actor 的梯度来自 ∂Q/∂μ_i如果 Critic 不知道其他智能体的动作当队友行为改变导致回报改变时Critic 只能把这归因于噪声Q 值方差会急剧变大集中 Critic 拥有完整联合状态可以更精确地定位「回报变化来自我的动作还是队友的动作」。在某个模拟项目 X 里我用 MADDPG 和独立 DDPG 对比过同类协作搬运任务独立 DDPG 的两个个体互相推搡MADDPG 学会走位配合。核心差异就来自 Critic 的全局视野。2.3 目标网络、经验回放与策略集成MADDPG 稳定训练的三根支柱MADDPG 不只是靠集中 Critic。它从单智能体版本继承了稳住训练的三个技巧并在多智能体语境里做了改造。第一是目标网络软更新。每个智能体维护目标 Actor 和目标 Critic参数来自在线网络的缓慢逼近θ_target ← (1-τ)θ_target τθ_onlineτ 通常在 0.01 以下。目标 Q 值由此缓慢漂移不会因为一次大的参数跳变而剧烈变动。多智能体场景里所有目标网络同步漂移τ 一旦偏大整个系统就像在晃动的地基上盖楼。第二是共享经验回放池。MADDPG 把联合观测、联合动作、各智能体奖励、下一联合观测这组转移样本全部存入同一个池子每个智能体按自己的视角从中采样学习。共享池带来了样本多样性也实现了多智能体之间的隐式信息共享但代价是各行为策略差异过大时采样的分布偏移会更明显。第三是策略集成Policy Ensemble。当其他智能体的策略不可观测时MADDPG 可以为每个智能体保存 K 个历史 Actor评估对手动作时用投票或取平均。maddpg-keras 这类实现通常默认关闭它因为 K 个 Actor 会带来 K 倍的存储与推理开销在协作场景里不一定划算但在对抗、欺骗场景这是 MADDPG 最锋利的改进点之一。K 一般取 3 到 5太大反而引入过高方差。提示策略集成的核心收益在于处理「对手策略漂移」带来的 Q 值误估纯协作任务不一定需要。需要时先确认你确实观测不到对手的真实动作再决定是否启用。3. 源码级拆解maddpg-keras 的 Actor-Critic 是怎么搭起来的3.1 网络结构用 Keras 函数式 API 表达集中 Critic在 Python 生态里用 Keras 函数式 API 搭 MADDPG 网络最关键的一步是正确处理「Critic 的全局输入」和「Actor 的局部输入」。下面的代码展示了 maddpg-keras 这类实现里最常见的两种网络定义方式。def build_actor(obs_dim, act_dim, act_bound1.0): 每个智能体的 Actor只看自己的观测输出连续动作 obs Input(shape(obs_dim,), nameactor_obs) x Dense(256, activationrelu)(obs) x Dense(256, activationrelu)(x) # tanh 把输出压到 [-1, 1]避免动作越界 raw_action Dense(act_dim, activationtanh, nameraw_action)(x) # 环境动作范围可能不是 [-1, 1]用 Lambda 做线性缩放 action Lambda(lambda z: z * act_bound, namescaled_action)(raw_action) actor Model(obs, action) return actor def build_critic(n_agents, obs_dim, act_dim, concatTrue): Critic输入所有智能体的观测动作输出当前智能体的 Q 值 if concat: # 集中模式输入是所有智能体的拼接 all_obs Input(shape(n_agents * obs_dim,), namecritic_all_obs) all_act Input(shape(n_agents * act_dim,), namecritic_all_act) else: # 独立模式只看自己的观测和动作用于对照实验 all_obs Input(shape(obs_dim,), namecritic_own_obs) all_act Input(shape(act_dim,), namecritic_own_act) x Concatenate()([all_obs, all_act]) x Dense(256, activationrelu)(x) x Dense(256, activationrelu)(x) q_value Dense(1, activationlinear, nameq_value)(x) critic Model([all_obs, all_act], q_value) return critic这段代码里有几个值得注意的设计点。Actor 的输出层用 tanh 激活把动作限制在 [-1, 1]这是 DDPG 系算法的标准做法如果环境动作范围是 [-2, 2] 这类区间用 Lambda 层做线性缩放但缩放的是后处理层tanh 仍然负责控制梯度回传的稳定范围。Critic 的输入里没有把「智能体编号」作为显式特征传入而是通过拼接顺序隐式编码训练时必须确保所有智能体的观测和动作按固定顺序拼接一旦顺序错乱Critic 会学到混乱的对应关系。concat这个开关是这套实现里最有调试价值的地方把它设成 FalseMADDPG 就退化成完全独立的 DDPG。同一份代码、同一个环境只改 Critic 输入范围你就能隔离出集中信息带来的增益到底有多大。3.2 训练循环目标值计算、梯度冻结与更新时序有了网络结构核心就在于训练循环。每个训练步分为四步采样、算目标、更新 Critic、更新 Actor。下面给出一个批次的关键逻辑。def train_step(batch, agent_idx, config): obs_all, act_all, rew, next_obs_all, done batch # 第一步目标 Actor 输出下一时刻所有智能体的动作 # 注意用 predict 走前向模式不更新任何权重 next_act_all [] for i in range(len(target_actor)): next_act_all.append(target_actor[i].predict(next_obs_all[i], verbose0)) # 第二步目标 Critic 评估目标 Q 值 target_q target_critic[agent_idx].predict( [next_obs_all, np.concatenate(next_act_all, axis-1)], verbose0) y_target rew[agent_idx] gamma * (1 - done) * target_q # 第三步更新 Critic最小化 TD 误差 critic_loss critic[agent_idx].train_on_batch( [obs_all, np.concatenate(act_all, axis-1)], y_target) # 第四步更新 Actor策略梯度上升 # 关键技巧冻结 Critic 权重只让梯度经过 Actor 更新 critic[agent_idx].trainable False actor_loss actor[agent_idx].train_on_batch( obs_all[agent_idx], -critic[agent_idx]([obs_all, act_ph])) critic[agent_idx].trainable True这段逻辑里藏着三个关键机制。第一是「冻结 Critic」Keras 的 train_on_batch 默认更新所有可训练权重策略梯度更新 Actor 时要先把 critic.trainable 设为 False否则 Critic 参数会被 Actor 的损失反向传播误伤。很多实现翻车就翻在这里忘了冻结或者把 Actor 和 Critic 放在同一个计算图里一次反向传播造成两个网络的梯度互相污染。更干净的写法是用 GradientTape 手动控制但 freeze 技巧在经典 Keras 实现里更直观常见。第二是目标 Q 值计算必须完全脱离当前网络的梯度。上面的代码用 target_critic.predict 强制走前向模式如果你不小心让目标网络与在线网络共用了层对象梯度就会顺着目标 Q 回传到 CriticTD 目标会反向抖动。第三是「负数 loss 实现策略梯度上升」Actor 的目标是最大化 Q 值而优化器只能做梯度下降所以对 Q 值取负号。这个技巧几乎是 DDPG 系所有实现的标配。3.3 探索与参数OU 噪声为何是多智能体场景的高频选择探索是 DDPG 系算法最容易被忽略、却最容易翻车的地方。确定性策略输出的永远是同一方向的动作若不加噪声Actor 会在训练早期就固守在局部最优。MADDPG 延续了两种探索手段。第一种是高斯噪声每个时间步独立采样实现简单但在多智能体环境下独立同分布的噪声会让智能体之间的动作像随机抖动不利于连续的协作动作学习。第二种是 OU 噪声Ornstein-Uhlenbeckmaddpg-keras 这类 Keras 实现里常见的默认选择。它是有记忆的随机过程能让噪声在时间上平滑变化不会每步跳变适合需要平滑动作轨迹的移动、搬运类任务。class OUNoise: def __init__(self, action_dim, mu0.0, theta0.15, sigma0.2): self.mu mu self.theta theta self.sigma sigma self.action_dim action_dim self.reset() def reset(self): self.state np.zeros(self.action_dim) def sample(self): # OU 过程向均值回归 随机增量 dx self.theta * (self.mu - self.state) self.sigma * np.random.randn(self.action_dim) self.state dx return self.statetheta 控制噪声回归均值的速度theta 越大噪声越快地回到 0探索时间越短sigma 控制随机强度。多智能体建议每回合 reset 一次否则上一个回合的噪声状态会延续到新回合开头污染样本的初始状态。我习惯在环境 reset 函数里同步调用 noise.reset()。还有一个容易被忽略的细节探索噪声是在执行时加的但存入经验池时act_all应该记录实际执行的动作含噪声而不是 Actor 的原始输出。因为 Critic 的输入是「实际执行的动作」其他智能体观测到的也是实际动作存不含噪声的版本会让目标 Q 的计算出现系统性偏差。4. 避坑与排查maddpg-keras 训练不收敛的四个高频原因多智能体强化学习的调试比单智能体痛苦得多——你甚至不知道失败到底该怪哪个智能体。以下是我在多个模拟项目里实际踩过、且这类 Keras 实现里反复出现的四类问题按「现象 → 原因 → 解决」整理。4.1 Q 值虚高Critic 的 Q 值一路涨奖励却纹丝不动现象训练日志里 Critic loss 在下降Q 值稳步上升但环境返回的平均奖励曲线走平甚至向下。原因Critic 把 TD 目标里的「未来高 Q」当成了已经兑现的回报估计误差逐步累积形成正反馈循环。最常见触发点是目标网络软更新系数 tau 设得太大目标 Q 值跟着在线网络一起跳丧失了稳定靶心的作用。稀疏奖励也会放大这个效应绝大多数经验给出负回报Critic 没有足够的机会学习「哪个动作导致高回报」只能消极拟合。解决先把 tau 从默认的 0.01 降到 0.001 甚至 0.0005。训练初期如果 Q 值上涨速度超过真实回报速度的两倍以上优先怀疑 tau。第二个办法是检查奖励设计给接近目标的中间状态加一个小的距离奖励——注意不要改变任务本身的奖励语义只为了让 Critic 有可学的梯度信号。4.2 Actor 饱和输出稳定贴在边界值上训练进入假死现象训练到中段Actor 输出的动作值长期贴近 -1 或 1不再变化即使 Critic 加载新的权重也没有反应。原因梯度回传到 tanh 激活函数时如果输入过于极端导数趋近于 0tanh 进入饱和区。常见触发条件是 Actor 学习率过大或者 Critic 对动作的梯度方向突然变得尖锐把 Actor 推进饱和区。解决降低 Actor 学习率在 maddpg-keras 里通常把 Actor 学习率设置为 Critic 的二分之一到十分之一。还可以在 tanh 前的 Dense 层后加 LayerNormalization防止输入值持续累积。排查手法很简单打印 Actor 输出的均值与方差如果均值长期接近 ±1说明饱和已经发生。4.3 共享经验池偏差训练曲线忽上忽下各智能体进度严重失衡现象每个智能体的个体奖励曲线各自在涨但团队总回报波动剧烈甚至出现某个智能体明显学得比另一个快。原因共享经验池里各智能体的经验分布差异过大。假设池子有 75000 条样本其中 50000 条来自已经比较成熟的智能体 1智能体 2 的样本只占少数两方的更新节奏完全错位。共享池破坏了独立学习里「样本服从同一策略分布」的隐含假设此时池子不再是 IID 样本集合而是多种行为策略的混合体。解决给每个智能体单独分配一部分独立回放池定期做池间同步或者更简单的工程做法是给每个智能体配置不同的初始探索噪声方差让它们在早期就产生行为分化。这样各智能体写入池子的样本分布自然不同减缓镜像式的采样偏差。4.4 策略趋同协作任务变成「大家各走各的」现象任务明明需要分工两个智能体的动作输出却高度相似比如搬运任务里总是朝同一个方向使劲。原因Actor 的输入太相似加上初始化相同、更新顺序固定很容易陷入对称性陷阱。多智能体训练里的对称性很隐蔽初期所有损失都正常下降你根本不会怀疑网络结构有问题直到发现角色分工完全没出现。解决分流初始化给每个 Actor 不同的随机种子或不同的初始权重尺度每回合打乱智能体编号对应的目标次序。更彻底的办法是在观测里拼接一个 one-hot 的智能体 ID 特征让每个 Actor 从一开始就拿到「我是谁」的信息。如果项目限定不能改观测至少也要保证各 Actor 的初始化参数和探索噪声是独立采样。提示排查「不收敛」时最先做的不是调超参而是跑一个降级对照——把 Critic 的 concat 参数设成 False让 MADDPG 退化成独立 DDPG。如果独立版本反而表现更好说明集中信息没有被 Critic 有效利用问题大概率在特征拼接或编码方式上。5. 机制对比MADDPG 与 QMIX、独立 DDPG 的取舍边界5.1 三种范式的底层差异多智能体强化学习领域有几条不同的技术路线。MADDPG 代表「集中批评家」路线QMIX 代表「价值分解」路线独立 DDPG 代表「完全独立」路线。它们训练时的信息使用方式决定了各自的能力边界。范式训练时使用信息执行时使用信息动作空间核心机制独立 DDPG自己的观测动作自己的观测连续无集中各学各的MADDPG所有智能体观测所有动作自己的观测连续集中 Critic 评估联合动作QMIX全局状态联合动作自己的观测离散混合网络分解联合 Q 值这个对比表至少揭示两层信息。第一三者执行时都只用自身观测这是「分布式执行」的共性部署阶段往往无法实时获取其他智能体信息也不希望在通信层面增加负担。区别全在训练期的信息使用方式。第二MADDPG 和 QMIX 的最大差异是处理联合动作空间的方式。MADDPG 用一个稠密网络直接拟合联合 Q 值输入维度随智能体数量线性增长函数表达自由但样本效率会随维度急剧下降QMIX 假设联合 Q 值可以分解为个体效用的某种单调组合通过混合网络实现分解这大大缩小了搜索空间适合搞数量较多、离散动作的场景。所以动作是连续力矩、连续速度优先选 MADDPG动作是离散格子选择、技能选择优先考虑 QMIX。5.2 什么时候选 MADDPG什么时候别硬选选 MADDPG 有三个场景信号。第一智能体数量少通常 2 到 4 个。Critic 的输入维度是观测维度乘以智能体数数量一多训练所需样本量呈指数上升。MADDPG 论文本身也只在少量智能体上做实验。第二任务有明确的连续动作控制需求比如机器人控制、多车协同导航。第三希望训练中能评估每个智能体对价值的独立贡献因为集中 Critic 天然支持反事实评估——固定其他智能体动作替换某个智能体的动作观察 Q 值变化就能衡量它的边际贡献。不适合的场景也有信号。智能体超过 8 个时集中 Critic 的输入空间爆炸收敛慢到不可接受存在明确角色分工且各角色动作空间异构时分层策略往往比混合 Critic 更合适训练资源有限时MADDPG 需要同时维护 2N 个网络各自的在线副本和目标副本开销大约是独立 DDPG 的 2.5 倍。5.3 一个可复现的对比实验思路拿到 maddpg-keras 这类资源比「直接跑通」更有价值的做法是跑一个三分支的对照实验原始 MADDPG 默认参数把 concat 设为 False 退化成的独立 DDPG在原始版本基础上打开策略集成如果项目有实现。三个变体共享同一经验池容量、同一训练步数、同一奖励定义。记录三个指标平均回报曲线的斜率、个体动作的差异化程度比如动作向量的范数方差、Critic 的 Q 值与实际回报的偏差曲线。通常你会看到独立 DDPG 的曲线更早开始震荡最终收敛值低于 MADDPG策略集成版本如果实现正确样本效率会更高。这个实验不需要额外成本唯要注意的是三个变体必须用同一随机种子保证环境初始化一致否则基线不齐。6. 设计取舍的再思考集中训练分布执行的隐性成本与验证习惯集中训练分布执行之所以成为 MADDPG 的招牌是因为它在训练期付出昂贵的全局信息代价换来执行期的轻量与去中心化。但这个设计其实还有三个隐性成本。第一个是执行与训练的不一致。训练时 Critic 拥有全局视野评估动作时知道队友的所有动作执行时 Actor 只靠自己的观测输出动作中间缺少一个「信念状态」桥梁——Actor 无法预测队友当前会做什么。这意味着 Critic 提供的 Q 值信号里包含 Actor 在部署时根本观测不到的队友信息梯度信号里藏着乐观偏差。缓解手段包括给 Critic 的动作输入加噪声、模拟队友行为不可精确预测或者直接扩展 Actor 观测去显式预测队友策略。第二个是扩展性天花板。集中训练要求所有智能体的观测被同步汇入同一训练流智能体数量上升后Critic 输入空间急剧膨胀样本复杂度指数增长。这是 MADDPG 天然的上限也是后续值分解、图注意力方法尝试突破的方向。第三个是信用分配模糊。共享奖励下集中 Critic 能评估联合动作却难以直接回答「这个联合动作里谁贡献最大」。反事实评估需要额外枚举或差异性分析如果项目需要细粒度奖励分解显式设计个体奖励函数往往比依赖集中 Critic 更直接。验证方法上我每次拿到一套多智能体实现第一件事是跑消融对比——把 MADDPG 降级成独立 DDPG像第 3.1 节把 concat 设为 False先确认降级基线和独立实现行为一致再逐步加回集中信息看每一步增益。这是一种快速察觉网络搭错的测试顺序。从那以后每次接手新环境、新任务我都会强制走一遍这个「降级到单智能体」的基线验证流程确认不是网络搭建问题再谈论改进——这个习惯帮我避免过许多把算法问题错怪成调参问题的夜晚。希望帮到你。本文还有配套的精品资源点击获取