简介基于MADDPG算法的多智能体编队控制源码包面向机器人、无人机及自动驾驶等领域的研究者和开发者重点解决多智能体协同编队的队形生成、保持与动态避障问题。包内共18个文件包含13个Python核心模块、4个编队状态文本文件及1个pyc缓存文件整体压缩包仅11KB。Python模块按功能清晰划分覆盖MADDPG算法主程序、DDPG代理、演员-评论家网络、经验回放缓冲区、参数配置、训练主循环与测试脚本并附有多套控制器实现便于直接阅读与二次开发。该资源已有1785人学习下载适合具备一定深度强化学习基础、希望快速上手多智能体编队控制任务的开发者。通过源码可系统学习MADDPG的训练流程理解多智能体环境下的协作策略更新机制、经验回放采样实现以及编队控制中的状态定义与奖励塑造为后续科研与工程实践提供可运行、可扩展的参考实现。1. FCMADDPG 是什么把“保持队形”写进奖励函数的多智能体深度强化学习如果你同时指挥过三台无人车会发现最难的不是让每台车走到目标点而是让它们始终维持一个倒 V 队形——前车稍微加速后车就得立刻跟着变速否则队形瞬间就散了。传统做法用领导-跟随或虚拟结构控制律硬算期望位置环境一复杂、队友一耦合参数就很难调。FCMADDPG 换了一条路把编队约束写进 MADDPG 的多智能体深度强化学习框架里让智能体在奖励函数的引导下自己学会“该守在哪个相对位置、队形散了怎么回补”。这个全称是 Formation Control MADDPG。适合做多机协同、集群仓储、编队避障的从业者也适合刚入门深度强化学习、想拿编队场景练手的研究生。2. 从 MADDPG 到 FCMADDPG集中训练、分散执行编队控制为什么缺不了奖励塑形2.1 MADDPG 的核心机制多智能体场景为什么不能直接套 DDPGMADDPG 是 OpenAI 在 2017 年前后提出的多智能体深度确定性策略梯度算法。它的设计动机很明确把单智能体的 DDPG 直接搬到多智能体场景训练大概率是崩的。原因在于多智能体环境是非平稳的——每个智能体同时在学习对其他智能体来说自己的状态转移和奖励函数里藏着一个不断变化的“环境”这破坏了 DDPG 对环境的平稳性假设。你训练一个智能体它的策略还没收敛队友的策略已经变了经验回放里的老数据全成了旧时代的样本。MADDPG 用“集中训练、分散执行”Centralized Training with Decentralized ExecutionCTDE绕开了这个问题。训练时每个智能体的Critic网络能拿到所有智能体的状态和动作把“队友的策略变化”显式地喂给价值函数让价值估计不再被队友的行为带偏执行时Actor只用自己的局部观测做决策不依赖通信条件下的全局信息。这个结构天然匹配编队控制训练阶段你可以开上帝视角给每个智能体看所有人的位置和速度让它学会怎么配合部署阶段每台车只能看到自己的传感器数据用学好的 Actor 独立出动作。FCMADDPG 并没有推翻这套框架而是把编队控制这个任务“塞”进奖励函数和观测设计里。说白了MADDPG 是通用骨架FCMADDPG 是在骨架上长出来的编队专项策略。2.2 编队控制的经典解法与强化学习路线的差异编队控制在机器人领域有三大经典流派领导-跟随Leader-Follower、虚拟结构Virtual Structure、基于行为Behavior-based。领导-跟随的思路是选一个领航者其余智能体跟踪领航者加上固定偏移量的期望位置控制律只需要做点位跟踪实现最简单虚拟结构把整个编队当成一个刚体每个智能体跟踪刚体上的一个固定点队形一致性最好但对通信要求高基于行为把避碰、队形、目标趋向设计成多个行为并加权融合灵活但数学上很难证明稳定性。这三条路线的共同痛点是控制参数和队形变换规则需要人手工设计。队形从三角换横排你得重新算偏移量环境里多一个障碍避碰阈值和权重又要重调。深度强化学习不直接设计控制律而是设计奖励函数智能体自己试错出一个从观测到动作的映射因此它对非线性动力学、传感器噪声、障碍物变化的容忍度要高得多。这就是 FCMADDPG 这类方案在过去几年被反复尝试的根本原因——把“调控制律”的痛苦转换成“调奖励权重”的痛苦而后者通常更直观、更接近人描述任务的方式。2.3 FCMADDPG 的关键改进编队偏移量与编队奖励塑形FCMADDPG 在 MADDPG 之上做了三件具体的事。第一定义编队参数化每个智能体在领导者坐标系下有一个期望偏移量比如左后 1 米偏 15 度这个偏移量就是编队形状的数学表达训练时可以固定也可以随机变化以提升泛化性。第二重设计观测空间每个 follower 的观测里除了自身状态还要包含领导者位置、领导者速度以及自身与期望位置的偏差这样策略才能知道“我现在偏了多少”。第三构造奖励塑形Reward Shaping这是最核心的改动。常见的做法是把奖励拆成三项编队误差项、避碰项、速度匹配项。编队误差项用当前相对位置与期望偏移量的差来构造误差越小奖励越高避碰项是两个智能体距离太近时给惩罚速度匹配项鼓励 follower 的速度向量接近领导者避免编队移动时前后拉扯。调参的关键在于这三项的权重比例——编队误差权重太大智能体会为了队形一动不动避碰权重太大队形松散速度匹配权重太大智能体会盲目追随速度指令而忽略位置误差。我一般会先固定编队误差权重再逐步加避碰和速度项每次只动一个参数避免两个权重同时调导致分不清是谁让曲线变坏的。3. 搭建编队仿真环境用 Gym 接口定义观察、动作与编队奖励3.1 动力学模型一阶积分器与双积分器怎么选编队控制仿真环境可以简到不能再简二维平面智能体用一阶积分器模型动作是速度指令状态更新就是pos vel * dt。这个模型适合验证算法收敛性跑得快调试方便但离真实机器人差得远。要做更接近实物的验证用双积分器模型动作是加速度指令速度也进入状态还必须限制速度幅值否则智能体在仿真里横冲直撞。我一般建议第一版用一阶积分器把算法跑通等奖励曲线稳定了再换双积分器不然动力学扰动和算法不收敛叠在一起分不清是环境写错还是策略没学出来。环境本身用 OpenAI Gym 的接口风格封装至少实现reset和step两个方法。下面是环境骨架的核心部分。import numpy as np class FormationEnv: def __init__(self, num_followers2, dt0.1, max_vel2.0): self.dt dt self.num_followers num_followers self.max_vel max_vel self.leader_state np.zeros(4) # [x, y, vx, vy] self.follower_states np.zeros((num_followers, 4)) # 三角队形领航者在前两台跟随者在后左右 self.offsets np.array([[-1.0, 1.0], [-1.0, -1.0]]) self.collision_dist 0.4 def reset(self, goalNone): # 领航者从原点出发跟随者从随机位置出发 self.leader_state[:2] np.array([0.0, 0.0]) self.follower_states[:, :2] ( np.random.rand(self.num_followers, 2) * 4.0 - 2.0 ) return self._get_obs() def step(self, actions): # actions: [actor_num, 2]每行是一个速度指令 clip_actions np.clip(actions, -self.max_vel, self.max_vel) for i in range(self.num_followers): self.follower_states[i, 2:] clip_actions[i] self.follower_states[i, :2] clip_actions[i] * self.dt # 领导者朝目标点直线移动 self.leader_state[:2] self.leader_state[2:] * self.dt return self._get_obs(), self._compute_rewards()reset里故意把 follower 的初始位置随机化避免智能体记住固定的开局坐标。step把动作直接当成速度指令写进状态一阶积分器没有惯性这是刻意的——让你先验证学习算法本身而不是被动力学拖住。如果后续换成双积分器只需要把clip_actions当作加速度累加到速度上再对速度做截断即可。3.2 观察空间与动作空间编队控制需要知道队友的哪些信息编队控制里观察空间设计比网络结构更敏感。太多冗余信息会拖慢收敛太少则学不出队形。我常用的做法是给 follower 构造这样一个观测向量自身位置、自身速度、领导者位置、领导者速度、自身与期望位置的偏差。注意这里不需要把其余所有 follower 的位置都塞进去因为编队控制的核心约束是“我和领导者的相对关系”其他 follower 的影响通过 Critic 的全局信息在训练阶段体现执行阶段不需要。动作空间是二维连续速度指令。如果你想让队形更稳定可以再加一个维度表示“是否切换队形”但这会让动作空间从连续变成混合空间MADDPG 的确定性策略梯度处理起来比较麻烦。第一版建议不要做混合动作保持纯连续二维把队形切换交给上层逻辑。观测向量拼接时要注意量纲统一。位置在 0 到 10 米量级速度在 0 到 2 米每秒量级偏差可能在 0.1 米量级直接拼在一起数值尺度差几十倍。我会在环境层先做归一化位置除以一个最大范围常数速度除以max_vel偏差单独给一个缩放系数。这个小细节能让训练稳定很多属于低成本高收益的一步。3.3 编队奖励函数代码把队形保持、避碰、速度一致压进同一个 reward 标量奖励函数是 FCMADDPG 的灵魂。下面这段代码是编队奖励的一个实用实现把编队误差、碰撞避免、速度匹配三个目标压进一个标量。def _compute_rewards(self): rewards np.zeros(self.num_followers) leader_pos self.leader_state[:2] leader_vel self.leader_state[2:] for i in range(self.num_followers): follower_pos self.follower_states[i, :2] follower_vel self.follower_states[i, 2:] # 编队误差当前相对位置与期望偏移量的欧氏距离 expected_pos leader_pos self.offsets[i] formation_err np.linalg.norm(follower_pos - expected_pos) # 编队奖励误差越小越好用负误差作为即时惩罚 formation_reward -formation_err # 碰撞惩罚距离越近惩罚越大用光滑函数避免梯度突变 collision_penalty 0.0 for j in range(self.num_followers): if i j: continue dist np.linalg.norm( follower_pos - self.follower_states[j, :2] ) if dist self.collision_dist: collision_penalty - (self.collision_dist - dist) * 10.0 # 速度匹配跟随者速度与领导者速度的差异 vel_diff np.linalg.norm(follower_vel - leader_vel) vel_reward -0.1 * vel_diff # 三个目标加权合成 rewards[i] ( formation_reward collision_penalty vel_reward ) return rewards两个设计点值得强调。第一碰撞惩罚用的是(threshold - dist) * 10.0这种连续形式而不是距离小于阈值就罚一个固定值——固定惩罚会产生严重的不连续梯度Critic 很难从这个陡峭的悬崖上学到平滑策略。第二速度匹配权重只有 0.1比编队误差权重小一个量级原因是速度匹配只是辅助项权重太大会压制编队误差项智能体最后只学到追速度矢量而不是守位置。第一次跑的时候建议把碰撞惩罚系数 10.0 也调低一点先让队形误差收敛再加碰撞让队形“收紧”两个目标同时优化的收敛难度显著更高。4. 跑通 FCMADDPG 训练Actor-Critic 网络、经验回放与关键参数4.1 Actor-Critic 网络结构编队任务里 Critic 为什么必须看见所有智能体FCMADDPG 沿用 MADDPG 的“每个智能体一对 Actor-Critic”结构。Actor 输入自己的观测输出速度指令Critic 输入所有智能体的观测和动作输出当前状态-动作对的价值估计。Critic 的“上帝视角”是 CTDE 的精髓它可以在训练阶段学习“如果队友这样做我这个动作值多少”这样价值函数就能包含对队友行为的建模这是稳定性的关键来源。网络深度不需要很深三层全连接通常就够。隐藏层 256 是实用起点编队任务的状态维度撑不起超大网络过深反而容易过拟合到仿真环境的特定布局。Actor 输出层要用tanh激活把动作限制在有界区间再乘一个速度缩放系数。如果你直接让 Actor 输出原始线性值训练中后期动作可能突然飞出边界导致环境里出现 NaN 或者智能体瞬移。import torch import torch.nn as nn import torch.nn.functional as F class Actor(nn.Module): def __init__(self, obs_dim, act_dim, hidden256, max_act2.0): super().__init__() self.max_act max_act self.fc1 nn.Linear(obs_dim, hidden) self.fc2 nn.Linear(hidden, hidden) self.out nn.Linear(hidden, act_dim) def forward(self, obs): x F.relu(self.fc1(obs)) x F.relu(self.fc2(x)) return torch.tanh(self.out(x)) * self.max_act class Critic(nn.Module): def __init__(self, obs_dim, act_dim, n_agents, hidden256): super().__init__() # 集中式 Critic拼接所有智能体的观测与动作 self.fc1 nn.Linear(n_agents * (obs_dim act_dim), hidden) self.fc2 nn.Linear(hidden, hidden) self.out nn.Linear(hidden, 1) def forward(self, obs_all, act_all): x torch.cat([obs_all, act_all], dim-1) x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) return self.out(x)Critic的输入维度是n_agents * (obs_dim act_dim)这意味着训练时必须把所有智能体的观测和动作按固定顺序拼接。顺序不能乱从环境中取到的 obs 列表必须和拼接顺序一致否则训练出来的价值函数是完全错位的。这个坑经常出现在“为省事把 obs 存成 dict”的场景里Python 的 dict 遍历顺序虽然现在稳定但不同版本的行为差异确实踩过。4.2 训练主循环OU 噪声、梯度更新与软目标网络训练主循环要做四件事采样动作加探索噪声、推进环境、存经验回放、更新网络。探索噪声我一般用 Ornstein-Uhlenbeck 过程它生成的是时间相关的噪声序列比独立高斯噪声更平滑适合持续控制类任务。OU 噪声有两个关键参数sigma控制噪声幅值theta控制噪声回到均值的速度。编队任务初始阶段需要较强探索sigma 设在 0.3 左右随着训练衰减到 0.05。def train_episode(env, agents, replay_buffer, batch_size256, sigma0.3): obs env.reset() episode_rewards [0.0 for _ in range(env.num_followers)] max_steps 200 for step in range(max_steps): actions [] # 给每个智能体的确定性策略叠加时间相关噪声 for i, agent in enumerate(agents): action agent.actor(obs[i]) action action np.random.randn(2) * sigma action np.clip(action, -agent.max_act, agent.max_act) actions.append(action) next_obs, rewards env.step(np.array(actions)) replay_buffer.add(obs, actions, rewards, next_obs) obs next_obs for i, agent in enumerate(agents): episode_rewards[i] rewards[i] # 经验足够后开始更新 if replay_buffer.size() batch_size: for i, agent in enumerate(agents): agent.update(replay_buffer, batch_size) return episode_rewards更新时注意一个常见误用不要在每个 step 都对所有智能体做完整的多步梯度更新。一步到位更新 10 次会造成样本利用过度价值函数剧烈抖动。我一般每个 step 每个智能体更新一次整轮 episode 跑完即结束效果稳定且训练速度快。目标网络用软更新tau设为 0.01每步都往目标网络的参数上挪一小步避免硬拷贝带来的价值估计跳变。4.3 编队训练效果怎么评估平均编队误差、避碰次数与任务成功率奖励曲线是最直观的监控指标但它不够——文字上的奖励值会因为权重不同而不可比。我每次训练都会额外记录三个指标平均编队误差所有 follower 与期望位置的误差均值、碰撞次数每轮 episode 内 follower 间距小于阈值的次数、队形保持成功率误差小于某阈值的步数占比。这三个指标比奖励值更容易向非技术角色解释也更接近真实部署要求。下表是我常用的训练参数起点实际以你改完动力学模型后的情况为准。参数建议值说明actor 学习率1e-4太低收敛慢太高动作会震荡critic 学习率1e-3Critic 需要比 Actor 学得快折扣因子 gamma0.95编队是短时任务gamma 太高会引入滞后OU 噪声 sigma0.3探索阶段用后期衰减软更新 tau0.01目标网络更新速度经验回放容量1e6容量大要配足够的采样覆盖batch size256与隐层 256 匹配偏大稳定编队误差权重1.0基准项先确定它碰撞惩罚系数10.0在 1.0 到 20.0 之间调训练到一半如果奖励还在上涨不要急着停。深度强化学习不像监督学习那样有明确的验证集收敛点我通常是看编队误差曲线连续 200 个 episode 不再下降后再额外训练 200 个 episode 确认平台期最后保存模型做部署测试。5. 编队控制训练避坑FCMADDPG 不收敛或队形散掉的 5 个常见问题5.1 训练过程损失一直不掉智能体停在原地不动现象是 Critic loss 从第一轮开始就趋近于 0Actor loss 也几乎不变化episode 总奖励一直是负的跟随者的速度输出始终接近 0。原因通常是奖励函数里编队误差项的梯度太弱或者动作用tanh激活后初始输出接近 0 且探索噪声太小智能体根本没有尝试离开原点。这个问题在奖励函数设计不当的场景里高发。解决先把编队误差权重提高到 5.0 以上强制策略朝“移动”方向优化然后把 OU 噪声的sigma从 0.3 提高到 0.5让初期动作有足够偏移最后检查 Critic 是否把价值估计饱和到了负区间如果是把奖励整体加一个常数值比如所有分量统一 0.5把价值基线抬到 0 附近给出一个非零梯度方向。5.2 奖励值震荡剧烈编队误差忽大忽小现象是训练曲线不是平滑下降而是每几十个 episode 出现一次尖峰平均编队误差在 0.1 和 1.5 之间大幅跳变。这通常不是随机性造成的而是不同奖励分量量级差太多——编队误差项数值在 0 到 5 之间碰撞惩罚项可能突然跳到 -10急剧变化主导了梯度。另一个因素是 batch 里混入了太早期和太晚期的经验价值估计被旧数据干扰。解决在环境层对每个奖励分量做归一化。算出当前 episode 三个分量的均值把碰撞惩罚除以它的均值再乘一个固定系数或者干脆用更平滑的1 / (dist 0.1)形式作为避碰项避免惩罚值出现数量级跳变。经验回放容量也可以从 1e6 降到 2e5排除过旧样本对训练后期的影响。5.3 智能体互相碰撞或挤成一团避碰惩罚失效现象是编队误差已经收敛到很小的值但 follower 之间距离小于安全阈值。原因大概率是碰撞惩罚权重比编队误差权重小太多或者碰撞项只在距离小于阈值时激活距离刚好大于阈值时梯度为零智能体可以停在阈值边缘形成一种“贴边”的局部最优。解决用连续距离函数重写碰撞项把if dist threshold的硬剪枝改成-1.0 / (dist 0.05)让距离越近惩罚越陡峭梯度在任意距离上都存在。同时把碰撞惩罚的起点放宽比如阈值从 0.4 米改成 0.8 米让智能体提前规避而不是等撞上了才收到惩罚信号。5.4 收敛速度慢1000 个 episode 队形还是歪的现象是训练曲线在缓慢下降但效率极低编队误差到 0.8 米就卡住。常见原因是奖励是稀疏的——只在 episode 结束时给一个队形成功的大奖励中间过程全靠编队误差在推。第二个原因是 Critic 的输入拼接顺序不稳定导致价值函数学出来的信息是错的。解决做势函数塑形。把关于编队误差的负指数函数加入每个 step 的即时奖励替代稀疏的终点奖励。势函数塑形的特性是不改变最优策略只改变学习速度这是实践中性价比最高的加速手段。同时检查torch.cat的拼接逻辑确保从经验回放里取出的 obs 顺序和构建 Critic 输入时的顺序完全一致可以加一行断言来保底。5.5 训练正常但部署到新起点就翻车泛化能力差现象是训练环境里队形漂亮把起始位置换到 10 米外或者目标点换一个方向编队直接散掉。原因是训练时随机初始化只覆盖了很小的状态空间策略过拟合到了几种固定起始布局。编队偏移量如果也是固定的智能体甚至可能记住“偏移 1 米”而不是学会“跟随领导者”。解决在reset里随机化领导者起始点和目标方向最好让目标点在以领导者为中心、半径 5 到 15 米的圆周上均匀采样同时把每个 follower 的偏移量乘以一个随机扰动系数比如offset * np.random.uniform(0.8, 1.2)。这种 domain randomization 让智能体学到的是相对关系而不是绝对坐标是编队强化学习泛化性最有效的提升方式。6. 验证编队效果用编队误差曲线与多起点回放确认智能体真的学会了队形6.1 记录队形误差曲线与碰撞距离曲线训练结束后先做一次“冷验证”把 OU 噪声置 0加载训练好的 Actor 权重跑 50 个新的随机起点画出每个 step 的编队误差曲线。不要只看平均值把 50 条误差曲线叠在同一个图上观察是否有某条曲线在中期出现尖峰。尖峰意味着智能体在某个特定相对位置时策略失效这是价值函数覆盖不足的信号。同时记录 follower 之间的最小距离看是否全程高于安全阈值。6.2 参数迁移测试第二步是改环境参数测试鲁棒性把max_vel从 2.0 改成 1.5 或 2.5把dt从 0.1 改成 0.05不改 Actor 权重直接跑。如果编队误差上升幅度在 30% 以内说明策略学到的是控制规律而不是环境参数。这一步非常重要因为仿真到真机移植时动力学参数不可能完全一致。实际上这也是我判断一个 FCMADDPG 结果能不能转实物部署的门槛标准连dt都没换过的策略我不会让它接近真机。最后说一点个人习惯我会把训练过程中编队误差第一次降到 0.3 米以下的 episode 序号记下来作为“学习速度”的指标。这样做不是为了写报告而是下次改奖励权重时不需要重新跑完整个训练就能快速判断改动是正向还是负向。经验多了以后你会发现同一个小改动在不同随机种子下的表现差异往往比权重本身的差异还大——深度强化学习确实有玄学成分但把玄学控制在可控范围内靠的就是这些落到实处的验证手段。希望这些思路对你手头的编队控制项目有实际帮助。本文还有配套的精品资源点击获取