MADDPG多智能体强化学习Keras实现与协同训练原理 简介本资源是基于Keras框架实现的多智能体强化学习算法MADDPGMulti-Agent Deep Deterministic Policy Gradient完整代码工程面向深度学习与强化学习进阶学习者、多智能体系统研究者及机器人协同控制实践者。项目解决了多智能体环境下策略协同优化难题适用于交通调度、群体机器人、多人博弈等需联合决策的场景。压缩包共21个文件含12个.h5模型权重文件对应各智能体actor/critic及其target网络、7个.py核心模块涵盖训练器train.py、环境env.py、噪声noise.py、经验回放buffer.py等、1份README.md说明文档及1份LICENSE协议整体大小为7.86MB结构清晰、模块解耦度高便于理解算法架构与调试复现。目前已有813人学习下载读者可直接加载预训练模型快速验证效果或基于完整训练流程开展多智能体协作/竞争实验掌握从网络构建、分布式策略更新到联合Q值评估的全链路实现细节。1. MADDPG 不是单 agent 的简单堆叠Keras 实现里藏着多智能体协同的硬核边界你手头有个双机械臂协同抓取任务或者一个交通信号灯联合调度场景想用深度强化学习——但直接套 DDPG 会翻车。因为 DDPG 假设环境是马尔可夫的、观测是全局的而真实多智能体系统里每个 agent 只能看到局部信息还互相影响策略更新。MADDPG 就是为破这个局设计的它让每个 agent 拥有独立的 actor 网络但在 critic 训练时偷偷“开小灶”——把所有 agent 的动作拼起来喂给自己的 critic相当于在训练时偷偷看了队友的底牌。这个项目maddpg-keras正是用纯 Keras不依赖 TensorFlow 2.x 的 eager mode 或 PyTorch 动态图实现的轻量级 MADDPG代码结构清晰、模块解耦明确适合想吃透 MADDPG 核心机制、又不想被复杂框架裹挟的工程师。它不是玩具 demo而是能跑通 simple_spread、simple_adversary 等经典 multi-agent benchmark 的完整训练 pipeline如果你正卡在“为什么我的多 agent 策略训着训着就崩溃”“critic 输入维度到底该拼谁的动作”这类问题上这份资源就是你该拆的第一份源码包。2. 从环境建模到网络构建为什么必须用 centralized critic decentralized execution2.1 多智能体环境的观测与动作空间设计以 simple_spread 为例MADDPG 的成败一半取决于环境接口是否满足算法假设。maddpg-keras默认集成的是基于 OpenAI Gym API 改写的 multi-agent 环境非 PettingZoo其关键特征在于每个 agent 返回独立的 observation、reward、done但共享一个 global state用于 critic 训练。以simple_spread场景为例Observation 维度每个 agent 观测包含自身位置、速度、最近 3 个 landmark 的相对坐标 → 典型 shape 是(18,)Action 维度连续控制输出[vx, vy]→ shape(2,)Global state所有 agent 位置 所有 landmark 位置拼接 → shape(n_agents * 2 n_landmarks * 2)提示这不是 Gym 原生支持的格式。项目中MultiAgentEnv类重写了step()和reset()返回obs_n,reward_n,done_n,info_n四元组并额外提供get_state()方法获取 global state。你若替换自定义环境必须保证这五个输出完备且维度对齐。2.2 Actor-Critic 网络的 Keras 实现细节参数共享与梯度隔离Keras 实现 MADDPG 最容易踩坑的地方是误以为“多个 actor 就是复制粘贴几份模型”。实际上每个 agent 的 actor 必须完全独立不同权重但 critic 必须接收所有 agent 的动作输入。项目中通过build_actor_model()和build_critic_model()两个工厂函数实现def build_actor_model(self, obs_dim, act_dim): inputs Input(shape(obs_dim,)) x Dense(128, activationrelu)(inputs) x Dense(128, activationrelu)(x) outputs Dense(act_dim, activationtanh)(x) # 连续动作tanh 归一化 return Model(inputsinputs, outputsoutputs) def build_critic_model(self, obs_dim, act_dim, n_agents): # critic 输入本 agent 观测 所有 agent 动作含自己 obs_input Input(shape(obs_dim,)) act_inputs [Input(shape(act_dim,)) for _ in range(n_agents)] merged_actions Concatenate()(act_inputs) x Concatenate()([obs_input, merged_actions]) x Dense(128, activationrelu)(x) x Dense(128, activationrelu)(x) outputs Dense(1)(x) # Q value return Model(inputs[obs_input] act_inputs, outputsoutputs)这段代码的关键点在于build_critic_model()的act_inputs是一个长度为n_agents的 Input 列表不是把动作向量 flatten 后再切片——这是保证反向传播时梯度只流回对应 actor 的前提Concatenate()操作在 critic 内部完成actor 输出不参与 critic 的前向计算图拼接避免梯度污染actor 输出用tanh但实际动作需乘以action_bound如 1.0这是连续控制的标准做法项目在agent.py的act()方法里做了封装。2.3 Replay Buffer 的多智能体适配不是简单存 tuple而是存 batched dict标准 DQN 的 replay buffer 存(s, a, r, s, done)但 MADDPG 需要同时保存n_agents份观测、动作、奖励。项目采用MultiAgentReplayBuffer类其store_transition()接收的是transition { obs_n: [obs_0, obs_1, ..., obs_n], # list of arrays, each (obs_dim,) act_n: [act_0, act_1, ..., act_n], # list of arrays, each (act_dim,) rew_n: [rew_0, rew_1, ..., rew_n], # list of scalars obs_next_n: [obs_next_0, ..., obs_next_n], done_n: [done_0, done_1, ..., done_n] }而sample_batch()返回的 batch 是按 agent 维度堆叠的obs_batch: shape(batch_size, n_agents, obs_dim)act_batch: shape(batch_size, n_agents, act_dim)rew_batch: shape(batch_size, n_agents)obs_next_batch: shape(batch_size, n_agents, obs_dim)done_batch: shape(batch_size, n_agents)这种结构直接服务于后续的train_critic()中的target_q_values self.critic_target.predict([obs_next_batch[:, i, :], *act_next_batch.T])——注意*act_next_batch.T是将(batch_size, n_agents, act_dim)转为n_agents个(batch_size, act_dim)的 unpack 操作精准匹配 critic 的 input signature。3. 训练流程拆解从软更新到策略噪声每一步都决定收敛稳定性3.1 MADDPG 的双层软更新机制actor target 与 critic target 必须不同步DDPG 只需一套 soft updateτ ≈ 0.001但 MADDPG 中 critic 的 target 更新频率必须高于 actor 的 target 更新。原因在于critic 依赖所有 agent 的动作若某个 actor 的 target 网络滞后太多critic 的 target Q 值就会严重失真。项目中设定critic_target_update_freq 1每个 step 都执行 critic target 的 soft updateactor_target_update_freq 100每 100 个 step 才更新一次 actor targetdef update_target_networks(self, tau1e-3): # critic target update: every step weights self.critic_model.get_weights() target_weights self.critic_target.get_weights() for i in range(len(weights)): target_weights[i] tau * weights[i] (1 - tau) * target_weights[i] self.critic_target.set_weights(target_weights) # actor target update: only when counter % 100 0 if self.update_counter % self.actor_target_update_freq 0: weights self.actor_model.get_weights() target_weights self.actor_target.get_weights() for i in range(len(weights)): target_weights[i] tau * weights[i] (1 - tau) * target_weights[i] self.actor_target.set_weights(target_weights)这个差异是项目能稳定收敛的核心设计之一。很多复现失败的案例都是把两个 τ 设成一样导致 critic 过早信任了过时的 actor target 动作Q 值估计持续高估。3.2 Ornstein-Uhlenbeck 噪声的 agent 级别实例化避免所有 agent 同步抖动探索策略对 MADDPG 至关重要。项目没有用简单的高斯噪声而是为每个 agent 单独初始化 OU 噪声对象self.noise [OUNoise(mu0, theta0.15, sigma0.2) for _ in range(n_agents)]OUNoise类继承自np.random.Generator其sample()方法生成符合均值回归特性的连续噪声。关键点在于theta0.15控制回归速度太小则探索不足太大则震荡sigma0.2控制噪声幅度需与action_bound匹配如 action_bound1.0则 sigma0.2 合理每个 agent 拥有独立的噪声实例确保它们的探索是异步的——否则所有 agent 同时往左猛打方向盘系统必然崩溃。3.3 Critic loss 的 multi-agent 加权设计reward shaping 的隐式实现标准 critic loss 是mse(Q_pred, Q_target)但 MADDPG 中Q_target的计算涉及所有 agent 的 next action。项目中compute_target_q_values()的核心逻辑是# 对每个 agent i构造其 critic 的 target 输入 obs_next_i obs_next_batch[:, i, :] # 本 agent 下一时刻观测 act_next_i act_next_batch[:, i, :] # 本 agent 下一时刻动作来自其 own actor_target # 其他 agent 的动作也来自各自的 actor_target act_next_others [act_next_batch[:, j, :] for j in range(n_agents) if j ! i] # 拼成 critic 输入[obs_next_i, act_next_i, act_next_others[0], ...] q_target_inputs [obs_next_i, act_next_i] act_next_others q_target self.critic_target.predict(q_target_inputs).flatten() # target Q reward_i gamma * q_target * (1 - done_i) target_q rew_batch[:, i] self.gamma * q_target * (1 - done_batch[:, i])这里rew_batch[:, i]是第 i 个 agent 的 reward意味着每个 agent 的 critic 是独立优化的目标是拟合自身 reward 的 discounted sum。这天然支持 reward shaping你可以给协作 agent 设计稀疏 reward如只在成功抓取时给正奖而 critic 仍能通过 bootstrapping 学到中间策略价值。4. 避坑指南五个血泪经验总结出的 MADDPG 复现雷区4.1 现象训练初期 critic loss 突然爆炸1e5随后 actor loss 归零原因Q_target计算时未屏蔽done的 agent。当某个 agentdoneTrue其obs_next可能是非法值如全零但代码仍将其喂给 critic_target导致输出 NaN。解决在compute_target_q_values()中强制对doneagent 的q_target设为 0且target_q计算时用np.where(done_batch[:, i], rew_batch[:, i], target_q)替代简单加法。4.2 现象所有 agent 的策略趋同最终集体撞墙或原地打转原因actor 网络初始化权重相同且 critic 输入拼接顺序固定如总是act_0, act_1, act_2导致梯度更新方向高度一致。解决为每个 agent 的 actor 使用不同随机种子初始化tf.keras.utils.set_random_seed(i)并在build_critic_model()中对act_inputs列表做随机 shuffle训练前固定一次非每 step。4.3 现象训练 10k step 后 reward 曲线平台期但 agent 行为明显未收敛原因Ornstein-Uhlenbeck 噪声的sigma衰减策略缺失。固定sigma0.2会导致后期探索过强无法精细调优。解决在agent.py的act()方法中加入线性衰减sigma max(0.05, 0.2 - episode * 0.0001)并记录episode数非 step。4.4 现象obs_next_batch维度报错ValueError: Input 0 is incompatible with layer...原因环境reset()返回的obs_n是 list of array但某些 step 中某个 agent 的obsshape 异常如(17,)而非(18,)导致 batch stack 失败。解决在MultiAgentReplayBuffer.store_transition()开头插入断言assert all(len(obs.shape) 1 and obs.shape[0] self.obs_dim for obs in obs_n)快速定位环境 bug。4.5 现象GPU 显存 OOM即使 batch_size32原因critic model 的 input signature 包含n_agents个 Input 层Keras 默认为每个 Input 分配独立显存缓冲区n_agents4时显存占用翻 4 倍。解决改用tf.functiontf.TensorArray动态拼接动作需重写 critic call或更实用的方案——将所有 agent 动作预拼为(batch_size, n_agents * act_dim)修改 critic 输入为单个 Input内部 reshape 后 split。项目已提供critic_flat_input.py作为备选实现。5. 模型诊断与可视化用三张图看穿 MADDPG 的健康状态5.1 Critic Q-value 分布图判断 reward scaling 是否合理MADDPG 的 critic 输出是 scalar Q 值其分布应随训练逐渐右移正值增大且方差缩小。在train.py的 logging 部分添加# 在每个 episode 结束后 q_values self.critic_model.predict([ obs_batch[:, 0, :], *act_batch.T # act_batch shape: (batch_size, n_agents, act_dim) ]) plt.hist(q_values.flatten(), bins50, alpha0.7, labelfEpisode {ep}) plt.xlabel(Q Value) plt.ylabel(Frequency) plt.title(Critic Q-value Distribution Evolution) plt.legend() plt.savefig(fq_dist_ep{ep}.png)健康指标训练中期~2000 ep直方图主峰应从 [-5, 5] 移至 [0, 10]若长期卡在负值区间说明 reward 设计过苛刻如reward -distance未加偏置若方差始终很大跨度 20说明 critic 过拟合或 learning rate 过高。5.2 Actor Policy Gradient Norm 曲线识别梯度消失/爆炸Keras 默认不暴露梯度需手动 hook。在train_actor()中插入with tf.GradientTape() as tape: actions self.actor_model(obs_batch[:, i, :]) # critic 输入本 agent obs 所有 agent actions q_input [obs_batch[:, i, :]] [actions if ji else act_batch[:, j, :] for j in range(self.n_agents)] q_value self.critic_model(q_input) grads tape.gradient(q_value, self.actor_model.trainable_variables) grad_norm tf.linalg.global_norm(grads) self.writer.add_scalar(fgrad_norm/agent_{i}, grad_norm.numpy(), step)健康指标梯度 norm 应稳定在1e-3 ~ 1e-1区间。若 1e-4说明 actor 已饱和tanh 输出接近 ±1或 critic 梯度太弱若 1需立即降低 actor learning rate项目默认 1e-4可试 5e-5。5.3 Multi-agent Trajectory Overlay用 Matplotlib 直观验证协同性对simple_spread环境提取每个 episode 的obs_n位置信息绘制轨迹图Agent IDColorRoleExpected Behavior0redLeader主动靠近 landmark引导 others1blueFollower-1保持与 leader 的固定距离2greenFollower-2填补 leader 与 landmark 间空隙def plot_episode_trajectory(self, obs_history, ep_id): fig, ax plt.subplots(figsize(8, 8)) for i in range(self.n_agents): pos_x [obs[i][0] for obs in obs_history] # x coordinate pos_y [obs[i][1] for obs in obs_history] # y coordinate ax.plot(pos_x, pos_y, o-, labelfAgent {i}, color[red,blue,green][i]) ax.set_xlim(-1.5, 1.5) ax.set_ylim(-1.5, 1.5) ax.legend() ax.grid(True) plt.savefig(ftraj_ep{ep_id}.png)关键观察点健康训练下leader 轨迹应呈放射状指向各 landmarkfollowers 轨迹应呈现平行或镜像特征。若所有轨迹重叠说明 policy 未分化若频繁交叉碰撞说明 critic 的 collision penalty 未生效或 reward 设计缺陷。从那以后我每次调试新环境都强制走一遍这三张图的生成流程——不是为了炫技而是用最原始的视觉反馈逼自己直面模型到底在学什么。Q 值分布不对我就去查 reward 函数梯度 norm 爆了我就先砍 learning rate 再看轨迹图乱成毛线团我就停掉训练打开 env 的 step 函数单步 debug。这些习惯省下的 debug 时间够我多跑三个 baseline。希望帮到你。本文还有配套的精品资源点击获取