☰
FCMADDPG:基于MADDPG的无人机编队控制深度强化学习工程解析
2026/10/10 13:54:26 网站建设 项目流程

简介:一套基于MADDPG算法的多智能体编队控制学习项目,面向深度强化学习研究者以及无人机、自动驾驶编队控制开发者,目标是解决多智能体在保持队形、避免碰撞和应对动态环境时的协同决策难题。压缩包共18个文件,以Python源码为主体(13个py),另有4个txt状态文件与1个pyc缓存,整体仅11KB。其中Python代码覆盖MADDPG核心训练逻辑、DDPGAgent策略网络、演员-评论家结构、经验回放缓冲区、参数配置、主程序与测试脚本,txt文件保存编队状态供分析,结构简洁清晰。下载后可直接运行main.py启动训练,通过test.py检验效果,灵活调整params.py中的超参数,或基于Controller模块扩展更多控制场景,整套代码为理解多智能体强化学习提供了可复用的实验框架,也适合作为编队控制课题的入门参考。目前已有1787人学习下载。

1. FCMADDPG 是什么:一套把编队控制写进 MADDPG 训练闭环的深度强化学习工程

做无人机编队或自动驾驶车队控制的人,搜到“FCMADDPG”时多数已经读过 MADDPG 论文,但卡在写代码这一步。这个压缩包解决的就是这个痛点:它是一份完整的深度强化学习编队控制工程,maddpg.py 是 MADDPG 算法核心,Controller 目录提供多个控制器实现,main.py 和 test.py 串起训练与评估,state 目录则把每个智能体的编队状态落盘,方便回放和分析。适合两类人:一类刚接触多智能体强化学习,想拿一份能跑通的全流程代码做基线;另一类已经在做多智能体编队,需要对比不同奖励设计和控制器结构。这套工程把论文里模糊的“编队奖励”变成了可运行的代码,直接改参数就能看到队形变化。

2. 先把算法吃透:MADDPG 的集中训练分散执行与编队奖励设计

2.1 从 DDPG 到 MADDPG:多智能体为什么需要集中评论家

先说要解决什么问题。单智能体 DDPG 里,actor 根据自身观测输出动作,critic 根据“状态-动作”对估计 Q 值,训练目标是让 Q 值最大化。这个套路在单智能体环境里成立,是因为环境转移只受一个策略影响。到了多智能体编队场景,四个智能体同时运动,任何一个智能体的策略改变都会改变其他智能体观测到的状态分布。此时每个智能体独立维护一个 DDPG,等价于在非平稳环境下做强化学习,critic 的 Q 值估计会被其他智能体的策略变化不断干扰,训练很难收敛。这就是 MADDPG 存在的理由。

MADDPG 的核心改动是“集中训练、分散执行”。训练阶段,每个智能体的 critic 不再只看自己的观测和动作,而是拼接所有智能体的观测和动作作为输入,这样 Q 函数能感知全局状态,避免了非平稳问题;actor 仍然只吃自己的局部观测,保证推理阶段每个智能体可以独立决策,不需要通信依赖。这个设计直接影响了 actor_critic.py 里的网络维度定义。拿到代码后第一件事就是看 critic 的输入维度是不是“所有智能体观测维度之和加所有智能体动作维度之和”,如果只写了单智能体的维度,说明实现被简化过,训练效果会打折扣。

actor_critic.py 里还有一个容易被忽略的地方:MADDPG 论文提出,为了计算某个智能体策略梯度时能模拟其他智能体的行为,需要为每个智能体额外维护一个对其他策略的近似函数。很多工程实现会省略这一步,直接用 replay buffer 里记录的其他智能体真实动作。这个压缩包的实现方式需要看代码里 actor 的更新过程,如果只是拿历史动作做输入,属于简化版。简化版在智能体数量少、动作空间小的编队场景下通常够用,但如果后续扩展到对抗场景,还是要把策略近似补回来。

2.2 编队控制怎么翻译成奖励函数:队形保持、避碰与速度一致

编队控制在数学上可以描述为:N 个智能体相对于领航者或某个编队参考点,保持一组期望的相对位置偏移。以 4 机编队为例,1 号机是领航者,2、3、4 号机相对 1 号机的期望偏移量分别为某个固定几何构型。每个时刻,控制器计算实际相对位置与期望相对位置的误差,误差越小说明编队保持得越好。问题在于,这个误差没法直接用于梯度下降,必须转换成强化学习能优化的奖励信号。

常见做法是把编队奖励拆成三项叠加:

  • 队形保持项:领航者与跟随者实际相对位置和期望偏移的负距离,或者高斯函数 exp(-dist²/σ²)。
  • 避碰项:当两个智能体距离小于安全半径时给负奖励,距离越近惩罚越大,通常写成 max(0, safe_radius - d_ij) 的形式。
  • 速度一致项:跟随者速度与领航者速度的差值范数,用于抑制队形振荡。

这三项对应的代码逻辑一般写在环境或 reward 函数里。调参顺序上,我会先把队形保持项的权重调大,让智能体先学会向目标点移动,等队形能看到雏形了,再逐渐加入避碰项。如果一开始就把避碰项权重拉满,智能体会倾向于原地不动,因为不动就不会碰撞,奖励反而可能比乱动更高。这是编队训练里最常见的局部最优陷阱。state 目录里那一批 state1.txt 到 state4.txt,从命名看就是 4 个智能体在训练或测试过程中的状态记录,每行大概率是“智能体编号、位置坐标、速度分量、与其他智能体的距离”这类字段。用这批文件可以离线分析某个回合里队形误差是怎么变化的,排查训练中的异常行为。

3. 从代码到训练:params.py 参数怎么设、main.py 训练循环怎么走

3.1 params.py 参数解读:学习率、噪声、更新间隔的一次完整配置

params.py 是整个工程的配置入口。它里面装的是所有会直接影响训练行为的超参数,拿到代码第一件事不是跑,而是打开这个文件把每个参数的意义核对一遍。常见的配置项包括:

参数常见范围说明
actor_lr1e-4 ~ 1e-3actor 网络学习率,编队场景建议从 1e-4 起步
critic_lr1e-3 ~ 1e-2critic 网络学习率,通常比 actor 高一个量级
gamma0.95 ~ 0.99折扣因子,编队任务属于连续决策,0.99 更合适
tau0.001 ~ 0.01target 网络软更新系数,太小则 target 更新过慢
batch_size64 ~ 256经验回放采样数量,4 智能体场景 128 够用
buffer_size1e5 ~ 1e6经验池容量,越大越稳定但占内存
max_episodes几千到几万训练回合数,编队收敛通常需要数千回合
noise_scale0.1 ~ 0.5探索噪声幅度,训练后期需要衰减

这里重点说两个参数。第一个是 tau,它控制 target 网络更新的平滑程度。tau 太大,target 网络跟着当前网络猛跑,训练不稳定;tau 太小,target 网络基本冻结,学习速度显著下降。我一般设 0.005,如果发现训练后期 Q 值振荡,会降到 0.002。第二个是噪声的衰减策略。MADDPG 用的是确定性策略,训练早期靠 OU 噪声或高斯噪声探索动作空间。如果噪声不衰减,训练到后期智能体还在乱抖,动作输出始终不够干净,测试时表现会差距很大。正确做法是设定一个衰减系数,比如每 1000 回合把噪声幅度乘以 0.9,让智能体从探索逐渐过渡到利用。

3.2 main.py 训练主循环:从采样到 target 更新,一个回合看全流程

main.py 是训练主循环的骨架。它做的事情可以用一段结构清晰的代码描述。实际工程里,主循环代码通常长这样:

# main.py 训练主循环(节选,结构与常见 MADDPG 实现一致) for episode in range(max_episodes): obs_list = env.reset() # 每个智能体各自的初始观测 episode_reward = 0.0 while not done: actions = [] for i in range(num_agents): # 训练阶段加探索噪声,测试阶段不加 action = agent[i].get_action(obs_list[i], add_noise=True) actions.append(action) next_obs_list, reward_list, done = env.step(actions) # 把整条转移存入回放缓冲区 replay_buffer.add(obs_list, actions, reward_list, next_obs_list, done) obs_list = next_obs_list if replay_buffer.size() > batch_size: # 随机采样一个 batch,更新所有智能体的 actor 和 critic agent.update(replay_buffer.sample(batch_size)) episode_reward += sum(reward_list)

这段逻辑说明几点。第一,add_noise=True是训练和测试的分水岭,测试时必须改成 False,否则动作里带的噪声会让轨迹抖动。第二,env.step(actions)传入的是所有智能体动作的列表,环境内部会把动作映射为位置和速度的更新,同时计算编队奖励和碰撞惩罚。第三,replay_buffer.add做的是整体存入,这一点非常关键。MADDPG 的经验回放必须以“整步”为单位,即一条经验里包含所有智能体的观测、动作、奖励和下一观测。如果拆成单智能体分别存放,采样时不同智能体的经验步数错位,critic 拿到的全局信息就是错乱的,整个集中评论家设计就失效了。这一点在 review replay_buffer.py 时要重点确认。

训练循环里还有一个经常被忽视的细节:target 网络的更新时机。actor 和 critic 各自的 target 网络不是每一步都更新,而是每更新一次当前网络后,按照 tau 做一次软更新。如果 target 网络更新频率和当前网络不同步,比如每步都硬拷贝,训练会剧烈震荡;如果一直不更新,TD 目标会越来越偏离真实值。用 tau=0.005 的软更新,相当于当前网络每走 200 步,target 才跟上一点点,这是 DDPG 系算法稳定的基本前提。

4. 一次跑通:环境依赖、训练启动与 test.py 的三种验证姿势

4.1 环境准备:先在 Python 3.7 系虚拟环境里跑

压缩包里出现 function.cpython-37.pyc,说明这份工程是在 Python 3.7 环境下编译运行的。虽然现代 PyTorch 在 3.8、3.9 上也能跑,但稳妥起见,我建议用 conda 建一个 Python 3.7 的虚拟环境,再装依赖,能省掉很多莫名其妙的兼容性问题。依赖方面,核心是 PyTorch 和 NumPy,如果环境里的仿真基于 gym,就再补 gym。装包命令通常是一行:

conda create -n fcmaddpg python=3.7 conda activate fcmaddpg pip install torch numpy gym

装完后先检查 PyTorch 能不能调用 GPU。编队控制的训练速度受限于网络更新的次数,四智能体场景下 CPU 也能跑,但速度差距明显。用python -c "import torch; print(torch.cuda.is_available())"看输出,返回 True 就接着用 CUDA 版,返回 False 也不影响小规模调试,只是训练回合数要相应减少。

4.2 跑训练的最小命令:从零到权重文件落地

环境就绪后,训练入口是 main.py,默认配置从 params.py 读取。启动命令通常只需要一行:

python main.py --mode train

如果 main.py 没接命令行参数,那就直接python main.py。训练开始后,终端会周期性地输出当前 episode 的累计奖励、队形误差或碰撞次数。这里要注意输出的节奏:如果每个 episode 都输出了完整指标,但数值始终没有上升趋势,说明奖励设计有问题,不要盲目加训练时长,先停下来检查参数。训练正常的情况下,奖励曲线会在前几百回合明显爬升,之后进入缓慢上升阶段。等累计奖励基本平稳,训练得到的一组 actor 权重会保存为 .pth 文件,这就是后续测试要用的模型。

训练时长没有统一标准。环境简单、四智能体直线编队可能两三千回合就够;如果带避障、动态目标,往往要上万回合。我的习惯是先跑一个 200 回合的小实验,用--max_episodes 200之类的方式限制,确认程序能完整走完训练循环、奖励能正常累计,再放开跑完整训练。不要一上来就跑几万回合,万一环境或奖励写崩了,时间全浪费在等待上。

4.3 state 目录和 Controller 目录分别装的是什么

跑通流程后,不少人有同样的困惑:state1.txt 和 controller1.py 到底哪个是训练用的?这里做个区分。Controller 目录里是一组可单独调用的控制器实现,controller1.py 到 controller4.py 对应四个智能体的控制逻辑。它们可能是传统 PID 形式的控制器,也可能是与深度强化学习策略杂交的控制器,具体看代码里输入输出接口。它们的存在价值在于,可以在同一份环境代码里对比“传统控制器”和“训练出来的 MADDPG 策略”在编队保持上的差异。

state 目录则完全是数据产物,state1.txt、state2.txt 这些文件记录的是训练或测试过程中每个智能体的状态快照,包括位置、速度、间距等信息。它们的用途有两个:一是复盘某个回合为什么发散,比如在哪个时间点两个智能体距离突然小于安全半径;二是做训练数据的可视化,把 state 文件读出来画轨迹,能直观看到编队形态是收敛的还是发散的。阅读 state 文件时,第一行通常是初始状态,后续行按时间步排列,真正要注意的是文件的最后几行——训练结束时智能体是保持在队形里,还是已经飘走了。

4.4 test.py 能做什么:加载权重、回放轨迹、量化编队误差

test.py 的目的是用训练好的权重验证策略,而不是重新训练。它一般会加载保存的 actor 网络参数,把探索噪声关掉,在相同的初始条件下跑若干个 episode,记录每回合的队形误差。这段逻辑的核心部分可以抽象成下面这段:

# test.py 核心流程:加载权重并做确定性推理 for episode in range(num_test_episodes): obs_list = env.reset() while not done: actions = [] for i in range(num_agents): # 测试阶段必须关闭噪声,否则动作不稳定 action = agent[i].get_action(obs_list[i], add_noise=False) actions.append(action) obs_list, reward_list, done = env.step(actions) # 记录相对位置误差,用于统计编队质量 formation_error.append(compute_formation_error(obs_list))

这段代码体现了一个容易被忽略的习惯:测试时环境随机种子要保持一致。如果每次测试随机种子都不同,初始位置差异会直接反映在编队误差上,误判模型性能。我会把随机种子固定,然后用多组不同种子取平均,这样得到的评估数据才有说服力。测试结果一般输出两类指标:平均队形误差和碰撞次数。前者越小说明编队保持越好,后者为 0 说明避碰有效。如果误差很大而碰撞次数很少,很可能是智能体为了避碰放弃了队形;反过来碰撞次数多,说明避碰奖励权重太低。这些都是 test.py 输出结果最常见的解读方式。

5. 避坑与排查:MADDPG 编队训练里最常翻车的五个现场

5.1 训练发散:奖励曲线冲到高点后断崖式崩盘

现象:训练初期几百回合累计奖励快速上升,然后突然掉到负值,之后再也回不来。从 state 文件看,智能体在某个时间点集体向某个方向冲出去,队形完全散掉。

原因:这是经验回放里混入了“坏经验”。策略在探索阶段偶然做出一个高奖励动作,但后续 policy 快速改变,buffer 里的旧经验分布与当前策略不再匹配,critic 对 Q 值的估计就失真了。另一个常见原因是 batch_size 太大,采样到的经验互相矛盾,训练信号被冲突梯度覆盖。

解决:先把 batch_size 降到 64 试试;同时把 tau 调小到 0.002,让 target 网络更新更平滑。如果还没用,检查奖励是否出现了极端大值,比如距离奖励直接用未归一化的坐标差值,数值超过 10 就会让梯度爆炸,需要把奖励整体除以一个缩放系数,把数值压到 0~1 量级。

5.2 队形“缩成一团”:避碰项被队形项吃掉

现象:训练结束后,智能体没有形成期望的几何构型,而是聚在一个很小的区域内,看起来像“一堆点重叠了”。碰撞次数很低,但队形误差很大。

原因:避碰项和队形项在几何上互相冲突。队形项把智能体往目标相对位置拉,如果目标相对位置之间的距离小于安全半径,避碰项和队形项就会打架。收益函数里如果避碰项权重偏低,智能体宁可牺牲避碰也去满足队形,最终缩成一团。

解决:检查编队期望偏移量配置:4 机编队的期望队形尺寸必须大于安全半径的两倍。比如安全半径是 1,那么相邻智能体的期望间距至少应该是 2.2。其次把避碰项的惩罚形式从线性改成指数形式,当距离小于安全半径时惩罚迅速增大,让策略“不敢”进入危险区域。

5.3 只有一个智能体学到东西,其他三个全程乱动

现象:训练过程中,1 号机行为稳定,队形误差在下降;但 2、3、4 号机的轨迹很混乱,像在随机游走。看各自的 reward 曲线,只有一个智能体的奖励在上升。

原因:这是编号从 0 开始的动作分配顺序问题。如果 agent[0] 和 env 中实际控制的智能体编号不一致,或者经验回放时 reward_list 的索引对应错了,某个智能体就一直在用错误的奖励信号学习。还有可能是四个智能体共享了一个 actor 参数,但编队任务里领航者和跟随者的策略本来就不同,共用参数会让跟随者学成四不像。

解决:第一步,在训练循环里打印每个智能体每一步的观测、动作、奖励,核对索引是否一一对应。第二步,确认 actor_critic.py 里每个智能体是否有独立的 actor 网络。MADDPG 的标准做法是每个智能体一组 actor-critic 参数,如果为了省内存共享了网络,后续者策略会被领航者的梯度污染。把共享参数拆开,重新训练。

5.4 训练时奖励正常,测试时队形立即散架

现象:训练阶段累计奖励稳步上升,状态文件里也能看到队形。但是用 test.py 跑,智能体的行为明显退化,编队坚持不了多久就散掉。

原因:最大嫌疑是探索噪声没有关闭。训练阶段 actor 输出的动作上加了一层高斯噪声,模型已经学会了在“有噪声的视角”下做决策,一旦测试时关闭噪声,观测分布和训练时不匹配,策略表现自然大幅下降。另一个可能性是训练时环境随机性太小,智能体把某个固定初始位置记住了,测试时换了初始位置就不会应对。

解决:去到 test.py 里确认add_noise=False一定生效。其次,在训练时给初始位置加随机扰动,并周期性评估一次当前策略的无噪声表现。每训练 500 回合就让智能体不带噪声跑一个 episode,看奖励是否稳定,这个评估值才是真实水平。

5.5 训练速度慢到无法忍受:几十个回合就要跑很久

现象:训练几小时只完成了不到一千回合,终端输出的每秒步数很低,GPU 利用率也上不去。

原因:最容易出问题的是环境步进和网络更新串行执行,环境在 CPU 上跑一步,网络在 GPU 上更新一次,两者互相等待。另一个常见的瓶颈是 replay_buffer 采样用了全量遍历,buffer 超过 10 万条以后随机采样一次要做大量内存复制,速度断崖式下降。

解决:先把 batch_size 和 buffer_size 调小,比如 batch_size 128、buffer 5 万条,确认训练能跑动。再把目标网络更新频率从“每步更新”改成“每 2 步更新一次”,减少反向传播次数。如果经验池太大,检查采样代码是否可以直接用 NumPy 的np.random.choice做索引采样,而不是用 Python 的random.sample。这几个优化做完,训练速度通常能翻一倍以上。

6. 进阶:把 FCMADDPG 跑出自定义队形的三个技巧

6.1 编队参考点改成动态目标,别用固定全局坐标

很多编队实现把期望队形写成全局固定坐标,比如“2 号机去 (5, 3),3 号机去 (5, 5)”。这在小场景里能跑通,但领航者一旦移动,跟随者会在原地打转。正确写法是以领航者位置为原点做相对偏移,让每个跟随者的目标点随领航者实时更新。我会在奖励函数里这样写:对每个跟随者计算target_pos = leader_pos + offset[i],再让跟随者朝这个目标位置移动。这样领航者转弯或者加速,编队整体会跟着走,不会散开。

6.2 奖励函数加一个距离梯度项,替代稀疏的到达奖励

如果只在智能体到达目标位置时给一个 +1 奖励,智能体在到达之前收到的全是 0,学习效率极低。更好的做法是给一个连续的距离变化奖励:在某一步,如果智能体与目标点的距离比上一步小,就给一个小的正奖励;距离变大就给负奖励。这个奖励信号是稠密的,训练时每一步都在生效,而且让智能体养成“靠近目标”的趋势。我用过的最简单写法是reward_dist = -dist/scale,其中 scale 设为期望编队尺寸的 1/3,这个值越大,智能体对距离越不敏感,队形会比较松散;越小越敏感,队形会更紧,但也更容易振荡。

6.3 评估编队质量时,把稳定误差和碰撞次数分开统计

测试脚本里只输出一个平均奖励评估不了编队质量,因为奖励是多目标的加权和。我会在 test.py 里额外统计两个指标:稳定误差和碰撞次数。稳定误差是最后 N 个时间步的队形误差平均值,它反映的是收敛后的稳态精度;碰撞次数反映的是安全性。训练时奖励接近收敛后,这两个指标比奖励更能说明问题——奖励高但碰撞次数高,说明避碰权重不够;稳定误差小但碰撞次数多,说明队形间距没留够。从那以后我每跑一组新实验,第一件事就是去 test.py 里把这两个指标打印出来,先看它们再决定调参方向,而不是干盯着那个总是飘忽不定的 reward 曲线。这个习惯帮我省了不少试错的回合数,也希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询