简介:本资源是基于Keras框架实现的多智能体强化学习算法MADDPG(Multi-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 execution
2.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, activation='relu')(inputs) x = Dense(128, activation='relu')(x) outputs = Dense(act_dim, activation='tanh')(x) # 连续动作,tanh 归一化 return Model(inputs=inputs, outputs=outputs) 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, activation='relu')(x) x = Dense(128, activation='relu')(x) outputs = Dense(1)(x) # Q value return Model(inputs=[obs_input] + act_inputs, outputs=outputs)这段代码的关键点在于:
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 target
def update_target_networks(self, tau=1e-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(mu=0, theta=0.15, sigma=0.2) for _ in range(n_agents)]OUNoise类继承自np.random.Generator,其sample()方法生成符合均值回归特性的连续噪声。关键点在于:
theta=0.15控制回归速度,太小则探索不足,太大则震荡;sigma=0.2控制噪声幅度,需与action_bound匹配(如 action_bound=1.0,则 sigma=0.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。当某个 agentdone=True,其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衰减策略缺失。固定sigma=0.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_size=32
原因:critic model 的 input signature 包含n_agents个 Input 层,Keras 默认为每个 Input 分配独立显存缓冲区,n_agents=4时显存占用翻 4 倍。
解决:改用tf.function+tf.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(), bins=50, alpha=0.7, label=f'Episode {ep}') plt.xlabel('Q Value') plt.ylabel('Frequency') plt.title('Critic Q-value Distribution Evolution') plt.legend() plt.savefig(f'q_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 j==i 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(f'grad_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 ID | Color | Role | Expected Behavior |
|---|---|---|---|
| 0 | red | Leader | 主动靠近 landmark,引导 others |
| 1 | blue | Follower-1 | 保持与 leader 的固定距离 |
| 2 | green | Follower-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-', label=f'Agent {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(f'traj_ep{ep_id}.png')关键观察点:健康训练下,leader 轨迹应呈放射状指向各 landmark,followers 轨迹应呈现平行或镜像特征。若所有轨迹重叠,说明 policy 未分化;若频繁交叉碰撞,说明 critic 的 collision penalty 未生效或 reward 设计缺陷。
从那以后我每次调试新环境,都强制走一遍这三张图的生成流程——不是为了炫技,而是用最原始的视觉反馈,逼自己直面模型到底在学什么。Q 值分布不对,我就去查 reward 函数;梯度 norm 爆了,我就先砍 learning rate 再看;轨迹图乱成毛线团,我就停掉训练,打开 env 的 step 函数单步 debug。这些习惯省下的 debug 时间,够我多跑三个 baseline。希望帮到你。
本文还有配套的精品资源,点击获取