简介一套基于MADDPG的多智能体编队控制学习工程包面向深度强化学习初学者和无人机、自动驾驶领域的编队控制研究者旨在解决多智能体在动态环境中形成并保持特定队形、避碰及协同决策等问题。压缩包共18个文件以Python源码为主13个py文件涵盖MADDPG算法主体、DDPGAgent、Actor-Critic网络、经验回放缓冲区、参数配置以及训练与测试脚本另含4个txt状态文件和1个pyc文件包体仅11KB。这些模块清晰展示了从环境搭建、智能体策略更新到结果评估的完整流程模块间分工明确便于直接运行和二次开发调整params.py中的学习率、探索参数或控制器逻辑即可验证不同场景下的编队效果。目前已有1787人学习下载。通过该系统化代码读者能够深入理解MADDPG的多智能体协作机制与深度强化学习的落地实现也可作为课程设计、毕业设计或相关科研工作的有力参照。1. 编队控制为什么押注 MADDPG一个能自己协商的决策框架编队控制在机器人集群、无人车编组和无人机协同里一直是个硬需求传统做法要么依赖集中式规划器做全局路径分配要么靠人工势场这类反应式算法维持队形。前者在智能体数量增加时计算量爆炸后者在动态障碍物和通信延迟面前经常翻车。深度强化学习里的 MADDPGMulti-Agent Deep Deterministic Policy Gradient能解决这个问题它把每个编队成员当成独立决策的智能体通过集中训练、分布执行的框架让所有成员在只拿到局部观测的情况下学出协同编队策略。这篇笔记面向想用 MADDPG 做编队控制的开发者从环境建模、算法改造到训练调参给出可以直接落地的方案和参数。2. 把编队问题翻译成 MARL 建模状态空间、动作空间与奖励塑形2.1 为什么编队控制适合用 MADDPG 而不是单智能体 PPO编队控制的本质是多智能体协同问题队形保持要求每个成员不仅知道自己该去哪还要知道队友在哪、相对位置关系如何。单智能体强化学习把其他成员当作环境的一部分来处理这在智能体数量固定、交互规律简单的场景下勉强可用但一旦编队规模变化或者成员角色互换策略就会失效。MADDPG 采用集中式训练分布式执行CTDE的范式训练时评论家网络能看到所有智能体的状态和动作演员网络只依靠自身观测做决策正好匹配编队控制的实际部署约束。另一个关键点是 MADDPG 天然支持连续动作空间。编队控制里每个成员通常输出速度和转向角这类连续量如果用 DQN 系列算法需要做动作离散化精度和训练效率都会打折。DDPG 家族算法解决连续控制问题是成熟方案MADDPG 又通过多智能体评论家解决了 DDPG 在竞争或合作环境下环境非平稳性的问题这两个特性叠加让它在编队控制里成为首选。2.2 状态空间设计的三个层次自车状态、队友观测与目标信息状态空间设计直接决定训练能不能收敛。我在做编队控制时把状态分成三个层次第一层是自车运动学信息包括位置、速度、航向角第二层是队友的相对位置和相对速度这部分信息是编队保持的核心输入让每个智能体知道自己在队形中的位置第三层是目标和环境信息包括目标点位置、障碍物距离。一个常见误区是把绝对坐标直接塞进状态空间。绝对坐标会让智能体过拟合到特定起点编队起点一换就失效。正确做法是用相对坐标自车与队形参考点的偏移量、自车与每个队友的相对位置关系。这样状态空间在任意全局位置下都有相同的分布结构迁移性要好得多。我一般把状态维度控制在 15 到 30 之间太多冗余特征反而让策略收敛变慢。# 编队控制环境的单步状态构建示例 def get_state(agent_id, fleet_positions, dt): # agent_id: 当前智能体编号 # fleet_positions: 所有智能体位置列表, shape: (n_agents, 4) my_pos fleet_positions[agent_id][:2] my_vel fleet_positions[agent_id][2:] formation_offset formation_pattern[agent_id] # 目标队形下的相对偏移 # 相对位置信息: 以自车为中心的坐标系表示队友位置 state [my_pos[0], my_pos[1], my_vel[0], my_vel[1]] for j, pos in enumerate(fleet_positions): if j ! agent_id: rel_pos pos[:2] - my_pos # 只保留相对位置和相对速度, 避免绝对坐标 state.extend([rel_pos[0], rel_pos[1], pos[2]-my_vel[0], pos[3]-my_vel[1]]) # 队形参考点误差: 当前相对目标队形位置的误差 ref_pos target_pos formation_offset error ref_pos - my_pos state.extend([error[0], error[1]]) return np.array(state, dtypenp.float32)这段代码的核心逻辑是把绝对坐标换算成相对量。第 5 行到第 7 行组装自车速度信息第 9 行到第 14 行遍历所有队友计算相对位置和相对速度第 16 行加入队形误差。注意最后一项 error 是相对目标编队位置的偏移这个信号是奖励函数的直接对应项有很强的引导作用。在实现时要把 state 统一成 numpy float32 格式避免后续网络输入类型不匹配的问题。2.3 动作空间设计连续控制下的输出限幅与安全约束编队控制的动作输出一般是两个维度线速度和角速度。这里有个容易翻车的点如果直接让策略网络输出原始控制量训练初期可能会出现疯狂震荡的输出现象因为网络在探索阶段不知道什么动作是安全的。我的做法是在环境侧做动作限幅把网络输出映射到物理可行的区间。比如线速度限制在 -1.0 到 2.0 m/s角速度限制在 -1.5 到 1.5 rad/s超出范围的输出直接裁剪。更稳妥的做法是把动作输出经过一个平滑滤波器避免相邻时间步的剧烈变化。实际部署时电机或舵机都有响应频率上限如果相邻控制指令跳变太大执行器会跟不上表现为编队抖动甚至发散。我通常在环境里加一阶惯性环节对动作做平滑处理虽然会引入一点延迟但稳定性大幅提升。动作空间还有一层安全约束要考虑智能体之间的碰撞避免。最简单的方式是在环境物理引擎层面做硬约束两个智能体距离小于安全半径时直接报警并给予惩罚。也有更软的做法在动作输出后叠加一个斥力场修正量让智能体自动远离队友。不过我不推荐在环境里做太多软修正因为会掩盖策略本身的缺陷训练出来的策略离开这个修正就失效了。2.4 奖励塑形编队误差、到达奖励与碰撞惩罚的权重博弈奖励函数是编队控制训练的另一个胜负手。一个完整的编队奖励通常包含三部分编队保持奖励、目标到达奖励和碰撞惩罚。编队保持奖励一般设计为队形误差的负函数误差越小奖励越高常见形式是-alpha * formation_error其中 formation_error 是当前队形和期望队形的位置偏差总和。目标到达奖励采用稀疏奖励智能体进入目标点一定范围内给正向奖励这个部分引导智能体最终完成编队移动任务。权重分配是实际调试中最玄学的部分。我一开始给碰撞惩罚设了很大的负值结果智能体全部停在原地不动因为任何移动都可能撞到队友。后来把碰撞惩罚从 -10 降到 -2让智能体在避开碰撞的前提下探索移动策略才慢慢学出来。实践经验是碰撞惩罚的绝对值不能超过到达奖励的 2 到 3 倍否则智能体会过度保守。编队保持奖励的系数也要控制好太大会让智能体只顾保持队形而忽略目标移动训练后期队形成了但编队整体不动。# 编队奖励计算示例: 三项奖励加权求和 def get_reward(agent_id, fleet_pos, target_pos, action): my_pos fleet_pos[agent_id][:2] dist np.linalg.norm(my_pos - target_pos) # 第一项: 目标到达奖励(稀疏) arrive_reward 10.0 if dist 0.5 else 0.0 # 第二项: 编队保持奖励(稠密, 用队形误差) formation_error 0 for j, pos in enumerate(fleet_pos): desired_pos target_pos formation_pattern[j] formation_error np.linalg.norm(pos[:2] - desired_pos) formation_reward -0.1 * formation_error # 第三项: 碰撞惩罚 collide_penalty 0.0 for j, pos in enumerate(fleet_pos): if j ! agent_id: d np.linalg.norm(my_pos - pos[:2]) if d 0.3: collide_penalty -2.0 break reward arrive_reward formation_reward collide_penalty return reward这个奖励函数体现了编队控制任务的两个核心矛盾。第一是稠密奖励和稀疏奖励的配合目标到达給 10 分但只有到达才有编队误差给连续的小负值智能体在探索过程中始终有即时信号可以学习。第二是权重博弈碰撞惩罚 -2.0 与到达奖励 10.0 的比值控制在五倍以内让智能体愿意冒险移动而不是全军蜷缩。实际调参时可以把这三项奖励分别打印出来看曲线判断是哪一项在主导策略的行为。3. FCMADDPG 的算法核心集中式评论家、门控融合与目标策略平滑3.1 从 DDPG 到 MADDPG集中训练分布式执行的原理拆解MADDPG 的核心思想是在 DDPG 的基础上做了两个关键改动。第一个改动是评论家网络的输入从单个智能体的状态动作扩展为所有智能体的状态动作拼接这样评论家在训练时可以感知全局信息对动作价值的评估更准确。第二个改动是每个智能体拥有独立的演员和评论家网络评论家只服务于对应演员的训练不像一些共享参数的方法会引入干扰。环境非平稳性是多智能体强化学习面临的核心难题。在单智能体 DDPG 里环境转移函数是固定的经验回放没有问题。但在多智能体环境里每个智能体的策略都在变化对其他智能体来说环境转移动态不稳定直接套用单智能体经验回放会失败。MADDPG 通过让评论家看到所有智能体的动作来缓解这个问题当某个智能体更新策略时它能意识到其他智能体的动作改变从而把环境的非平稳性对价值估计的影响降低。这就是为什么 FCMADDPG 这类变体都保留了集中式评论家的结构——它是整个算法有效性的根基。3.2 FCMADDPG 的改动点全连接网络、特征融合与目标网络机制FCMADDPG 里的 FC 通常指 Full Connected即所有智能体的状态特征在评论家网络中融合的方式。常见的做法是保留 MADDPG 的多智能体评论家结构但在特征融合层使用全连接方式处理全局状态拼接后的高维输入。这个改动直接影响价值函数的拟合精度编队控制任务里智能体数量通常在三到五个全局状态拼接后的维度在 60 到 150 之间全连接层能有效捕捉特征间的交叉关系。训练稳定方面FCMADDPG 继承了目标网络机制。演员和评论家各维护一套目标网络通过软更新方式缓慢向在线网络靠近。参数 tau 控制更新速度我一般取 0.01这个值的核心作用是防止训练震荡。回放缓冲区也是稳定性的关键一环编队控制经验数据需要较大的缓冲区容量来保证样本多样性我设置容量在 100 万条以上采样批量大小取 256。3.3 评论家网络的输入拼接顺序一致性与归一化处理评论家网络的输入拼接有一个容易踩的坑智能体顺序必须固定。训练时每次采样的经验数据里全局状态拼接顺序错了网络就会学到错误的依赖关系。这个顺序一般在环境初始化时确定训练全程保持不变。我在实现里专门写了一个断言检查拼接顺序防止数据预处理环节出问题。另一个关键点是归一化处理。编队控制里不同特征量纲差异很大位置坐标可能是几十甚至上百的数值相对速度只有个位数目标误差又可能是小数。直接把原始值输入网络会导致梯度被大数值特征主导。我的做法是在环境层面对所有状态做标准化处理用在线均值方差统计做归一化而不是固定的 minmax 缩放。在线归一化能适应训练过程中状态分布的变化经验回放里的数据分布会随着策略提升而改变固定归一化参数会导致后期训练不稳定。# 评论家网络输入拼接: 所有智能体状态与动作的统一编排 def build_critic_input(states, actions): # states: list of state arrays, 每个元素是一个智能体的状态 # actions: list of action arrays, 每个元素是一个智能体的动作 assert len(states) len(actions) state_flat np.concatenate(states).astype(np.float32) action_flat np.concatenate(actions).astype(np.float32) # 归一化: 状态和动作分别做标准归一化 state_norm (state_flat - state_mean) / (state_std 1e-8) action_norm (action_flat - action_mean) / (action_std 1e-8) obs_full np.concatenate([state_norm, action_norm]) return obs_full这个拼接函数的核心是维度对齐和归一化。第 5 行的断言确保智能体数量一致第 7 到 8 行分别把状态和动作展平第 10 到 11 行做标准化。注意归一化的均值方差参数需要预计算或在线更新没有经过归一化的原始输入会让评论家网络的价值估计方差特别大直接影响策略更新的梯度方向。实际训练时我发现不做归一化的评论家 loss 在训练早期会高出几个数量级。3.4 演员网络输出层的激活函数选择双曲正切与动作缩放演员网络输出层的激活函数决定动作分布的范围。我一般用 tanh 作为输出层激活函数它把输出压缩到 [-1, 1] 区间再乘上一个缩放系数就得到实际控制量。tanh 的好处是两端饱和限制了动作的极端值训练前期不会出现疯狂输出。这里有个细节值得注意tanh 在接近饱和区域时梯度很小策略更新效率低。如果训练中发现智能体的动作经常落在最大最小值附近说明缩放系数设得不合理。常见做法是先不加缩放训练一段时间观察动作的实际分布再根据分布区间设置缩放系数。我见过有人在输出层直接接线性激活函数输出范围完全不受控训练到后期全部散开编队飞得乱七八糟。4. 训练一个可用的编队策略环境搭建、核心超参与收敛性判断4.1 环境层面的必要条件多智能体动作同步与终止条件编队环境的动作同步机制经常被忽视。真实编队中每个智能体的决策频率应当一致训练环境里的动作执行要保证同步更新所有智能体同时决定动作然后同时推进环境状态。如果改成异步更新不同智能体看到的转移数据时间基准不一致评论家网络的全局信息就错位了训练必然出问题。终止条件的设置也有讲究。一段训练回合应该在什么情况下结束我的做法是三个终止条件取其一所有智能体都到达目标点、超过最大时间步、发生碰撞。碰撞检测在训练中承担重要的数据清洗作用发生碰撞的回合直接终止避免无效的探索数据污染回放缓冲区。最大时间步我在编队移动任务里设为 200 步太短策略来不及学出完整路径太长训练效率低、收尾困难。4.2 核心超参数初始化学习率、折扣因子、软更新系数超参数配置是编队控制训练前期投入最大的环节。我的初始配置如下演员学习率 1e-4评论家学习率 1e-3折扣因子 0.95软更新系数 0.01回放缓冲区容量 1e6批量大小 256。评论家学习率比演员高一个数量级的原因在于评论家承担更重的拟合任务需要更快的收敛速度。折扣因子 0.95 在编队控制里比常用的 0.99 更合适。编队控制的回合长度一般不长200 步封顶0.95 的折扣让远期奖励衰减快智能体更注重近期行为策略收敛速度加快。如果用 0.99在 200 步的回合里最后一步的奖励对当前决策的影响仍然很大会引入不必要的长期规划负担。我之前踩过这个坑从 0.99 调到 0.95 后训练速度快了接近一倍。# 训练主循环: 经验收集与网络更新 for epoch in range(max_epochs): obs env.reset() done False while not done: # 每个智能体通过演员网络输出动作, 同时加入探索噪声 actions [] for agent_id in range(n_agents): action actor_net(obs[agent_id], evalFalse) np.random.normal(0, 0.1) actions.append(action) next_obs, rewards, done env.step(actions) # 存储五元组经验 replay_buffer.push(obs, actions, rewards, next_obs, done) obs next_obs if len(replay_buffer) batch_size: # 从回放缓冲区采样并更新评论家和演员网络 samples replay_buffer.sample(batch_size) critic_loss update_critic(samples) # 最小化 TD error actor_loss update_actor(samples) # 最大化 critic 输出 soft_update(tau0.01) # 目标网络软更新训练主循环的代码体现了 MADDPG 训练的标准流程。第 4 行的探索噪声用的是高斯噪声标准差 0.1这个值需要随着训练推进衰减。第 10 行到第 13 行的经验存储是基础保障第 15 行到第 17 行的更新顺序是固定的先更新评论家再更新演员最后做目标网络软更新。更新顺序不能颠倒因为演员的梯度依赖评论家的输出评论家没更新就先更新演员相当于拿着旧标尺量新动作。4.3 训练曲线怎么读编队误差下降与策略崩坏的分辨训练曲线是判断模型好坏的第一依据。编队控制任务里最值得关注的是两个指标编队误差均值和回合平均奖励。编队误差均值应该呈现缓慢下降趋势如果在训练早期出现剧烈波动大概率是探索噪声设太大或奖励权重不合适。策略崩坏的显著表现是训练曲线先涨后跌。我在一次实验里看到编队误差降到了 1.0 以下然后断崖式跳到 5.0 以上再也没恢复。排查发现是回放缓冲区里积累了太多成功经验网络开始过拟合到少部分高回报样本加上评论家网络对全局信息的价值估计出现偏差策略瞬间走向反面。解决方法是增大回放缓冲区容量并降低评论家学习率。训练收敛的判断标准不是曲线平滑而是编队误差是否低于预设的阈值以及这个状态能否稳定维持多个回合。我一般把验证间隔设为 20 个 epoch在验证阶段关掉探索噪声用纯贪婪策略跑 50 个回合取平均编队误差。5. 避坑指南编队控制训练里最常见的五个翻车现场5.1 现象一训练开始后编队误差居高不下下降到某个值就停滞不前这个现象的典型表现是编队误差曲线在某个平台上徘徊无论怎么加大训练步数都不下降。原因通常是状态空间里缺少关键信息。编队控制里最常见的缺失信息是目标点与队形参考点的相对关系如果智能体只知道队友位置而不知道当前队形相对目标队的偏差它无法判断应该往哪个方向调整。解决方法是检查状态空间是否包含完整的误差信号自车相对目标队形位置的偏移量、自车相对队友的位置差。我在 2.2 节强调了相对坐标的重要性这里再补充一点如果状态变量里混入了与编队无关的噪声特征也会导致平台期。可以通过在环境里随机丢弃状态特征做对比实验来定位干扰特征。5.2 现象二训练中期策略突然崩坏奖励曲线断崖式下跌策略崩坏是 MADDPG 系列算法最常见的问题我在 4.3 节提到过一个案例。深层原因在于评论家网络对全局状态的价值估计产生偏差导致演员网络被错误的梯度方向引导。复现这个问题的典型条件是回放缓冲区容量小、评论家学习率设置过高。解决时先缩小评论家学习率从 1e-3 降到 3e-4同时把回放缓冲区容量扩充一倍。如果崩坏仍然发生检查目标网络软更新系数把 tau 从 0.01 降到 0.005降低目标网络漂移速度。我用的一个排查口诀是崩坏时先调评论家再调回放最后调 tau——这三个参数按顺序调整不要同时改。5.3 现象三训练结束后只有部分智能体能到达目标点编队无法整体移动这个现象非常诡异编队里的某个智能体表现出色总能到达目标点但其他智能体原地打转或者尾随不了。产生这个问题的原因通常是智能体之间的奖励分配不均或者初始状态不对称导致优势积累。最初几个回合里跑得快的智能体获得了更多到达奖励评论家对它给出的价值估计更高策略更新进一步强化了这种行为形成马太效应。解决思路是在奖励设计里加入编队整体性指标比如队形中心点的移动进度作为共享奖励加给所有智能体让每个智能体都能从编队整体前进中获益。同时检查初始状态的随机化程度如果初始位置太固定智能体会过拟合到特定初始队形换个起点就失效。5.4 现象四策略在训练环境里表现良好换一个起点或队形就完全失效这个问题指向泛化性不足根源在于训练环境的随机性不够。编队控制里常见的随机性来源有三个初始位置随机偏移、目标点位置随机分布、队形参考点的旋转角度随机变化。如果这三个随机性都没加或者加得不够策略只能学会在固定条件下工作。我的做法是在环境初始化阶段加入均匀分布的随机扰动每个智能体的初始位置在基准点附近偏移正负 1 米目标点在 5 米半径内随机选取队形每次训练回合随机旋转一个角度。投入产出比最高的还是队形旋转因为编队控制策略必须对任意朝向都有响应能力。5.5 现象五训练过程非常不稳定loss 曲线充满毛刺loss 曲线毛刺多、训练过程抖动剧烈通常是经验回放数据分布不均匀导致的。编队控制任务里的数据天然不平衡早期多数是碰撞和乱走的数据后期才是高质量编队轨迹。如果回放缓冲区没有做优先级采样所有经验被等概率采样早期低质量样本会持续干扰后期策略提升。解决方法是改用优先经验回放或者在缓冲区里对成功回合的数据做额外标记、提高采样权重。更简单的替代方案是加大缓冲区容量让高质量经验在缓冲区里占更大比例。我在项目里用的是容量 2e6 的缓冲区加上每 5000 步清理一次低质量样本的机制效果稳定。6. 用收敛后的模型做编队验证三个维度与一组实用参数模型训练收敛只是第一步能不能在真实场景里用起来才是编队控制项目的最终检验。我习惯从行为、鲁棒性和安全三个方面做验证。行为层面关掉探索噪声让编队跑 100 个随机初始化的回合统计编队误差的均值和标准差误差均值低于 0.5 米、标准差小于 0.3 说明编队保持效果可用。鲁棒性层面在环境里注入通信延迟和传感器噪声观察编队误差是否出现发散正常的策略应该能容忍 100 毫秒的通信延迟。安全层面在目标点附近布置静态障碍物强制策略走避障路径而不是直接穿越编队前往目标点看会不会发生碰撞。我在编队控制项目里最常用的一组验证参数是随机初始位置偏移正负 1.5 米目标点距离 8 到 15 米随机队形旋转角度 0 到 360 度随机障碍物数量 2 到 4 个。这组参数下的验证结果比固定场景更有说服力能帮助判断策略是否真的学到了编队协同逻辑而非死记硬背特定路径。要提醒的是仿真里验证通过的策略迁移到真机时最大的坑在于状态观测的真实噪声水平通常远高于仿真设定。如果仿真里传感器噪声标准差设 0.05真机可能是 0.2策略很可能直接失效。这个方向目前没有后悔药唯一的办法是在仿真里把噪声和通信延迟拉到比预期真机值更严苛的水平给安全余量留足。编队控制在 MADDPG 框架下已经能有不错的表现但距离完全可靠还有距离。我自己的教训是不要把大量精力花在调网络结构上编队控制在状态空间设计和奖励塑形上的改进空间远大于网络结构的微调把这两块做扎实比换任何花哨变体都管用。希望这些经验能帮你在编队控制的学习和部署上少走弯路。本文还有配套的精品资源点击获取