简介:本资源是一套基于MADDPG(Multi-Agent Deep Deterministic Policy Gradient)算法实现的多智能体博弈对抗完整Python项目,面向计算机、人工智能、自动化等专业的本科生与研究生,适用于毕业设计、课程设计、期末大作业及科研入门实践。项目涵盖环境建模(含predator_prey、pong_duel等典型对抗场景)、MADDPG核心算法实现(DDPG.py、MADDPG.py、network.py)、训练主流程(main.py)、工具函数(rl_utils.py)及可视化记录(GIF动画共10个),代码均经实测可运行,并配有详细中文注释与README说明。压缩包共91个文件,以76个Python源码为主,辅以配置文件(cfg)、Jupyter示例(ipynb)、环境依赖说明(txt、md)及测试脚本,整体大小3.26MB,目录结构模块清晰,便于理解多智能体协作与对抗机制。目前已有1256人学习下载,适合零基础入门者系统掌握MADDPG框架,也支持进阶用户快速二次开发与算法改进。
1. 项目概述:从单打独斗到群体博弈的智能跃迁
在人工智能领域,让一个智能体学会玩雅达利游戏或者下围棋,已经不是什么新鲜事了。AlphaGo、DQN这些明星算法,本质上解决的都是“单智能体”问题——一个大脑,面对一个环境,目标是最大化自己的长期收益。但现实世界远比这复杂。想象一下一场足球赛,或者金融市场上的高频交易,甚至是一群无人机协同执行搜索任务。这里没有唯一的“主角”,每个参与者(智能体)都在根据其他参与者的行为,动态调整自己的策略,彼此之间既有合作,也有竞争。这就是多智能体强化学习的核心战场。
我最近花了不少时间,复现并深入研究了MADDPG这个算法。MADDPG,全称Multi-Agent Deep Deterministic Policy Gradient,可以看作是经典单智能体算法DDPG在多智能体场景下的自然延伸与革新。它要解决的,正是上述群体博弈中的核心难题:环境的不稳定性。在单智能体设定中,环境是静止的(或遵循固定动力学),智能体可以安心地探索和学习。但在多智能体环境中,环境的变化直接源于其他智能体的策略更新,这就导致每个智能体眼中的“环境”都在剧烈波动,传统的经验回放机制会因数据过时而失效,学习过程极难收敛。
这个项目,就是基于Python,从零开始实现MADDPG算法,并构建了一个经典的多智能体博弈对抗环境进行验证。代码包含了完整的模型定义、训练循环、环境交互以及详尽的注释。对于想深入理解多智能体强化学习,尤其是想搞清楚“集中式训练,分布式执行”这个核心范式如何落地的朋友,这份源码和解析会是一个很好的起点。无论你是强化学习的研究者,还是对AI博弈论感兴趣的工程师,都能从中获得可以直接运行、修改和拓展的实践框架。
2. 核心原理拆解:MADDPG如何破解多智能体学习困局
要理解MADDPG的巧妙之处,我们必须先直面多智能体学习的根本挑战,然后看它是如何层层递进地给出解决方案的。
2.1 多智能体学习的核心挑战:非平稳性与信用分配
在单智能体强化学习中,我们通常用马尔可夫决策过程来建模。智能体在状态s_t下采取动作a_t,环境转移到新状态s_{t+1}并给出奖励r_t。环境的状态转移概率P(s_{t+1} | s_t, a_t)是固定的。然而,在多智能体系统中,假设有N个智能体,状态转移概率变成了P(s_{t+1} | s_t, a^1_t, a^2_t, ..., a^N_t)。当其他智能体的策略π_j随着学习不断变化时,对于智能体i而言,这个转移概率就在不断变化。这就破坏了经验回放池中数据的一致性:你之前存储的(s, a, r, s')经验元组,是基于旧的对手策略产生的,现在对手“学聪明了”,这些旧经验指导下的梯度更新很可能将你引向错误的方向。
另一个挑战是信用分配。当所有智能体共享一个全局奖励时(例如,团队赢了比赛),很难判断每个智能体的具体贡献。是前锋的射门关键,还是后卫的抢断重要?这需要算法能够区分个体贡献。
2.2 MADDPG的核心思想:集中式批评家与分布式执行者
MADDPG的论文标题点明了精髓:Multi-Agent Actor-Critic for Mixed Cooperative-Competitive Environments。它的核心是一种“集中式训练,分布式执行”的范式。
1. 对每个智能体,配备一个“上帝视角”的批评家:这是MADDPG最关键的创新。每个智能体i都有自己的执行者网络和批评家网络。执行者网络μ_i是分布式的,它只观察局部信息o_i(可能是全局状态的一部分),并输出动作a_i。但它的批评家网络Q_i则拥有“特权信息”。在训练时,Q_i的输入不仅仅是o_i和a_i,而是所有智能体的观测o_1, ..., o_N和所有智能体的动作a_1, ..., a_N。也就是说,批评家是在一个全局的、信息完备的视角下,来评估智能体i的动作好坏。
为什么这能解决非平稳性问题?因为在训练批评家时,我们将其他智能体的动作也作为输入。假设其他智能体的策略变化了,它们产生的动作分布也就变了。但只要我们能在输入中给出当前时刻其他智能体的真实动作(或来自它们目标策略的动作),那么对于批评家网络来说,问题就重新变得“平稳”了——它要学习的是一个关于(全观测,全动作)到Q值的静态映射。这相当于把环境的不确定性,转移到了批评家网络的输入中,而网络本身要拟合的函数是稳定的。
2. 执行者只依赖局部观测:尽管批评家训练时用了全局信息,但执行者网络在执行时,依然只使用它自己的局部观测o_i。这保证了算法的可扩展性和分布式执行的可行性。智能体在部署时,不需要知道其他智能体的状态或动作,完全自主决策。
3. 借鉴DDPG的稳定训练技巧:MADDPG继承了DDPG的成功经验,为每个智能体都使用了目标网络、经验回放和软更新。
- 目标网络:每个智能体都有目标执行者网络
μ'_i和目标批评家网络Q'_i,用于计算稳定的目标Q值,缓解自举带来的波动。 - 经验回放:存储的经验元组是
(o, a, r, o'),其中o和o'是所有智能体的观测集合。这为批评家提供了充足且多样的训练数据。 - 软更新:目标网络的参数通过缓慢跟踪在线网络参数来更新:
θ' ← τθ + (1-τ)θ',通常τ是一个很小的数(如0.01),这进一步稳定了学习过程。
注意:这里有一个非常重要的实现细节。在计算目标Q值时,我们需要下一个状态
o'下所有智能体的动作。MADDPG论文中提出,这里应该使用目标执行者网络μ'_j来计算每个智能体j的下一个动作a'_j,然后将(o', a'_1, ..., a'_N)输入目标批评家网络Q'_i。这能防止因在线策略剧烈变化而导致的目标值震荡。
2.3 策略集成与对手建模的拓展
基础的MADDPG已经很强大了,但论文还讨论了两种高级技巧,以应对更复杂的博弈场景:
- 策略集成:为每个智能体维护多个策略,在训练时随机选择一个策略来执行。这可以防止智能体过度适应某几个特定对手的策略,提升鲁棒性。类似于自我对弈,但更高效。
- 推断其他智能体的策略:在某些无法获得其他智能体策略的设定下,每个智能体可以维护一个对手模型,用来预测其他智能体的动作或策略参数,并将预测结果输入自己的批评家网络。这更贴近完全分布式、信息不对称的现实场景。
我们这个基础实现主要聚焦于核心的MADDPG框架,即假设在训练时可以获得所有智能体的动作信息(这在仿真环境中是容易实现的)。掌握了这个基础,再拓展到策略集成或对手建模就会容易很多。
3. 代码架构与核心模块实现解析
理解了原理,我们来看代码如何组织。一个清晰的项目结构是复现复杂算法的第一步。我们的项目主要包含以下几个核心模块:
maddpg_project/ ├── agents/ │ ├── __init__.py │ ├── maddpg.py # MADDPG智能体类,核心中的核心 │ └── noise.py # 动作探索噪声(如OU噪声) ├── networks/ │ ├── __init__.py │ ├── actors.py # 执行者网络定义 │ └── critics.py # 批评家网络定义 ├── envs/ │ └── simple_env.py # 一个自定义的简单多智能体博弈环境 ├── replay_buffers/ │ └── multi_agent_replay_buffer.py # 多智能体经验回放池 ├── config.py # 超参数配置 ├── train.py # 主训练脚本 └── utils.py # 工具函数3.1 神经网络定义:执行者与批评家
我们使用PyTorch来构建网络。执行者网络通常是一个多层感知机,输入是智能体的局部观测维度,输出是动作维度(通常经过tanh激活函数映射到[-1, 1]区间,再根据环境缩放)。
# networks/actors.py import torch import torch.nn as nn import torch.nn.functional as F class Actor(nn.Module): def __init__(self, obs_dim, action_dim, hidden_dim=256): super(Actor, self).__init__() self.fc1 = nn.Linear(obs_dim, hidden_dim) self.fc2 = nn.Linear(hidden_dim, hidden_dim) self.fc3 = nn.Linear(hidden_dim, action_dim) # 层标准化可以加速训练并稳定学习过程 self.ln1 = nn.LayerNorm(hidden_dim) self.ln2 = nn.LayerNorm(hidden_dim) def forward(self, obs): x = F.relu(self.ln1(self.fc1(obs))) x = F.relu(self.ln2(self.fc2(x))) # 使用tanh将输出限制在[-1, 1],适用于连续控制 actions = torch.tanh(self.fc3(x)) return actions批评家网络是MADDPG的特色所在。它的输入是所有智能体的观测和所有智能体的动作的拼接。
# networks/critics.py class Critic(nn.Module): def __init__(self, total_obs_dim, total_action_dim, hidden_dim=256): super(Critic, self).__init__() # 输入维度 = 所有观测总维度 + 所有动作总维度 input_dim = total_obs_dim + total_action_dim self.fc1 = nn.Linear(input_dim, hidden_dim) self.fc2 = nn.Linear(hidden_dim, hidden_dim) self.fc3 = nn.Linear(hidden_dim, 1) self.ln1 = nn.LayerNorm(hidden_dim) self.ln2 = nn.LayerNorm(hidden_dim) def forward(self, all_obs, all_actions): # all_obs, all_actions 已经是拼接好的张量 x = torch.cat([all_obs, all_actions], dim=1) x = F.relu(self.ln1(self.fc1(x))) x = F.relu(self.ln2(self.fc2(x))) q_value = self.fc3(x) # 输出单个Q值 return q_value实操心得:网络初始化与激活函数神经网络的初始化对强化学习的稳定性至关重要。对于执行者网络的最后一层,我们通常使用一个非常小的权重初始化(例如
nn.init.uniform_(self.fc3.weight, -3e-3, 3e-3)),以确保初始策略的输出动作接近零(即无动作),便于探索。对于批评家网络,也可以采用类似的小规模初始化。使用ReLU和LayerNorm的组合,在实践中比单纯的ReLU或Tanh激活函数更能稳定深度网络的训练,尤其是在处理多智能体这种高维输入时。
3.2 MADDPG智能体类的封装
这是整个算法的枢纽,它封装了一个智能体所需的所有组件:在线/目标执行者、在线/目标批评家、优化器、噪声生成器以及更新逻辑。
# agents/maddpg.py class MADDPGAgent: def __init__(self, obs_dim, action_dim, agent_id, args): self.id = agent_id self.obs_dim = obs_dim self.action_dim = action_dim self.gamma = args.gamma # 折扣因子 self.tau = args.tau # 目标网络软更新参数 self.lr_actor = args.lr_actor self.lr_critic = args.lr_critic # 网络定义 self.actor = Actor(obs_dim, action_dim).to(device) self.actor_target = Actor(obs_dim, action_dim).to(device) self.critic = Critic(args.total_obs_dim, args.total_action_dim).to(device) self.critic_target = Critic(args.total_obs_dim, args.total_action_dim).to(device) # 硬拷贝初始化目标网络参数,确保起始一致 self.actor_target.load_state_dict(self.actor.state_dict()) self.critic_target.load_state_dict(self.critic.state_dict()) # 优化器 self.actor_optimizer = torch.optim.Adam(self.actor.parameters(), lr=self.lr_actor) self.critic_optimizer = torch.optim.Adam(self.critic.parameters(), lr=self.lr_critic) # 动作探索噪声 self.noise = OUNoise(action_dim) # Ornstein-Uhlenbeck噪声,适合惯性系统 def select_action(self, obs, explore=True): obs = torch.FloatTensor(obs).unsqueeze(0).to(device) with torch.no_grad(): action = self.actor(obs).cpu().numpy().flatten() if explore: noise = self.noise.sample() action = np.clip(action + noise, -1.0, 1.0) # 添加噪声并裁剪 return action def learn(self, batch, agents): # batch: (obs, actions, rewards, next_obs, dones) # agents: 所有智能体的列表,用于获取目标动作 obs, actions, rewards, next_obs, dones = batch agent_idx = self.id # 1. 更新批评家网络 with torch.no_grad(): # 计算目标动作:使用目标执行者网络 target_next_actions = [] for i, agent in enumerate(agents): target_next_actions.append(agent.actor_target(next_obs[:, i*self.obs_dim:(i+1)*self.obs_dim])) target_next_actions = torch.cat(target_next_actions, dim=1) # 计算目标Q值 target_q_next = self.critic_target(next_obs, target_next_actions) target_q = rewards[:, agent_idx].unsqueeze(1) + self.gamma * (1 - dones.unsqueeze(1)) * target_q_next current_q = self.critic(obs, actions) critic_loss = F.mse_loss(current_q, target_q.detach()) # 注意detach self.critic_optimizer.zero_grad() critic_loss.backward() # 梯度裁剪,防止批评家网络训练不稳定 torch.nn.utils.clip_grad_norm_(self.critic.parameters(), 1.0) self.critic_optimizer.step() # 2. 更新执行者网络 # 重新计算当前状态下,本智能体执行者网络给出的动作 current_actions_pred = [] for i, agent in enumerate(agents): if i == agent_idx: # 对于当前智能体,使用在线执行者网络,且需要梯度 current_actions_pred.append(self.actor(obs[:, i*self.obs_dim:(i+1)*self.obs_dim])) else: # 对于其他智能体,使用它们当前的动作(从batch中取),且不需要梯度 current_actions_pred.append(actions[:, i*self.action_dim:(i+1)*self.action_dim]) current_actions_pred = torch.cat(current_actions_pred, dim=1) actor_loss = -self.critic(obs, current_actions_pred).mean() # 最大化Q值 self.actor_optimizer.zero_grad() actor_loss.backward() self.actor_optimizer.step() # 3. 软更新目标网络 self.soft_update(self.actor_target, self.actor) self.soft_update(self.critic_target, self.critic) return critic_loss.item(), actor_loss.item()注意事项:梯度计算与停止梯度在更新执行者时,我们构造的
current_actions_pred张量需要特别注意。对于当前正在更新的智能体,我们使用其在线执行者网络生成动作,这部分需要计算梯度。对于其他智能体的动作,我们直接使用经验回放池中存储的动作,这部分必须被当作常量处理(在PyTorch中,它们来自actions张量,本身没有requires_grad,或者我们需要detach())。如果错误地将其他智能体的动作也连接到计算图中,那么在更新当前智能体执行者时,会错误地尝试去优化其他智能体的策略,这完全违背了分布式执行者的设定,会导致训练彻底失败。
3.3 多智能体经验回放池
与单智能体不同,多智能体的经验回放池需要存储所有智能体的联合观测和联合动作。
# replay_buffers/multi_agent_replay_buffer.py import numpy as np import random class MultiAgentReplayBuffer: def __init__(self, capacity, obs_dims, action_dims): self.capacity = capacity self.obs_buffers = [np.zeros((capacity, dim)) for dim in obs_dims] self.action_buffers = [np.zeros((capacity, dim)) for dim in action_dims] self.reward_buffers = [np.zeros((capacity, 1)) for _ in obs_dims] self.next_obs_buffers = [np.zeros((capacity, dim)) for dim in obs_dims] self.done_buffers = [np.zeros((capacity, 1)) for _ in obs_dims] self.ptr = 0 self.size = 0 def store(self, obs, actions, rewards, next_obs, dones): idx = self.ptr for i in range(len(self.obs_buffers)): self.obs_buffers[i][idx] = obs[i] self.action_buffers[i][idx] = actions[i] self.reward_buffers[i][idx] = rewards[i] self.next_obs_buffers[i][idx] = next_obs[i] self.done_buffers[i][idx] = dones[i] self.ptr = (self.ptr + 1) % self.capacity self.size = min(self.size + 1, self.capacity) def sample_batch(self, batch_size): indices = np.random.choice(self.size, batch_size, replace=False) # 将每个智能体的数据分别取出,然后拼接成联合张量 obs = np.concatenate([buf[indices] for buf in self.obs_buffers], axis=1) actions = np.concatenate([buf[indices] for buf in self.action_buffers], axis=1) rewards = np.concatenate([buf[indices] for buf in self.reward_buffers], axis=1) next_obs = np.concatenate([buf[indices] for buf in self.next_obs_buffers], axis=1) dones = np.concatenate([buf[indices] for buf in self.done_buffers], axis=1) return (torch.FloatTensor(obs), torch.FloatTensor(actions), torch.FloatTensor(rewards), torch.FloatTensor(next_obs), torch.FloatTensor(dones))这种设计使得采样时,我们直接得到一个批次的多智能体联合经验,可以直接用于批评家网络的输入。
4. 训练流程与超参数调优实战
有了上述模块,训练流程就清晰了。主训练循环train.py负责协调环境交互、数据收集和智能体学习。
4.1 主训练循环结构
# train.py (核心循环部分) def train(): # 初始化环境、智能体、回放池 env = SimpleMultiAgentEnv() agents = [MADDPGAgent(...) for _ in range(env.n_agents)] replay_buffer = MultiAgentReplayBuffer(capacity=args.buffer_size, ...) total_steps = 0 episode_rewards = [] for episode in range(args.max_episodes): obs = env.reset() episode_reward = np.zeros(env.n_agents) for step in range(args.max_episode_len): # 1. 收集动作 actions = [] for i, agent in enumerate(agents): action = agent.select_action(obs[i], explore=True) actions.append(action) # 2. 环境交互 next_obs, rewards, dones, _ = env.step(actions) # 3. 存储经验 replay_buffer.store(obs, actions, rewards, next_obs, dones) obs = next_obs episode_reward += rewards # 4. 如果回放池数据足够,开始学习 if replay_buffer.size > args.batch_size: for i, agent in enumerate(agents): batch = replay_buffer.sample_batch(args.batch_size) agent.learn(batch, agents) # 传入所有智能体用于计算目标动作 total_steps += 1 if any(dones): break episode_rewards.append(episode_reward) # 定期打印日志、保存模型 if episode % args.print_interval == 0: print(f"Episode {episode}, Total Steps {total_steps}, Avg Reward: {np.mean(episode_rewards[-args.print_interval:])}") if episode % args.save_interval == 0: save_models(agents, episode)4.2 关键超参数解析与调优经验
MADDPG的训练对超参数比较敏感。以下是一些核心参数及其典型设置和调优思路:
| 超参数 | 典型值/范围 | 作用与调优心得 |
|---|---|---|
| 学习率 (lr_actor, lr_critic) | 1e-4 到 1e-3 | 批评家学习率通常比执行者大(如1e-3 vs 1e-4)。批评家需要快速拟合Q值,而执行者策略更新需要更平滑。如果训练不稳定(奖励剧烈震荡),首先尝试降低学习率。 |
| 折扣因子 (gamma) | 0.95 - 0.99 | 控制未来奖励的重要性。在回合制或目标明确的对抗中(如“捉迷藏”),可以设高一些(0.99)。在持续任务中,0.95-0.98是常见选择。 |
| 软更新系数 (tau) | 0.01 - 0.05 | 控制目标网络更新速度。值越小,目标网络越稳定,但学习速度越慢。通常从0.01开始,如果学习太慢可微增至0.05。 |
| 回放池大小 (buffer_size) | 1e5 - 1e6 | 存储历史经验。越大越好,但受内存限制。对于复杂环境,至少需要1e6量级以保证经验多样性。 |
| 批次大小 (batch_size) | 128 - 1024 | 每次从回放池采样的经验数量。太小会导致梯度噪声大,太大会降低采样效率并增加内存负担。256或512是较好的起点。 |
| 探索噪声 | OUNoise参数 | theta(均值回归速度)常设为0.15,sigma(波动率)设为0.2。初始sigma可稍大(如0.3)鼓励探索,后期可随训练衰减。 |
| 网络隐藏层维度 | 128, 256, 512 | 智能体越多、观测/动作空间越复杂,网络需要越大的容量。从128或256开始,如果学习能力不足(奖励上不去),再尝试增加。 |
实操心得:训练监控与早期诊断多智能体训练耗时很长,动辄几十万步。建立有效的监控机制至关重要。除了记录回合总奖励,我强烈建议:
- 记录每个智能体的个体奖励:这能帮你判断是某个智能体“拖后腿”,还是协同出了问题。
- 记录批评家和执行者的损失值:批评家损失应逐渐下降并趋于平稳。如果批评家损失爆炸(变成NaN或极大值),几乎肯定是梯度爆炸了,需要检查学习率、梯度裁剪或网络初始化。执行者损失(负Q值)的绝对值应逐渐增大(因为它在最大化Q值),但波动可能较大。
- 定期可视化策略:在简单环境中,可以定期让智能体运行一个回合,并渲染出来观察其行为。看到智能体从随机乱跑到逐渐有策略地互动,是最直接的反馈。
4.3 一个简单的对抗环境示例
为了验证算法,我实现了一个极简的“追击-逃避”环境。有两个智能体:追捕者(红色)和逃跑者(蓝色),在一个二维平面内。追捕者的目标是靠近逃跑者,逃跑者的目标是远离追捕者。观测是各自的位置,动作是二维平面内的速度向量(归一化到[-1,1])。奖励函数设计如下:
- 追捕者奖励:每一步获得一个与两者距离负相关的奖励(如
-0.1 * distance),如果距离小于某个阈值,获得一个大正奖励(如+10)。 - 逃跑者奖励:每一步获得一个与距离正相关的奖励(如
+0.1 * distance),如果被抓住(距离过近),获得一个大负奖励(如-10)。
这个环境虽然简单,但完美体现了竞争性多智能体的特性:一个智能体的最优策略完全依赖于另一个智能体的策略。通过训练,你可以观察到追捕者学会预测逃跑者的移动方向进行拦截,而逃跑者则学会迂回和突然变向。
5. 常见问题排查与实战调试技巧
即使代码逻辑正确,第一次运行MADDPG也常常会遇到训练失败的情况。以下是我在复现过程中踩过的坑和总结的排查清单。
5.1 训练不收敛或奖励曲线震荡剧烈
这是最常见的问题。
检查点1:批评家损失是否爆炸?
- 现象:批评家损失在几次更新后突然变成NaN或一个巨大的数值。
- 排查:
- 梯度裁剪:确保在更新批评家网络后执行了梯度裁剪(
torch.nn.utils.clip_grad_norm_(critic.parameters(), max_norm))。max_norm通常设为0.5或1.0。 - 学习率过高:尝试将批评家学习率降低一个数量级(例如从1e-3降到1e-4)。
- 奖励尺度:检查环境给出的奖励值是否过大。如果单步奖励在几百上千的量级,Q值会很容易爆炸。考虑对奖励进行缩放(如除以10或100)。
- 网络初始化:确认批评家网络最后一层的权重初始化是否足够小。
- 梯度裁剪:确保在更新批评家网络后执行了梯度裁剪(
检查点2:智能体策略没有改进,奖励始终很低。
- 现象:奖励曲线没有上升趋势,智能体行为看起来像随机动作。
- 排查:
- 探索是否足够?在训练初期,确保噪声的
sigma足够大,让智能体充分探索。可以尝试在训练初期使用更高的噪声强度。 - 回放池是否已填充?确保在开始学习(调用
learn)之前,回放池中的数据量已经超过了一个批次的大小。通常需要先进行数千步的随机探索来填充回放池。 - 目标Q值计算是否正确?这是最容易出错的地方。仔细核对
learn函数中计算target_q_next和target_q的代码,确保dones掩码和gamma折扣应用正确。 - 执行者更新是否生效?检查执行者损失的计算。它应该是
-critic(obs, current_actions_pred).mean()。确保current_actions_pred中只有当前智能体的动作需要梯度。打印执行者损失的数值,看它是否有变化(通常是负值,且绝对值在增大)。
- 探索是否足够?在训练初期,确保噪声的
5.2 智能体策略陷入局部最优或出现奇怪行为
- 现象:追捕者总是朝一个固定方向移动,或者两个智能体在原地打转。
- 排查:
- 探索噪声衰减:如果你设置了噪声衰减,可能衰减得太快,导致后期探索不足,策略无法跳出局部最优。可以尝试更慢的衰减速度,或者不使用衰减。
- 奖励函数设计:多智能体的奖励函数设计是门艺术。不合理的奖励会导致意想不到的纳什均衡。例如,如果追捕者只有最终抓住才有奖励,中间每一步都是0或小负奖励,它可能干脆“躺平”不动。尝试加入密集奖励(如每一步根据距离给予奖励)。
- 策略集成:尝试实现论文中的策略集成技巧。为每个智能体维护多个策略,定期随机切换,这能有效防止对特定对手策略的过拟合。
5.3 训练速度慢或内存占用高
- 排查:
- 批量大小与网络大小:过大的批量大小(如2048)和过大的网络(如1024维隐藏层)会显著增加计算量和内存占用。从较小的配置开始(批量256,隐藏层128)。
- 经验回放池:如果环境步频很快,回放池大小设为1e6可能会占用数GB内存。可以根据需要调整。对于简单环境,1e5可能就够了。
- 向量化环境:如果可能,使用类似
SubprocVecEnv的并行环境,可以同时收集多条经验,极大提升数据收集效率。 - 定期保存与评估:不需要每回合都保存模型或渲染评估。可以每1000或5000步进行一次,减少I/O开销。
5.4 代码调试技巧
- 单元测试:为每个核心函数(如
select_action,learn)编写简单的单元测试。例如,用固定的输入检查learn函数一次前向和反向传播是否能正常执行,输出loss是否为标量。 - 前向传播检查:在训练循环开始前,手动构造一批虚拟数据,送入网络和执行
learn函数,确保没有形状错误或数据类型错误。 - 梯度流可视化:在PyTorch中,可以使用
torch.autograd.grad或调试器检查关键张量(如执行者网络参数相对于批评家输出的梯度)是否不为None且数值合理。 - 简化环境:首先在一个最简单的、你知道最优解的环境(例如,两个智能体在一条直线上移动)中测试算法。如果在这个环境中都无法学习到合理策略,那么代码肯定有问题。
最后,多智能体强化学习本身就是一个充满挑战的领域,MADDPG作为其中的一个里程碑算法,为我们提供了一个强大而清晰的框架。这个项目的实现过程,让我深刻体会到“集中式批评家”这一设计的精妙之处——它通过改变信息的流向,巧妙地化解了多智能体学习中的根本矛盾。代码中的每一个细节,从网络输入输出的拼接,到目标动作的计算,再到梯度流的控制,都直接影响着训练的成败。希望这份详细的源码和解读,能帮你绕过我踩过的那些坑,更顺畅地踏入多智能体博弈这个有趣而又复杂的世界。
本文还有配套的精品资源,点击获取