跳转至

第 11 章 MARL 用于多机运动协调——从基础框架到导航/避障/编队的落地

性质:算法工程教学 | 难度跨度:⭐⭐ ~ ⭐⭐⭐⭐ | 预计精读:15-21 小时

一句话定位:第 10 章把合作型 MARL 的数学骨架(随机博弈、非平稳性、CTDE、值分解/策略梯度、信用分配)讲透了,但那是"在抽象的协作游戏里学策略"。本章回答一个更具体、也更工程的问题——当智能体不再是棋盘上的格子而是有质量、有惯性、有真实传感器和电机的机器人,要在共享的物理空间里同时完成导航、避障、编队时,MARL 这套机器该怎么改、怎么用、怎么从仿真搬到真机。换句话说,第 10 章给了"学习器",本章给"把学习器接到机器人运动上"的全部接口:观测怎么设计才能让策略既看得见队友又可扩展到任意 \(N\)、动作放在速度层还是关节层、奖励怎么塑形才能让一群自利的智能体涌现出协作而非互相挤兑、CTDE 的 critic 在多机运动里到底喂什么、以及多机 sim-to-real 比单机多出哪些坑。


前置自测

开始前先回答下面 5 个问题。答不出 2 题以上,建议先回前置章节补齐——本章不再重新教 MARL 的基础机制,而是默认你已经掌握它们,直接在其上谈"怎么用到多机器人运动协调"。

  1. (接第 10 章)CTDE(Centralized Training, Decentralized Execution)范式的两个"半"分别是什么? 为什么"训练时让 critic 看全局状态、执行时让 actor 只看本地观测"能同时绕开非平稳性和部署时的通信约束?如果训练和执行都用本地观测(independent learning)会丢掉什么? (答不出 → 回第 10 章 §10.3)

  2. (接第 10 章)MAPPO 与 IPPO 的唯一结构差异是什么? 为什么"一个共享参数的 PPO actor + 一个吃全局信息的中心化 critic"会成为合作 MARL 的最强基线之一?共享参数(parameter sharing)在什么前提下是合理的、又在什么情况下会害了你? (答不出 → 回第 10 章 §10.5)

  3. (接第 10 章)什么是信用分配(credit assignment)问题?\(N\) 个机器人共享一个团队奖励 \(r^{\text{team}}\) 时,单个机器人怎么知道"刚才那个动作到底是帮了团队还是拖了后腿"?COMA 的反事实基线(counterfactual baseline)和 QMIX 的值分解分别从哪个角度缓解它? (答不出 → 回第 10 章 §10.6)

  4. (接 04_移动机器人规控/70_无人机01_数学/70_强化学习数学)马尔可夫决策过程(MDP)的状态、动作、奖励、转移、折扣五元组各是什么? 部分可观测(POMDP)下,智能体拿到的"观测"和真实"状态"是什么关系?为什么多机器人天然是部分可观测的? (答不出 → 回 01_数学/70_强化学习数学/10_MDP与动态规划.md

  5. (接第 3 章)多机路径规划(MAPF)里的"顶点冲突"和"边冲突"是怎么定义的? 传统 MAPF 用搜索保证无碰撞,而本章要让一个学出来的策略去避障——这两条路线在"碰撞保证"上的本质区别是什么(硬约束 vs 软惩罚)? (答不出 → 回第 3 章 §3.3)

参考答案要点(先自己答,再对照):

  1. CTDE 的两半:**训练时**用一个能访问全局信息(所有智能体的观测/动作甚至真值状态)的中心化 critic 来估计价值或优势,从而把"别的智能体在学习导致环境非平稳"这件事吸收进 critic 的输入——对中心化 critic 而言环境是平稳的;**执行时**只保留各自的 actor,每个机器人仅凭本地观测决策,不需要运行时通信或中央节点。Independent learning 训练时也只用本地观测,critic 看不到队友,于是把队友的策略变化当成环境噪声,收敛性失去保证(这正是第 10 章讲的非平稳性)。

  2. 唯一差异是 critic 的输入:IPPO 的 critic 只吃本地观测 \(o_i\),MAPPO 的 critic 吃全局状态 \(s\)(或所有智能体观测的拼接)。actor 两者都只用 \(o_i\)。MAPPO 强是因为:中心化 critic 给出方差更低、更准的优势估计,而 PPO 的裁剪信赖域又让每步更新稳健。参数共享在智能体**同质**(observation/action 空间同构、角色可互换)时合理,能极大提高样本效率;当智能体异质(不同机型、不同传感器)或需要分化角色时,强行共享会逼所有智能体收敛到同一个折中策略,反而有害。

  3. 信用分配:团队只给一个标量奖励,单个智能体无法直接区分自己的贡献。COMA 固定其他智能体动作、只反事实地变化自己的动作,用"当前动作的 Q 减去对自己动作求期望的基线"得到该智能体的边际贡献;QMIX 则学一个把个体 \(Q_i\) 单调混合成 \(Q_{\text{tot}}\) 的网络,让每个 \(Q_i\) 隐式承担一份责任,且单调性保证分布式 argmax 等于集中式 argmax(IGM)。

  4. MDP 五元组:状态空间 \(\mathcal{S}\)、动作空间 \(\mathcal{A}\)、奖励 \(r(s,a)\)、转移 \(P(s'\mid s,a)\)、折扣 \(\gamma\)。POMDP 下智能体看到的是观测 \(o = O(s)\)(状态的一个有损函数),不是 \(s\) 本身。多机器人天然部分可观测,因为每个机器人只有自己的传感器(有限视野、有遮挡、有噪声),看不到全局,也看不到队友的内部状态和意图。

  5. 顶点冲突:两个机器人在同一时刻占据同一格;边冲突(交换冲突):两个机器人在同一时刻交换相邻格。传统 MAPF 把无碰撞作为**硬约束**写进搜索(CBS 的约束树、优先级规划的预留表),解一定无碰撞;学出来的策略把碰撞写成奖励里的**软惩罚**,策略只是"倾向于"不撞,没有任何一步的硬保证——这是 learned policy 在安全关键场景必须叠加安全层(CBF/盾)的根本原因,§11.6 会专门处理。


本章目标

学完本章后,你应该能够:

  1. 把一个多机器人运动协调任务(导航/避障/编队)规范地建模为 Dec-POMDP——能写出每个机器人的观测函数 \(O_i\)、动作空间 \(\mathcal{A}_i\)(速度层 vs 关节层的选择依据)、单步奖励 \(r_i\) 的分解,并说明为什么这个建模比"把 \(N\) 个机器人拼成一个巨型 MDP"在样本效率和可扩展性上都更优。

  2. 设计可扩展到任意 \(N\) 的观测表示——理解固定槽位拼接为什么会随 \(N\) 爆炸且不可迁移,掌握排列不变(permutation-invariant)聚合(mean/max pooling、deep sets、自注意力、图神经网络)如何把策略的有效搜索空间从"对 \(N\) 指数依赖"降到"多项式依赖",并能从零实现一个排列不变的邻居编码器。

  3. 为协作与竞争任务塑形奖励——掌握全局团队奖励、个体局部奖励、混合奖励(individual + shared)三种结构的取舍,理解奖励塑形如何调控"涌现协作 vs 各自为政"的程度,能诊断并修复"懒惰智能体""奖励黑客""碰撞惩罚压过任务奖励导致冻结"等典型病态。

  4. 把第 10 章的 CTDE/MAPPO 落到多机运动上——说清在多机运动协调里中心化 critic 到底该喂什么(全局状态、特权信息、所有智能体观测的聚合),actor 端如何只用本地观测,以及参数共享 + 排列不变 + 本地坐标系这三件套如何共同支撑"训练时 \(N{=}2\)、部署时 \(N{=}10\)"的零样本可扩展。

  5. 从零搭建并训练一个最小的多机导航/避障/编队策略——能在一个 GPU 并行的多智能体环境(如 MQE 风格的接口)里配置 MAPPO 参数共享训练循环,定义观测/动作/奖励,跑通双机器人协同到达目标,并把训练好的策略零微调部署到更多机器人上、量化成功率衰减。

  6. 完成多机 sim-to-real 迁移——理解多机比单机多出的现实差距维度(每个智能体独立随机化、智能体数量随机化、通信延迟、接触力建模误差),掌握多机域随机化清单、本体感知 + 相对观测的可迁移表征设计、以及"接触隐式通信"为什么在真机上比"显式通信"更鲁棒。

  7. 为学出来的策略叠加安全保证并与传统规控混合——理解软惩罚的固有不安全性,掌握把控制障碍函数(CBF)、安全盾(safety shield)、可达性约束接到 learned policy 输出端的工程模式,建立"什么时候用纯 MARL、什么时候用 MARL+传统混合"的选型判断(为第 12 章混合架构铺路)。

本章知识导航

本章的知识结构是一棵以"如何让一群机器人通过强化学习学会在物理空间里协调运动"为根的树。第 10 章已经把树根(学习框架)种好了,本章要长出"接到机器人运动"的全部枝干:问题怎么建模(一个 Dec-POMDP 的运动协调实例)→ 接口三件套(观测、动作、奖励)→ 训练范式落地(CTDE 在运动里喂什么)→ 三大应用(导航、避障、编队)→ 落到真机(sim-to-real)→ 守住安全(与传统规控的边界)

第 10 章:合作 MARL 的学习框架(随机博弈/CTDE/值分解/策略梯度/信用分配)
        │  [本章默认你已掌握,仅 2-3 行重述,不重讲]
怎么把"多机运动协调"写成 MARL 问题? (§11.1 运动协调的 Dec-POMDP 建模)
        ├─→ 观测设计:看得见队友 + 可扩展到任意 N (§11.2 观测表示与排列不变性)
        ├─→ 动作设计:速度层 vs 关节层,连续 vs 离散 (§11.3 动作空间设计)
        ├─→ 奖励塑形:协作怎么涌现、竞争怎么处理 (§11.4 奖励塑形)
        ├─→ 训练范式落地:CTDE/MAPPO 在运动里喂什么 (§11.5 CTDE 实践)
        ├─→ 三大应用 (§11.6)
        │       导航到目标 / 去中心化避障 / 学习式编队
        │       └─→ 软惩罚不安全 → 叠加安全层(CBF/盾)
        ├─→ 从零实战:搭一个多机导航/编队训练循环 (§11.7)
        └─→ 多机 sim-to-real:比单机多出的坑 (§11.8 多机迁移)
        与传统规控的边界与混合 (§11.9,为第 12 章铺路)
小节 主题 难度 一句话
§11.1 运动协调的 Dec-POMDP 建模 ⭐⭐ 把导航/避障/编队写成一个部分可观测随机博弈
§11.2 观测表示与排列不变性 ⭐⭐⭐ 看得见队友又能扩展到任意 \(N\) 的关键
§11.3 动作空间设计 ⭐⭐ 速度层 vs 关节层,分层让二者各司其职
§11.4 奖励塑形:协作 vs 竞争 ⭐⭐⭐ 奖励结构决定涌现的是协作还是挤兑
§11.5 CTDE 在多机运动里的实践 ⭐⭐⭐ 中心化 critic 到底喂什么
§11.6 三大应用与安全层 ⭐⭐⭐ 导航/避障/编队 + 软惩罚为何必须叠硬约束
§11.7 从零搭建多机训练循环 ⭐⭐ MQE 风格环境 + MAPPO 参数共享,跑通双机
§11.8 多机 sim-to-real 迁移 ⭐⭐⭐⭐ 多机域随机化、接触隐式通信、数量随机化
§11.9 与传统规控的边界与混合 ⭐⭐⭐ 什么时候用 MARL、什么时候混合

两条阅读线

  • 应用工程线(会用就行):§11.1→§11.2→§11.3→§11.4→§11.7→§11.8,目标是能配置环境、设计接口、跑通训练、部署到真机。
  • 原理深入线(理解为什么):§11.1→§11.2→§11.4→§11.5→§11.6→§11.9,重点是排列不变性的理论、奖励塑形与信用分配的关系、CTDE 在运动里的喂料原则、以及 learned policy 安全性的根本局限。

无论哪条线,§11.1(建模)和 §11.2(观测/排列不变)都是必读——前者定义了"我们到底在优化什么",后者是多机 MARL 区别于单机 RL 的第一个、也是最重要的工程分水岭。

前置知识桥接

本章紧接第 10 章,并复用第 3 章、01_数学/70_强化学习数学 的若干前置。这里把最关键的点重新激活,你不必翻回去就能跟上。

  • 回顾第 10 章:CTDE 是本章一切训练的底座。第 10 章证明了多智能体一起学习会让单智能体 RL 的收敛保证失效(非平稳性:在 \(i\) 看来,队友 \(j\) 的策略在变,于是转移和奖励都在变),而 CTDE 用"训练时中心化 critic 看全局、执行时 actor 只看本地"绕开它。本章不再重推这个结论,而是直接问:在多机器人运动协调里,那个"全局"具体是什么? 答案不唯一(真值状态、特权信息、所有智能体观测的排列不变聚合),§11.5 会把这件事讲清楚。一句话记住:CTDE 给了"训练时可以作弊、执行时必须自立"的许可证,本章是去用这张许可证。

  • 回顾第 10 章:MAPPO 是本章默认的训练算法。MAPPO = 一个共享参数的 PPO actor(所有同质机器人用同一个网络 \(\pi_\theta(a_i \mid o_i)\))+ 一个吃全局信息的中心化 critic \(V_\phi(s)\)。它在合作 MARL 上是最强基线之一。本章的所有实战(§11.7)默认用 MAPPO,并在它之上叠加多机运动特有的观测/动作/奖励设计。需要值分解(QMIX/VDN)的场合本章会指出,但主线是策略梯度路线。

  • 回顾第 10 章:信用分配贯穿奖励设计。当一群机器人共享团队奖励时,"谁的功谁的过"难以区分。本章 §11.4 的奖励塑形本质上是在**问题侧**缓解信用分配(用个体局部奖励直接给每个机器人可归因的信号),与第 10 章在**算法侧**缓解(COMA 反事实基线、QMIX 值分解)形成互补。两条路可以、也常常同时用。

  • 回顾第 3 章:离散决策层与连续运动层的分工。第 3 章解决"谁去做哪件任务(任务分配)"和"怎样不撞上(MAPF)"这两个**离散决策**问题,用的是搜索和组合优化,碰撞是**硬约束**。本章处理的是它们之下的**连续运动协调**:机器人已经知道大致去哪,但要在连续状态空间里实时地协调速度、躲避彼此、维持队形——而且用的是**学出来的策略**,碰撞退化成**软惩罚**。两者不是替代关系:实际系统常常 MAPF 在上层出离散计划、MARL 或传统控制在下层执行;§11.6 和 §11.9 会讲这种分层。

  • 前向预告:本章的 learned policy 把碰撞写成软惩罚,没有任何一步的硬安全保证。下一章(第 12 章,MARL 与传统规控混合) 会系统地把控制障碍函数(CBF)、分布式 MPC、安全盾接到 learned policy 上,得到"既有学习的灵活与协调、又有传统方法的安全保证"的混合架构。现在只需要记住一个结论:纯 MARL 适合协调与适应,传统规控适合安全与可验证,二者互补而非替代——§11.9 会把这条边界画清楚,第 12 章把它做成系统。

如果跳过本章会怎样

跳过本章,你会卡在三个具体的地方——它们都是"学了第 10 章的 MARL 基础、却没学怎么用到机器人运动上"时必然会撞的墙。

场景一:"训练时两个机器人好好的,加到四个就全乱了"。 你用第 10 章学的 MAPPO 训了一个双机器人导航策略,观测里把"队友的位置"按固定槽位(队友 1 的 xy、队友 2 的 xy……)拼进去。两个机器人时一切完美。可当你想扩展到四个机器人,发现网络的输入维度变了、必须重新训练;更糟的是,即便重训,策略对"哪个队友排在第几个槽位"敏感——交换两个队友的编号,输出居然变了。根本原因是你没有用**排列不变**的观测表示。§11.2 会告诉你:多机器人之间没有天然的顺序,策略必须对队友的排列不变,否则既不可扩展、又在浪费样本去学本该对称的东西。

场景二:"碰撞惩罚一调大,机器人就全冻在原地不动了"。 你给避障任务加了个碰撞惩罚,想让机器人别撞。一开始惩罚太小,机器人横冲直撞;你把惩罚调大,结果机器人学会了一个"绝妙"的策略——待在原地一动不动,因为不动就永远不会撞,惩罚为零。任务奖励(到达目标)反而被它放弃了。这是奖励塑形里最经典的病态:安全惩罚压过了任务激励,最优策略退化成"什么都不做"。§11.4 会教你怎么平衡任务奖励与安全惩罚、怎么用势函数塑形(potential-based shaping)在不改变最优策略的前提下加引导、怎么诊断"冻结""懒惰""挤兑"这些典型失败。

场景三:"仿真里编队走得漂漂亮亮,搬到真机就散架了"。 你在仿真里训了个多足协同搬运/编队策略,仿真录像里队形保持得严丝合缝。一上真机,机器人之间的相对距离飘忽、负载被拽得东倒西歪、甚至有机器人原地踏步。根本原因是多机 sim-to-real 比单机多出好几个维度的现实差距:每个机器人的电机延迟/摩擦各不相同、接触力在仿真里建得不准、机器人之间通过物体接触传递的力在仿真和真机差异巨大。§11.8 会给你一份多机域随机化清单、讲清为什么"接触隐式通信"比"显式通信"在真机上更鲁棒、以及可迁移观测表征该怎么设计。

预计阅读时间

模式 时长 适合
精读 15-21 小时 第一次系统学 MARL 在多机运动上的落地:逐节读建模→设计→实战,亲手实现排列不变编码器、塑形一个多机奖励、跑通双机训练并部署到多机、做完每节练习。建议分 4-5 次。
速读 5-7 小时 已掌握第 10 章 MARL 基础、想建立"怎么用到运动"的全局图景:读每节的"动机"和"理论"小标题、关键设计表(观测/动作/奖励对比)、§11.7 实战骨架、§11.8 多机迁移清单,跳过代码细节。
速查 50-60 分钟 已学过、回来查特定设计:直接定位到对应小节,看观测/动作/奖励设计表、排列不变聚合对比、多机 DR 清单、安全层接法、故障排查表与 API 速查。

科研发展脉络

在钻进具体设计前,先把这条研究线的来龙去脉理清——多机运动协调的 MARL 化大致沿着一条清晰的主线推进:从"在抽象游戏里验证 MARL 算法",走到"在有物理的多机器人导航/避障上学策略",再走到"在真实多足/多机器人上学协同运动并迁移到真机"。知道每个工作"从哪来、解决了前人什么痛点、又留下什么给后人",比孤立地记名字有用得多。

年份 论文 Venue 核心贡献
2017 Long, Fan, Pan 等 RSS/ICRA 去中心化避障的 RL 化奠基:每个机器人用本地激光 + 邻居相对状态,端到端学一个分布式避障策略,无需通信
2017 Lowe, Mordatch 等 (MADDPG) NeurIPS CTDE 范式奠基(第 10 章已讲):集中 critic + 去中心 actor,混合协作/竞争环境的统一框架
2018-2020 Everett, Chen, How (GA3C-CADRL) IROS/IJRR 多机社会化导航:把"社会礼貌"编进奖励,LSTM/注意力处理变长邻居集合
2024 Xiong 等 (MQE) IROS MQE:IsaacGym 多四足 MARL 标准基准(cooperative_transport/formation/chase),GPU 大规模并行
2024 An, Hutter 等 RSS 排列不变网络:让多机协作策略天然对队友排列不变,从 2 到 \(N\) 零样本可扩展
2024 (MASQ) arXiv 把单台四足的四条腿当作多智能体,用合作 MARL 协调单机运动——MARL 思想的"向内"应用
2025 Pandit, Shrestha, Fern 等 (decPLM) arXiv decPLM\(N\) 台四足-臂零通信协同搬运(pinch-lift-move),分层策略 + constellation 奖励,仅靠接触力协调,真机迁移
2025 (Cable-towed load via MARL) arXiv 缆绳牵引负载:分层去中心 MARL 规划器各自基于本地观测出速度指令,协同拖拽
2025 (SeqWM) arXiv 顺序世界模型:序贯式多智能体规划,首个在 BottleCap/PushBox 真机多足任务上成功的方法
2025 Jose, Zhang (Dual-Quadruped Safe RL) arXiv HAPPO + 约束马尔可夫博弈:把安全约束写进双四足协同的优化里
2025 An, Hutter 等 (Scalable Multi-Robot Cooperation) RA-L 可扩展多足协调:系统化地从 2 扩到 \(N\),量化可扩展性

关键脉络:早期(2017 前后)的工作证明了"去中心化避障/导航可以端到端学出来、且不需要通信"——这是把 MARL 用到机器人运动的第一个里程碑。中期(2018-2020)在导航上加入了对**变长邻居集合**的处理(LSTM、注意力),开始触及"可扩展性"这个核心难题。近两年(2024-2025)随着 GPU 大规模并行仿真(IsaacGym/MQE)成熟,重心转向**多足/多机器人的协同运动**,并把**排列不变性**确立为可扩展的关键技术、把**多机 sim-to-real**(尤其是接触力建模和零通信协调)确立为最硬的开放问题。

看这条线,有一条贯穿的主线:让策略对"有多少个队友、队友怎么编号"这件事不敏感——从早期用 LSTM/注意力硬处理变长输入,到近年用排列不变网络从架构上根除这个问题。本章把这条主线作为 §11.2 的核心,因为它是多机 MARL 区别于单机 RL 最本质的工程差异。

本质洞察:多机运动协调的 MARL,难点几乎从不在"单个机器人怎么动"(那是单机 RL 的事,第 10 章之前的强化学习数学已覆盖),而在"怎么表示和利用'其他机器人'这个动态、变长、无序的集合"。整章你会反复看到同一个主题以不同面貌出现:观测层是排列不变聚合、奖励层是信用分配、训练层是中心化 critic 的喂料、迁移层是接触隐式通信——它们都是在回答同一个问题:"别的机器人"这个信息,到底该以什么形式进入一个机器人的决策回路。


本章符号约定

符号 含义 首见
\(N\) 机器人(智能体)数量 §11.1
\(i, j\) 智能体下标,\(i\) 为"自己",\(j\) 为"队友" §11.1
\(\mathcal{S}\) 全局(环境真值)状态空间 §11.1
\(o_i\) 智能体 \(i\) 的本地观测 §11.1
\(O_i\) 智能体 \(i\) 的观测函数,\(o_i = O_i(s)\) §11.1
\(a_i\) 智能体 \(i\) 的动作 §11.1
\(\mathcal{A}_i\) 智能体 \(i\) 的动作空间 §11.1
\(r_i\) 智能体 \(i\) 的单步奖励 §11.1
\(r^{\text{team}}\) 团队(共享)奖励 §11.4
\(\pi_\theta\) 共享参数的 actor 策略(参数 \(\theta\) §11.5
\(V_\phi(s)\) 中心化 critic(参数 \(\phi\) §11.5
\(o_i^{\text{self}}\) 自身本体状态部分(proprioception) §11.2
\(o_{ij}^{\text{nbr}}\) 队友 \(j\) 相对于 \(i\) 的观测(相对位置/速度) §11.2
\(\phi_{\text{enc}}, \rho\) 排列不变聚合的逐元素编码器与聚合后处理 §11.2
\(\oplus\) 排列不变聚合算子(mean/max/attention 求和) §11.2
\(g_i\) 智能体 \(i\) 的目标(goal) §11.6
\(\Phi(s)\) 势函数(potential-based shaping 用) §11.4
\(h(x)\) 控制障碍函数(CBF) §11.6
\(d_{ij}\) 机器人 \(i,j\) 之间的距离 §11.4
\(d_{\text{safe}}\) 安全距离阈值 §11.4

§11.1 运动协调的 Dec-POMDP 建模 ⭐⭐

这一节解决什么问题:在写任何观测、动作、奖励的代码之前,必须先回答一个更根本的问题——"多机器人运动协调"这件事,到底要被写成一个什么样的优化对象?这一节把导航、避障、编队这三类典型任务统一建模为一个**部分可观测的随机博弈(Dec-POMDP)**,并解释为什么这个建模比"把 \(N\) 个机器人拼成一个巨型单智能体 MDP"在样本效率、可扩展性、部署可行性上全面占优。它是后面所有设计(§11.2-§11.8)的地基——观测设计是在填这个建模的 \(O_i\),动作设计是在填 \(\mathcal{A}_i\),奖励塑形是在填 \(r_i\)

动机:一群机器人在共享空间里运动,到底在求解什么?

设想一个再具体不过的场景:仓库地面上有 4 台移动机器人,各自要从当前位置去一个指定货架取货。它们共享同一片地面——会在过道里相遇、在路口可能撞上、在狭窄通道里得互相礼让。再设想一个更难的:4 台四足机器人要抬着一块大板子一起走,既要往目标方向前进、又要始终维持一个矩形队形不让板子翻掉。还有一个:一群无人机要穿过一片有静态障碍的空域,彼此不能相撞、还要尽快到达各自终点。

这三个场景——导航(到目标)、编队(维持队形)、避障(不相撞)——看起来很不一样,但它们共享同一个数学骨架:

  • \(N\) 个**决策者**(机器人),每个在每个时刻要选一个动作(往哪走、怎么迈腿)。
  • 每个机器人**看不到全局**,只有自己的传感器(有限视野、有噪声)——它看得见附近的队友和障碍,看不见远处的、看不见队友脑子里在想什么。
  • 它们的动作**通过共享的物理世界相互影响**——我往左你也往左,我们就可能撞上;我抬高一点你压低一点,板子就歪了。
  • 有一个(或每人一个)目标,用奖励来表达——到达目标加分、撞上扣分、队形偏离扣分。
  • 这是一个**序贯**问题——不是一锤子买卖,而是每个时刻都要决策,当前的动作会改变下一时刻所有人的处境。

这五条,恰好就是一个**部分可观测随机博弈**的定义要件。我们要做的第一件事,就是把这个直觉精确化。

本质洞察:第 3 章的任务分配(指派问题)和 MAPF 也是多机问题,但它们是**离散、可以一次性算完**的——分配是一个静态匹配,MAPF 是在已知地图上一次性搜出所有路径。本章的运动协调不同:它是**连续状态空间里的、随时间持续展开的、每个智能体只看局部的**序贯决策。这个差别决定了为什么前者用搜索/组合优化、后者用强化学习——不是"哪个更先进",而是问题结构本就不同。

如果用最朴素的办法会怎样:把 N 个机器人拼成一个巨型 MDP

最朴素、也最容易想到的办法是:别搞什么多智能体了,把 \(N\) 个机器人看成一个"超级机器人",它的状态是所有机器人状态的拼接,动作是所有机器人动作的拼接,然后用单智能体 RL(第 10 章之前学的 PPO/SAC)去训这个超级机器人。 这叫**联合学习(joint learning)**或中心化方案。

这个办法理论上没错——它就是一个标准 MDP,单智能体 RL 的所有保证都适用。但它有三个致命问题,每一个都足以让它在真实多机器人系统上不可用:

问题一:动作空间随 \(N\) 指数爆炸。 如果每个机器人有 \(|\mathcal{A}|\) 个可选动作(离散情形),\(N\) 个机器人的联合动作空间是 \(|\mathcal{A}|^N\)。4 个机器人、每个 7 个离散动作,联合动作空间就是 \(7^4 = 2401\);10 个机器人就是 \(7^{10} \approx 2.8 \times 10^8\)。连续动作下维度线性增长看似温和,但策略要在这个高维联合空间里探索,样本复杂度同样急剧恶化。第 3 章前置自测第 5 题问的"把 \(N\) 个机器人拼成联合智能体在 \(N\) 倍维度空间搜索会怎样",答案就是这里的爆炸——MAPF 用解耦回避它,MARL 用去中心化 actor 回避它。

问题二:要求全局通信,部署时根本做不到。 超级机器人需要在每个时刻收集所有机器人的状态、算出联合动作、再把动作分发回每个机器人。这意味着运行时必须有一个能与所有机器人实时通信的中央节点。但真实部署里——仓库 AGV 之间、无人机蜂群之间、野外多足之间——通信是有延迟、会丢包、甚至完全没有的。一个依赖全局实时通信的方案,在通信一断就整体瘫痪。

问题三:完全不可扩展、不可迁移。 超级机器人的网络输入输出维度被 \(N\) 焊死了。训练时是 4 个机器人,部署时来了第 5 个,对不起,整个网络得重训。这与真实需求(机器人数量经常变化、希望训一次到处用)完全背道而驰。

本质洞察:联合学习的三个问题(动作爆炸、要求全局通信、不可扩展)不是实现层面的小毛病,而是"把多智能体问题硬塞回单智能体框架"的结构性代价。CTDE 的全部价值就在于**把这三个问题留在训练阶段、而让执行阶段干净地去中心化**——训练时我们可以在仿真里开天眼(不在乎全局通信,因为是离线的),执行时每个机器人只用本地观测自己的 actor(动作空间是单机的 \(|\mathcal{A}|\)、不需要通信、换几个机器人都行)。这就是第 10 章 CTDE 那张"许可证"在本章兑现的第一笔价值。

历史:从 Markov Game 到 Dec-POMDP,再到机器人运动

把"多个决策者在共享环境里序贯交互"形式化的源头,是博弈论与控制论的交汇。1994 年 Littman 提出**马尔可夫博弈(Markov Game,又称随机博弈 Stochastic Game)**,把单智能体 MDP 推广到多智能体——状态转移和奖励依赖所有智能体的联合动作(第 10 章 §10.1 已讲)。但 Markov Game 默认每个智能体能观测到全局状态,这对机器人不成立。

更贴合机器人现实的是 Dec-POMDP(Decentralized Partially Observable Markov Decision Process,去中心化部分可观测马尔可夫决策过程),由 Bernstein 等在 2002 年形式化。它在 Markov Game 基础上加了"部分可观测"——每个智能体只能通过自己的观测函数看到状态的一部分。Dec-POMDP 一般性极强,但也极难(最优求解是 NEXP-完全的,比 NP 还难)。好在我们不追求精确最优,而是用 MARL(CTDE/MAPPO)在仿真里**近似**地学一个好策略——这正是第 10 章建立的方法论,本章是把它的对象从抽象游戏换成机器人运动。

到了机器人领域,2017 年前后 Long/Fan/Pan 等的去中心化避障工作是关键的一步:他们第一次令人信服地证明,一个完全去中心化、只用本地观测、无需通信的避障策略,可以端到端地学出来并在真实多机器人上跑。这把 Dec-POMDP 从理论框架变成了可落地的机器人方案,也奠定了本章"观测设计→端到端学策略→去中心执行"的基本范式。

理论:运动协调 Dec-POMDP 的精确定义

现在把上面的直觉写成精确的数学对象。一个多机器人运动协调任务被建模为一个 Dec-POMDP,由元组 $$ \mathcal{M} = \langle N, \mathcal{S}, {\mathcal{A}i}^N, P, {O_i}{i=1}^N, {r_i}^N, \gamma \rangle $$ 描述。逐项解释每个元素在多机器人运动协调里的具体含义——这张"对应表"是本章后续所有设计的导航图:

元素 数学含义 在多机器人运动协调里是什么 在本章哪节填充
\(N\) 智能体数量 机器人数量(关键:希望策略对 \(N\) 可扩展) §11.2 排列不变
\(\mathcal{S}\) 全局状态空间 所有机器人的位姿/速度 + 障碍 + 目标 + 负载状态(真值,仅训练时可用) §11.5 critic 喂料
\(\mathcal{A}_i\) 智能体 \(i\) 的动作空间 机器人 \(i\) 能下的指令:速度指令 / 关节位置偏移 §11.3 动作设计
\(P(s'\mid s, \mathbf{a})\) 转移核,依赖联合动作 \(\mathbf{a}=(a_1,\dots,a_N)\) 物理引擎/真实动力学:所有机器人动作共同决定下一状态 由仿真器/真机实现
\(O_i(s)\) 智能体 \(i\) 的观测函数 机器人 \(i\) 的传感器:本体感知 + 邻居相对状态 + 局部障碍 + 目标 §11.2 观测设计
\(r_i(s, \mathbf{a})\) 智能体 \(i\) 的奖励 到达目标 + 维持队形 − 碰撞 − 控制代价 等的加权 §11.4 奖励塑形
\(\gamma\) 折扣因子 通常 0.99 左右,权衡即时与长期 训练超参

几个必须讲清的细节

(1)观测 \(o_i = O_i(s)\) 是状态的有损函数。 机器人 \(i\) 拿到的 \(o_i\) 不是 \(s\),而是 \(s\) 经过传感器这个"有损通道"后的结果。这个损失是双重的:一是**空间局部性**(只看得见附近,看不见远处的机器人和障碍),二是**信息缺失**(看得见队友的位置,但看不见队友的速度意图、看不见队友奖励里在乎什么)。后者是多机器人特有的难点——你的队友也在学习、也在变策略,但你看不到它的"脑子",这就是非平稳性在观测层的体现。

(2)转移 \(P\) 依赖联合动作,这是"博弈"二字的来源。 在单智能体 MDP 里 \(P(s'\mid s, a)\) 只依赖自己的动作;这里 \(P(s'\mid s, \mathbf{a})\) 依赖所有人的动作 \(\mathbf{a} = (a_1, \dots, a_N)\)。这意味着即使机器人 \(i\) 的动作 \(a_i\) 完全不变,只要队友改了策略,\(i\) 经历的状态转移分布就变了——从 \(i\) 单方面的视角看,环境是非平稳的。这正是第 10 章反复强调、并用 CTDE 来对治的核心困难。本章默认你已理解它,不再重推。

(3)奖励可以是个体的 \(r_i\),也可以共享为 \(r^{\text{team}}\) 上面的元组写成每个智能体一个 \(r_i\)(最一般的形式,能表达竞争)。但合作型运动协调常常让所有人共享同一个团队奖励 \(r_1 = \dots = r_N = r^{\text{team}}\),或者混合(个体部分 + 共享部分)。奖励结构的选择直接决定涌现的是协作还是各自为政——这是 §11.4 的核心,这里先埋下。

(4)我们要优化的"解"是什么? 是一组**去中心化策略** \(\{\pi_i(a_i \mid \tau_i)\}\),其中 \(\tau_i\)\(i\) 的本地观测-动作历史(部分可观测下一般要用历史,工程上常用当前观测 + 一点记忆近似)。目标是最大化(折扣累积)团队回报的期望。注意"去中心化"是写进解的结构里的——每个 \(\pi_i\) 只能依赖 \(i\) 自己的信息。这与联合学习的"一个看全局的大策略"形成根本对立,也是 CTDE 之所以必要的原因:我们想要去中心化的**解**,但又想在**训练**时利用全局信息来稳定学习。

为什么这个建模比联合 MDP 好——三点对照总结

维度 联合 MDP(超级机器人) Dec-POMDP + CTDE(本章)
动作空间 \(\|\mathcal{A}\|^N\) 指数爆炸 每个 actor 只管单机 \(\|\mathcal{A}_i\|\)
部署通信 必须全局实时通信 各自本地观测,零运行时通信
可扩展性 \(N\) 焊死,换数量要重训 配合排列不变可零样本扩展(§11.2)
样本效率 \(N\) 倍维度空间探索 参数共享 + 单机动作空间,高效
训练稳定性 标准单智能体保证 靠 CTDE 中心化 critic 恢复(§11.5)

代价是:Dec-POMDP 本身极难(NEXP-完全),我们放弃精确最优、用 MARL 近似。但对工程而言,"一个可部署、可扩展、样本高效的好策略"远胜于"一个理论最优但不可部署的方案"。

代码验证:把建模写成环境接口

理论讲完,用代码把这个 Dec-POMDP 建模"实例化"成一个环境接口。这里给出一个最小的、与 MQE(Multi-agent Quadruped Environment)和主流多智能体环境(PettingZoo 风格)兼容的接口骨架,让你看清"建模的每个元素"如何落成可调用的方法。注意:这里是**教学简化**版,剥离了物理引擎细节,只保留 Dec-POMDP 的结构。

import numpy as np

class MultiRobotCoordEnv:
    """多机器人运动协调环境(教学简化版,体现 Dec-POMDP 结构)。
    把 §11.1 的元组 <N, S, {A_i}, P, {O_i}, {r_i}, gamma> 落成接口。
    """
    def __init__(self, n_robots=4, world_size=10.0, dt=0.1):
        self.N = n_robots                  # ← 元组里的 N
        self.world_size = world_size
        self.dt = dt
        # 全局状态 S:每个机器人的 (x, y, vx, vy) + 目标 (gx, gy)
        # 真值状态仅训练时给 critic 用(§11.5),actor 看不到
        self.pos = None      # (N, 2) 位置
        self.vel = None      # (N, 2) 速度
        self.goal = None     # (N, 2) 各自目标
        self.sense_radius = 3.0   # 传感半径:观测的"有损通道"由它决定

    def reset(self):
        rng = np.random.default_rng()
        self.pos  = rng.uniform(0, self.world_size, size=(self.N, 2))
        self.goal = rng.uniform(0, self.world_size, size=(self.N, 2))
        self.vel  = np.zeros((self.N, 2))
        return self._get_obs_all()         # 返回每个 agent 的本地观测 o_i

    def global_state(self):
        """全局真值状态 s——仅训练时供中心化 critic 使用(CTDE)。
        执行时禁止调用:actor 只能用 _get_obs_all() 的本地观测。"""
        return np.concatenate([
            self.pos.reshape(-1), self.vel.reshape(-1), self.goal.reshape(-1)
        ])

    def _get_obs_i(self, i):
        """观测函数 O_i(s):机器人 i 的本地、部分可观测视角。
        结构 = 自身状态(到目标的相对向量+自身速度) + 可见邻居的相对状态。
        '可见'由 sense_radius 决定,体现空间局部性这个有损来源。"""
        self_part = np.concatenate([
            self.goal[i] - self.pos[i],    # 到目标的相对向量(本地坐标语义)
            self.vel[i],                   # 自身速度
        ])
        nbr_parts = []
        for j in range(self.N):
            if j == i:
                continue
            rel = self.pos[j] - self.pos[i]
            if np.linalg.norm(rel) <= self.sense_radius:   # 只看得见半径内的队友
                nbr_parts.append(np.concatenate([rel, self.vel[j] - self.vel[i]]))
        # 注意:nbr_parts 是"变长、无序"的集合——这正是 §11.2 要处理的核心
        return {"self": self_part, "neighbors": nbr_parts}

    def _get_obs_all(self):
        return [self._get_obs_i(i) for i in range(self.N)]

    def step(self, actions):
        """转移 P(s'|s, a):联合动作共同决定下一状态。
        actions: 长度 N 的列表,actions[i] 是机器人 i 的 (ax, ay) 加速度指令。"""
        actions = np.asarray(actions).reshape(self.N, 2)
        self.vel = self.vel + actions * self.dt          # 简化点质量动力学
        self.pos = self.pos + self.vel * self.dt
        rewards = self._compute_rewards()                # 每个 agent 的 r_i
        obs = self._get_obs_all()
        done = self._check_done()
        return obs, rewards, done, {}

    def _compute_rewards(self):
        """奖励 r_i:到达目标(+) − 碰撞(−) − 控制代价(−)。§11.4 会大改这里。"""
        rewards = np.zeros(self.N)
        for i in range(self.N):
            dist_to_goal = np.linalg.norm(self.goal[i] - self.pos[i])
            rewards[i] += -0.1 * dist_to_goal            # 距离越近奖励越高(稠密)
            for j in range(self.N):
                if j != i and np.linalg.norm(self.pos[j] - self.pos[i]) < 0.5:
                    rewards[i] += -10.0                  # 碰撞惩罚(软惩罚!见 §11.6)
        rewards += -0.01 * np.sum(np.zeros(self.N))      # 占位:控制代价
        return rewards

    def _check_done(self):
        return [np.linalg.norm(self.goal[i] - self.pos[i]) < 0.3
                for i in range(self.N)]

这段代码把 Dec-POMDP 的每个元素都对应到了一个具体方法:_get_obs_i 是观测函数 \(O_i\)step 里的动力学是转移 \(P\)_compute_rewards 是奖励 \(r_i\)global_state 是仅训练可用的全局状态 \(s\)请特别注意 _get_obs_i 返回的 neighbors 是一个变长、无序的列表——这一个细节,就是整个 §11.2 存在的理由:策略必须能吃下"长度不定、顺序无关"的邻居集合。下一节就解决它。

⚠️ 常见陷阱

⚠️ 编程陷阱:执行时偷偷在 actor 里调用了 global_state()
   错误做法:图省事,给 actor 也喂全局状态("反正环境里有这个方法"),
            训练曲线很漂亮,成功率很高
   现象:仿真里完美,一上真机(或一旦没有中央通信)策略直接失效——
        因为真机上每个机器人拿不到 global_state(),actor 的输入分布
        和训练时完全不同,输出垃圾动作
   根本原因:违反了 CTDE 的铁律——critic 可以用全局状态,actor 绝不可以。
            CTDE 的全部价值就在于"训练作弊、执行自立",actor 用了全局
            就等于把"执行自立"这一半丢了,退化成不可部署的联合方案
   正确做法:用代码强制隔离——actor 的 forward 只接收 o_i(本地观测),
            critic 的 forward 才接收 s(全局状态)。两个网络的输入接口
            从类型上就分开,让"actor 用全局"成为编译/类型层面的错误
   自检方法:把 global_state() 改名加上 _TRAIN_ONLY 后缀并加断言,
            或在部署模式下让它抛异常,任何 actor 路径触发就立刻暴露
💡 概念误区:以为"部分可观测"只是"传感器有噪声"
   新手想法:"观测就是状态加点高斯噪声,o_i ≈ s + ε,本质上还是能看到全局"
   实际上:部分可观测的核心不是噪声,而是【信息缺失】和【局部性】。
          机器人 i 根本看不到三公里外的机器人 k——这不是"看得不准",
          是"完全看不到"。更要命的是它看不到队友的【意图】(队友下一步
          想往哪走、队友的策略是什么)。把部分可观测误解成"加噪声",
          会让你低估问题难度,设计出依赖"其实能恢复全局"的错误方案
   为什么重要:正因为信息是真的缺失(而非只是带噪),才需要:
             (a) 排列不变地处理"我能看到的那部分队友"(§11.2);
             (b) 用历史/记忆去推断看不到的部分(POMDP 要用 τ_i 而非 o_i);
             (c) CTDE 在训练时用全局 critic 补上 actor 看不到的信息(§11.5)
   延伸思考:如果机器人之间允许通信,通信就是在"主动减少信息缺失"——
            这把 Dec-POMDP 推向 Dec-POMDP-Com,是另一条研究线(§11.8 会提)
🧠 思维陷阱:认为"既然是多智能体,就一定要用最复杂的 MARL 算法"
   新手想法:"这是多机器人协调,得上 QMIX/QPLEX/MAPPO+注意力+通信
            这些最 fancy 的东西才配得上问题的难度"
   实际上:建模(这一节)和算法(第 10 章)是两件事。很多多机运动协调
          任务,用最朴素的"MAPPO + 参数共享 + 排列不变观测"就能解得很好,
          甚至 IPPO(独立 PPO,连中心化 critic 都不要)在弱耦合任务上
          就够用。算法复杂度应该由【任务的耦合强度】决定,而不是由
          "这是多智能体"这个标签决定
   正确思维:先判断任务的耦合强度——机器人之间是强物理耦合(一起抬一个
            刚体,一个动全身动)还是弱耦合(各走各的偶尔避让)?
            弱耦合 → IPPO/独立学习常常够;强耦合 → 才需要中心化 critic、
            值分解、甚至显式通信。先从最简单的基线开始,不够了再加复杂度
   自检方法:永远先跑一个 IPPO 或 MAPPO 朴素基线,记录它的成功率。
            只有当朴素基线明显不够、且你能定位到"是耦合没处理好"时,
            才有理由上更复杂的方法。否则复杂度只会增加调试痛苦

练习

  1. (建模题) 把"3 台无人机穿过一片有 5 个静态圆柱障碍的空域、各自飞到指定终点、彼此不相撞"这个任务,写成 §11.1 的 Dec-POMDP 元组。具体写出:(a) 状态 \(\mathcal{S}\) 包含哪些量;(b) 观测 \(O_i\) 应该包含哪些部分(提示:本体 + 邻居 + 障碍 + 目标)、为什么障碍也要进观测;(c) 动作 \(\mathcal{A}_i\) 你会选速度层还是加速度层、为什么;(d) 奖励 \(r_i\) 的各项。不要求代码,要求把每个元素的语义写清楚。

  2. (改写题) 上面 MultiRobotCoordEnv_get_obs_i 目前只把"半径内队友"放进观测。请改写它,使其同时支持"障碍观测":环境里新增 self.obstacles(一组 \((x,y,半径)\)),观测里加入"半径内最近的 \(K\) 个障碍的相对位置"。思考一个设计问题:障碍数也是变长的,你打算怎么处理这个变长集合?(这道题为 §11.2 埋伏笔,先想,不必现在实现完美。)

  3. (思辨题) 第 3 章用 MAPF(搜索)解多机无碰撞路径,本章用 MARL(学习)解多机避障。请从三个维度对比二者:(a) 碰撞保证(硬约束 vs 软惩罚);(b) 对环境变化的适应性(地图变了/出现动态障碍时各自要做什么);(c) 计算发生的时机(规划时一次性算 vs 执行时实时推理)。最后回答:什么样的任务你会优先选 MAPF、什么样的会优先选 MARL、什么样的会想把两者结合(这指向第 12 章)?


§11.2 观测表示与排列不变性 ⭐⭐⭐

这一节解决什么问题:§11.1 的 _get_obs_i 留下了一个刺眼的尾巴——每个机器人观测里的"邻居集合"是**变长、无序**的。一个普通的神经网络策略只接受固定维度、有固定顺序的输入向量,它没法直接吃下"有时 2 个邻居、有时 5 个邻居、且邻居没有天然编号"的集合。这一节解决多机 MARL 最核心的工程问题:怎么把'其他机器人'这个动态、变长、无序的集合,编码成策略能用、又能扩展到任意 \(N\)、又不浪费样本的表示。这是单机 RL 完全不会遇到、而多机 MARL 绕不过去的分水岭。

动机:邻居是一个集合,不是一个列表

回到 §11.1 那个 4 机器人仓库场景。机器人 \(i\) 在某一时刻看到附近有 3 个队友,下一时刻可能一个队友走远了只剩 2 个、又或者另一个队友靠近了变成 4 个。它的观测里"邻居"这部分的长度**一直在变**。

更微妙的是:这 3 个队友**没有天然的顺序**。它们不是"第 1 号队友、第 2 号队友、第 3 号队友"——对机器人 \(i\) 而言,重要的是"我周围有这么几个队友,它们各自在什么相对位置、以什么相对速度运动",至于"先数哪个、后数哪个"完全无所谓。如果我把队友 A 和队友 B 在观测里的位置对调一下,机器人 \(i\) 该做的决策应该**一模一样**——因为物理世界没变,只是我数它们的顺序变了。

这两个性质——变长**和**无序——合起来说明:邻居是一个**集合(set),不是一个**列表(list)。而几乎所有标准神经网络(MLP、CNN)的输入都是有固定维度、有固定顺序的张量,也就是"列表"。直接把集合硬塞进列表式网络,就是一切麻烦的根源。

如果用最朴素的办法会怎样:固定槽位拼接

最朴素的办法,也是新手几乎必然第一次会写的办法:给每个队友分配一个固定槽位,按编号拼进观测向量。

# ❌ 朴素做法:固定槽位拼接(fixed-slot concatenation)
obs_i = concat([
    self_state,            # 自己的状态
    neighbor_1_relative,   # 队友 1 的相对状态(槽位 1)
    neighbor_2_relative,   # 队友 2 的相对状态(槽位 2)
    neighbor_3_relative,   # 队友 3 的相对状态(槽位 3)
])  # 固定长度,喂给普通 MLP

这个做法在双机器人、且数量永远不变时勉强能用,但它有三个层层递进的致命缺陷:

缺陷一:输入维度被 \(N\) 焊死,不可扩展。 槽位数等于队友数。训练时 4 个机器人(3 个槽位),部署时想用 6 个机器人(5 个槽位),网络的输入层维度变了,整个网络作废,必须重训。这与"训一次、不同规模到处用"的真实需求直接冲突。

缺陷二:对排列敏感,浪费样本。 把队友 A 放槽位 1、队友 B 放槽位 2,和反过来放,对普通 MLP 是**两个完全不同的输入**,会算出**两个不同的输出**。但物理上这两种情况一模一样!这意味着:网络必须额外耗费大量样本,去学习"槽位 1 和槽位 2 应该被对称对待"这件**本不该学、本就应该由架构保证**的事。这是巨大的样本浪费,而且学不彻底——总有些排列组合训练时没见过。

缺陷三:槽位分配本身就是个无解的难题。 当可见队友数少于槽位数怎么办(补零?补零会被网络误解成"有个队友在原点")?多于槽位数怎么办(截断?截断哪几个)?怎么决定哪个队友进哪个槽位(按距离排序?按编号?任何排序规则都在人为地往无序集合里强加顺序)?这些问题没有干净的答案,因为问题本身就被错误地建模了——你在用列表的工具处理集合的对象。

对比性思维(反事实):如果不解决排列不变性会怎样?→ 你的策略会把 50% 以上的训练样本浪费在学习一个数学上本该恒等的对称性上;→ 你的策略永远绑死在训练时的机器人数量上;→ 你会陷入"补零还是截断"的无解工程泥潭。排列不变的观测表示不是一个"锦上添花的优化",而是多机 MARL 可扩展、样本高效的**必要条件**。

历史:从 LSTM/注意力硬扛,到 Deep Sets 从架构根除

处理"变长邻居集合"这个问题,机器人 MARL 走过了一条清晰的进化路线:

  • 早期(2018-2020,硬扛阶段):把邻居集合先按某种规则排序(如按距离从近到远),然后用一个 LSTM/RNN 顺序地"读"进去,或者用**注意力机制**对邻居加权求和。Everett/Chen/How 的 CADRL/GA3C-CADRL 系列就是这条路的代表——用 LSTM 处理变长的邻居观测。这能处理变长,但排序这一步仍然在往集合里强加顺序,而且 RNN 的顺序处理引入了不必要的、与物理无关的"先后"偏置。

  • 中期(Deep Sets,2017,理论根除):Zaheer 等的 Deep Sets 给出了一个漂亮的理论结果——任何对集合的排列不变函数,都可以写成"对每个元素先做同一个变换、再做排列不变的聚合(如求和)、最后再做一个变换"的形式。这从根本上告诉我们:处理集合不需要排序、不需要 RNN,只需要"逐元素编码 + 对称聚合"。

  • 近期(2024-2025,机器人多机协调的主流):An、Hutter 等在 RSS 2024 和后续 RA-L 工作中,把排列不变网络确立为**多机器人协作策略可扩展到任意 \(N\) 的关键技术**——训练时 \(N{=}2\)、部署时 \(N{=}10\) 零样本可扩展,正是靠排列不变。SPECTra(2025)等进一步用排列无关网络提升大规模 MARL 的可扩展性。一句话:排列不变从"一个 trick"变成了"多机 MARL 的标准架构组件"。

本质洞察:排列不变性把策略/价值函数的有效搜索空间,从"对智能体数量 \(N\) 指数依赖"降到"多项式依赖"。直觉是:如果不利用对称性,网络要分别学习每一种"哪个队友在哪个位置"的排列(\(N!\) 种);利用排列不变性后,网络只需学习"一个队友在某相对位置时该怎么反应"这一个函数,再对所有队友聚合。这是把组合爆炸的复杂度,压缩成单个元素函数 + 一次聚合的复杂度——和卷积用平移不变性压缩图像识别复杂度,是同一种思想的不同应用。

理论:排列不变性的精确定义与 Deep Sets 定理

把"对调队友顺序、输出不变"这件事写成精确的数学条件。

定义(排列不变函数):设一个函数 \(f\) 接受一个集合 \(\{x_1, x_2, \dots, x_M\}\)(这里 \(x_j\) 是队友 \(j\) 的相对观测)。称 \(f\) 是**排列不变(permutation-invariant)**的,如果对任意排列 \(\sigma\)(对 \(\{1,\dots,M\}\) 的任意重排),都有 $$ f(x_1, x_2, \dots, x_M) = f(x_{\sigma(1)}, x_{\sigma(2)}, \dots, x_{\sigma(M)}). $$ 直白说:把输入元素的顺序怎么打乱,输出都不变。我们的策略对"邻居集合"这部分的处理,必须满足这个条件。

注意区分一个相关但不同的概念——排列等变(permutation-equivariant):如果把输入元素重排,输出也跟着以同样方式重排,即 \(f(x_{\sigma(1)}, \dots) = (\,\)对应重排后的输出\(\,)\)。什么时候要不变、什么时候要等变?

场景 要的性质 原因
把"我看到的所有队友"聚合成"周围环境"的单一表征,喂给我自己的策略 不变 我的决策不该依赖我数队友的顺序
一个中心化网络同时输出所有 \(N\) 个机器人的动作 等变 \(k\) 个输出要对应第 \(k\) 个机器人,重排输入应重排输出

本节聚焦**不变**(每个机器人用自己策略、聚合邻居),等变主要出现在某些中心化 critic 或中心化策略里(§11.5 会提)。

Deep Sets 定理(核心工具):Zaheer 等 2017 证明,一个作用在集合上的函数 \(f\) 是排列不变的,当且仅当它可以分解为 $$ f({x_1, \dots, x_M}) = \rho!\left( \bigoplus_{j=1}^{M} \phi_{\text{enc}}(x_j) \right), $$ 其中: - \(\phi_{\text{enc}}\) 是一个对**每个元素分别施加的相同**编码器(比如一个共享权重的 MLP),把每个队友的相对观测 \(x_j\) 映射成一个特征向量; - \(\bigoplus\) 是一个**排列不变的聚合算子**——最常见的是逐元素求和 \(\sum_j\)、求平均 \(\frac{1}{M}\sum_j\)、或逐元素取最大 \(\max_j\); - \(\rho\) 是一个对聚合结果再施加的后处理网络(也是 MLP)。

为什么这个形式天然排列不变?关键在中间的聚合 \(\bigoplus\)。求和、求平均、取最大这些操作,本身就与求和项的顺序无关——\(a+b+c = c+a+b\)\(\max(a,b,c)=\max(c,b,a)\)。因为聚合抹掉了顺序,所以无论 \(\phi_{\text{enc}}\)\(\rho\) 怎么取,整体一定排列不变。这就是 Deep Sets 的精髓:把"对集合不变"这个看似复杂的约束,简化为"逐元素编码 + 一次对称聚合"这个简单的架构。

三种聚合算子的取舍(这是工程中真正要做的选择):

聚合算子 数学形式 直觉含义 适合 局限
mean pooling \(\frac{1}{M}\sum_j \phi(x_j)\) "周围队友的平均态势" 队友影响可平均、数量变化大 抹平个体差异,最危险的那个队友被平均稀释
max pooling \(\max_j \phi(x_j)\)(逐维) "最显著/最危险的那个队友" 避障(最近的队友最重要) 丢掉非极值队友的信息
attention(注意力) \(\sum_j \alpha_j \phi(x_j)\)\(\alpha_j\) 学习得到 "按重要性加权的队友态势" 不同队友重要性差异大、需自适应 计算量大、需更多样本学权重

注意力可以看成"可学习权重的加权求和",它比 mean(权重都等于 \(1/M\))和 max(权重是 one-hot 在极值上)更一般、更灵活,代价是更难训。工程建议:先用 mean 或 max 起步(简单、稳健、足够多任务用),只在明确观察到"需要自适应区分队友重要性"时才上注意力。

多视角理解(类比 + 边界):排列不变聚合和**卷积神经网络**很像——CNN 用同一个卷积核扫过图像每个位置(参数共享),靠平移不变性把"识别一个模式"的能力推广到全图任意位置;Deep Sets 用同一个 \(\phi_{\text{enc}}\) 编码每个队友(参数共享),靠排列不变性把"理解一个队友"的能力推广到任意数量、任意顺序的队友。像的地方:都用参数共享 + 对称性压缩复杂度。不像的地方:CNN 的不变性是**平移**(有空间结构、有局部性),Deep Sets 的不变性是**排列**(集合无空间结构、无顺序);CNN 聚合用的是带空间结构的池化窗口,Deep Sets 聚合是全局对称聚合。不要把"卷积处理网格"的直觉(如局部感受野、层级特征)生搬到集合上——集合没有"相邻"的概念。

理论-工程桥接:本地坐标系——排列不变之外的第二个支柱

排列不变解决了"队友无序变长",但还有一个同样重要、却容易被忽略的设计:观测必须用本地坐标系(egocentric / 相对坐标)表达,而不是全局坐标。

回看 §11.1 的 _get_obs_i:队友观测用的是 self.pos[j] - self.pos[i](相对位置),到目标用的是 self.goal[i] - self.pos[i](相对向量)——全都是"相对于我自己"的。这不是随手写的,而是和排列不变同等重要的可扩展性支柱:

  • 为什么不能用全局坐标? 如果观测里放队友的全局绝对坐标 \((x_j, y_j)\),那么"队友在我右前方 1 米"这个对决策真正重要的信息,在不同的全局位置会表现成完全不同的数值。策略得在每一个全局位置分别学一遍"右前方有队友该怎么躲",样本效率极低,且无法泛化到训练时没去过的区域。

  • 本地坐标系的威力:用相对坐标后,"右前方 1 米有个队友"无论发生在地图的哪个角落,喂给策略的数值都一样。策略只需学一次"相对态势 → 动作"的映射,就能用在任何全局位置。这与"参数共享让所有同质机器人用同一个网络"是配套的——本地坐标让"同一个网络"这件事在空间上也成立。

  • 三件套合体参数共享(所有机器人同一个 \(\pi_\theta\))+ 排列不变(对队友集合不变)+ 本地坐标(相对表达),这三者共同支撑了"训练 \(N{=}2\)、部署 \(N{=}10\) 零样本可扩展"这个多机 MARL 最诱人的性质。缺任何一个,可扩展性都会打折。decPLM、An/Hutter 的可扩展多足协调,本质上都是把这三件套做扎实。

本质洞察:多机策略能"训少部署多",不是因为网络有什么魔力,而是因为我们用三个设计(参数共享、排列不变、本地坐标)把问题里的**对称性**全榨干了——智能体是可互换的(参数共享)、队友是无序的(排列不变)、空间是平移齐次的(本地坐标)。每利用一种对称性,就少学一类本不必学的东西。可扩展性是对称性的红利,不是免费的午餐。

代码:从零实现排列不变的邻居编码器

现在把 Deep Sets 定理落成 PyTorch 代码。我们实现一个完整的、可扩展到任意邻居数的多机器人策略网络。先讲清楚每一块在做什么,再给正确实现,再给错误对照,最后给三种聚合的对比。

Step 1:为什么要这样写

为什么策略网络要拆成 self_encoder + neighbor_encoder + 聚合 + action_head 四块?

因为观测有两种结构完全不同的部分:
  - 自身状态 o_self:固定维度、单个向量 → 普通 MLP(self_encoder)就行
  - 邻居集合 {o_nbr}:变长、无序 → 必须用 Deep Sets(neighbor_encoder + 聚合)

把两者分开编码、再拼起来送进 action_head,是因为它们要用不同的工具:
自身部分是"列表",邻居部分是"集合"。混在一起用一个 MLP,就会重蹈
固定槽位拼接的覆辙——丢掉排列不变性。

排列不变的关键就一行:对邻居编码后做 mean/max(对称聚合),
这一步把"无序集合"压成"固定维度向量",且与邻居顺序无关。

Step 2:正确实现

import torch
import torch.nn as nn

class PermInvariantPolicy(nn.Module):
    """排列不变的多机器人策略网络(Deep Sets 架构)。
    输入:自身观测 o_self + 变长无序的邻居观测集合 {o_nbr}。
    输出:动作分布参数。对邻居集合排列不变、可扩展到任意邻居数。
    所有同质机器人共享这一个网络(参数共享)。"""
    def __init__(self, self_dim, nbr_dim, hidden=128, act_dim=2, agg="mean"):
        super().__init__()
        self.agg = agg
        # 自身状态编码器:处理固定维度的"列表"部分
        self.self_encoder = nn.Sequential(
            nn.Linear(self_dim, hidden), nn.ReLU(),
            nn.Linear(hidden, hidden), nn.ReLU(),
        )
        # 邻居逐元素编码器 φ_enc:对每个队友施加【相同】的变换(Deep Sets 的 φ)
        self.neighbor_encoder = nn.Sequential(
            nn.Linear(nbr_dim, hidden), nn.ReLU(),
            nn.Linear(hidden, hidden), nn.ReLU(),
        )
        # 聚合后处理 ρ + 动作头:吃 [自身特征 ⊕ 邻居聚合特征]
        self.action_head = nn.Sequential(
            nn.Linear(hidden * 2, hidden), nn.ReLU(),
            nn.Linear(hidden, act_dim),          # 输出动作均值(连续动作)
        )

    def forward(self, self_obs, neighbor_obs, neighbor_mask):
        """
        self_obs:      (B, self_dim)         每个样本的自身观测
        neighbor_obs:  (B, M_max, nbr_dim)   补齐到 M_max 的邻居观测(padding)
        neighbor_mask: (B, M_max)            1=真实邻居, 0=padding(关键!)
        """
        self_feat = self.self_encoder(self_obs)              # (B, hidden)

        # 逐元素编码所有邻居(包括 padding 的,稍后用 mask 屏蔽)
        nbr_feat = self.neighbor_encoder(neighbor_obs)       # (B, M_max, hidden)

        # 用 mask 把 padding 位置的贡献清零——这是正确处理变长的关键
        mask = neighbor_mask.unsqueeze(-1)                   # (B, M_max, 1)
        nbr_feat = nbr_feat * mask

        # 排列不变聚合 ⊕:对真实邻居求 mean 或 max
        if self.agg == "mean":
            cnt = mask.sum(dim=1).clamp(min=1.0)             # 每个样本的真实邻居数
            nbr_agg = nbr_feat.sum(dim=1) / cnt              # (B, hidden) 仅对真实邻居平均
        elif self.agg == "max":
            nbr_feat = nbr_feat.masked_fill(mask == 0, -1e9) # padding 设为 -inf 不参与 max
            nbr_agg = nbr_feat.max(dim=1).values             # (B, hidden)
        else:
            raise ValueError(self.agg)

        combined = torch.cat([self_feat, nbr_agg], dim=-1)   # (B, 2*hidden)
        return self.action_head(combined)                    # (B, act_dim)

Step 3:错误写法对照

# ❌ 错误 1:忘了 mask,padding 的邻居被当成真实邻居参与聚合
nbr_agg = self.neighbor_encoder(neighbor_obs).mean(dim=1)  # 对 M_max 求平均
# 问题:padding 的零向量被编码成某个非零特征也参与了平均,
#      于是"我周围有 2 个真邻居"和"3 个真邻居(1 个 padding)"的聚合结果
#      被 padding 数量污染。邻居越少、padding 越多,污染越严重——
#      策略会因为"凑数的假邻居"做出错误决策。必须用 mask!

# ❌ 错误 2:mean 聚合时除以 M_max 而不是真实邻居数
nbr_agg = (nbr_feat * mask).sum(dim=1) / M_max  # 错误的分母
# 问题:真实邻居数是 2、M_max 是 6 时,聚合幅值被错误地缩小到 1/3,
#      数值尺度随 padding 量漂移,策略输入分布不稳定

# ❌ 错误 3:对邻居用了带位置的处理(如给每个槽位不同的权重)
nbr_feat = neighbor_obs @ self.per_slot_weight  # 每个槽位一套权重
# 问题:这直接破坏了排列不变性——又退回固定槽位拼接的老路。
#      Deep Sets 要求对每个元素施加【完全相同】的 φ_enc,
#      任何"按位置区别对待"都会让排列不变性失效

# ❌ 错误 4:max 聚合时 padding 没设成 -inf 就直接 max
nbr_agg = (nbr_feat * mask).max(dim=1).values  # padding 是 0
# 问题:若真实邻居特征某维全为负,padding 的 0 反而成了那一维的最大值,
#      把"不存在的邻居"当成了最显著的邻居。必须把 padding 设为 -1e9

Step 4:三种聚合方式对比

# === 同一个网络骨架,三种聚合的行为差异 ===

# 方式 A:mean pooling —— "周围队友的平均态势"
#   nbr_agg = Σ φ(x_j) / M
#   适合:编队(关心队友整体分布的质心/平均)、队友数量变化大的导航
#   风险:最近、最危险的那个队友被平均稀释,避障可能反应不够快

# 方式 B:max pooling —— "最显著的那个队友"
#   nbr_agg = max_j φ(x_j)  (逐维取最大)
#   适合:避障(最近的队友威胁最大,max 天然突出极值)
#   风险:丢掉非极值队友信息,多个队友同时逼近时只"看到"一个

# 方式 C:attention —— "按学到的重要性加权"
#   nbr_agg = Σ α_j φ(x_j),  α_j = softmax(query·key_j)
#   适合:不同队友重要性差异大且需自适应(有的队友要躲、有的可忽略)
#   代价:多一套 query/key/value 参数,样本需求和计算量都更高

# 对比:
# | 聚合 | 排列不变 | 计算量 | 样本需求 | 默认推荐场景 |
# |------|---------|--------|---------|-------------|
# | mean | ✅      | 最低    | 最低    | 编队、起步基线 |
# | max  | ✅      | 低      | 低      | 避障 |
# | attn | ✅      | 高      | 高      | 异质重要性、强耦合 |

注意:上面四块网络是**所有同质机器人共享的同一套参数**——这正是第 10 章 MAPPO 参数共享在多机运动里的具体落地。配合本地坐标系观测(§11.1 已用相对坐标)和这里的排列不变聚合,三件套齐活,策略就具备了"训少部署多"的可扩展性。

深入理解:为什么 padding + mask 是工程上的标准做法

读到这里你可能会问:既然邻居是变长的,为什么代码里还要 padding 到固定的 M_max?这不是又回到固定长度了吗?

这是一个常见且重要的疑问。答案是:padding + mask 是为了让 GPU 批处理高效,而 mask 保证了它在数学上仍然排列不变、且不受 padding 污染。 两件事分开看:

  • 为什么 padding:GPU 喜欢规整的张量。一个 batch 里不同样本的真实邻居数不同(有的 2 个、有的 5 个),但张量必须是规整的 (B, M_max, nbr_dim)。所以把每个样本的邻居补零到统一的 M_max,纯粹是为了能塞进一个张量做并行计算。

  • 为什么有 mask 就不破坏排列不变:mask 标记哪些是真邻居、哪些是 padding。聚合时(mean 用真实计数当分母、max 把 padding 设 -inf),padding 的贡献被精确地剔除。所以最终聚合结果只取决于真实邻居的集合,与 padding 数量无关、与邻居顺序无关——排列不变性完好。padding 只是计算上的脚手架,mask 确保它不进入语义。

  • \(M_{\max}\) 怎么定:取一个略大于"实际最多能同时看到的邻居数"的值。设传感半径内最多 8 个邻居,\(M_{\max}\) 取 8 或 10 即可。看不见那么多时就 padding,多到超过 \(M_{\max}\)(罕见)就保留最近的 \(M_{\max}\) 个。注意这与固定槽位拼接的根本区别:固定槽位是**语义性**地把第 \(k\) 个队友绑死在第 \(k\) 个输入位置(破坏排列不变);padding+mask 是**计算性**的补齐,配合 mask 后语义上仍是无序集合。

⚠️ 常见陷阱

⚠️ 编程陷阱:padding 的邻居没有用 mask 屏蔽就参与聚合
   错误做法:neighbor_encoder(neighbor_obs).mean(dim=1),直接对补齐后的
            M_max 个邻居求平均,不区分真假
   现象:训练时成功率上不去、且诡异地与"平均可见邻居数"挂钩——邻居稀疏
        的场景(padding 多)策略表现明显更差,因为聚合结果被假邻居污染。
        debug 时极难发现,因为代码"能跑"、loss 也在降,只是降不到位
   根本原因:补零的 padding 经过 neighbor_encoder 后【不是零】(因为有
            bias 和非线性),它作为一个虚假的"邻居特征"混进了平均/最大,
            污染了真实邻居的聚合。padding 数量越多污染越重
   正确做法:聚合前必须用 mask 清零(mean)或设 -inf(max),且 mean 的
            分母用真实邻居数而非 M_max(见 Step 2 正确实现)
   自检方法:构造一个测试——同一组真实邻居,分别用 M_max=4 和 M_max=8
            (即不同 padding 数量)跑前向,输出必须完全一致。不一致就说明
            mask 没生效,padding 在污染聚合
⚠️ 编程陷阱:用全局绝对坐标而非相对坐标做观测
   错误做法:观测里放队友的绝对位置 (x_j, y_j) 和自己的绝对位置,
            以为"信息更全"
   现象:训练奇慢、需要海量样本,且策略无法泛化到训练时没覆盖过的
        地图区域——把机器人放到地图一个新角落,策略就失灵
   根本原因:绝对坐标下,"右前方有队友该怎么躲"这个本该位置无关的技能,
            被网络在每个全局位置分别学了一遍,样本利用率极低。物理世界
            对平移是齐次的(哪里躲队友都一样),绝对坐标却没利用这个对称性
   正确做法:所有空间量用相对/本地坐标——队友相对位置 (x_j - x_i, ...)、
            到目标相对向量、必要时连朝向也转到本体坐标系。让"相对态势"
            成为唯一输入,策略学一次就能用在任何全局位置
   自检方法:把整个场景(所有机器人+目标+障碍)整体平移一个随机向量,
            每个机器人的本地观测应当【完全不变】。若变了,说明观测里
            漏掉了某个没转成相对的绝对量
💡 概念误区:以为"排列不变"和"排列等变"是一回事
   新手想法:"反正都是处理顺序无关的多智能体,不变等变差不多"
   实际上:不变是【输出不随输入重排而变】(聚合邻居→单一表征,用于自己
          的决策);等变是【输出随输入重排而同样重排】(中心网络一次性
          输出所有 N 个动作,第 k 个输出必须对应第 k 个机器人)。用错会
          导致:本该不变的地方用了等变,输出维度随邻居数变化、无法接固定
          的 action_head;本该等变的地方用了不变,所有机器人拿到一样的
          动作、无法区分
   为什么重要:本节"每个机器人聚合邻居用自己的策略"是【不变】;§11.5 某些
            中心化 critic/策略一次性处理所有智能体是【等变】。两者用不同的
            架构(不变用 Deep Sets 聚合,等变用共享权重但保留每个元素的输出)
   延伸思考:自注意力(self-attention)天然是排列等变的(重排输入则重排
            输出),在它之后接一个对称聚合(如对所有 token 求和)就变成
            排列不变——这正是很多多机注意力策略的标准结构
🧠 思维陷阱:以为邻居越多、观测信息越全,策略一定越好
   新手想法:"把视野半径开到最大,让每个机器人看到所有队友,信息最全
            策略肯定最强"
   实际上:(a) 看到所有队友 = 失去了部分可观测的去中心化优势,且聚合了
          大量与当前决策无关的远处队友,引入噪声;(b) 真机部署时传感器
          视野本就有限,训练时假设"看到所有"会造成 sim-to-real 的观测
          分布不匹配;(c) 远处队友对"我下一步怎么走"几乎无影响,纳入它们
          只会稀释近处关键队友的信号(尤其 mean 聚合)
   正确思维:观测半径应匹配【任务的交互尺度】和【真机传感器能力】——
            只纳入"会在接下来几步内与我产生交互"的邻居。避障看近处、
            编队看队形相关的邻居。半径是一个要调的超参,不是越大越好
   自检方法:做消融——固定其他设置,扫描传感半径,看成功率曲线。通常
            存在一个"足够看到交互对象、又不过度引入无关队友"的最优区间,
            超过它再增大半径,收益递减甚至变负

练习

  1. (实现题) 基于 Step 2 的 PermInvariantPolicy,把它接到 §11.1 的 MultiRobotCoordEnv 上:写一个函数,把 _get_obs_i 返回的 {"self": ..., "neighbors": [...]} 字典转换成 (self_obs, neighbor_obs, neighbor_mask) 三个张量(处理变长邻居的 padding 和 mask)。然后构造一批假数据,验证一个关键性质:对调任意两个邻居的顺序,网络输出必须不变(数值上完全相等,用 torch.allclose)。这道题让你亲手确认排列不变性真的成立。

  2. (调试挑战) 下面这段聚合代码有一个隐藏 bug,它在邻居数恒定时表现正常、邻居数变化时性能下降。请定位并修复:

    def aggregate(self, neighbor_obs, neighbor_mask):
        feat = self.neighbor_encoder(neighbor_obs)   # (B, M_max, H)
        feat = feat * neighbor_mask.unsqueeze(-1)
        return feat.mean(dim=1)                        # 这里有 bug
    
    提示:问题出在 mean 的分母。写出修复后的代码,并解释为什么原版在邻居数变化时会出错(联系本节"mean 分母应是真实邻居数"的讨论)。

  3. (设计扩展题,跨章综合) 综合第 3 章(MAPF 的邻居/冲突概念)、第 10 章(CTDE)和本节(排列不变观测),设计一个"局部避障策略"的完整观测方案:(a) 自身部分放什么;(b) 邻居部分如何用排列不变方式编码(选 mean/max/attention 哪个、为什么);(c) 障碍如何纳入(障碍也是变长无序集合,能否复用同一个 Deep Sets 思路?需要为障碍单独一套 \(\phi_{\text{enc}}\) 还是和队友共用?给出你的判断和理由);(d) 这个 actor 在 CTDE 下训练时,对应的中心化 critic 应该看到什么(为 §11.5 预热)。要求画出网络结构示意(文字描述即可)并说明每个设计决策。


§11.3 动作空间设计:速度层 vs 关节层 ⭐⭐

这一节解决什么问题:观测设计回答了"机器人看到什么",动作设计回答"机器人能下什么指令"。一个看似简单、实则影响整个系统成败的决策是:MARL 策略的输出应该是高层的速度指令(往哪走、走多快),还是底层的关节位置/力矩(每条腿每个关节怎么动)? 这个选择决定了学习的难度、可迁移性、以及能不能复用已有的运动控制器。这一节讲清两种动作层级的取舍,以及把它们组合起来的"分层"做法——这是多足/复杂机器人 MARL 协调的主流工程范式。

动机:同一个"往左走",可以是一个指令,也可以是十二个

设想 §11.1 那个多足协同搬运任务里的一台四足机器人。它需要"往目标方向走、同时和队友保持队形"。这个意图,可以用两种截然不同的粒度表达成动作:

  • 高层(速度层):策略输出一个二维速度指令 \((v_x, v_y)\) 或加上偏航角速度 \(\omega\)——"以 0.5 m/s 向右前方走、同时左转 0.1 rad/s"。怎么迈腿才能实现这个速度,交给一个**已有的底层运动控制器**(比如第 6 章/足式控制章节里学的步态控制器或单机 locomotion 策略)。

  • 底层(关节层):策略直接输出 12 个关节的目标位置偏移(四条腿 × 每腿 3 关节)——"左前腿髋关节 +0.1 rad、膝关节 -0.05 rad……"。从原始关节动作里,直接长出"既能行走、又能协调"的行为。

同一个"往左走和队友保持队形"的意图,前者是 1 个(或 3 个)数,后者是 12 个数。这两种动作空间的选择,会从根本上改变 MARL 要学的东西的难度和性质。

如果选错动作层级会怎样:两个极端的代价

极端一:所有任务都用关节层,让 MARL 从零学行走 + 协调。 听起来很"端到端"很优雅,但代价巨大:MARL 策略不仅要学"怎么和队友协调"(多机问题),还要同时学"怎么用 12 个关节走路而不摔倒"(单机 locomotion,本身就是个难问题)。把两个难问题耦合在一个策略里,样本需求暴涨、训练极不稳定、而且学出来的"走路"质量往往远不如专门训练的单机 locomotion 控制器。更糟的是,多机协调的奖励信号(队形、避障)很稀疏,要它穿透"先学会走路"这一层传导到关节,信用分配极其困难。

极端二:所有任务都用速度层,把底层完全黑箱化。 这在导航/避障这类"机器人本体能力不是瓶颈"的任务上很合理,但在强物理耦合的协同操作(一起抬一个刚体、缆绳牵引)上会丢信息:速度层指令无法精细表达"用左前腿多使点劲压住板子这一角"这种需要落到接触力/具体肢体的协调。当协调的本质发生在接触力层面时,速度抽象就太粗了。

对比性思维(不是 X 而是 Y):动作层级的选择,不是"端到端越底层越先进",而是"让 MARL 只学它真正该学的那部分协调,把本体运动能力交给更合适的工具"。MARL 的稀缺资源是样本和稳定性,不该浪费在"重新发明走路"上。

历史与主流范式:分层(hierarchical)让二者各司其职

机器人多机 MARL 在实践中收敛到一个主流答案——分层架构

  • 上层:MARL 策略,输出高层指令(速度、落脚点、或一个低维的协调隐变量),只负责"多机协调"这件它擅长且必须学的事。
  • 下层:一个已经训好或设计好的运动控制器(单机 locomotion 策略、MPC、步态控制器),把上层指令翻译成关节动作,负责"让单台机器人稳稳地执行指令"。

2025 年的 decPLM(多四足协同搬运)就是这个范式的典型——它明确地**把基座运动(base locomotion)和手臂控制分离**:上层策略协调多机怎么一起 pinch-lift-move,下层有专门的运动控制。Cable-towed load 的工作同样是分层去中心 MARL 规划器出速度指令、底层执行。把"协调"和"运动"解耦,是这些工作能成功迁移到真机的关键工程决策之一。

层级 谁来做 负责什么 动作粒度
上层 MARL 策略(本章主角) 多机协调(去哪、保持什么队形、怎么避让) 速度 / 落脚点 / 协调隐变量
下层 已有运动控制器 单机稳定执行(怎么迈腿/转向实现上层指令) 关节位置 / 力矩

但分层不是唯一答案。MASQ(2024)反其道而行——把单台四足的四条腿当作四个智能体,用合作 MARL 在**关节层**协调单机的腿。这说明动作层级的选择强依赖于"协调发生在哪个尺度":多机器人协调发生在机器人之间(上层速度足够),单机多肢协调发生在肢体之间(必须到关节层)。

理论:连续 vs 离散,以及动作空间的几个设计维度

确定了层级(速度还是关节),还有几个正交的设计维度:

维度一:连续 vs 离散。

连续动作 离散动作
形式 \(a_i \in \mathbb{R}^d\)(如速度 \((v_x,v_y)\) \(a_i \in \{\)前/后/左/右/停\(\}\)
策略输出 分布参数(如高斯均值方差) 类别概率(softmax)
适合算法 MAPPO/MADDPG/SAC MAPPO/QMIX
适合场景 真实机器人运动(速度/力矩本就连续) 栅格世界、与 MAPF 对接、动作语义离散
优点 精细、贴合物理 探索简单、易与离散规划层对接

机器人真实运动几乎总是连续的(速度、力矩都是连续量),所以本章主线用**连续动作**。离散动作主要出现在"与第 3 章 MAPF 那种栅格离散规划对接"或"动作本身就是离散选择"的场合。

维度二:动作的物理范围与缩放。 策略网络的原始输出通常在 \((-\infty, \infty)\) 或经 \(\tanh\) 压到 \([-1, 1]\),必须**缩放**到物理可行范围。比如速度指令要 clip 到机器人实际能达到的最大速度。这一步看似琐碎,却是大量 sim-to-real 失败的隐藏原因——仿真里允许的动作范围比真机大,策略学会用真机做不到的激进动作。

维度三:动作平滑性。 相邻两步动作如果跳变太大,真机执行器跟不上、还会抖。常见做法是在奖励里加动作变化惩罚(\(-\|a_t - a_{t-1}\|^2\),§11.4 会讲),或在动作输出端加低通滤波/动作残差。多机场景这点尤其重要——一台机器人动作抖动会通过物理耦合(接触、队形)传染给队友。

代码:速度层动作的实现与缩放

把速度层动作的"原始输出 → 物理指令"这条链写清楚。这里展示动作缩放和裁剪这个最易出错的环节。

import torch
import torch.nn as nn

class VelocityActionHead(nn.Module):
    """速度层动作头:把策略特征映射成物理速度指令。
    输出连续动作,经 tanh 压缩后缩放到机器人物理速度范围。"""
    def __init__(self, feat_dim, v_max=1.0, w_max=1.0):
        super().__init__()
        self.mean_layer = nn.Linear(feat_dim, 3)             # (vx, vy, ω) 的均值
        self.log_std = nn.Parameter(torch.zeros(3))          # 可学习的对数标准差
        self.register_buffer("scale", torch.tensor([v_max, v_max, w_max]))

    def forward(self, feat):
        raw_mean = self.mean_layer(feat)                     # 原始输出,范围无界
        # tanh 压到 [-1,1] 再乘物理上限——保证动作物理可行(关键的缩放步!)
        action_mean = torch.tanh(raw_mean) * self.scale      # 缩放到 [-v_max, v_max] 等
        std = torch.exp(self.log_std)                        # 标准差恒正
        return action_mean, std

    def sample(self, feat):
        mean, std = self.forward(feat)
        dist = torch.distributions.Normal(mean, std)
        raw_action = dist.rsample()                          # 重参数化采样(训练用)
        # 注意:采样后仍可能超界(高斯尾巴),执行前必须再 clip 到物理范围
        action = torch.clamp(raw_action, -self.scale, self.scale)
        log_prob = dist.log_prob(raw_action).sum(dim=-1)     # 用于 PPO 的对数概率
        return action, log_prob
# ❌ 错误 1:原始输出不经任何缩放直接当速度指令
action = self.mean_layer(feat)   # 范围 (-∞, ∞)
# 问题:网络初始化时可能输出一个 50 m/s 的疯狂速度,仿真里机器人瞬移、
#      物理炸开(NaN),训练直接崩。必须用 tanh*scale 约束到物理范围

# ❌ 错误 2:仿真允许的 v_max 比真机大,没对齐
# 仿真训练用 v_max=3.0(仿真里机器人能跑这么快)
# 真机实际只能 1.0 → 策略学会的高速协调动作真机做不出来 → sim-to-real 失败
# 正确:训练时的 v_max 必须 ≤ 真机实际能力,宁可保守

# ❌ 错误 3:采样后忘了 clip,高斯尾巴产生越界动作
action = dist.rsample()  # 高斯有长尾,偶尔采出 5*scale 的极端值
# 问题:偶发的越界动作在真机上可能触发急停或不安全行为
# 正确:执行前 torch.clamp 到物理范围(见 sample 方法)

⚠️ 常见陷阱

⚠️ 编程陷阱:动作未缩放/裁剪到机器人物理范围
   错误做法:策略网络的原始输出直接作为速度/力矩指令下发
   现象:训练初期物理引擎炸开(位置/速度变 NaN)、或学出真机无法执行的
        激进动作;sim-to-real 时真机急停或失控
   根本原因:神经网络初始输出无界,且高斯采样有长尾,必然偶尔产生超出
            机器人物理能力的指令。仿真容忍这些"超能力"动作,真机不容忍
   正确做法:tanh 压缩 + 乘物理上限做缩放,采样后再 clamp 兜底;训练时的
            动作上限对齐(不超过)真机实际能力
   自检方法:训练中记录动作的分位数(如 99% 分位),确认它在物理范围内;
            把动作上限设成真机参数,在仿真里先验证策略仍能完成任务
💡 概念误区:以为底层关节动作"更端到端所以更好"
   新手想法:"直接输出关节力矩最 fancy、最 end-to-end,速度层是偷懒"
   实际上:动作层级的选择是工程权衡不是先进性比赛。关节层让 MARL 同时
          背负"学走路"和"学协调"两个难题,样本需求和不稳定性暴涨,且
          走路质量常不如专门的 locomotion 控制器。多机协调任务里,把
          本体运动交给成熟的下层控制器、让 MARL 专注协调,几乎总是更
          稳、更快、更易迁移
   为什么重要:决定了你的训练能否在合理算力内收敛、能否迁移真机。选错
            层级是很多"训不出来"的根因——不是算法不行,是把太多负担
            压给了一个策略
   延伸思考:例外是"协调本身发生在关节/接触尺度"的任务(如 MASQ 把
            单机四腿当多智能体、或协同操作里需精细分配接触力)——这时
            才有理由下沉到关节层
🧠 思维陷阱:在多机场景忽视动作平滑性,只看单步最优
   新手想法:"每步选当前最优动作就行,平不平滑无所谓"
   实际上:多机器人通过物理耦合(接触、队形约束)相互影响,一台机器人
          动作抖动会传染给队友——你猛地变向,拽着共同负载的队友被带得
          踉跄,连锁放大。单机时动作抖动只是自己抖,多机时是全队抖
   正确思维:在多机强耦合任务里,动作平滑性是协调质量的一部分,要主动
            塑形(动作变化惩罚 −‖a_t − a_{t−1}‖²、动作残差、低通滤波)
   自检方法:录制多机执行轨迹,看动作时间序列是否高频抖动;看队形误差
            是否随某台机器人的动作突变而尖峰——若是,加平滑约束

练习

  1. (设计判断题) 对以下三个任务,分别判断你会用速度层还是关节层动作、为什么:(a) 4 台轮式 AGV 在仓库过道里导航避障;(b) 4 台四足机器人合抬一块刚性大板维持水平;(c) 单台六足机器人在崎岖地形上协调六条腿前进。对每个任务说明"协调发生在什么尺度",并据此给出动作层级选择。

  2. (实现题) 把 §11.3 的 VelocityActionHead 接到 §11.2 的 PermInvariantPolicy 后面(替换原来直接输出 act_dimaction_head),构成一个完整的"排列不变观测 → 速度动作"策略。然后加入动作平滑:修改采样逻辑,使其接受上一步动作 prev_action,并返回一个用于奖励的"动作变化量" \(\|a_t - a_{t-1}\|^2\)(供 §11.4 的平滑惩罚使用)。

  3. (思辨题,承上启下) 分层架构把"协调(上层 MARL)"和"运动(下层控制器)"解耦。请讨论这个解耦的两个潜在风险:(a) 上层 MARL 假设下层能完美执行速度指令,但下层在某些状态下做不到(如机器人正在恢复平衡),这种"执行偏差"会怎么影响协调?(b) 上层和下层分开训练 vs 联合训练,各有什么利弊?联系第 12 章"MARL 与传统规控混合",你认为分层接口处最需要传递什么信息才能让两层配合好?


§11.4 奖励塑形:协作如何涌现、竞争如何处理 ⭐⭐⭐

这一节解决什么问题:奖励是 MARL 唯一的"指挥棒"——机器人最终学到的协调行为,完全由奖励的结构决定。这一节回答多机运动协调里最微妙、也最容易翻车的设计问题:奖励该给团队整体(共享)还是给每个机器人(个体)?怎么塑形才能让一群各自优化自己回报的机器人涌现出协作而不是互相挤兑?碰撞惩罚、队形惩罚、任务奖励之间怎么配比才不会把策略逼成"原地不动"或"懒惰摸鱼"? 这一节也把第 10 章的信用分配问题在"问题侧"重新审视——奖励结构本身就是缓解信用分配的第一道闸门。

动机:同样的任务,奖励一改,行为天差地别

回到多机导航避障。你想让 4 个机器人各自到达目标、彼此不撞。最直接的奖励写法:每个机器人,到达目标给大正奖励、撞上队友给大负惩罚、每步给一点距离引导。看起来天经地义。

但魔鬼在配比里。同样这套结构,三种不同的配比会涌现出三种完全不同的"性格":

  • 碰撞惩罚 ≫ 任务奖励:机器人发现"只要不动就永远不撞",于是全部冻结在起点,宁可拿不到任务奖励也不冒撞的风险。你得到一群"胆小鬼"。
  • 任务奖励 ≫ 碰撞惩罚:机器人横冲直撞奔向目标,把队友当空气,撞了就撞了反正到达的收益更大。你得到一群"莽夫"。
  • 配比恰当 + 塑形得法:机器人学会减速、绕行、礼让,既到达又不撞。你得到想要的协作。

更深一层的问题是奖励的**归属**:奖励给团队整体(大家共享一个数),还是给每个机器人(各算各的)?这个选择决定了"协作"是被直接激励的,还是要靠机器人"自发"涌现的——而且直接关系到第 10 章那个老大难:信用分配。

本质洞察:在 MARL 里,你不是在"编程"机器人的行为,而是在"设计一个让目标行为成为最优解的优化问题"。机器人会精确地最大化你写下的奖励——一字不差,包括你没意识到的漏洞。奖励塑形的全部艺术,就是确保"你真正想要的行为"和"奖励函数的最优解"是同一个东西。绝大多数 MARL 失败不是算法不行,是奖励没设对。

如果用最朴素的奖励会怎样:三种经典病态

新手最容易踩的三个奖励陷阱,每一个都对应一种可识别的失败行为:

病态一:冻结(freezing)——安全惩罚压过任务激励。 如前所述,碰撞惩罚过大时,"不动"成了风险最低的策略。本质:你把"避免坏事"的权重设得远超"完成好事",于是"什么都不做"(既不得分也不失分,期望为 0)反而优于"努力但偶尔出错"(期望为负)。

病态二:懒惰智能体(lazy agent)——共享奖励下的搭便车。 当所有机器人共享一个团队奖励时,会出现"搭便车":一部分机器人努力干活把团队奖励做上去,另一部分机器人发现"反正奖励是共享的,我躺平也能分到那份",于是摆烂。这是共享奖励 + 信用分配失败的直接后果——团队奖励无法区分谁努力谁摸鱼,摸鱼者没有受到惩罚。

病态三:奖励黑客(reward hacking)——钻奖励的空子。 机器人找到一个能刷高奖励、但不是你本意的行为。比如你用"离目标越近奖励越高"做引导,机器人可能学会在目标附近**反复横跳**来持续领取接近奖励,而不是停在目标上完成任务。或者编队任务里,机器人挤成一团(队形误差为零的退化解)而不是展开成你想要的队形。

这三个病态有一个共同的根源:奖励函数的最优解,和你心里想要的行为,不是同一个

历史与理论:三种奖励结构

把奖励的"归属"问题理论化。多机运动协调的奖励,按归属可分为三种结构:

结构一:全局团队奖励(global / shared reward)。 所有机器人共享同一个标量奖励: $$ r_1 = r_2 = \dots = r_N = r^{\text{team}}(s, \mathbf{a}). $$ 适合**强协作、共同目标**的任务(一起搬一个物体——成败是集体的)。优点:直接对齐团队目标,不会出现"个体最优损害团队"。致命缺点:信用分配极难——团队奖励涨了,每个机器人都不知道是不是自己的功劳,于是滋生懒惰智能体。这正是第 10 章 COMA 的反事实基线、QMIX 的值分解要在算法侧解决的问题。

结构二:个体局部奖励(individual / local reward)。 每个机器人有自己可归因的奖励: $$ r_i = r_i^{\text{task}}(o_i, a_i) + \dots $$ 比如导航任务里,每个机器人到达自己的目标只奖励自己。优点:信用分配天然清晰——每个机器人的奖励直接反映自己的表现,努力就有回报、摸鱼就没有,从问题侧根除了懒惰智能体。缺点:纯个体奖励**不激励协作**——机器人只顾自己到达,不会主动帮队友、不会为团队牺牲(比如为了让队友先过而自己等一下)。

结构三:混合奖励(mixed / hybrid reward)—— 工程主流。 个体部分 + 共享部分的加权: $$ r_i = \underbrace{r_i^{\text{individual}}}{\text{可归因,治懒惰}} + \lambda \cdot \underbrace{r^{\text{shared}}}. $$ 个体部分给每个机器人清晰的信用(到达自己目标、自己别撞),共享部分注入团队目标(全队都到达的额外大奖、整体队形质量),}\(\lambda\) 调节"个体 vs 集体"的权重。这是绝大多数成功的多机运动协调工作采用的结构——既避免纯共享的懒惰,又避免纯个体的自私。decPLM 的 constellation 奖励本质上就是这类设计:既有让每个机器人做好自己那部分的项,又有约束整体"星座"队形/协同的共享项。

奖励结构 信用分配 是否促协作 主要风险 典型适用
全局团队 极难 ✅ 强 懒惰智能体、搭便车 强耦合共同目标(搬一个刚体)
个体局部 天然清晰 ❌ 弱 自私、不互助 弱耦合各自导航
混合 个体部分清晰 ✅ 可调 需调 λ 平衡 绝大多数运动协调(推荐起点)

对比性思维(反事实):如果只用全局团队奖励会怎样?→ 信用分配失败 → 一部分机器人搭便车摆烂。如果只用个体奖励会怎样?→ 没有协作激励 → 机器人互相把对方当障碍而非队友,狭窄通道里互相挤兑谁也不让。混合奖励不是"两种的折中妥协",而是**用个体部分解决信用分配、用共享部分注入协作目标**——各取所长,缺一不可。

理论:势函数塑形——加引导但不改变最优策略

稠密引导奖励(如"离目标越近奖励越高")能加速学习,但前面说了,它会引入奖励黑客(反复横跳刷接近奖励)。有没有办法"既加稠密引导、又保证不改变最优策略"?有——势函数塑形(Potential-Based Reward Shaping, PBRS),Ng、Harada、Russell 1999 的经典结果。

定理(PBRS 保最优性不变):如果塑形奖励取如下形式 $$ F(s, a, s') = \gamma \Phi(s') - \Phi(s), $$ 其中 \(\Phi\) 是任意一个只依赖状态的"势函数",那么把 \(F\) 加到原奖励上,不改变最优策略——原问题和塑形后问题有完全相同的最优策略集。

为什么?直觉是:\(\gamma \Phi(s') - \Phi(s)\) 沿任何轨迹累加会**望远镜式相消**——一条从 \(s_0\)\(s_T\) 的轨迹,塑形奖励的折扣累加等于 \(\gamma^T \Phi(s_T) - \Phi(s_0)\),只依赖起点和终点的势,与中间怎么走无关。所以它只是给每个状态贴了个"高度标签"引导下山方向,不改变"哪条路最优"。

怎么用:取 \(\Phi(s) = -\text{(到目标的距离)}\)(离目标越近势越高),则 \(F = \gamma\Phi(s') - \Phi(s)\) 在"朝目标移动"时为正、"远离"时为负——这就是一个**保最优**的接近引导。关键区别在于:它不是简单的"近就给分"(那会被反复横跳刷分),而是"**朝目标前进一步**才给分、退一步就扣回去",横跳一来一回净收益为零,黑客行为被堵死。

本质洞察:势函数塑形的威力在于它把"加速学习"和"定义任务"这两件事彻底分开。原始稀疏奖励(到达目标 +1,其余 0)定义了"什么是成功";势函数塑形提供"怎么更快学会"的引导,且数学上保证不污染"什么是成功"。这是奖励工程里少数有严格理论保证的工具——能放心地加引导而不担心改变任务。

理论-工程桥接:碰撞惩罚的"软"与"硬"

奖励里的碰撞惩罚是**软约束**——它只是让碰撞"不划算",但不禁止碰撞。这与第 3 章 MAPF 的硬约束(碰撞在搜索里根本不被允许)有本质区别,也是 §11.6 要叠加安全层的原因。这里先把软惩罚本身设计好:

  • 距离型软惩罚(推荐):不要只在"已经撞上"时才惩罚(那太晚了,撞上已经发生),而要在"接近到危险距离"时就开始惩罚,随距离平滑增大: $$ r_i^{\text{collision}} = -k \cdot \max!\big(0, d_{\text{safe}} - d_{ij}\big)^2 \quad \text{对每个邻居 } j. $$ 当机器人间距 \(d_{ij}\) 大于安全距离 \(d_{\text{safe}}\) 时无惩罚;一旦逼近就平滑加大惩罚。平方让"越近惩罚增长越快"。这比"撞上才 -10"的稀疏惩罚好学得多——它给了机器人"提前减速绕行"的梯度信号。

  • 为什么不能只惩罚"已碰撞":稀疏的碰撞惩罚(撞上才扣分)梯度信息极差——机器人只在撞的瞬间得到信号,学不到"该提前多远开始避让"。距离型软惩罚提供了连续的、可微的"危险梯度",机器人能学到平滑的避让行为。

代码:一个完整的多机协调奖励函数

把上面所有要素组装成一个可用的奖励函数,逐项注释每一项的作用和配比考量。

import numpy as np

def compute_coordination_reward(pos, vel, goal, prev_pos, actions,
                                 d_safe=1.0, gamma=0.99,
                                 w_progress=1.0, w_collision=5.0,
                                 w_formation=0.0, w_action=0.01,
                                 w_arrive=10.0, formation_target=None):
    """多机运动协调的混合奖励(个体 + 共享)。
    返回长度 N 的个体奖励数组。每一项的权重 w_* 是要调的关键超参。
    """
    N = pos.shape[0]
    rewards = np.zeros(N)

    for i in range(N):
        # ---- 个体部分(可归因,治懒惰)----
        # (1) 势函数塑形的进度奖励:朝目标前进才给分(保最优、防横跳)
        d_now  = np.linalg.norm(goal[i] - pos[i])
        d_prev = np.linalg.norm(goal[i] - prev_pos[i])
        phi_now, phi_prev = -d_now, -d_prev               # 势 Φ = −距离
        rewards[i] += w_progress * (gamma * phi_now - phi_prev)   # F = γΦ(s')−Φ(s)

        # (2) 到达奖励:到自己目标的稀疏大奖(定义"成功")
        if d_now < 0.3:
            rewards[i] += w_arrive

        # (3) 距离型碰撞软惩罚:逼近危险距离就平滑惩罚(不是撞上才罚)
        for j in range(N):
            if j == i:
                continue
            d_ij = np.linalg.norm(pos[j] - pos[i])
            violation = max(0.0, d_safe - d_ij)            # 侵入安全距离的程度
            rewards[i] += -w_collision * violation ** 2    # 平方:越近增长越快

        # (4) 动作平滑/控制代价:抑制抖动(多机耦合下尤其重要,见 §11.3)
        rewards[i] += -w_action * np.sum(actions[i] ** 2)

    # ---- 共享部分(对齐团队,促协作)----
    if w_formation > 0 and formation_target is not None:
        # (5) 队形质量:所有机器人共享同一个队形误差惩罚(共享奖励)
        formation_err = _formation_error(pos, formation_target)
        rewards += -w_formation * formation_err            # 广播到每个 agent(共享)

    return rewards


def _formation_error(pos, target_offsets):
    """队形误差:当前相对构型与目标相对构型的差异。
    用'相对'构型而非绝对位置,使队形可平移/可旋转(避免退化解)。"""
    centroid = pos.mean(axis=0)
    rel = pos - centroid                                   # 相对质心的构型
    return np.mean(np.linalg.norm(rel - target_offsets, axis=1))
# ❌ 错误 1:碰撞惩罚权重远大于到达奖励 → 冻结
# w_collision=100, w_arrive=10 → "不动"期望为 0,优于"努力但偶尔擦碰"的负期望
# 现象:所有机器人冻在起点。修复:让"成功完成"的总收益明显高于"零动作"

# ❌ 错误 2:只惩罚已碰撞,不用距离型软惩罚
# if d_ij < 0.3: rewards[i] -= 10   # 撞上才罚,太晚
# 问题:梯度极稀疏,学不到提前避让。改用 max(0, d_safe−d_ij)² 的距离型

# ❌ 错误 3:进度奖励直接用 −距离(非势函数形式)
# rewards[i] += -w * d_now   # 不是 γΦ(s')−Φ(s)
# 问题:机器人在目标附近反复横跳持续刷"近"的奖励(reward hacking)。
#       必须用势函数差分形式 γΦ(s')−Φ(s),横跳净收益为零

# ❌ 错误 4:队形误差用绝对位置 → 退化解
# err = ‖pos − fixed_absolute_target‖   # 焊死在某个绝对位置的队形
# 问题:队形被钉死在地图某处,无法跟随团队移动;或机器人挤成一点也能
#       让某些误差为零。用相对质心的构型(见 _formation_error)才正确

实践指南:奖励配比的调试方法论

奖励的各项权重 \(w_*\) 是 MARL 里最磨人的超参。给一套可操作的调试方法:

步骤 做法 判断
1. 先只放任务奖励 只开进度 + 到达,关掉碰撞/队形 机器人能学会到达吗?不能说明任务奖励本身就有问题
2. 逐步加约束 再开碰撞软惩罚,从小权重起调 看碰撞率下降、到达率是否被压垮
3. 监控分项 训练时分别记录每一项奖励的均值 某项异常大说明配比失衡(如碰撞项 ≫ 任务项 → 要冻结了)
4. 观察行为 定期录制策略行为视频/轨迹 冻结?挤兑?横跳?对应回去查是哪种病态
5. 二分调权 出现病态时对相关权重做二分调整 冻结→降碰撞权重;莽撞→升碰撞权重

最重要的一条:永远**分项记录奖励**(reward components logging)。把进度、到达、碰撞、队形、动作各项的均值分别画出来。绝大多数奖励病态,从"某一项奖励的曲线明显压过其他项"就能一眼看出,比盯着总奖励和行为视频猜要快得多。

⚠️ 常见陷阱

⚠️ 编程陷阱:用纯共享团队奖励训练,出现懒惰智能体却归因为"算法不行"
   错误做法:r_1=...=r_N=r_team,所有机器人共享一个数,不给个体可归因信号
   现象:一部分机器人积极干活,另一部分原地摸鱼/打转,团队成功率卡在
        某个水平上不去;换个随机种子,摸鱼的换成另几台
   根本原因:团队奖励无法区分谁贡献谁摸鱼(信用分配失败),摸鱼者不受罚、
            搭便车分享了努力者的成果——这是博弈论意义上的稳定摸鱼,不是
            算法 bug,是奖励结构的结构性缺陷
   正确做法:(a) 问题侧——改混合奖励,加个体可归因项(自己到达只奖自己、
            自己撞只罚自己);(b) 算法侧——用 COMA 反事实基线或 QMIX 值
            分解(第 10 章)。两者可叠加
   自检方法:分项记录每个 agent 各自的"贡献"(如各自的到达/碰撞),若
            存在长期零贡献却拿正回报的 agent,就是懒惰智能体
💡 概念误区:把"加稠密引导奖励"等同于"会改变任务最优解"
   新手想法:"稀疏奖励太难学,但加了稠密引导就会让机器人学歪、刷分,
            所以稠密奖励是把双刃剑只能忍着不用"
   实际上:用【势函数形式】 F=γΦ(s')−Φ(s) 的稠密塑形,有严格定理保证
          【不改变最优策略】。它沿轨迹望远镜式相消,只依赖首末状态的势,
          不污染"哪条路最优"。会学歪的是【非势函数形式】的朴素稠密奖励
          (如直接 −距离),那才会被横跳刷分
   为什么重要:理解 PBRS 让你能放心地加引导加速学习,而不必在"学得慢
            (纯稀疏)"和"学歪(朴素稠密)"之间二选一。这是奖励工程里
            少有的有理论保证的工具
   延伸思考:势函数可以编入领域知识(如离目标的最短路距离、与队形的偏差),
            只要写成 γΦ(s')−Φ(s) 的差分形式,就是免费的、保最优的加速
🧠 思维陷阱:以为"奖励塑形"和"信用分配"是两个不相干的话题
   新手想法:"信用分配是第 10 章算法(COMA/QMIX)的事,奖励塑形是这章
            的事,井水不犯河水"
   实际上:奖励结构本身就是缓解信用分配的【第一道闸门】。用个体局部奖励,
          等于在【问题侧】直接给了每个 agent 可归因的信号,信用分配问题
          被大幅简化甚至消解;用纯团队奖励,则把全部信用分配负担甩给算法
          (COMA/QMIX 在算法侧硬解)。两者是同一问题的两个切入点
   正确思维:先在问题侧用混合奖励把能归因的信用直接给清楚(便宜、有效),
            剩下确实只能集体评价的部分(如整体队形)再交给算法侧的值分解/
            反事实基线。问题侧和算法侧配合,而不是只指望一边
   自检方法:问自己"团队奖励涨的时候,能不能从奖励结构本身看出是谁的功劳"。
            能→信用分配在问题侧已解决大半;完全不能→要么改奖励结构、
            要么必须上算法侧的信用分配工具
🧠 思维陷阱:先把所有奖励项一次性全加上再调
   新手想法:"进度、到达、碰撞、队形、平滑、能耗……我把想到的奖励项全
            写上,一起调权重"
   实际上:N 个奖励项的权重构成一个高维耦合的调参空间,一次性全开会让你
          根本无法判断"成功率上不去到底是哪一项的锅"。各项还会相互掩盖
          (碰撞项把冻结造成的低到达率"解释"成安全,让你误以为没问题)
   正确思维:增量式加奖励项——先只放任务核心项(进度+到达)确认能学会,
            再逐项加约束(碰撞→队形→平滑),每加一项观察行为和分项曲线
            的变化。出问题时新加的那项就是首要嫌疑
   自检方法:遵循前面"奖励配比调试方法论"的 5 步;任何时候只调 1-2 个
            权重,并对照分项奖励曲线判断因果

练习

  1. (诊断题) 你训练 4 机器人导航避障,观察到:训练前期到达率上升,但训到一半后所有机器人开始**在目标附近来回打转、不肯停下**,到达率反而下降。请:(a) 判断这是哪种奖励病态;(b) 检查奖励函数里最可能的肇因项;(c) 给出修复方案(提示:和势函数形式 vs 朴素稠密形式有关)。

  2. (设计题) 为"4 台四足合抬一块刚性大板、维持水平、向目标前进"设计一个完整的混合奖励。要求:(a) 写出个体部分有哪些项、共享部分有哪些项;(b) "板子保持水平"应该是个体奖励还是共享奖励、为什么;(c) 用相对构型而非绝对位置表达队形的理由;(d) 给出各项权重的相对大小关系(不需要精确值,但要说明谁应该明显大于谁、为什么)。联系 decPLM 的 constellation 奖励思想。

  3. (跨章综合题) 综合第 10 章(COMA 反事实基线、QMIX 值分解)和本节(混合奖励):对"3 台机器人共享团队奖励搬运一个物体、但出现一台懒惰智能体"这个问题,分别从**问题侧**(改奖励结构)和**算法侧**(用第 10 章的信用分配工具)各给一套解决方案,并讨论两者能否、以及如何同时使用。最后回答:如果这两套都用上了懒惰智能体仍然存在,你还会怀疑哪里出了问题(提示:观测里机器人能区分自己和队友的贡献吗?这又回到 §11.2 的观测设计)?


§11.5 CTDE 在多机运动里的实践:中心化 critic 到底喂什么 ⭐⭐⭐

这一节解决什么问题:第 10 章告诉我们 CTDE 是"训练时 critic 看全局、执行时 actor 看本地",并证明了它为什么能绕开非平稳性。但"全局"是一个抽象词——在具体的多机运动协调任务里,那个喂给中心化 critic 的"全局信息"到底是什么?真值状态?所有机器人观测的拼接?还是某种聚合?这一节把第 10 章的 CTDE 原理落到多机运动的工程细节上,回答"critic 喂料"这个看似简单、却直接决定训练成败的实战问题,并把 §11.2 的排列不变性延伸到 critic 端。

动机:actor 端已经定了,critic 端喂什么还没定

经过 §11.2-§11.4,我们的 actor 已经设计完整了:排列不变地编码本地观测(看自己 + 看可见队友)、输出速度动作、由混合奖励驱动。actor 只用本地观测 \(o_i\),这是 CTDE 的"执行自立"那一半,没有商量余地。

但 CTDE 还有"训练作弊"那一半——中心化 critic。MAPPO 的 critic \(V_\phi(\cdot)\) 在训练时可以看全局,给 actor 提供低方差的优势估计。问题来了:这个"全局"具体喂什么进去?这不是一个有唯一标准答案的问题,而是一个有几种选项、各有取舍的工程决策。喂错了,要么 critic 学不准(优势估计噪声大、训练慢),要么 critic 用了执行时拿不到的信息却没关系(因为 critic 执行时本就丢弃)——但喂的方式会显著影响训练效率和最终性能。

如果 critic 喂错了会怎样

喂太少(critic 只看本地,退化成 IPPO):critic 只吃 \(o_i\),看不到队友的真实状态。那么从 critic 视角,队友的策略变化和真实状态都是看不见的噪声——非平稳性没有被吸收,回到了第 10 章独立学习的困境。优势估计方差大,训练慢且不稳。这其实就是 IPPO(独立 PPO),在弱耦合任务上还行,强耦合任务上明显弱于 MAPPO。

喂太杂(把执行时也用不到、且与价值无关的信息一股脑塞进去):critic 维度爆炸、难训练,且容易过拟合到训练环境的特定信息上。critic 喂料要"全"但也要"相关"——全局状态里真正影响团队价值的部分要进,无关的噪声不必。

喂得不对称(critic 用固定槽位拼接所有机器人状态,不排列不变):又踩 §11.2 的坑——critic 也对智能体排列敏感,浪费样本、不可扩展。critic 端同样需要对智能体集合的对称性处理。

理论:中心化 critic 的三种喂料方案

在多机运动协调里,中心化 critic 的输入主要有三种选择,从"最理想但需仿真特权"到"最现实":

方案一:真值全局状态 \(s\)(ground-truth state)。 直接喂仿真器能提供的真值状态——所有机器人的精确位姿/速度、所有障碍的精确位置、负载的精确状态。这是最"开天眼"的喂法。

  • 优点:信息最全最准,critic 能学到最精确的价值估计。
  • 前提:只有**仿真**能提供真值状态(真机上机器人自己估不准这些)。但这恰恰没问题——CTDE 下 critic 只在训练时用,训练在仿真里做,真值状态唾手可得;执行时 critic 被丢弃,actor 只用本地观测。这正是"仿真训练"和"CTDE"的天作之合。
  • 这是 MAPPO 在多数仿真训练里的默认、也是最常用的选择。

方案二:特权信息(privileged information)。 喂 actor 看不到、但对价值很关键的"特权"量——比如真实的地面摩擦系数、负载的真实质量、队友的真实意图/目标。这与单机 locomotion 里的"teacher-student / privileged learning"思想一脉相承(教师用特权信息训练,学生从本体感知蒸馏)。

  • 在多机运动里,特权信息常包括:队友的真实目标 \(g_j\)(actor 看不到队友想去哪,但 critic 知道)、负载/接触的真实物理参数、全局任务进度。
  • 这能让 critic 把"为什么团队回报高/低"归因得更准。

方案三:所有智能体观测的排列不变聚合。 当真值状态不易定义或想让 critic 也具备可扩展性时,把所有机器人的观测 \(\{o_1, \dots, o_N\}\) 用 §11.2 的 Deep Sets/注意力聚合成一个固定维度的全局表征,喂给 critic。

  • 优点:critic 也对智能体排列不变、可扩展到任意 \(N\),与 actor 的设计哲学统一。
  • 这是 An/Hutter 等可扩展多机协调工作的做法——actor 和 critic 都用排列不变结构,整个系统才能"训少部署多"。
critic 喂料 信息量 可扩展性 前提 典型用法
真值全局状态 \(s\) 最全 差(维度随 \(N\) 变) 仿真能给真值 MAPPO 仿真训练默认
特权信息 高(针对性强) 仿真能给特权量 强物理耦合、需归因物理参数
观测排列不变聚合 好(对 \(N\) 不变) 无特殊前提 要 critic 也可扩展时

实践中常**组合使用**:真值状态 + 关键特权信息,并对其中"按智能体组织"的部分做排列不变处理。

本质洞察:CTDE 的不对称(actor 看本地、critic 看全局)不是一个"为了方便的近似",而是把"我们想要什么"和"我们怎么稳定地学到它"两件事解耦的精巧设计。我们**想要**的是去中心化的 actor(可部署、可扩展);我们**为了稳定学到它**,允许训练时用一个看全局的 critic 来降低优势估计的方差、吸收非平稳性。critic 是"训练时的脚手架"——盖楼时用,楼盖好(actor 训好)就拆掉。理解这一点,你就不会纠结"critic 用了真值会不会作弊"——会,但作弊只发生在训练、且 critic 执行时被丢弃,所以无害且有益。

多视角理解(类比 + 边界):CTDE 的"特权 critic + 本地 actor"和单机 locomotion 里的 teacher-student(特权学习) 范式高度相似——足式控制中,教师网络用特权信息(真值地形、真实摩擦)训练得到强策略,学生网络只用本体感知(关节角/IMU)从教师那里蒸馏出可部署的策略。像的地方:都"训练时用特权信息、部署时丢弃特权",都让"上真机的那部分"只依赖真机可得的信息,本质都是"训练作弊、执行自立"。不像的地方:teacher-student 是**两阶段、两个网络**(先训教师、再蒸馏学生,特权信息进的是教师的 actor);CTDE 是**单阶段、actor 与 critic 同时训**(特权信息进的是 critic 而非另一个 actor,actor 全程只用本地观测、不经蒸馏)。不要把二者等同——CTDE 没有"蒸馏"这一步,actor 是被中心化 critic 的优势信号直接拉着学的。

理论-工程桥接:MAPPO 在多机运动里的完整数据流

把 actor、critic、奖励、优势串成一条完整的 MAPPO 训练数据流,看清各部分如何咬合:

仿真并行 rollout(4096 环境 × N agent)
   ├─ 每个 agent i:本地观测 o_i ──→ 共享 actor π_θ(a_i|o_i) ──→ 动作 a_i
   │                                  (排列不变编码邻居,§11.2)
   ├─ 环境 step:联合动作 (a_1..a_N) ──→ 转移 ──→ 混合奖励 r_i(§11.4)
   ├─ 中心化 critic:全局状态 s(或聚合)──→ V_φ(s) ──→ 价值估计
   │                  (仅训练用,§11.5 这一节)
   ├─ 用 r_i 和 V_φ 算 GAE 优势 A_i(critic 越准,A_i 方差越小)
   └─ PPO 裁剪目标更新 θ(actor,所有 agent 共享)和 φ(critic)
        重复直到收敛

几个关键工程点:

  • actor 参数共享:所有同质 agent 用同一个 \(\pi_\theta\),rollout 时把 \(N\) 个 agent 的经验都当作这个共享策略的样本——这是 MAPPO 样本效率高的来源,也要求 agent 同质(§11.3 讨论过异质时的代价)。
  • critic 可以共享也可以中心化单一:常见做法是一个中心化 critic \(V_\phi(s)\) 评估全局,或每个 agent 一个吃全局的 critic(参数也共享)。要点是 critic 的**输入**含全局信息,而非 critic 的数量。
  • 优势归一化:多 agent 的优势放在一起归一化,稳定训练。
  • GPU 大规模并行:MQE 基于 IsaacGym,4096 环境 × \(N\) agent 同时跑(如 4 agent 就是 16384 个 agent 并行),这是多机 MARL 能在合理时间训出来的算力基础。

代码:中心化 critic 的实现(含排列不变选项)

import torch
import torch.nn as nn

class CentralizedCritic(nn.Module):
    """中心化 critic:训练时吃全局信息,估计价值 V(s)。
    执行时丢弃——actor 才是要部署的东西。
    支持两种喂料:真值全局状态,或所有 agent 观测的排列不变聚合。"""
    def __init__(self, mode="global_state", state_dim=None,
                 per_agent_dim=None, hidden=256):
        super().__init__()
        self.mode = mode
        if mode == "global_state":
            # 方案一:直接吃真值全局状态(仿真特权)
            self.net = nn.Sequential(
                nn.Linear(state_dim, hidden), nn.ReLU(),
                nn.Linear(hidden, hidden), nn.ReLU(),
                nn.Linear(hidden, 1),
            )
        elif mode == "perm_inv_agg":
            # 方案三:排列不变聚合所有 agent 的观测/状态(critic 也可扩展)
            self.agent_encoder = nn.Sequential(
                nn.Linear(per_agent_dim, hidden), nn.ReLU(),
                nn.Linear(hidden, hidden), nn.ReLU(),
            )
            self.value_head = nn.Sequential(
                nn.Linear(hidden, hidden), nn.ReLU(),
                nn.Linear(hidden, 1),
            )

    def forward(self, x):
        if self.mode == "global_state":
            return self.net(x)                       # x: (B, state_dim)
        elif self.mode == "perm_inv_agg":
            # x: (B, N, per_agent_dim) —— 所有 agent 的状态
            feat = self.agent_encoder(x)             # (B, N, hidden)
            agg = feat.mean(dim=1)                   # 对 agent 排列不变聚合
            return self.value_head(agg)              # (B, 1)
# ❌ 错误 1:critic 也只喂本地观测 o_i(退化成 IPPO 还自称 MAPPO)
critic_input = o_i   # 只有本地,看不到队友真实状态
# 问题:非平稳性没被吸收,优势估计方差大,强耦合任务上明显弱于真 MAPPO。
#       要么承认在用 IPPO(弱耦合够用),要么给 critic 喂全局

# ❌ 错误 2:global_state 模式下用固定槽位拼接所有 agent,破坏排列不变
state = concat([agent_0_state, agent_1_state, ...])  # 焊死顺序
# 问题:critic 对 agent 排列敏感、维度随 N 焊死。若想可扩展,用 perm_inv_agg

# ❌ 错误 3:把 critic 的全局信息泄露给 actor
# 训练时图省事让 actor 也读了 critic 的输入 → 见 §11.1 那个陷阱
# 后果:仿真完美、真机失效。actor 输入永远只能是本地观测

⚠️ 常见陷阱

⚠️ 编程陷阱:声称用 MAPPO,实际 critic 只喂了本地观测(其实是 IPPO)
   错误做法:critic 的输入写成 o_i(本地观测),却以为自己在用 MAPPO
   现象:在弱耦合任务上还行,一到强耦合(协同搬运、紧密编队)就训不好、
        收敛慢、最终性能明显低于预期
   根本原因:MAPPO 之所以比 IPPO 强,唯一来源就是 critic 看全局——它降低
            优势估计方差、吸收非平稳性。critic 只看本地就丢了这个唯一优势,
            名为 MAPPO 实为 IPPO
   正确做法:明确给 critic 喂全局信息(真值状态/特权信息/排列不变聚合),
            并在代码里把 actor 输入和 critic 输入从接口上分开
   自检方法:检查 critic 的 forward 入参——若它的维度等于单 agent 观测维度,
            就是 IPPO;若含全局/多 agent 信息,才是 MAPPO。对比二者在
            同一强耦合任务上的曲线,差距会很明显
💡 概念误区:担心"critic 用了真值状态/特权信息是作弊,部署会出问题"
   新手想法:"critic 偷看了真值摩擦、队友真实目标这些真机上拿不到的东西,
            这不科学,部署肯定要露馅"
   实际上:CTDE 的设计就是允许 critic 作弊——因为 critic 只在【训练】时用,
          训练在仿真里做(真值唾手可得),执行时 critic 被【丢弃】,只有
          actor 上真机,而 actor 从头到尾只用本地观测。critic 的作弊不会
          传染给 actor,除非你犯了"把 critic 输入泄露给 actor"那个错
   为什么重要:理解这点你才敢放心给 critic 喂最全的信息(真值+特权),从而
            得到最准的价值估计、最稳的训练。畏手畏脚不敢喂,反而训不好
   延伸思考:这与单机 locomotion 的 teacher-student(特权教师训练、学生从
            本体感知蒸馏)是同一思想——训练时用特权,部署时不用。CTDE 是
            它在多智能体上的体现
🧠 思维陷阱:以为 critic 喂料方案选哪个无所谓、反正执行时都丢弃
   新手想法:"critic 执行时都不要了,喂真值还是喂聚合随便选个就行"
   实际上:critic 执行时丢弃,但训练时它的质量【直接决定 actor 学得好不好】——
          critic 越准,优势估计方差越小,actor 收敛越快越稳。喂料方案
          影响 critic 能多准、训练能多快、以及 critic 本身可不可扩展(若
          critic 对 N 焊死,换 agent 数也要重训 critic,间接影响整个流程)
   正确思维:按任务选——强物理耦合且需归因物理参数→真值+特权;想让整个
            系统训少部署多→actor 和 critic 都用排列不变聚合;最常规的
            仿真训练→真值全局状态起步
   自检方法:固定 actor 设计,对比不同 critic 喂料下的训练曲线(收敛速度、
            最终性能、方差),选最稳最快的那个

练习

  1. (设计判断题) 对以下场景,分别选择最合适的 critic 喂料方案并说明理由:(a) 仿真训练 4 机器人导航,机器人数固定为 4,追求训练简单稳定;(b) 想训一个能从 2 扩到 8 的可扩展协调策略;(c) 4 足协同搬运,负载真实质量/摩擦对协调成败影响巨大但 actor 估不准。

  2. (实现题) 给 §11.5 的 CentralizedCritic 增加第四种 mode "state_plus_privileged":输入 = 真值全局状态 + 特权信息(如每个 agent 的真实目标 \(g_j\)、负载真实质量)。写出 forward,并说明哪些信息属于"actor 也有"、哪些属于"特权(仅 critic 有)"。然后回答:为什么把特权信息只给 critic 而不给 actor,能让训练既稳又保持 actor 可部署?

  3. (综合分析题,跨章) 把第 10 章的 MAPPO(共享 PPO actor + 中心化 critic)、§11.2(排列不变 actor)、§11.5(critic 喂料)串起来,画出一个能"训练 \(N{=}2\)、部署 \(N{=}10\)"的完整系统的数据流图(文字描述),明确标出:(a) 哪些组件参数共享;(b) 哪些组件排列不变;(c) actor 和 critic 各看到什么;(d) 执行时哪些组件被丢弃。最后指出:这个系统里,"可扩展到任意 \(N\)"这个性质,分别依赖前面哪几个设计决策共同支撑(提示:至少涉及参数共享、本地坐标、排列不变三处)。


§11.6 三大应用:导航、避障、编队,以及为何必须叠加安全层 ⭐⭐⭐

这一节解决什么问题:前面五节把"通用接口"(建模、观测、动作、奖励、训练)都搭好了。这一节把它们具体落到多机运动协调的三类典型任务——导航到目标、去中心化避障、学习式编队——讲清每一类在观测/奖励/评价上的特化设计。然后处理一个贯穿全章的安全软肋:learned policy 把碰撞写成软惩罚,没有任何一步的硬安全保证。这一节给出把控制障碍函数(CBF)、安全盾接到 learned policy 输出端的工程模式,并把"什么时候纯 MARL、什么时候叠安全层"的判断讲清楚(为 §11.9 和第 12 章铺路)。

应用一:多机导航到目标 ⭐⭐

任务\(N\) 个机器人各自从起点到达各自目标 \(g_i\),路上彼此不撞、躲开静态障碍。这是最基础、也最被研究透的多机运动协调任务(Long/Fan/Pan 2017 的去中心化导航是奠基)。

特化设计(在通用接口上的具体填充):

组件 导航任务的具体设计
观测 \(o_i\) 自身:到目标相对向量 + 自身速度;邻居(排列不变):可见队友相对位置/速度;障碍(排列不变):可见障碍相对位置
动作 \(a_i\) 速度层 \((v_x, v_y)\) 或加偏航(导航本体能力不是瓶颈,速度层足够,§11.3)
奖励 \(r_i\) 个体为主:势函数进度奖励 + 到达大奖 + 距离型碰撞软惩罚 + 动作平滑(§11.4)
评价指标 成功率(全部到达)、平均到达时间、碰撞次数、路径效率(实际/最短)

导航是个体性较强的任务(每个机器人有自己的目标),所以奖励**以个体为主**,协作主要体现在"避让时的相互礼让"——这种礼让不需要共享奖励直接激励,而是从"个体避障软惩罚 + 大家都想尽快到达"中自然涌现(你不让路大家都慢、都吃接近惩罚)。

应用二:去中心化避障 ⭐⭐⭐

任务:避障常作为导航的子能力,但也可单独研究——重点是**在没有通信、只有本地感知的前提下,一群机器人如何不相撞**。Long/Fan/Pan 2017 的核心贡献正是证明了这件事能端到端学出来且无需通信。

特化设计的关键点

  • 观测聚合选 max 而非 mean:避障里"最近、最危险的那个邻居"最重要,max pooling 天然突出极值(§11.2),比 mean 更适合——mean 会把危险邻居平均稀释。
  • 互惠性(reciprocity):经典的非学习方法 ORCA/RVO 假设"对方也会避让",各让一半。学习式避障里,这种互惠性是从"所有机器人共享同一个策略(参数共享)"中自然得到的——大家都用同一套避让逻辑,自然就互相协调、各让一半,不会出现"我以为你会让、你以为我会让"的对撞。
  • 碰撞软惩罚的距离型设计(§11.4)尤其关键:要在逼近时就有梯度,学到提前减速绕行。

多视角理解(类比 + 边界):学习式去中心化避障和经典的 ORCA/RVO(速度障碍法)解决同一个问题。像的地方:都不需要中央协调、都靠每个机器人本地决策、都隐含互惠假设。不像的地方:ORCA 是**几何方法**,对每个邻居算出"会导致碰撞的速度集合"并显式避开,有几何意义上的安全保证(在假设成立时);学习式避障是**统计方法**,从数据里学一个倾向于不撞的策略,没有硬保证但能处理 ORCA 难建模的复杂情况(如非圆形机器人、动力学约束、传感噪声)。不要把学习式避障的"看起来很会躲"误当成"有 ORCA 那样的安全保证"——它没有,这正是下面要叠安全层的原因。

应用三:学习式编队 ⭐⭐⭐

任务\(N\) 个机器人维持一个指定队形(如矩形、楔形、或抬一个刚体所需的构型)向目标移动。编队是三类里**协作性最强**的——队形是集体属性,单个机器人无法独自定义"队形好不好"。

特化设计的关键点

  • 奖励必须含共享部分:队形质量是共享奖励(§11.4 的混合奖励里 \(w_{\text{formation}}\) 那一项),因为队形是集体的。但同时保留个体项(自己别撞、自己跟上)治懒惰。
  • 相对构型而非绝对位置(§11.4 代码里的 _formation_error):队形应能整体平移/旋转地跟随团队移动,所以用"相对质心的构型偏差"而非"焊死在某绝对位置"。否则要么队形钉死在地图某处、要么机器人挤成一点也算"零误差"(退化解)。
  • 与协同搬运的联系:抬一个刚体本质上就是一个"由物体刚性约束强制的编队"——decPLM 的 constellation(星座)奖励就是这类编队/协同的奖励设计,既约束整体构型、又让每个机器人做好自己那部分。
  • **FoX 等工作**用互信息(mutual information)量化编队、增强探索——这是编队 MARL 的一个前沿方向。

三大应用对比总表

维度 导航 避障 编队
协作强度 中(各有目标,避让时协作) 中(互惠避让) 强(队形是集体属性)
奖励侧重 个体为主 个体软惩罚为主 个体 + 共享队形
邻居聚合 mean/attention max(突出最危险) mean(关心整体分布)
动作层级 速度层 速度层 速度层(搬运可能需更细)
安全软肋 软惩罚不保证不撞 软惩罚不保证不撞 队形偏离 + 内部碰撞

贯穿问题:软惩罚的固有不安全,与安全层的叠加

三大应用都共享一个根本软肋:碰撞被写成奖励里的软惩罚,learned policy 只是"倾向于"不撞,没有任何一步的硬保证。 训练得再好,也只能把碰撞率压到很低(如 99.9% 无碰撞),但 0.1% 的碰撞在安全关键场景(载人、贵重设备、密集人群)里就是不可接受的。这是 §11.1 前置自测第 5 题、以及全章反复强调的:软惩罚 ≠ 硬约束

为什么 learned policy 无法靠训练根除碰撞

  • 神经网络策略是连续函数逼近,对没见过的状态(分布外,out-of-distribution)行为无保证——而真实部署必然遇到训练时没覆盖的极端构型。
  • 软惩罚是统计意义的"平均不撞",不是每一步的"绝不撞"。优化期望回报不等于满足每条轨迹的硬约束。
  • 这是软约束方法的结构性局限,不是"再多训练就能解决"的工程问题。

解决之道:把硬安全约束作为一层叠加在 learned policy 输出端。 核心思想是——让 MARL 策略负责"协调与意图"(往哪走、怎么配合),让一个有保证的安全层负责"否决会导致碰撞的动作"。安全层在策略输出动作后、执行前介入,把不安全的动作修正成最接近的安全动作。

安全层的几种工程模式

模式 机制 保证 代价
控制障碍函数(CBF) 定义安全集 \(\{h(x)\geq 0\}\),求解 QP 把策略动作投影到使 \(\dot h \geq -\alpha h\) 的安全动作 前向不变性(一旦安全永远安全) 需可微/可解的 \(h\),多机时 QP 耦合
安全盾(safety shield) 检测策略动作是否会进入不安全状态,是则替换成一个已知安全的备用动作 取决于盾的设计 可能过保守,频繁触发损害性能
可达性 / 速度障碍 用 ORCA/可达集计算安全速度集合,把策略速度裁剪进去 几何安全(假设成立时) 假设(如对方也避让)可能不成立

控制障碍函数(CBF)是最有理论保证的一种。 简述其思想(详细推导在足式控制/安全控制章节,这里只讲怎么接到 MARL 上):

  • 定义一个安全函数 \(h(x)\),使 \(h(x) \geq 0\) 当且仅当状态安全(如机器人间距 \(\geq d_{\text{safe}}\)\(h = \|p_i - p_j\| - d_{\text{safe}}\))。
  • CBF 条件要求 \(\dot h(x, a) \geq -\alpha\, h(x)\)\(\alpha > 0\))——它保证安全集 \(\{h \geq 0\}\) 是**前向不变**的:只要现在安全,按此约束行动就永远安全。
  • 把它做成一个 QP:在所有满足 CBF 条件的动作里,找**最接近 MARL 策略输出 \(a_i^{\text{RL}}\)** 的那个: $$ a_i^{\text{safe}} = \arg\min_{a_i} |a_i - a_i^{\text{RL}}|^2 \quad \text{s.t.}\quad \dot h_{ij}(x, a_i) \geq -\alpha\, h_{ij}(x) \forall j. $$
  • 直觉:CBF-QP 像一个"安全过滤器"——大部分时候 \(a_i^{\text{RL}}\) 本就安全,QP 原样放行;只在 \(a_i^{\text{RL}}\) 会导致碰撞时,QP 把它"最小幅度地"修正到安全边界上。MARL 的协调意图被最大限度保留,只在必要时被安全否决。

本质洞察:learned policy + 安全层,是"灵活性"和"安全性"的分工,而非妥协。MARL 擅长在高维、复杂、难建模的多机交互中学出协调行为(灵活),但给不了硬保证;CBF/盾擅长给出可证明的安全保证(安全),但不会"协调"和"完成任务"。把前者放上层出意图、后者放输出端守安全,得到的系统既会协调又不会撞——这正是 §11.9 要画的边界、第 12 章要做的混合架构。纯 MARL 适合协调,传统控制适合安全,最好的系统是二者的组合。

代码:CBF 安全层接到 MARL 输出端(教学简化)

展示如何把一个 CBF-QP 安全过滤器接在策略输出之后。这里用点质量模型、简化的 CBF,重在展示"接法"。

import numpy as np
from scipy.optimize import minimize

def cbf_safety_filter(a_rl, pos_i, pos_others, vel_i, vel_others,
                      d_safe=1.0, alpha=2.0, a_max=2.0):
    """CBF 安全过滤器:把 MARL 策略动作 a_rl 修正成最近的安全动作。
    a_rl: MARL 输出的加速度指令 (2,);返回修正后的安全加速度。
    模型:点质量,状态含位置和速度,动作是加速度。"""
    # 安全函数 h_ij = ‖p_i − p_j‖ − d_safe(≥0 表示与 j 安全)
    # 对每个邻居 j 构造一个 CBF 约束,QP 求最接近 a_rl 的安全动作

    def objective(a):
        return np.sum((a - a_rl) ** 2)          # 最小幅度偏离 MARL 意图

    constraints = []
    for p_j, v_j in zip(pos_others, vel_others):
        rel_p = pos_i - p_j                      # 相对位置
        rel_v = vel_i - v_j                      # 相对速度
        dist = np.linalg.norm(rel_p)
        if dist < 1e-6:
            continue
        h = dist - d_safe                        # 安全函数值
        # 二阶 CBF(位置安全,动作是加速度需用 h 的二阶关系,这里教学简化为
        # 用相对运动的一阶近似构造约束):要求 ḣ + α h ≥ 0
        h_dot = rel_p @ rel_v / dist             # ḣ = d/dt ‖rel_p‖
        # a 通过改变 rel_v 影响 ḣ 的变化率;简化:约束 a 在远离方向的分量
        def make_con(rel_p=rel_p, dist=dist, h=h, h_dot=h_dot):
            # 约束:rel_p·a/dist + h_dot + α·h ≥ 0(教学简化形式)
            return lambda a: (rel_p @ a) / dist + h_dot + alpha * h
        constraints.append({"type": "ineq", "fun": make_con()})

    # 动作幅值约束(物理可行,呼应 §11.3)
    bounds = [(-a_max, a_max), (-a_max, a_max)]
    res = minimize(objective, a_rl, bounds=bounds, constraints=constraints,
                   method="SLSQP")
    return res.x if res.success else np.clip(a_rl, -a_max, a_max)
# 用法:策略输出后、执行前插入安全过滤
a_rl = policy(obs_i)                              # MARL 策略的协调意图
a_safe = cbf_safety_filter(a_rl, pos[i], pos_others, vel[i], vel_others)
env.step_single(i, a_safe)                        # 执行被安全过滤后的动作

# ❌ 错误 1:把安全层放在训练奖励里而非执行端
# 以为"奖励里加了碰撞惩罚就等于有安全层" → 不!那还是软惩罚,无硬保证
# 安全层必须是【执行时的动作修正】,不是【训练时的奖励项】

# ❌ 错误 2:CBF 的 α 取太大 → 过保守,机器人离老远就开始躲,任务做不动
# α 太小 → 安全裕度不足,逼太近才修正,可能来不及
# α 要权衡:安全裕度 vs 任务效率,需调

# ❌ 错误 3:QP 不可行时直接放行原始动作(无兜底)
# 多机时多个 CBF 约束可能冲突导致 QP 无解 → 必须有安全兜底(如急停/减速)

⚠️ 常见陷阱

⚠️ 编程陷阱:以为奖励里的碰撞惩罚就是"安全保证"
   错误做法:在奖励里加了大碰撞惩罚,就认为系统是安全的、可以上真机载人
   现象:99.9% 的情况不撞,但那 0.1% 的分布外极端构型下照撞不误,在安全
        关键场景造成事故
   根本原因:奖励里的碰撞惩罚是【软约束】,只优化"平均不撞",对单条轨迹、
            对分布外状态无任何硬保证。神经网络对没见过的状态行为不可控
   正确做法:安全关键场景必须叠加【执行端】的硬安全层(CBF-QP/安全盾/
            速度障碍),它在动作执行前否决不安全动作,提供可证明的保证。
            奖励里的软惩罚负责"平时表现好",安全层负责"绝不越界"
   自检方法:问"这个系统能保证任何一步都不撞吗?" 只靠软惩罚→答案是否;
            有正确接入的 CBF→在 CBF 假设下答案是是。安全关键场景只接受后者
💡 概念误区:以为学习式避障"看起来很会躲"就等于 ORCA 那样有安全保证
   新手想法:"训练好的策略躲障碍躲得行云流水,比 ORCA 还灵活,那它的安全性
            至少不比 ORCA 差吧"
   实际上:灵活 ≠ 有保证。ORCA 是几何方法,在其假设(如对方也避让、圆形
          机器人)成立时有数学安全保证;学习式避障是统计方法,"会躲"是
          从数据里学的倾向,分布外就可能失效,没有任何一步的硬保证。两者
          的"安全"是不同性质的东西
   为什么重要:会让你在安全关键场景误用纯学习方法。学习式避障适合"复杂但
            非安全关键"(如游戏、仿真、有人监督),安全关键场景要么用有
            保证的几何/优化方法,要么学习方法上叠加 CBF 等硬安全层
   延伸思考:理想方案常是二者结合——学习方法处理 ORCA 难建模的复杂情况
            (非凸形状、动力学、传感噪声)出"意图",CBF/ORCA 在输出端
            守住硬安全。这正是 §11.9 和第 12 章的主题
🧠 思维陷阱:认为"只要训练够久、数据够多,learned policy 就能根除碰撞"
   新手想法:"碰撞率还有 0.1%?那就再训 10 倍步数、再加 10 倍域随机化,
            总能降到 0"
   实际上:碰撞率可以无限逼近 0,但【结构上】到不了硬 0——因为 (a) 真实
          部署必遇训练未覆盖的分布外构型;(b) 软惩罚优化的是期望,不是
          每条轨迹的硬约束;(c) 函数逼近对 OOD 输入无保证。这不是训练量
          问题,是软约束范式的固有上限
   正确思维:把精力分配对——用训练把"平时表现"做好(低碰撞率、高效率、
            会协调),用执行端安全层把"绝不越界"这条硬底线兜住。指望训练
            根除碰撞是把无限的训练资源投在一个结构上不可达的目标上
   自检方法:若你的安全论证是"我们训了很久碰撞率很低",那它不是安全【保证】
            而是安全【统计】。需要保证就必须有形式化的硬约束机制

练习

  1. (特化设计题) 从导航、避障、编队三个任务里任选一个,完整地填充它的"通用接口实例化":写出观测各部分、动作层级、奖励各项(区分个体/共享)、邻居聚合算子选择(mean/max/attention 并说明为什么)、以及三个评价指标。要求每个设计决策都给出理由,体现你对该任务"协作强度"的判断。

  2. (实现题) 把 §11.6 的 cbf_safety_filter 接到 §11.2+§11.3 构成的完整策略后面,形成"排列不变观测 → 速度动作 → CBF 安全过滤 → 执行"的完整管线。然后做一个实验设计(不需真跑,描述即可):如何量化"安全层介入的频率"和"安全层对任务性能的影响"?提示:记录 \(\|a^{\text{safe}} - a^{\text{RL}}\|\) 的分布、对比有/无安全层的碰撞率和到达时间。

  3. (思辨题,承接第 12 章) CBF 安全层在多机场景有一个棘手问题:每个机器人各自解 CBF-QP(去中心化),但多个机器人的安全约束是耦合的(我躲你的同时你也在躲我),各自独立修正可能产生"互相误判对方意图"的死锁或抖动。请讨论:(a) 这个问题和经典 ORCA 的"互惠假设"有什么联系;(b) 有哪些可能的解法(提示:共享意图?分布式 QP?给每个机器人分配"谁让谁"的优先级——这又回到第 3 章 MAPF 的优先级思想);(c) 这是否说明"安全层"本身也需要某种协调,从而模糊了"MARL 管协调、安全层管安全"的清晰分工?这个问题指向第 12 章 MARL 与分布式 MPC/CBF 的深度混合。


§11.7 从零搭建:MQE 风格环境 + MAPPO 参数共享训练循环 ⭐⭐

这一节解决什么问题:前六节的设计要素(观测、动作、奖励、critic、应用)现在要拼成一个能真正跑起来的训练系统。这一节给出一个最小可运行的多机运动协调训练管线骨架——基于 MQE(Multi-agent Quadruped Environment)风格的接口,用 MAPPO 参数共享训练一个双机器人协同到达策略,并演示把它零微调部署到更多机器人上、量化成功率衰减(这是排列不变性的实证验证)。这一节是全章的"动手集成",把前面所有零件焊在一起。

动机:零件齐了,怎么装成一台能跑的机器

到这里你已经有了所有零件:Dec-POMDP 建模(§11.1)、排列不变观测编码器(§11.2)、速度动作头(§11.3)、混合奖励(§11.4)、中心化 critic(§11.5)。但零件不是系统。把它们装成一个能跑的训练管线,还需要解决几个集成层面的问题:怎么组织 GPU 并行的 rollout、怎么把多个 agent 的经验喂给共享策略、训练循环的每一步怎么咬合、训练好后怎么换个机器人数量直接部署。这一节就做这件集成的事。

MQE 环境:多四足 MARL 的标准平台

MQE(ziyanx02/multiagent-quadruped-environment 是多四足 MARL 的标准基准,基于 IsaacGym 的 GPU 大规模并行仿真。它的设计直接对应本章的建模:

MQE 环境配置(对应 §11.1 的 Dec-POMDP 元组):
  robot:       Go2(多 agent,支持 2/4/8 灵活切换 ← 对应 N 可变)
  task:        cooperative_transport / formation / chase(← 三大应用)
  observation: 本体感知(≈39D) + 邻居相对位置/状态 + 任务特征  ← O_i(§11.2)
  action:      关节位置偏移(12D)或经底层控制器的速度层      ← A_i(§11.3)
  reward:      任务 reward + 个体 alive reward + 组惩罚         ← 混合奖励(§11.4)

关键工程特性:
  - 支持 2/4/8 agent 灵活切换——天然适合验证可扩展性
  - IsaacGym GPU 并行:4096 环境 × N agent 同时模拟
    (如 4 agent → 16384 个 agent 并行,§11.5 提到的算力基础)
  - 自定义地形生成(平地/斜坡/阶梯)——为 sim-to-real 域随机化铺路(§11.8)

MQE 之所以适合教学和研究,正是因为它把"多 agent、可变 \(N\)、GPU 大规模并行、三大任务"这些本章关心的要素都封装好了。我们的训练管线就建在这种接口之上。

理论-工程桥接:MAPPO 参数共享训练循环的结构

MAPPO 训练循环的核心结构(呼应 §11.5 的数据流图),用伪代码讲清每一步:

初始化:共享 actor π_θ(排列不变,§11.2)、中心化 critic V_φ(§11.5)
循环 直到收敛:
  ┌─ 1. Rollout 阶段(在 4096 并行环境里收集经验)
  │     for 每个时间步 t:
  │        for 每个 agent i(GPU 上并行):
  │           a_i ~ π_θ(·| o_i)            # 共享策略,本地观测
  │        s', {r_i} = env.step({a_i})     # 联合动作 → 转移 + 混合奖励
  │        记录 (o_i, a_i, r_i, logp_i) 和全局 s(给 critic)
  ├─ 2. 优势计算
  │     V = V_φ(s)                          # 中心化 critic 估值
  │     A_i = GAE(r_i, V)                   # 广义优势估计,critic 越准方差越小
  │     A = normalize(所有 agent 的 A_i)    # 跨 agent 归一化
  └─ 3. 更新阶段(PPO 裁剪目标)
        把所有 agent 的经验【合并】成一个大 batch(参数共享的关键)
        for 若干 epoch:
           L_actor  = PPO_clip(π_θ, A)      # 裁剪信赖域更新 actor
           L_critic = MSE(V_φ(s), returns)  # 回归更新 critic
           θ, φ ← Adam 更新

最关键的工程点是**第 3 步的"合并 batch"**:因为所有同质 agent 共享 \(\pi_\theta\)\(N\) 个 agent 在 4096 环境里产生的经验可以全部当作这一个策略的训练样本拼在一起。这把有效样本量乘以了 \(N\) ——这正是 MAPPO 参数共享样本效率高的根源。

代码:最小 MAPPO 训练循环骨架

把上面的结构落成可读的 PyTorch 骨架。这是**教学简化**版(省略了 IsaacGym 的具体 API、用前面的 MultiRobotCoordEnv 代替),重在展示训练循环的咬合。

import torch
import torch.nn as nn
import numpy as np

class MAPPOTrainer:
    """最小 MAPPO 训练器(教学简化)。
    组合 §11.2 排列不变 actor + §11.5 中心化 critic + §11.4 混合奖励。
    所有同质 agent 共享 actor——这是参数共享的体现。"""
    def __init__(self, env, actor, critic, gamma=0.99, lam=0.95,
                 clip=0.2, lr=3e-4):
        self.env = env
        self.actor = actor            # 共享的排列不变策略(含速度动作头)
        self.critic = critic          # 中心化 critic(吃全局状态)
        self.gamma, self.lam, self.clip = gamma, lam, clip
        self.opt = torch.optim.Adam(
            list(actor.parameters()) + list(critic.parameters()), lr=lr)

    def collect_rollout(self, n_steps):
        """Rollout:所有 agent 用【同一个】共享 actor 采样。"""
        buf = {"obs": [], "act": [], "logp": [], "rew": [],
               "state": [], "done": []}
        obs = self.env.reset()
        for _ in range(n_steps):
            actions, logps = [], []
            for i in range(self.env.N):
                # 把 o_i 转成张量(含排列不变所需的 padding+mask,§11.2)
                self_obs, nbr_obs, nbr_mask = self._to_tensor(obs[i])
                with torch.no_grad():
                    feat = self.actor.encode(self_obs, nbr_obs, nbr_mask)
                    a, logp = self.actor.action_head.sample(feat)
                actions.append(a.numpy().squeeze())
                logps.append(logp.item())
            state = self.env.global_state()              # 全局状态给 critic
            next_obs, rewards, done, _ = self.env.step(actions)
            # 记录这一步所有 agent 的经验(稍后会跨 agent 合并)
            buf["obs"].append(obs); buf["act"].append(actions)
            buf["logp"].append(logps); buf["rew"].append(rewards)
            buf["state"].append(state); buf["done"].append(done)
            obs = next_obs
            if all(done):
                obs = self.env.reset()
        return buf

    def compute_gae(self, rewards, values, dones):
        """广义优势估计 GAE:critic 越准,优势方差越小(§11.5)。"""
        adv = np.zeros_like(rewards)
        last = 0.0
        for t in reversed(range(len(rewards))):
            mask = 1.0 - dones[t]
            delta = rewards[t] + self.gamma * values[t + 1] * mask - values[t]
            adv[t] = last = delta + self.gamma * self.lam * mask * last
        return adv

    def update(self, buf, epochs=4):
        """PPO 裁剪更新。关键:把所有 agent 的经验【合并】成一个大 batch。"""
        # 这里省略数据整形细节,核心逻辑:
        #   1. critic 对 buf["state"] 估值 → GAE 优势 A_i
        #   2. 跨所有 agent 把 (obs_i, act_i, logp_i, A_i) 拼成一个大 batch
        #      (参数共享让这合法——它们都是同一个 π_θ 的样本)
        #   3. for epoch: 算 PPO_clip 损失 + critic MSE,反向更新 θ,φ
        for _ in range(epochs):
            # ratio = exp(new_logp − old_logp)
            # L_clip = −min(ratio·A, clip(ratio,1−ε,1+ε)·A).mean()
            # L_v    = MSE(V_φ(state), returns)
            # loss = L_clip + 0.5·L_v − 0.01·entropy
            # self.opt.zero_grad(); loss.backward(); self.opt.step()
            pass   # 教学骨架:完整实现见练习 1

    def train(self, n_iters=1000, n_steps=128):
        for it in range(n_iters):
            buf = self.collect_rollout(n_steps)
            self.update(buf)
            if it % 50 == 0:
                sr = self.evaluate()
                print(f"iter {it}: success_rate={sr:.2f}")

    def evaluate(self, n_episodes=20):
        """评估成功率(全部 agent 到达目标)。"""
        successes = 0
        for _ in range(n_episodes):
            obs = self.env.reset()
            for _ in range(200):
                actions = [self._greedy_action(obs[i])
                           for i in range(self.env.N)]
                obs, _, done, _ = self.env.step(actions)
                if all(done):
                    successes += 1
                    break
        return successes / n_episodes

实战:训练双机器人、部署到多机器人

排列不变 + 参数共享 + 本地坐标三件套(§11.2、§11.5)的回报,就在这里兑现——训练时用最少的机器人,部署时直接用更多,无需重训

# === 训练阶段:用 N=2 训练(快、省算力)===
env_train = MultiRobotCoordEnv(n_robots=2)
actor  = PermInvariantPolicy(self_dim=4, nbr_dim=4, agg="mean")  # §11.2
critic = CentralizedCritic(mode="perm_inv_agg", per_agent_dim=6) # §11.5 可扩展
trainer = MAPPOTrainer(env_train, actor, critic)
trainer.train(n_iters=1000)

# === 部署阶段:直接换成 N=4 或 N=8,不动网络、不微调 ===
for N_deploy in [2, 4, 8]:
    env_deploy = MultiRobotCoordEnv(n_robots=N_deploy)
    trainer.env = env_deploy           # 同一个 actor,只换环境的机器人数
    sr = trainer.evaluate(n_episodes=50)
    print(f"N={N_deploy}: 零微调部署成功率 = {sr:.2f}")
    # 预期:N=2(训练值)最高,N 增大成功率平滑下降——
    #       下降幅度就是排列不变可扩展性的实证度量(§11.7 练习 2)

为什么换 \(N\) 不用改网络:actor 的输入是"自身观测 + 排列不变聚合的邻居"——邻居数变化只改变聚合前求和的项数,聚合后维度不变,网络结构纹丝不动;critic 用 perm_inv_agg 模式同样对 \(N\) 不变。这就是三件套的红利。如果当初用了固定槽位拼接(§11.2 的反面教材),这里换 \(N\) 就得重训整个网络。

对比性思维(反事实):如果没有排列不变 + 参数共享,这一步会怎样?→ 训练时 \(N{=}2\) 的网络输入输出维度被焊死,部署 \(N{=}4\) 必须改网络结构、从头训练;→ 即便重训,每个 \(N\) 都得单独训一个模型,无法"一次训练、多规模复用"。排列不变把"训一次、任意规模部署"从奢望变成默认。

⚠️ 常见陷阱

⚠️ 编程陷阱:rollout 时每个 agent 用了不同的策略实例(破坏参数共享)
   错误做法:为每个 agent 创建一个独立的 actor 副本,各自训练
   现象:样本效率低下(没享受到参数共享的 N 倍样本红利)、且 N 个策略
        各自漂移,同质 agent 却学出不一致的行为,协调变差
   根本原因:MAPPO 的样本效率来自所有同质 agent 共享【同一个】 π_θ——
            N 个 agent 的经验合并成一个大 batch 训这一个网络。用 N 个独立
            副本就退化成了 N 个独立 PPO,丢了参数共享
   正确做法:所有同质 agent 引用同一个 actor 对象;rollout 时循环调用它
            (或批量并行调用),update 时把所有 agent 经验合并成一个 batch
   自检方法:检查代码里 actor 是不是只 new 了一次、被所有 agent 共用;
            update 的 batch 大小是否约等于 (步数 × 环境数 × N)
⚠️ 编程陷阱:update 时没有把多 agent 经验合并,逐 agent 分别更新
   错误做法:for i in range(N): update(agent_i 的经验),分 N 次更新同一网络
   现象:训练不稳定、梯度方向打架(先用 agent 0 的梯度更新,再用 agent 1 的
        更新,后者基于已变的网络),有效 batch 偏小、噪声大
   根本原因:参数共享的正确用法是把所有 agent 经验【拼成一个 batch 一次性
            更新】,而非串行地用各 agent 经验分别更新。串行更新等于 N 个
            互相干扰的小更新
   正确做法:把 (o_i, a_i, A_i, logp_i) 沿 agent 维度 concat 成大 batch,
            算一次 PPO 损失、一次反向、一次 optimizer.step
   自检方法:update 里 optimizer.step() 每个 iteration 只调一次(针对合并
            batch),而不是调 N 次
🧠 思维陷阱:用最终成功率单一指标判断训练好坏
   新手想法:"成功率 95% 就是好策略,达标收工"
   实际上:单看成功率会掩盖大量问题——95% 成功可能伴随大量擦碰(碰撞率高
          但没撞到完全失败)、路径极不效率(绕大圈)、动作剧烈抖动(真机
          做不出)、或只在训练的 N 上好、换 N 就崩。这些在 sim-to-real 时
          全会爆发
   正确思维:多指标联合评估——成功率 + 碰撞率/次数 + 路径效率 + 动作平滑度
            + 跨 N 的可扩展性。每个指标对应一类潜在问题(§11.6 三大应用
            的评价指标表)
   自检方法:评估时同时记录上述多个指标;尤其在不同 N 上各评一遍,看
            成功率衰减曲线——这是部署可扩展性的关键证据

练习

  1. (实现题,核心) 补全 MAPPOTrainer.update 的完整实现:(a) 用 critic 对 buf["state"] 估值,调 compute_gae 得到每个 agent 的优势;(b) 把所有 agent 的 (obs, act, old_logp, advantage, return) 沿 agent 维度合并成一个大 batch;(c) 写出 PPO 裁剪损失(actor)+ critic MSE + 熵正则,做若干 epoch 的 minibatch 更新。在 MultiRobotCoordEnv(n_robots=2) 上跑通,画出成功率曲线。

  2. (可扩展性实验题)\(N{=}2\) 训练好策略后,在 \(N \in \{2, 3, 4, 6, 8\}\) 上各零微调评估 50 个 episode,记录成功率和平均碰撞次数,画成两条随 \(N\) 变化的曲线。回答:(a) 成功率随 \(N\) 如何衰减、衰减是平缓还是陡峭;(b) 对比"固定槽位拼接版本"(你需要额外实现一个,训练 \(N{=}2\),它根本无法在 \(N{=}4\) 上运行),从而实证排列不变带来的可扩展性;(c) 若 \(N{=}8\) 时成功率掉得厉害,可能是什么原因(提示:训练时只见过 1 个邻居,部署时要处理 7 个,是否需要训练时就随机化 \(N \in [2,4]\)?这指向 §11.8 的数量随机化)。

  3. (设计扩展题,跨章综合) 综合 §11.4(编队奖励)、§11.6(编队应用)和本节(训练循环),把这个导航训练管线改造成一个**编队**训练管线:(a) 修改奖励函数加入共享的队形项(用相对构型,§11.4 的 _formation_error);(b) 修改 critic 喂料以包含队形相关的全局信息;(c) 设计一个评估指标量化"队形保持质量随时间的变化"。然后讨论:编队比导航更依赖中心化 critic 看全局吗?为什么(提示:队形是集体属性、强耦合,回顾 §11.1 的耦合强度判断和 §11.5 的 critic 喂料选择)?


§11.8 多机 sim-to-real 迁移:比单机多出的坑 ⭐⭐⭐⭐

这一节解决什么问题:仿真里训出的多机协调策略,搬到真机几乎总会"散架"——比单机 sim-to-real 更难、坑更多。这一节系统梳理多机比单机**额外**多出的现实差距维度,给出一份多机域随机化(domain randomization)清单,讲清两个多机特有的关键设计:为什么"接触隐式通信"在真机上比"显式通信"更鲁棒、以及**为什么训练时就要随机化智能体数量 \(N\)**。这是全章最硬的一节(⭐⭐⭐⭐),因为多机 sim-to-real 至今仍是公认的开放难题(尤其接触力建模)。

动机:单机迁移已经难,多机为什么更难

单机 sim-to-real 的核心矛盾你应该已经熟悉:仿真和真机之间存在"现实差距(reality gap)"——质量、摩擦、电机延迟、传感噪声等参数仿真建得不准,导致仿真里的最优策略到真机上失效。标准解法是域随机化:训练时随机化这些参数,逼策略学一个对参数变化鲁棒的行为。

多机 sim-to-real 继承了单机的全部困难,还**额外**多出几个维度,每一个都是单机不会遇到的:

  • 每个机器人的参数各不相同:单机只有一套质量/摩擦/延迟;\(N\) 个机器人有 \(N\) 套,且彼此不同(这台电机老化快、那台负载重)。
  • 机器人之间通过物理接触传递力:协同搬运时机器人通过共同的物体相互施力,这种"机器人间接触力"在仿真里建得极不准——而它恰恰是协调的核心通道。
  • 机器人数量本身是变量:真机部署的机器人数可能和训练不同,策略要对 \(N\) 鲁棒。
  • 通信(如果用)有延迟丢包:显式通信在仿真里是即时无损的,真机上有延迟、抖动、丢包。

本质洞察:单机 sim-to-real 是"一个机器人对抗自己的现实差距",多机 sim-to-real 是"一群机器人对抗各自的现实差距 + 它们之间相互作用的现实差距"。后者多出的"相互作用的现实差距"(接触力、通信、数量)是质变而非量变——它正好打在多机协调最依赖的那个通道(机器人怎么相互影响)上。这就是为什么多机迁移比单机难得多,也是 §11.1 那个反复出现的主题("别的机器人这个信息怎么进决策回路")在迁移层的终极体现。

理论:多机 sim-to-real 的现实差距按维度穷举

把多机现实差距做穷举式分类(而非随意列举),分成"单机也有但多机要逐个体随机化"和"多机特有"两类:

维度 单机 DR 多机 DR(额外要求) 为什么多机更难
质量/惯性 ±20% 每个 agent 独立随机 \(N\) 套不同参数,策略要对"队友也各不相同"鲁棒
摩擦 [0.3, 1.0] 每个 agent 独立随机 同上
电机延迟 [5, 30] ms 每个 agent 不同延迟 队友响应快慢不一,协调时序更难
地形 斜坡/阶梯 同上(共享地形)
负载 质量 [1, 30] kg 协同搬运特有,负载是协调的耦合媒介
负载形状 圆柱/方形/不规则 形状决定接触点和力分配,极难建模
机器人间接触力 接触刚度/阻尼随机化 仿真接触模型本就不准,这是最硬的坑
通信延迟(若用通信) [0, 100] ms 显式通信在真机有延迟,仿真即时
agent 数量 训练时随机 \(N \in [2, 4]\) 策略要对队友数量鲁棒(见下文)

注意"额外"那几行(负载、负载形状、接触力、通信延迟、数量)——它们是多机特有的,单机域随机化清单里根本没有。多机 sim-to-real 的成败,很大程度上取决于这几个特有维度随机化得够不够。

关键设计一:接触隐式通信 vs 显式通信

多机协调里"机器人怎么相互传递信息"有两种范式,它们的 sim-to-real 鲁棒性天差地别:

显式通信(explicit communication):机器人之间通过通信信道交换消息(位置、意图、状态)。MARL 里可以学一个"通信策略"决定发什么。

  • 优点:信息带宽高,能传递丰富的意图。
  • sim-to-real 软肋:仿真里通信即时无损,真机上有延迟、抖动、丢包。策略若依赖即时通信,真机上通信一延迟/中断就失效。而且通信延迟的分布很难在仿真里建准。

接触隐式通信(implicit communication through contact):机器人不通过任何通信信道,纯粹通过**物理接触**(共同搬运的物体、相互推挤)感知彼此的动作并协调。decPLM 的核心贡献正是——\(N\) 台四足-臂**零通信**协同搬运,仅靠接触力协调。

  • 优点:没有通信信道就没有通信的 sim-to-real 问题。机器人通过本体感知(力/扭矩传感器、本体状态变化)感知队友施加的力,这种感知在仿真和真机里相对一致(虽然接触力本身建得不准,但"我感受到一个力"这件事是真实存在的物理)。
  • sim-to-real 优势:鲁棒、可扩展(加机器人不用扩通信)、部署简单(不需要通信基础设施)。
  • 代价:信息带宽低——只能传递"力"这一个通道的信息,无法传递复杂意图。

对比性思维(不是 X 而是 Y):接触隐式通信的价值,不是"它比显式通信更高级",而是"它把协调建立在仿真和真机都存在的物理通道(接触力)上,而非建立在仿真理想化、真机不可靠的通信通道上"。换句话说,它用"低带宽但物理真实"换"高带宽但 sim-to-real 脆弱"。在通信不可靠、或追求极致鲁棒/可扩展的场景,这个交换非常划算。decPLM 证明了:很多协同搬运任务,零通信 + 接触协调就足够,且迁移真机更稳。

本质洞察:从信息论角度看,接触隐式通信和显式通信是两条带宽截然不同的信道——接触力大约只能传递几个标量(力的大小方向),显式通信能传几十上百维的消息。但 sim-to-real 的鲁棒性恰恰反着来:带宽越低、越贴近物理本质的通道,仿真真机越一致。这是一个深刻的权衡——协调需要的信息带宽,和这个带宽通道的 sim-to-real 可靠性,往往成反比。选哪条信道,取决于任务真正需要多少协调带宽、以及对部署鲁棒性的要求。(这正是 §11_skeleton 那道思考题"接触隐式通信 vs ADMM 显式通信的信息带宽差异"的核心。)

关键设计二:为什么训练时要随机化智能体数量 N

§11.7 末尾留了个伏笔:\(N{=}2\) 训练、\(N{=}8\) 部署时成功率可能掉得厉害。根因是——训练时策略只见过"最多 1 个邻居"的态势,部署时却要处理"7 个邻居同时存在"的拥挤场景,这是分布外。

解法是**训练时就随机化 \(N\)**(如每个 episode 随机 \(N \in [2, 4]\)):

  • 让策略在训练时就见识不同密度的邻居态势——有时孤身、有时拥挤——从而学一个对"队友数量"鲁棒的行为。
  • 排列不变架构(§11.2)让这件事在技术上可行:网络结构对 \(N\) 不变,只需在训练时改变环境里的机器人数。
  • 这与"每个 agent 独立随机化物理参数"是同一个 DR 哲学的延伸——把部署时会变的东西(这里是 \(N\))在训练时随机化掉。
训练 N 策略 部署可扩展性 原因
固定 \(N{=}2\) 差(\(N\) 大时性能陡降) 没见过拥挤态势,分布外
随机 \(N \in [2,4]\) 好(\(N\) 大时平缓衰减) 见过多样密度,对 \(N\) 鲁棒

理论-工程桥接:可迁移观测表征——本体感知 + 相对观测

多机 sim-to-real 还有一个贯穿性的设计原则:观测表征要选"仿真和真机都能可靠获得、且分布一致"的量。这把 §11.2 的本地坐标/相对观测从"可扩展性"理由又叠上一层"可迁移性"理由:

  • 本体感知(proprioception)优先:关节角、关节速度、IMU 这些本体量,仿真和真机都能可靠测、分布相对一致,是 sim-to-real 最稳的观测基础(与单机 locomotion 的本体感知策略同理)。
  • 相对观测优先于绝对:队友/目标/障碍用相对量(§11.2 已论证可扩展性),这里再加一条——相对量比绝对量(如需要外部定位系统的全局坐标)在真机上更易获得、更鲁棒(不依赖可能不存在或不准的全局定位)。
  • 避免依赖仿真特有的"完美信息":仿真里能拿到队友精确速度/意图,真机拿不到。actor 观测里只能放真机也能获得的量;那些只在仿真有的(如队友真实意图、真值物理参数)只能进 critic(§11.5 的特权信息),执行时丢弃。

这条原则把全章串了起来:actor 观测(§11.2)= 可迁移的本体 + 相对量;critic 喂料(§11.5)= 可包含仿真特权信息(反正执行丢弃)。CTDE 的不对称,本质上也是 sim-to-real 友好的设计——让"上真机的那部分(actor)"只依赖真机可得信息。

⚠️ 常见陷阱

⚠️ 配置/迁移陷阱:多机域随机化照搬单机清单,漏掉多机特有维度
   错误做法:把单机的 DR(质量/摩擦/延迟/地形)直接用于多机,不加负载、
            接触力、通信延迟、数量随机化
   现象:仿真训练表现优异,真机上协同搬运负载被拽歪、机器人间相对距离
        飘忽、换机器人数量就崩——单机指标都正常,但"协同"那部分散架
   根本原因:多机的现实差距主要在"机器人相互作用"上(接触力、负载、通信、
            数量),这些是单机 DR 清单里根本没有的维度。漏掉它们 = 没有
            随机化掉真机上真正会变的东西
   正确做法:用本节的多机 DR 完整清单——除单机各项要【逐个体独立随机化】
            外,补上负载质量/形状、接触刚度阻尼、通信延迟(若用通信)、
            agent 数量随机化
   自检方法:对照本节 DR 表逐行检查;尤其确认"额外"那几行(负载/接触/
            通信/数量)都已纳入。在仿真里故意制造这些参数的极端值,看
            策略是否仍能协调
💡 概念误区:以为显式通信信息多就一定比接触隐式通信好
   新手想法:"让机器人互相通信交换位置和意图,信息越充分协调肯定越好,
            零通信太原始了"
   实际上:信息带宽和 sim-to-real 鲁棒性往往成反比。显式通信带宽高,但
          仿真即时无损、真机有延迟丢包,依赖即时通信的策略真机易失效;
          接触隐式通信带宽低(只有力),但建立在仿真真机都存在的物理通道
          上,鲁棒、可扩展、部署简单。decPLM 证明很多协同搬运零通信就够
   为什么重要:盲目上显式通信会引入一个 sim-to-real 的脆弱点,且增加部署
            复杂度(需通信基础设施)。应按"任务真正需要多少协调带宽"决定——
            够用接触就用接触,确需复杂意图传递才上通信,且通信策略要在
            训练时随机化延迟/丢包
   延伸思考:从信息论看,这是"协调所需带宽"与"该带宽通道的迁移可靠性"的
            权衡。也存在中间方案——低频、对延迟鲁棒的稀疏通信
🧠 思维陷阱:把多机 sim-to-real 当成"调参就能解决"的工程问题
   新手想法:"迁移不成功?再加大域随机化范围、再多训点、调调奖励,
            总能搬上真机"
   实际上:多机 sim-to-real(尤其接触力建模)是公认的【开放研究难题】,
          不是单纯调参能解决的。接触力在仿真里的精度,至今不足以支撑
          很多协同任务的零样本转移——这是仿真物理引擎的能力边界,不是
          你 DR 范围不够大。盲目加大 DR 范围还会让策略过保守、性能下降
   正确思维:(a) 认清哪些是能靠 DR 解决的(参数不确定性)、哪些是仿真物理
            本身的局限(接触力精度);(b) 对后者,考虑结构性方案——接触
            隐式通信(降低对精确接触建模的依赖)、真机微调(少量真机数据
            adapt)、或保守的安全层兜底(§11.6);(c) 把它当研究问题而非
            纯工程问题对待
   自检方法:若迁移失败且定位到"接触力相关",不要只顾加大接触 DR——
            评估是否该改用接触鲁棒的协调范式(如 decPLM 的零通信接触协调),
            或叠加真机自适应。区分"参数不确定"和"模型不准"两类问题

练习

  1. (清单设计题) 为"4 台四足机器人协同搬运一个不规则形状、质量未知的箱子,从仓库 A 区搬到 B 区"设计一份完整的多机域随机化清单。要求:(a) 列出所有要随机化的维度及其范围;(b) 明确标出哪些是"单机也有但要逐个体独立随机化"、哪些是"多机特有";(c) 对"机器人间接触力"这个最难的维度,说明你打算怎么随机化、以及为什么它即便随机化了仍可能 sim-to-real 失败(联系"仿真物理局限"的讨论)。

  2. (设计判断题) 对以下三个多机任务,判断你会用"接触隐式通信"还是"显式通信",并说明理由(从协调所需带宽 + sim-to-real 鲁棒性两个角度):(a) 4 台四足合抬一块刚性板;(b) 一群无人机编队穿越障碍区、需要交换各自规划的轨迹避免碰撞;(c) 多台 AGV 在仓库密集避让。对选显式通信的,说明你会如何在训练时处理通信延迟/丢包以保证可迁移。

  3. (综合分析题,跨章 + 全章总纲) 本节说"CTDE 的不对称本质上是 sim-to-real 友好的设计"。请综合 §11.2(可迁移观测)、§11.5(critic 特权信息)、§11.8(可迁移表征)论证这个论断:(a) 为什么把特权信息(真值物理参数、队友意图)放 critic 而非 actor,恰好让"上真机的部分(actor)"天然 sim-to-real 友好;(b) 这与单机 locomotion 的 teacher-student(特权教师 + 本体感知学生)是不是同一个思想,像在哪、(若有)不像在哪;(c) 把全章串起来回答本章开头那个本质问题——"'别的机器人'这个信息该以什么形式进入决策回路"——在观测层、奖励层、训练层、迁移层分别是什么答案,它们如何共同服务于"可扩展 + 可迁移"这个终极目标。


§11.9 与传统规控的边界与混合:什么时候用 MARL ⭐⭐⭐

这一节解决什么问题:学完了 MARL 用于多机运动协调的全套方法,最后一个、也是最重要的工程判断是——什么时候该用 MARL、什么时候该用传统规控(第 2-9 章的共识/分布式 MPC/任务分配/MAPF)、什么时候该把两者混合? 这一节把这条边界画清楚,给出选型决策框架,并为下一章(第 12 章 MARL 与传统规控混合)做正式铺垫。这不是又一个技术点,而是把全章、乃至整个 Part 的方法放在一起的"选型收口"。

动机:手里有锤子,但不是所有东西都是钉子

学完本章,你掌握了一把强大的锤子——MARL 多机运动协调。但 Part 1 前面的章节也给了你一整套工具:第 2 章的共识/分布式优化、第 3 章的任务分配/MAPF、第 5 章的分布式 MPC 编队、第 6 章的协同搬运力控。一个成熟的工程师不会因为新学了 MARL 就把所有问题都用 MARL 解——而是会判断"这个问题的结构,到底适合哪种工具,或哪几种工具的组合"。

这一节就建立这个判断力。核心是理解 MARL 和传统规控各自的**根本优势和根本软肋**——它们恰好互补。

理论:MARL vs 传统规控的根本对照

把两类方法在几个根本维度上对照,这张表是选型的依据:

维度 MARL(本章) 传统规控(第 2-9 章)
安全保证 软惩罚,无硬保证(§11.6) 硬约束(CBF/MPC 约束/MAPF 无碰撞),可证明
复杂交互建模 强(从数据学,能处理难建模的接触/非凸/高维) 弱(需显式建模,复杂交互难写进方程)
适应性/泛化 强(学一个对多样情况鲁棒的策略) 弱(模型/参数变化需重新设计或重调)
可解释性 弱(黑箱策略,难解释为什么这么动) 强(基于明确模型和优化目标,可分析)
样本/数据需求 高(需大量仿真训练) 低(基于模型,无需训练数据)
最优性 近似(学到的策略不保证最优) 可证明最优/有界次优(凸优化、MAPF 有界)
计算(部署时) 低(一次前向推理) 视方法(MPC 要在线解优化,较重)
协调涌现 强(协作行为可从奖励涌现) 需显式设计协调机制(共识/分布式 QP)

读这张表,一条清晰的互补关系浮现:MARL 强在"难建模的复杂交互 + 适应性 + 协调涌现",软在"安全 + 可解释 + 最优性保证";传统规控正好反过来。

本质洞察:MARL 和传统规控不是"新 vs 旧"或"谁取代谁"的关系,而是**能力维度上的互补**。它们的优势和软肋几乎完美互补——这不是巧合,而是源于方法论的根本差异:MARL 是数据驱动的(从经验里学,所以能处理难建模的东西、能适应,但给不了保证);传统规控是模型驱动的(从方程里推,所以有保证、可解释,但建不了复杂交互、不会自适应)。理解这个互补,你就知道为什么"最好的系统往往是二者的混合"——用各自的优势补对方的软肋。

系统性分类:选型决策框架

把"该用哪个"做成一个可操作的决策框架(穷举式维度分类,而非随意建议):

问题分类决策树:

1. 任务是否安全关键(载人/贵重/密集人群)?
   ├─ 是 → 必须有硬安全保证 → 纯 MARL 不可单独用
   │        └─→ 用传统规控,或 MARL + 安全层混合(§11.6/第12章)
   └─ 否 → 继续

2. 交互是否复杂到难以显式建模(非凸接触、高维耦合、难建模动力学)?
   ├─ 是 → 传统规控难写方程 → MARL 的数据驱动优势凸显
   │        └─→ 用 MARL(或 MARL 主导 + 传统兜底)
   └─ 否(交互可清晰建模)→ 继续

3. 环境/参数是否高度多变、需强适应性?
   ├─ 是 → MARL 的泛化优势 → 倾向 MARL
   └─ 否(环境稳定、模型已知)→ 传统规控更可控、可验证、无需训练

4. 是否需要可证明最优/有界次优?
   ├─ 是 → 传统规控(凸优化/MAPF 有界次优,第3章)
   └─ 否 → MARL 的近似解可接受

几个典型判断的归宿:

任务特征 推荐方案 理由
仓库 AGV 密集调度,要无碰撞保证 MAPF(第3章)或 MAPF + 局部 MARL 避障 安全关键,需硬保证;MAPF 出离散计划,MARL 可做局部精细避让
多足协同搬运不规则物体(难建模接触) MARL(接触隐式通信,§11.8) 接触交互难建模,数据驱动优势大
已知模型的多机编队,要可证明稳定 分布式 MPC(第5章)/共识(第2章) 模型清晰,需稳定性保证,传统方法可验证
复杂地形多机导航,环境多变 MARL + CBF 安全层(§11.6) 需适应性(MARL)+ 安全(CBF),混合
载人场景的多机协调 传统规控为主,MARL 仅做非安全关键的优化 安全压倒一切

理论-工程桥接:三种混合范式(铺垫第 12 章)

当纯 MARL 和纯传统都不够时,混合。第 12 章会系统讲,这里先给出三种主流混合范式的框架,让你知道"混合"具体长什么样:

范式一:分层混合(hierarchical)——上层传统/下层 MARL 或反过来。 - 例:上层用 MAPF(第3章)出离散无碰撞路径,下层用 MARL 做连续的精细避让和运动协调。离散层给安全骨架,连续层给灵活执行。 - 也可反过来:上层 MARL 出高层协调意图,下层传统控制器(MPC/locomotion)精确执行(§11.3 的分层就是这个)。

范式二:安全过滤混合(safety filtering)——MARL 出意图,传统守安全。 - 例:§11.6 的 CBF 安全层——MARL 策略输出动作,CBF-QP 在执行前否决不安全动作。MARL 管协调,CBF 管硬安全。这是最直接的"取 MARL 灵活 + 传统保证"的混合。

范式三:残差混合(residual)——传统出基线,MARL 学修正。 - 例:传统控制器(如分布式 MPC)出一个基线动作,MARL 学一个"残差"修正它,处理传统方法建模不准的部分。基线保证基本行为合理,残差注入数据驱动的灵活性。

混合范式 MARL 角色 传统角色 典型场景
分层 上层协调 或 下层精细执行 另一层 复杂任务的层级分解
安全过滤 出协调意图 执行端守硬安全 安全关键 + 需灵活协调
残差 学建模不准的修正 出可靠基线 模型大致已知但有难建模残差

对比性思维(不是 X 而是 Y):混合架构的设计哲学,不是"用 MARL 弥补传统方法的不足"或"用传统方法约束 MARL"这种单向的主从关系,而是"让每种方法只做它最擅长的那部分,用接口把它们的优势拼起来"。分层让传统管骨架、MARL 管细节;安全过滤让 MARL 管意图、传统管底线;残差让传统管基线、MARL 管修正。每种混合都是在问"这个任务的哪部分适合学、哪部分适合算",然后让对的方法做对的事。这正是第 12 章要展开的核心思想。

⚠️ 常见陷阱

💡 概念误区:以为 MARL 是更先进的方法、应该取代传统规控
   新手想法:"MARL 是数据驱动的现代 AI 方法,传统规控是老古董,新项目
            当然全用 MARL"
   实际上:MARL 和传统规控是能力互补、各有不可替代优势的两类方法,不是
          先进落后之分。传统规控的硬安全保证、可证明最优、可解释性、零
          训练数据需求,是 MARL 给不了的;MARL 的复杂交互建模、适应性、
          协调涌现,是传统给不了的。把 MARL 当万能解会在安全关键、需保证
          的场景翻车
   为什么重要:选型错误代价高昂——安全关键场景误用纯 MARL 可能酿成事故;
            简单可建模任务上用 MARL 则白白付出训练成本、丢掉可解释性和
            保证。成熟工程师按问题结构选工具,不被"新旧"标签绑架
   延伸思考:很多最强的系统是混合的(§11.6 的 CBF+MARL、第12章的各种
            混合)——这恰恰说明二者互补而非替代
🧠 思维陷阱:在能清晰建模的简单任务上硬上 MARL
   新手想法:"这是多机协调,肯定要用本章学的 MARL,越复杂的方法越显水平"
   实际上:如果任务的交互能清晰建模(如已知动力学的编队、可用凸优化解的
          分配)、环境稳定、且需要保证,那传统规控(共识/分布式MPC/MAPF)
          更可控、可验证、无需训练、可解释——MARL 在这里没有优势,只有
          训练成本和黑箱风险
   正确思维:先问"这个任务能不能用传统方法清晰建模并解决"。能且满足需求→
            用传统。只有当传统方法因"交互难建模 / 需强适应性 / 协调难显式
            设计"而吃力时,MARL 才有用武之地。把 MARL 留给它真正擅长的
            难问题
   自检方法:跑一个传统基线(如分布式MPC编队、ORCA避障)。若传统基线就
            满足需求,就没有理由上 MARL;若传统基线在"复杂交互/适应性"上
            明显吃力,才是 MARL 的场景

练习

  1. (选型决策题) 用本节的决策框架,为以下三个任务选择方案(纯 MARL / 纯传统 / 具体哪种混合),并走一遍决策树说明每一步的判断:(a) 医院里多台配送机器人在有行人的走廊运送药品;(b) 实验室里 4 台机械臂协同装配一个已知 CAD 模型的零件;(c) 灾后废墟上多台足式机器人协同搬运伤员担架,地形和负载都高度不确定。

  2. (混合设计题) 选一个你认为最适合"MARL + 传统规控混合"的多机任务,从三种混合范式(分层 / 安全过滤 / 残差)里选一种,画出混合架构的框图(文字描述):明确哪部分是 MARL、哪部分是传统、接口处传递什么信息、MARL 和传统各自的输入输出。说明为什么选这种混合范式而非另两种。

  3. (全章总结题,跨章综合) 本章是"第 10 章 MARL 基础"的应用延伸。请用一段话(不超过 200 字)总结:第 10 章给了什么、本章在其上加了什么、第 12 章将进一步做什么,三者构成怎样的递进。然后回答一个判断题:有人说"既然第 12 章的混合架构更强,本章纯 MARL 是不是就没必要学了?" 你怎么反驳(提示:理解纯 MARL 的能力边界,正是判断"何时需要混合、混合里 MARL 该承担什么"的前提;不懂纯 MARL 的软肋就不知道为什么要混合、怎么混合)?


本章常见误解汇总

把全章散落的概念误区集中成一张"误解 → 正确理解"对照表,便于回头速查。这些误解大多源于"用单机 RL 的直觉硬套多机",或"把软方法当硬保证"。

误解 正确理解 详见
\(N\) 个机器人拼成一个巨型 MDP 用单智能体 RL 解 动作空间指数爆炸、要求全局通信、不可扩展——用 Dec-POMDP + CTDE 去中心化解 §11.1
部分可观测只是"传感器有噪声" 核心是信息缺失和局部性(看不到远处、看不到队友意图),不只是噪声 §11.1
既然是多智能体就要用最复杂的 MARL 算法 算法复杂度由任务耦合强度决定,弱耦合 IPPO 就够,先跑朴素基线 §11.1
邻居观测用固定槽位拼接 邻居是变长无序集合,必须用排列不变聚合,否则不可扩展、浪费样本 §11.2
排列不变 = 排列等变 不变是输出不随输入重排而变(聚合邻居);等变是输出随之重排(中心网络出所有动作) §11.2
观测信息越全(看到所有队友)越好 应匹配交互尺度和真机传感器能力,过大引入无关噪声、损害可迁移 §11.2
用全局绝对坐标做观测 用相对/本地坐标,否则样本效率低、无法泛化到新区域 §11.2
底层关节动作"更端到端所以更好" 动作层级是工程权衡,多机协调常用速度层 + 下层控制器分层,更稳更易迁移 §11.3
加稠密引导奖励就会改变任务最优解 势函数形式 \(\gamma\Phi(s')-\Phi(s)\) 的塑形有定理保证不改变最优策略 §11.4
奖励塑形和信用分配是不相干的两件事 奖励结构是缓解信用分配的第一道闸门(问题侧),与算法侧(COMA/QMIX)互补 §11.4
纯团队奖励就能促协作 纯团队奖励导致懒惰智能体(搭便车),要用混合奖励(个体+共享) §11.4
critic 用真值/特权信息是作弊、部署会出问题 CTDE 下 critic 只训练时用、执行丢弃,作弊不传染给 actor,反而该放心喂全 §11.5
声称用 MAPPO 但 critic 只喂本地观测 那其实是 IPPO;MAPPO 的唯一优势就是 critic 看全局 §11.5
奖励里的碰撞惩罚就是"安全保证" 软惩罚无硬保证,安全关键场景必须叠执行端硬安全层(CBF/盾) §11.6
学习式避障"看起来很会躲"=有 ORCA 那样的安全保证 灵活≠有保证,学习式是统计倾向、分布外失效,需叠安全层 §11.6
训练够久就能让 learned policy 根除碰撞 软约束范式结构上到不了硬 0,是范式上限不是训练量问题 §11.6
多机域随机化照搬单机清单 多机要逐个体随机化 + 补负载/接触/通信/数量等特有维度 §11.8
显式通信信息多就一定更好 带宽与 sim-to-real 鲁棒性常成反比,接触隐式通信更鲁棒可扩展 §11.8
多机 sim-to-real 是调参就能解决的工程问题 接触力建模是公认开放难题,需区分参数不确定与模型不准 §11.8
MARL 是更先进方法、应取代传统规控 二者能力互补(MARL 灵活、传统有保证),最强系统常是混合 §11.9

本章小结

本章把第 10 章的 MARL 框架落到了多机器人运动协调上。一条主线贯穿始终——"别的机器人"这个动态、变长、无序的集合,到底该以什么形式进入一个机器人的决策回路:在观测层,答案是排列不变聚合(§11.2);在奖励层,是混合奖励缓解信用分配(§11.4);在训练层,是中心化 critic 的喂料(§11.5);在迁移层,是接触隐式通信与可迁移表征(§11.8)。把这四个层面的答案拼起来,就得到了一个可扩展、可迁移的多机协调系统。

回到章首的三个"如果跳过会怎样":现在你知道了——加机器人就乱(场景一)是因为没用排列不变(§11.2);碰撞惩罚一大就冻结(场景二)是奖励配比病态(§11.4);仿真好真机散架(场景三)是多机 sim-to-real 的特有差距没随机化(§11.8)。三个坑,本章都给了系统的填法。

最后一节(§11.9)把全章放回工具箱:MARL 不是万能锤,它和传统规控能力互补——这既是本章的收口,也是第 12 章混合架构的起点。

术语速查表

术语 英文 一句话定义 首见
去中心化部分可观测马尔可夫决策过程 Dec-POMDP 多个只看局部的智能体在共享环境里序贯决策的博弈框架 §11.1
联合学习 joint learning \(N\) 个智能体拼成一个超级智能体用单智能体 RL 解(动作爆炸、要全局通信) §11.1
集中训练分布执行 CTDE 训练时 critic 看全局、执行时 actor 只看本地的范式 第10章/§11.5
排列不变 permutation-invariant 输入元素重排、输出不变(聚合邻居用) §11.2
排列等变 permutation-equivariant 输入元素重排、输出随之同样重排(中心网络出所有动作用) §11.2
Deep Sets Deep Sets 排列不变函数 = 逐元素编码 + 对称聚合 + 后处理 §11.2
本地/相对坐标 egocentric / relative 一切空间量相对于自己表达,使策略平移不变、可迁移 §11.2
参数共享 parameter sharing 所有同质智能体用同一套策略网络参数 §11.5
分层架构 hierarchical 上层 MARL 出高层指令、下层控制器精确执行 §11.3
势函数塑形 potential-based reward shaping (PBRS) \(F=\gamma\Phi(s')-\Phi(s)\) 形式的塑形,保最优策略不变 §11.4
混合奖励 mixed reward 个体可归因部分 + 共享团队部分的加权 §11.4
懒惰智能体 lazy agent 纯团队奖励下搭便车摸鱼的智能体 §11.4
奖励黑客 reward hacking 钻奖励空子刷分而非完成本意任务 §11.4
中心化 critic centralized critic 训练时吃全局信息估值的 critic,执行时丢弃 §11.5
特权信息 privileged information actor 看不到、critic 训练时可用的量(真值参数/队友意图) §11.5
控制障碍函数 control barrier function (CBF) 用安全集前向不变性保证硬安全的约束,可做 QP 安全过滤 §11.6
安全盾 safety shield 检测并替换不安全动作的执行端安全机制 §11.6
速度障碍/互惠避障 ORCA / RVO 几何避障方法,假设对方也避让各让一半 §11.6
域随机化 domain randomization (DR) 训练时随机化物理参数逼策略鲁棒 §11.8
接触隐式通信 implicit communication through contact 零通信、纯靠物理接触力协调(sim-to-real 鲁棒) §11.8
数量随机化 agent-number randomization 训练时随机智能体数 \(N\),使策略对队友数量鲁棒 §11.8
残差混合 residual hybrid 传统出基线、MARL 学修正的混合范式 §11.9

知识点总表

编号 知识点 核心要点 对应节 难度
1 运动协调的 Dec-POMDP 建模 五要件 + 为何优于联合 MDP(动作爆炸/通信/可扩展) §11.1 ⭐⭐
2 联合 MDP 的三个致命问题 动作指数爆炸、要求全局通信、不可扩展 §11.1 ⭐⭐
3 排列不变性与 Deep Sets 逐元素编码 + 对称聚合,搜索空间从指数降到多项式 §11.2 ⭐⭐⭐
4 三种聚合算子 mean(平均态势)/max(最危险)/attention(自适应)的取舍 §11.2 ⭐⭐⭐
5 本地坐标 + 参数共享 + 排列不变三件套 共同支撑"训少部署多"的零样本可扩展 §11.2 ⭐⭐⭐
6 padding + mask 的正确用法 计算性补齐 + mask 保语义无序,与槽位拼接的本质区别 §11.2 ⭐⭐⭐
7 速度层 vs 关节层动作 协调发生在什么尺度决定层级;分层让二者各司其职 §11.3 ⭐⭐
8 动作缩放与裁剪 tanh×物理上限 + 采样后 clamp,对齐真机能力 §11.3 ⭐⭐
9 三种奖励结构 全局团队/个体局部/混合的信用分配与协作取舍 §11.4 ⭐⭐⭐
10 势函数塑形 PBRS \(\gamma\Phi(s')-\Phi(s)\) 保最优,防横跳刷分 §11.4 ⭐⭐⭐
11 三种奖励病态 冻结/懒惰智能体/奖励黑客的识别与修复 §11.4 ⭐⭐⭐
12 距离型碰撞软惩罚 逼近就罚(有梯度)优于撞上才罚(稀疏) §11.4 ⭐⭐
13 CTDE 在运动里的 critic 喂料 真值状态/特权信息/排列不变聚合三方案取舍 §11.5 ⭐⭐⭐
14 MAPPO 参数共享数据流 多 agent 经验合并成大 batch 是样本效率来源 §11.5/§11.7 ⭐⭐⭐
15 三大应用特化 导航(个体)/避障(max+互惠)/编队(共享队形)的设计差异 §11.6 ⭐⭐⭐
16 软惩罚的固有不安全 learned policy 结构上无硬保证,需叠安全层 §11.6 ⭐⭐⭐
17 CBF 安全层接法 安全集前向不变 + QP 最小修正策略动作 §11.6 ⭐⭐⭐
18 从零搭 MAPPO 训练循环 rollout→GAE→合并 batch PPO 更新 §11.7 ⭐⭐
19 训少部署多的实证 同一网络换 \(N\) 直接部署,量化成功率衰减 §11.7 ⭐⭐
20 多机 sim-to-real 现实差距 逐个体随机化 + 负载/接触/通信/数量特有维度 §11.8 ⭐⭐⭐⭐
21 接触隐式通信 零通信靠接触力协调,带宽低但 sim-to-real 鲁棒 §11.8 ⭐⭐⭐⭐
22 数量随机化 训练时随机 \(N\) 使策略对队友数量鲁棒 §11.8 ⭐⭐⭐⭐
23 MARL vs 传统规控对照 能力互补:MARL 灵活、传统有保证 §11.9 ⭐⭐⭐
24 三种混合范式 分层/安全过滤/残差,让对的方法做对的事 §11.9 ⭐⭐⭐

累积项目:本章新增模块

累积项目(多机协作方向):本 Part 的累积项目是从零构建 Mini-MultiBot——一个能让一小队机器人完成"分工 → 规划 → 协调运动 → 执行"全流程的多机系统(最终在第 13 章 Mini-MultiBot 综合实战集成)。每章给它加一个模块。

本章(第 11 章)新增模块:marl_coordination/——学习式多机运动协调层。

在前面章节已有的 consensus/(第2章共识)、planning/(第3章任务分配与 MAPF)、dmpc/(第5章分布式 MPC)基础上,本章新增一个可插拔的"学习式协调"模块:

mini_multibot/
├── consensus/              # 第2章:共识与分布式优化
├── planning/               # 第3章:任务分配(CBBA)+ MAPF(CBS/PIBT)
├── dmpc/                   # 第5章:分布式 MPC 编队
├── marl_coordination/      # ← 第11章新增:学习式多机运动协调
│   ├── envs/
│   │   └── multi_robot_env.py      # §11.1 Dec-POMDP 环境(MQE 风格接口)
│   ├── models/
│   │   ├── perm_invariant_policy.py # §11.2 排列不变 actor + §11.3 速度动作头
│   │   └── centralized_critic.py    # §11.5 中心化 critic(含可扩展模式)
│   ├── rewards/
│   │   └── coordination_reward.py   # §11.4 混合奖励 + 势函数塑形
│   ├── train/
│   │   └── mappo_trainer.py         # §11.7 MAPPO 参数共享训练循环
│   ├── safety/
│   │   └── cbf_filter.py            # §11.6 CBF 安全过滤器
│   └── transfer/
│       └── domain_randomization.py  # §11.8 多机域随机化清单

本模块对累积项目的贡献

  • 给 Mini-MultiBot 加上一个"学习式协调"选项——除了第5章的分布式 MPC(模型驱动编队),现在也能用学出来的策略做导航/避障/编队(数据驱动)。
  • safety/cbf_filter.py 为后续把 learned policy 接到真机/安全关键场景做准备。
  • transfer/domain_randomization.py 为第13章把策略迁移到真机/高保真仿真做准备。
  • 本模块与 planning/ 的接口(MAPF 出离散计划、MARL 做局部协调)和与 dmpc/ 的对比(学习式 vs 模型驱动编队),是第13章综合调度时"选哪个协调层"的依据,也是第12章混合架构的素材。

验收标准:能在 multi_robot_env.py 里用 mappo_trainer.py 训出一个 \(N{=}2\) 的导航策略,零微调部署到 \(N{=}4\) 并报告成功率衰减;能给策略接上 cbf_filter.py 对比有/无安全层的碰撞率。


延伸阅读

按主题分类,标注难度(⭐ 入门 / ⭐⭐ 进阶 / ⭐⭐⭐ 研究级)。

去中心化导航/避障的 RL 化(奠基)

  • Long, Fan, Pan 等, "Towards Optimally Decentralized Multi-Robot Collision Avoidance via Deep Reinforcement Learning"(ICRA 2018)⭐⭐ —— 去中心化避障 RL 化的奠基作,本地观测 + 无通信,端到端学避障策略。
  • Everett, Chen, How, "Motion Planning Among Dynamic, Decision-Making Agents with Deep Reinforcement Learning"(IROS 2018 / IJRR)⭐⭐ —— GA3C-CADRL,用 LSTM 处理变长邻居、把社会礼貌编进奖励。
  • "Multi-Robot Cooperative Socially-Aware Navigation Using Multi-Agent Reinforcement Learning"(arXiv 2309.15234)⭐⭐ —— 用 MAPPO/CTDE 做合作社会化导航的近期工作。

排列不变与可扩展性(本章 §11.2 核心)

  • Zaheer 等, "Deep Sets"(NeurIPS 2017)⭐⭐⭐ —— 排列不变函数表示定理的原始论文,§11.2 理论基础。
  • An, Hutter 等, "Permutation-invariant Networks for Multi-Robot Cooperation"(RSS 2024)⭐⭐⭐ —— 把排列不变确立为多机协作可扩展的关键技术。
  • An, Hutter 等, "Scalable Multi-Robot Cooperation"(RA-L 2025)⭐⭐⭐ —— 系统化地从 2 扩展到 \(N\)
  • "Solving Multi-Entity Robotic Problems Using Permutation Invariant Neural Networks"(arXiv 2402.18345)⭐⭐⭐ —— 多实体机器人问题的排列不变解法。
  • "SPECTra: Scalable Multi-Agent RL with Permutation-Free Networks"(arXiv 2503.11726)⭐⭐⭐ —— 排列无关网络提升大规模 MARL 可扩展性。

多足/多机器人协同运动与 sim-to-real(本章 §11.6/§11.8 核心)

  • Xiong 等, "MQE: Multi-agent Quadruped Environment"(IROS 2024)⭐⭐ —— 多四足 MARL 标准基准,本章实战平台。
  • Pandit, Shrestha, Fern, "Multi-Quadruped Cooperative Object Transport: Learning Decentralized Pinch-Lift-Move"(arXiv 2509.14342,decplm.github.io)⭐⭐⭐ —— decPLM,零通信接触协调 + 分层策略 + constellation 奖励 + 真机迁移,§11.8 的核心案例。
  • "Decentralized Navigation of a Cable-Towed Load using Quadrupedal Robot Team via MARL"(arXiv 2503.18221)⭐⭐⭐ —— 分层去中心 MARL 规划器出速度指令协同拖拽。
  • "MASQ: Multi-Agent RL for Single Quadruped Robot Locomotion"(arXiv 2408.13759)⭐⭐⭐ —— 把单机四腿当多智能体,MARL 思想"向内"应用的有趣视角。
  • "Empowering Multi-Robot Cooperation via Sequential World Models (SeqWM)"(arXiv 2509.13095)⭐⭐⭐ —— 序贯世界模型,真机多足协作。

奖励塑形与信用分配(本章 §11.4 核心)

  • Ng, Harada, Russell, "Policy Invariance Under Reward Transformations: Theory and Application to Reward Shaping"(ICML 1999)⭐⭐⭐ —— 势函数塑形保最优性的原始定理。
  • "Credit assignment in heterogeneous multi-agent RL for fully cooperative tasks"(Applied Intelligence 2023)⭐⭐⭐ —— 异质合作任务的信用分配。

安全 MARL(本章 §11.6/§11.9)

  • Jose, Zhang, "Dual-Quadruped Safe RL"(arXiv 2025)⭐⭐⭐ —— HAPPO + 约束马尔可夫博弈,把安全约束写进双四足协同优化。
  • 控制障碍函数(CBF)综述与多机 CBF 的相关文献(参见本项目足式控制/安全控制章节的延伸阅读)⭐⭐⭐。

教材/综述

  • Albrecht, Christianos, Schäfer, Multi-Agent Reinforcement Learning: Foundations and Modern Approaches(MIT Press 2024)⭐⭐ —— 系统的 MARL 教材,与第10章配合。
  • "Survey on Graph-Based RL for Networked Coordination and Control"⭐⭐⭐ —— 图结构 MARL 用于网络化协调的综述。

本章与后续章节的关系

后续章节 关系 本章铺垫的知识点
第 12 章 MARL 与传统规控混合 直接延伸 §11.6 的 CBF 安全层、§11.9 的三种混合范式(分层/安全过滤/残差)、§11.9 的选型决策框架——第12章把这些做成系统的混合架构
第 13 章 Mini-MultiBot 综合实战 集成落地 本章的 marl_coordination/ 模块直接被第13章复用;§11.7 的训练管线、§11.8 的迁移清单用于综合系统的协调层
第 5 章 分布式 MPC 多足编队 对照/可混合 §11.9 把学习式编队与分布式 MPC 编队对照;残差混合范式(§11.9)可让 MPC 出基线、MARL 学修正
第 3 章 任务分配与路径规划 分层互补 §11.6/§11.9 的分层混合:MAPF 出离散无碰撞计划、本章 MARL 做连续局部协调
第 6 章 协同搬运与力控 方法对照 §11.8 的接触隐式通信(decPLM)与第6章的显式力控形成数据驱动 vs 模型驱动的对照

向后预告:第 12 章会把本章 §11.6 的"CBF 安全层"和 §11.9 的"三种混合范式"展开成完整的系统——你将看到分布式 MPC(第5章)、CBF、共识(第2章)如何与 MARL 策略在一个架构里咬合,得到"既会协调又有保证"的混合多机系统。本章的纯 MARL,是理解"混合里 MARL 该承担什么、不该承担什么"的前提。


🔧 故障排查手册

多机 MARL 运动协调最常见的故障场景及排查路径(症状 → 可能原因 → 排查步骤 → 相关节)。

症状 可能原因 排查步骤 相关节
训练 \(N{=}2\) 正常,部署 \(N{=}4\) 直接报维度错误/无法运行 用了固定槽位拼接而非排列不变观测 1.检查 actor 输入是否含固定槽位的队友 2.确认邻居是否经 Deep Sets 聚合 3.改用排列不变编码器 §11.2
训练 \(N{=}2\) 正常,部署 \(N{=}8\) 成功率陡降 训练时只见过稀疏邻居,拥挤态势分布外 1.检查训练时 \(N\) 是否固定 2.改为训练时随机 \(N\in[2,4]\) 3.重训后再测 §11.7/§11.8
所有机器人冻结在起点不动 碰撞惩罚权重 ≫ 任务奖励,"不动"成最优 1.分项打印各奖励均值 2.看碰撞项是否压过任务项 3.降碰撞权重/升到达奖励 §11.4
部分机器人积极、部分长期摸鱼打转 纯团队奖励 → 懒惰智能体搭便车 1.记录各 agent 各自贡献 2.找零贡献却拿正回报的 agent 3.改混合奖励加个体项 / 上 COMA-QMIX §11.4
机器人在目标附近反复横跳不肯停 进度奖励非势函数形式,被刷分(reward hacking) 1.检查进度奖励是否为 \(\gamma\Phi(s')-\Phi(s)\) 形式 2.若是朴素 \(-\)距离则改势函数差分形式 §11.4
训练初期物理引擎炸开(位置/速度变 NaN) 动作未缩放裁剪,输出疯狂指令 1.检查动作头是否 tanh×物理上限 2.采样后是否 clamp 3.打印动作分位数确认在物理范围 §11.3
自称用 MAPPO 但强耦合任务训不好、收敛慢 critic 实际只喂本地观测(其实是 IPPO) 1.检查 critic 输入维度 2.若等于单 agent 观测维度则给 critic 喂全局 3.对比真 MAPPO 曲线 §11.5
邻居稀疏的场景策略明显更差 padding 的假邻居未用 mask 屏蔽,污染聚合 1.检查聚合是否用 mask 2.同组真邻居 + 不同 padding 数前向输出是否一致 3.修正 mask 和 mean 分母 §11.2
仿真编队完美,真机队形飘忽散架 多机 sim-to-real 差距未随机化(接触/负载/逐个体) 1.对照多机 DR 清单逐行检查 2.确认负载/接触/数量等特有维度已纳入 3.评估是否接触力建模本身不准 §11.8
加安全层后机器人离老远就躲、任务做不动 CBF 的 \(\alpha\) 取太大,过保守 1.调小 \(\alpha\) 2.记录安全层介入频率 3.权衡安全裕度 vs 任务效率 §11.6
多机各自解 CBF-QP 出现死锁/抖动 去中心 CBF 约束耦合、互相误判意图 1.检查是否多约束冲突致 QP 无解 2.加优先级/共享意图 3.QP 无解时安全兜底(减速/急停) §11.6
同质 agent 学出不一致行为、协调差 误用了 \(N\) 个独立 actor 副本(破坏参数共享) 1.确认 actor 是否只 new 一次被共用 2.确认 update 是否合并所有 agent 经验 §11.7

API 速查表

本章涉及的核心接口签名与说明(教学骨架,实际以 MQE/PyTorch/scipy 对应版本为准)。

接口 / 类 签名 说明 出处
MultiRobotCoordEnv __init__(n_robots, world_size, dt) Dec-POMDP 环境,n_robots\(N\)(可变) §11.1
.reset() → list[obs_i] 重置,返回每个 agent 的本地观测 §11.1
.step(actions) → (obs, rewards, done, info) 联合动作转移 + 混合奖励 §11.1
.global_state() → ndarray 全局真值状态,仅训练给 critic,执行禁用 §11.1/§11.5
._get_obs_i(i) → {"self":..., "neighbors":[...]} 观测函数 \(O_i\),邻居为变长无序集合 §11.1
PermInvariantPolicy __init__(self_dim, nbr_dim, hidden, act_dim, agg) 排列不变 actor,agg∈{mean,max} §11.2
.forward(self_obs, nbr_obs, nbr_mask) → action_params 排列不变前向,必须传 mask 屏蔽 padding §11.2
VelocityActionHead __init__(feat_dim, v_max, w_max) 速度动作头,tanh×物理上限缩放 §11.3
.sample(feat) → (action, log_prob) 重参数化采样,返回 clip 后动作 §11.3
compute_coordination_reward (pos, vel, goal, prev_pos, actions, d_safe, gamma, w_*, ...) 混合奖励 + 势函数塑形 + 距离型碰撞软惩罚 §11.4
CentralizedCritic __init__(mode, state_dim, per_agent_dim, hidden) mode∈{global_state, perm_inv_agg} §11.5
MAPPOTrainer __init__(env, actor, critic, gamma, lam, clip, lr) MAPPO 参数共享训练器 §11.7
.collect_rollout(n_steps) → buf 所有 agent 用共享 actor 采样 §11.7
.compute_gae(rewards, values, dones) → adv 广义优势估计 §11.7
.train(n_iters, n_steps) 训练主循环(rollout→update) §11.7
cbf_safety_filter (a_rl, pos_i, pos_others, vel_i, vel_others, d_safe, alpha, a_max) CBF-QP 安全过滤,修正策略动作到安全集 §11.6
scipy.optimize.minimize (obj, x0, bounds, constraints, method="SLSQP") 求解 CBF-QP(教学用,实际可用专用 QP 求解器) §11.6
torch.distributions.Normal Normal(mean, std) 连续动作的高斯策略分布 §11.3

研究实践建议

按学习者目标分层给出建议。

如果你是初学者(第一次接触多机 MARL)

  • 先把 §11.1(建模)和 §11.2(排列不变)吃透——这是多机 MARL 区别于单机 RL 的根本,其余都建立在它们之上。
  • 动手做 §11.2 练习 1(验证排列不变性)和 §11.7 练习 1(补全 MAPPO 训练循环),从代码里建立直觉。
  • 不要一上来就追求复杂任务/复杂算法。先在最小的双机导航上跑通 MAPPO,再逐步加避障、加编队、扩 \(N\)

如果你是算法工程师(要把多机 MARL 落到真实系统)

  • 重点掌握 §11.4(奖励塑形——这是落地时最磨人、最易翻车的环节)和 §11.8(sim-to-real——决定能不能上真机)。
  • 严格遵循 §11.4 的"奖励配比调试方法论"(增量加奖励项 + 分项记录),它能省下大量盲目调参时间。
  • 安全关键场景必须读透 §11.6——learned policy 的软惩罚不是安全保证,安全层不是可选项。
  • 对照 §11.8 的多机 DR 清单做迁移,特别关注"多机特有"那几行(负载/接触/通信/数量)。

如果你是研究者(要推进多机 MARL 前沿)

  • §11.8 的"接触力建模"是公认开放难题——接触隐式通信(decPLM 路线)、真机自适应、可微物理是有价值的方向。
  • §11.6 的"去中心化安全层协调"(多机 CBF-QP 的死锁/抖动)是个未完全解决的问题,与第12章混合架构深度相关。
  • §11.2 的可扩展性可以进一步追问——排列不变之外,图神经网络、注意力在超大规模(\(N>100\))下的极限在哪。
  • 跟踪本章科研脉络表里 2024-2025 的工作(MQE、An/Hutter 可扩展、decPLM、SeqWM、Dual-Quadruped Safe RL),它们代表当前前沿。

版本信息速查

项目 版本 / 配置 说明
MQE(multiagent-quadruped-environment) ziyanx02/multiagent-quadruped-environment(~200★) 多四足 MARL 基准,基于 IsaacGym
HARL ~300★ 多智能体训练框架(含 HAPPO/MAPPO 等)
训练算法 MAPPO(参数共享 + 中心化 critic) 本章默认,第10章 §10.5 详解
仿真器 IsaacGym(GPU 大规模并行) 4096 环境 × \(N\) agent 并行
典型训练规模 ~200M steps,4096 并行环境 双机协同行走量级
折扣因子 \(\gamma\) 0.99 通用默认
GAE \(\lambda\) 0.95 通用默认
PPO 裁剪 \(\epsilon\) 0.2 通用默认
学习率 3e-4 Adam,通用起点
深度学习框架 PyTorch 本章代码示例
QP 求解 scipy SLSQP(教学)/ 专用 QP 求解器(工程) CBF 安全层

说明:上述 Stars 数、训练规模为撰写时的量级参考,具体数值随项目演进变化;训练超参(\(\gamma\)/\(\lambda\)/\(\epsilon\)/lr)为常用起点,实际需按任务调整。版本相关事实以对应项目当前官方文档/仓库为准。


本章完。 你已经掌握了把第 10 章的 MARL 框架落到多机器人导航、避障、编队上的全套方法——从 Dec-POMDP 建模、排列不变观测、动作与奖励设计,到 CTDE 训练、安全层叠加、多机 sim-to-real 迁移,再到与传统规控的边界判断。下一章(第 12 章)将把 §11.6 的安全层和 §11.9 的混合范式展开成完整的 MARL 与传统规控混合架构。