跳转至

第 8 章 导航、自驾与无人机采样 MPC

前七章我们把采样式 MPC 的内核打磨完整、又在腿足全身这个"接触最复杂、维度最高"的战场上兑现了它(第 7 章)。但采样式 MPC 真正最早、也最广泛落地的舞台,其实不是腿足,而是**移动机器人的导航、自动驾驶和无人机飞行**——MPPI 这个名字第一次被世人记住,正是 2016 年 Williams 等人让一台 1/5 比例的越野车在土路上以激进姿态高速漂移过弯。本章把采样内核带到这三个同源的应用领域,回答一个贯穿性的问题:当"代价"不再来自一个手写的解析函数、而是来自感知系统实时生成的代价地图或一组工程化的评价器(critic),当被控对象是真实世界里会受风、受载、受模型误差影响的车和无人机时,采样式 MPC 该怎么落地、怎么调、怎么变得鲁棒。

这一章的气质和第 7 章不太一样。第 7 章偏"前沿研究"——讲的是 2024–2025 年刚上真机的腿足全身控制;本章偏"工程落地"——讲的是已经在工业界大规模部署、有成熟开源实现、被无数机器人公司用在生产线上的东西。这里你会读到三类系统:AutoRally(学术界第一个真车采样 MPC 闭环,把 MPPI + 感知代价地图 + 状态估计串成完整一环)、Nav2 MPPI Controller(ROS2 生态里生产级的导航局部规划器,被广泛部署,调它的 critic 权重是一项真实的工程技能)、L1-Adaptive MPPI 与 GP/Neural-MPPI(让无人机/导航在模型误差和未知环境下变鲁棒、变聪明的几种增强)。

本章不重新发明算法。MPPI 的内核你在第 2–3 章已经吃透,多目标代价的 critic 架构你在第 4 章 §4.5(Nav2)和第 7 章 §7.2(STORM cost buffer)已经见过两次。本章做的是**把内核接到真实的感知-控制闭环里**,并把接的过程中冒出来的真问题——代价从哪来(感知代价地图 vs critic 栈)、不同车怎么动(差速/全向/阿克曼运动模型)、模型不准/有扰动怎么办(L1 自适应外环)、环境未知怎么探索(GP 引导子目标)——一个个讲清楚。


📋 前置自测

答不出 ≥ 2 题,回对应章节补完再来:

  1. vanilla MPPI 一个控制周期里对 \(K\) 条 rollout 做哪些操作得到要执行的控制?加权公式长什么样?(→ 第 2 章 §2.1)
  2. 第 4 章 §4.5 讲的 Nav2 critic 架构是什么思想?多个目标(路径跟随、避障、朝向)怎么组织成一个总代价?(→ 第 4 章 §4.5)
  3. 采样式 MPC 对动力学模型的唯一要求是什么?为什么它能用"黑箱"仿真器或学习的模型做 rollout?(→ 第 2 章 §2.4、第 6 章)
  4. 第 7 章 §7.5 讲的"动作空间"——力矩级 vs 位置目标——核心区别是什么?为什么位置目标对模型误差更鲁棒?(→ 第 7 章 §7.5)
  5. 差速驱动(differential drive)小车的运动学模型是什么?它的控制量是什么(线速度 + 角速度,还是别的)?(→ 移动机器人运动学基础)

参考答案要点(自评用,不必逐字对照):

(1) 对每条 rollout 求总代价 \(J_k\),按 \(w_k \propto \exp(-\frac{1}{\lambda}(J_k-\rho))\) 加权(\(\rho=\min_k J_k\) 做数值稳定),归一化后对扰动加权平均更新名义控制 \(U \leftarrow U + \sum_k w_k \varepsilon_k\),执行第一步、滚动重来。核心是"代价越低权重越大"的指数加权。

(2) Critic 架构把"我想要很多东西"拆成一组**可插拔、可单独加权**的评价器(critic),每个 critic 对一条候选轨迹算一个分项代价,总代价是各项加权求和。Nav2 里有路径跟随、避障、朝向、限速等 critic,每个有独立的 cost_weight。这和第 7 章 §7.2 STORM 的 cost buffer 是同一个工程智慧。

(3) 唯一要求是"能前向推进"——给定状态和控制,能算出下一个状态并对轨迹打分。它的更新公式只用代价的**值**、从不用导数,所以动力学是不是可微、是不是解析式都不要求,黑箱仿真器和学习的网络都能直接当 rollout 模型(第 6 章 Neural/学习模型、本章 Neural-MPPI 都基于这点)。

(4) 力矩级直接把力矩当控制、全靠在线优化兜稳定,表现力强但对模型误差脆弱;位置目标把目标位置当控制、底层用固定的 PD 跟踪,稳定外包给鲁棒的反馈环。位置目标更鲁棒,因为 PD 闭环的稳态位置由目标决定、几乎与模型参数无关(§7.5 的稳态不变性)。本章的导航/自驾控制量也有类似的"高层规划 + 底层执行"分层。

(5) 差速小车状态常取 \((x, y, \theta)\),控制量是线速度 \(v\) 和角速度 \(\omega\)\(\dot x = v\cos\theta,\ \dot y = v\sin\theta,\ \dot\theta = \omega\)。它不能横向平移(非完整约束),这点和全向轮、阿克曼转向都不同——本章 §8.4 会讲 MPPI 怎么适配这几种运动模型。


本章目标

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

  1. **解释清楚**为什么采样式 MPC 是导航/自驾/无人机的天然选择——这些领域的代价大量来自感知(代价地图、可通行性),常常非光滑、不可微,而采样法不求导、只打分,正好吃得下。
  2. 复盘 AutoRally 的完整闭环:MPPI(GPU)+ CNN 代价地图(地形可通行性)+ iSAM2 状态估计,理解一个真车采样 MPC 系统是怎么把感知、估计、控制串成一环的。
  3. 掌握 Nav2 MPPI Controller 的工业部署模式:critic 架构、关键参数(batch_size/time_steps/temperature)、以及"调 critic 权重"这项核心工程技能。
  4. **做出**运动模型的工程判断:差速、全向、阿克曼三种车的运动学差异,以及 MPPI 怎么通过换 rollout 模型适配它们。
  5. 理解 L1-Adaptive MPPI 的"规划-估计分离"思想:MPPI 在名义模型上规划,L1 自适应外环在线估计并补偿模型误差/扰动,让真实系统表现得像名义仿真——几乎零额外算力。
  6. 理解 GP-MPPI 与 Neural-MPPI 如何用学习改善采样导航:稀疏 GP 学可通行性、推荐子目标帮 MPPI 跳出局部最优;神经网络学动力学残差、且无需可微(只用于前向 rollout)。
  7. 给你的累积项目 Mini-MPPI 接上"代价来源"维度(解析函数 / 代价地图 / critic 栈),并理解一个外层鲁棒化层(如 L1)如何与采样内核正交叠加。

本章知识导航

本章围绕一条主线展开:把采样内核接到真实的感知-控制闭环 → 让它在不同车上动起来 → 让它在模型误差和未知环境下鲁棒、聪明。七节的逻辑关系如下:

§8.1 为什么导航/自驾/无人机是采样 MPC 的天然主场(全章地基)
   │  代价来自感知、非光滑不可微 → 采样法只打分不求导, 正好吃得下
§8.2 AutoRally:第一个真车采样 MPC 闭环(历史地标)
   │  MPPI + CNN 代价地图 + iSAM2 状态估计, 完整一环
§8.3 Nav2 MPPI:工业级生产部署与 critic 调优(工程落地核心)
   │  critic 架构 + 参数调优, "调权重"是真实技能
§8.4 运动模型:差速 / 全向 / 阿克曼(导航专属决策)
   │  同一个 MPPI, 换 rollout 模型就能适配不同车
§8.5 L1-Adaptive MPPI:无人机鲁棒飞行(规划-估计分离)⭐⭐⭐⭐
   │  名义模型规划 + 自适应外环补偿误差, 近零额外算力
§8.6 GP-MPPI 与 Neural-MPPI:用学习改善采样导航
   │  GP 学可通行性推子目标; 网络学动力学残差(免可微)
§8.7 累积项目实战:给统一规划器接上"代价来源"维度
      analytic / costmap / critics, 并叠加外层鲁棒化

一条线串起来:§8.1 立地基(采样为何适配感知驱动的导航)→ §8.2 看第一个真车闭环 → §8.3 学工业部署与调参 → §8.4 适配不同车 → §8.5 加鲁棒外环 → §8.6 用学习增强 → §8.7 把这些汇入累积项目。 前半(§8.1–§8.4)是"把内核接进真实系统并部署",后半(§8.5–§8.6)是"让它更鲁棒、更聪明",§8.7 收束到累积项目。

前置知识桥接

本章重度复用前面几章的内核,读之前激活这些记忆:

  • 第 2–3 章(MPPI 内核):MPPI 主循环——采样扰动、rollout 打分、指数加权更新、滚动时域、warm-start。本章所有系统都跑这套内核,只是把 rollout 模型和代价函数换成导航/自驾/无人机的。
  • 第 4 章 §4.5(Nav2 critic 架构):把多目标代价拆成可插拔、可加权的 critic 插件。本章 §8.3 会把这套架构讲深、讲透,并教你怎么调权重——它在第 4 章是"见过",在本章是"会用"。
  • 第 7 章 §7.2(STORM cost buffer):和 Nav2 critic 同一个工程智慧(模块化多目标代价)的另一个实例。两者对照能看清"代价模块化"这个跨系统的通用模式。
  • 第 7 章 §7.5(动作空间与分层):力矩 vs 位置目标、在线规划 vs 固定底层环的划线母题。本章的"MPPI 高层规划 + 底层执行"、§8.5 的"MPPI 规划 + L1 补偿"都是这条母题在导航/无人机上的变奏。
  • 第 6 章(学习模型):用学习的动力学做 rollout。本章 §8.6 的 Neural-MPPI 是它在导航上的直接应用——且会强调"采样不需要可微网络"这个相对梯度法的独特优势。

如果跳过本章会怎样

举两个具体场景。其一,你要给一台仓库 AGV 或配送机器人做导航,老板说"用 ROS2 Nav2"。你打开 Nav2 发现局部规划器可以选 MPPI Controller,但有几十个参数、八个 critic、每个 critic 还有权重和指数——不懂这套 critic 架构和调参逻辑(§8.3),你要么用默认参数撞墙,要么瞎调几天调不出窄通道导航。其二,你要让一台无人机在有风的户外稳定飞行,你在仿真里用 MPPI 调得很好,一到户外就被风吹得偏离航线——不懂 L1 这类自适应外环(§8.5),你会以为是 MPPI 不行,其实是名义模型没考虑风、而你缺一个在线补偿模型误差的环。本章就是要让你在这两类场景里知道该怎么做、为什么这么做。

预计阅读时间

  • 精读(推导 + 跑通所有 demo + 调一遍 Nav2):约 15–21 小时
  • 速读(抓主线 + 看本质洞察 + 浏览代码):约 4–5 小时
  • 速查(按需查某个系统/参数/陷阱):按章节标题跳转,每节自包含

本章学习路径建议

  • 只想会用 Nav2 做导航:§8.1(地基)→ §8.3(Nav2 critic + 调参)→ §8.4(运动模型)→ 故障排查手册。这条线让你能上手部署和调参。
  • 想理解完整真车系统:§8.1 → §8.2(AutoRally 闭环)→ §8.3 → §8.4,看一个感知-估计-控制闭环怎么搭起来。
  • 做无人机/鲁棒控制:§8.1 → §8.5(L1-Adaptive,⭐⭐⭐⭐ 重点)→ 故障排查,理解规划-估计分离。
  • 做研究/想用学习增强:通读,重点 §8.5、§8.6,以及前沿与开放问题。

科研发展脉络

年份 工作 Venue 核心贡献 在本章的位置
2016 Williams 等,"Aggressive Driving with MPPI" ICRA 2016 MPPI 的奠基性真车应用:信息论推导 + GPU 实现,1/5 比例 AutoRally 土路激进驾驶 §8.2
2018 Williams 等,"Information-Theoretic MPC" IEEE T-RO 上文的期刊扩展,把信息论 MPC 理论与自驾应用讲完整 §8.2(理论背景)
2020 Pravitra 等,"L1-Adaptive MPPI" IROS 2020 把 L1 自适应控制叠加到无人机 MPPI 上,规划-估计分离,仿真竞速验证 §8.5
2023 Mohamed 等,"GP-guided MPPI" IROS 2023 稀疏高斯过程学可通行性、推荐子目标,帮 MPPI 在未知杂乱环境跳出局部最优 §8.6
2023 Macenski(Budyakov 原创),"Nav2 MPPI Controller" ROSCon 2023 ROS2 生态生产级导航 MPPI:插件化 critic 架构,工业大规模部署 §8.3、§8.4

一句话脉络:Williams(2016)让 MPPI 第一次在真车上跑出惊艳的激进驾驶、奠定了采样式 MPC 在自驾的地位 → Pravitra(2020)用 L1 自适应外环解决了 MPPI"名义模型不准就不鲁棒"的痛点、把它推上无人机 → Mohamed(2023)用 GP 给 MPPI 配上"全局引导"、解决它在未知环境陷局部最优的问题 → 而 Nav2 MPPI(2023)把这套方法工程化、插件化,让它从论文走进了千万台 ROS2 机器人。 注意这条线和第 3 章 MPPI 复兴讲的是同一个底层故事——采样式方法因为"只打分不求导、能吃复杂代价、易并行"而在感知驱动的真实机器人上特别合用,只是本章把舞台从腿足换成了轮式车和飞行器,从研究前沿换成了工业落地。

把脉络讲细一点,会看到一条清晰的"问题接力":Williams 解决"能不能用"——证明 MPPI 借助 GPU 能实时跑、能在真车上做复杂动作;Pravitra 解决"准不准/稳不稳"——MPPI 在名义模型上规划,真实世界有风有载有误差时怎么办,答案是叠一个在线补偿的自适应外环;Mohamed 解决"聪不聪明"——MPPI 只看有限时域、在未知大环境里会陷局部最优,用 GP 学一个全局引导帮它看得更远;Nav2 解决"好不好用/能不能普及"——把这套方法做成插件化、可配置、生产级的工业组件,让普通工程师也能部署。理解了这条接力,你读这个方向的任何新工作,都能立刻判断它在补哪一棒——是让采样更鲁棒、更有远见、更高效,还是更易用。


§8.1 为什么导航/自驾/无人机是采样 MPC 的天然主场 ⭐⭐

动机

第 7 章 §7.1 用"接触不可微"论证了腿足是采样法的主场。本章开篇要论证一个平行的、但来源不同的命题:导航、自驾、无人机这些移动机器人领域,是采样式 MPC 的另一个天然主场——不是因为动力学不可微,而是因为它们的"代价"天然不可微。

想想一台导航机器人要优化什么。它要"沿着全局路径走""别撞障碍物""保持朝向""别开太快"——这些目标里,"别撞障碍物"这一条的代价从哪来?来自感知:相机/激光雷达建出一张**代价地图(costmap),地图上每个栅格有一个"可通行性"代价(障碍物处高、空地处低)。这张地图是离散的、突变的、由神经网络或占据栅格算法实时生成的——它不是一个你能写下解析表达式、更不是能求导的光滑函数。同理,自驾里"这片地形能不能开"由一个 CNN 从图像预测,无人机"这个方向有没有障碍"由深度图给出。**这些代价的共同特点是:来自感知、离散或突变、不可微、甚至没有显式表达式。

梯度式 MPC(你的前置课程里的 iLQR/DDP/SQP)要对代价求导。面对一张神经网络生成的代价地图,"代价对位置的梯度"要么不存在(栅格突变处)、要么需要让整个感知网络可微并反向传播(代价高、且很多感知模块根本不可微)。而采样式 MPC 只需要查这张地图、给每条候选轨迹打个分——查表是任何代价地图都支持的操作,不管它来自什么、可不可微。这就是导航/自驾/无人机偏爱采样法的根本原因。

如果不这样做会怎样

假设你坚持用梯度式 MPC 做基于代价地图的导航。你会遇到三种尴尬。其一,代价地图不可微,你没法直接对它求梯度,只能要么用有限差分(在突变的栅格上数值梯度要么为零要么爆炸,和第 7 章 §7.1 梯度法在台阶上的困境一模一样),要么把代价地图近似成光滑函数(如用高斯混合拟合障碍,但这丢失了地图的精确形状、且拟合本身是额外工作)。其二,感知-控制要端到端可微**才能把梯度从代价传到控制,这要求整个感知栈可微,工程上极难、且很多现成感知模块(占据栅格、传统 SLAM)根本不可微。其三,**多目标代价的非凸性——避障代价天然制造大量局部极小(障碍物把空间割裂成多个可行区域),梯度法容易卡在某个局部最优(比如卡在障碍物错误的一侧)。采样法不求导、能全局探索,恰好避开这三个尴尬。

历史:为什么 MPPI 是从自驾火起来的

采样式 MPC 的现代复兴(第 3 章讲过)和自动驾驶紧密绑定,这不是偶然。2016 年 Williams 等人在 ICRA 发表 "Aggressive Driving with Model Predictive Path Integral Control",让一台 1/5 比例的 AutoRally 越野车在土路赛道上以接近失控的姿态高速过弯——这是 MPPI 这个名字第一次进入大众视野,也是采样式 MPC 第一次在真实硬件上做出"梯度法做不到"的事。为什么是自驾?因为自驾恰好同时满足采样法的几个偏好:(a) 代价复杂——要同时管赛道边界、车身姿态、速度、避障,还涉及轮胎打滑这种高度非线性的动力学;(b) 代价来自感知——赛道可通行性由相机/地图给出;(c) 有 GPU——车上可以装显卡做大规模并行 rollout;(d) 实时性要求高但可接受几十赫兹——正好是 MPPI 的能力范围。这几个条件的合流,让自驾成了 MPPI 的"首秀舞台",也奠定了"采样式 MPC 特别适合感知驱动的移动机器人"这个认知。

理论:代价地图导航的 MPPI 长什么样

把 MPPI 用到代价地图导航上,内核和第 2 章完全一样,只是 rollout 模型和代价函数换了。决策变量是一段控制序列(如差速车的航向角或角速度序列),rollout 用车辆运动学模型把控制序列前向积分成一条轨迹(一串 \((x,y)\) 位置),代价 = 沿轨迹查代价地图累加的"可通行性代价" + 终端"离目标距离"代价 + 可选的平滑/能量代价。形式化地,一条候选轨迹 \(\tau_k\) 的代价是:

\[J_k = \underbrace{\sum_{t=0}^{H-1} c_\text{map}(\mathbf{p}_t^k)}_{\text{沿途查代价地图}} + \underbrace{w_g \|\mathbf{p}_H^k - \mathbf{p}_\text{goal}\|^2}_{\text{终端到达}} + \underbrace{w_u \sum_t \|u_t^k\|^2}_{\text{控制能量}}\]

其中 \(\mathbf{p}_t^k\) 是第 \(k\) 条轨迹在 \(t\) 时刻的位置,\(c_\text{map}(\cdot)\) 是查代价地图的操作(给一个连续坐标、返回该处栅格的可通行性代价)。关键在于 \(c_\text{map}\) 这一项——它就是个查表,对它求导既无必要也无意义,但对采样法毫无障碍:每条 rollout 走到哪、查到哪、累加哪。得到 \(K\) 条轨迹的代价后,照第 2 章的指数加权 \(w_k \propto \exp(-\frac{1}{\lambda}(J_k - \rho))\) 更新名义控制即可。

读到这里你可能会问:这不就是把第 2 章的 MPPI 换了个代价函数吗,有什么特别的? 对,内核确实没变——但这恰恰是采样法的威力所在:换一个领域、换一种代价来源,内核一行不用动,只要能把代价写成"对一条轨迹打分"的形式。 梯度法换领域往往要重新推导梯度、处理新的不可微性;采样法换领域只要换打分函数。这种"对代价来源的无差别兼容",是采样法能横跨腿足、导航、自驾、无人机这么多领域的根本,也是它特别适合"代价来自感知"这类场景的原因。

深一层:代价地图的"不可微"和接触的"不可微"是同一类困难吗

第 7 章说接触不可微是采样法的主场,本章说代价地图不可微也是采样法的主场。这两种"不可微"是同一回事吗?值得辨析,因为理解它们的异同能让你更准地判断"什么问题适合采样法"。

相同之处:两者都让**梯度法的核心操作(求导)失效或代价高昂**,而采样法的核心操作(求值/打分)照常工作。无论是接触动力学在切换处的不连续,还是代价地图在栅格边界的突变,"对它求导"都是病态的,"用它打分"都没问题。从"求值 vs 求导"这个第 7 章 §7.1 的统一判据看,两者确实同类——都属于"求值可行、求导困难"的问题,因而都是采样法的主场。

不同之处在于**不可微的位置**:接触的不可微在**动力学**里(状态转移 \(f(x,u)\) 不光滑),代价地图的不可微在**代价**里(代价函数 \(c(x)\) 不光滑)。这个区别有实际含义——一个优化问题由"动力学 + 代价"两部分组成,梯度法需要这两部分**都**可微才能工作(因为它要把梯度从代价经动力学链式传到控制)。采样法对两部分**都**不要求可微:动力学只要能前向 rollout(不管接触多复杂),代价只要能对轨迹打分(不管来自什么感知)。

本质洞察:采样法相对梯度法的优势,可以统一概括为**它把对"可微性"的要求从"动力学和代价都要可微"降到了"零"。梯度法是一台精密但挑食的机器——动力学和代价里任何一处不可微,它就卡住。采样法是一台粗放但不挑食的机器——动力学随便多复杂的接触、代价随便来自什么感知,它只要能"前向走一遍、打个分"就行。腿足(动力学侧不可微)和导航(代价侧不可微)是这台不挑食机器的两类典型食材,看似不同,本质都是"喂给梯度法会噎住、喂给采样法照吃不误"。理解了这一层,你以后判断一个新问题适不适合采样法,就有了一把通用的尺子:**问它的动力学和代价里,有没有哪一处是"能算出来、但不好求导"的——只要有,采样法就值得一试,而且往往是最省事的选择。

深一层:真实代价地图是"分层"的,且障碍要"膨胀"

§8.1 把代价地图当成一张简单的栅格图,但真实系统(Nav2、AutoRally)里的代价地图有两个工程要点值得展开一层,否则你部署时会困惑。

代价地图是分层叠加的(layered costmap)。 真实代价地图不是单一一张,而是多个**层**叠加而成:静态层(static layer,来自先验地图,如建筑物的墙)、障碍层(obstacle layer,来自实时传感器,如激光雷达扫到的临时障碍、行人)、膨胀层(inflation layer,下面讲)。每层独立维护、按规则合并成最终代价地图。这又是"模块化叠加"思想的一个化身(和 critic、cost buffer、因子图同源)——加一种代价来源就加一层,互不干扰。理解分层,你才明白为什么 Nav2 里代价地图有一堆配置项分属不同层,以及为什么"静态障碍"和"动态障碍"是分开处理的。

障碍要"膨胀"(inflation)。 这是个关键且常被新手忽略的点:代价地图不会把障碍只标在它**实际占据**的格子上,而是以障碍为中心,向外**膨胀**出一圈代价递减的"危险区"(inflation)。为什么?因为机器人不是一个点,它有**体积**。如果代价只标在障碍实际占据处,MPPI(把机器人当质点 rollout)规划的轨迹可能让质点"贴着"障碍边缘过——但有体积的真实机器人就蹭上了。膨胀的作用是**把机器人的体积"转移"到代价地图上**:障碍向外膨胀机器人半径那么多,机器人质点只要不进入膨胀区,真实机器人就不会碰障碍。膨胀层通常还做成**代价梯度**(离障碍越近代价越高,而非非 0 即 1),这给 MPPI 一个"离障碍越近越不划算"的平滑信号,让它倾向于留出安全余量、而非贴边走。

本质洞察:障碍膨胀揭示了一个把"机器人有体积"这个复杂约束**转移到代价地图上**的漂亮工程技巧——与其在 rollout 里精确检测"有体积的机器人和障碍是否碰撞"(昂贵、复杂),不如在代价地图侧把障碍膨胀机器人半径,然后把机器人当质点处理(廉价、简单)。这是"把约束从一处转移到另一处、换取计算简化"的典范(类比第 7 章 §7.4 把约束内生于采样、§8.4 把运动学约束写进 rollout)。它也提醒你:MPPI 把机器人当质点 rollout 是有前提的——代价地图必须已经膨胀过。如果你自己搭系统、用了未膨胀的代价地图还把机器人当质点,规划出的轨迹就会让真实机器人蹭障碍。这个坑(质点假设 + 未膨胀地图)在自己实现导航时极常见。

深一层:滚动时域重规划,才是 MPPI 应对动态世界的根本

§8.1 的 demo 是"规划一次、执行整条轨迹",但真实导航是**滚动时域(receding horizon)闭环**——这一层是 MPPI 能应对动态、不确定环境的根本,值得讲清。

回想 MPPI(和所有 MPC)的工作方式:每个控制周期,它用当前状态和当前代价地图规划出一整段控制序列,但**只执行第一步**,然后下一周期用**更新后的**状态和代价地图**重新规划**。为什么只执行第一步、不执行整条?因为世界在变——障碍会移动、感知会更新、机器人实际走的和规划的会有偏差。如果规划一次就闭眼执行整条,等执行完,世界早变了,轨迹早过时了。只执行第一步、持续重规划,意味着 MPPI 每一步都在用最新的世界信息做决策

这解释了几件本章反复出现的事。其一,动态障碍怎么处理:行人移动了,下一周期的代价地图就更新了行人的新位置,MPPI 重规划自然避开新位置——它不需要"预测整条轨迹躲行人",只需"每步都基于行人当前位置重新规划"(当然,行人移动太快时这会滞后,这正是前沿二要用预测模型补的)。其二,为什么 warm-start 重要(§7 章):每周期重规划如果从零开始太慢,所以用上周期的解平移一下当这周期的初值(warm-start),既快又稳。其三,为什么 §8.2 状态估计延迟要补偿:重规划用的状态如果是旧的,这一步决策就基于过时信息——滚动重规划的有效性依赖"用最新状态"。

本质洞察:MPPI(及一切 MPC)应对不确定、动态世界的根本武器,不是"规划得多准",而是**"高频地用最新信息重新规划、只执行最近的一步"——这把"一次性规划一条完美长轨迹"的不可能任务,换成了"持续做短期的、可不断修正的决策"。这就是"反馈"的力量:不追求一次算对,而是不断观测、不断修正。理解这一点,你就明白为什么 MPPI 的单次规划不必(也不可能)完美——它的鲁棒性来自**闭环重规划的持续修正,而非单次开环的精度。这也是为什么"开环执行整条 MPPI 轨迹"(如第 7 章 §7.5 demo 里为了测试动作空间鲁棒性而做的开环回放)会暴露出比闭环差得多的表现——因为它放弃了 MPPI 最大的武器(持续重规划)。评价任何 MPC 系统,都要记得它是闭环的:单次规划的不完美会被高频重规划持续修正,这才是它真正的工作方式。

多视角:代价地图导航像"在等高线图上滚一颗球"

给代价地图导航配个直觉。把代价地图想象成一张**地形等高线图**——障碍物是高山(高代价),空地是平原(低代价),目标是一个洼地(被终端代价拉低)。MPPI 做的事,像是从起点同时撒出 \(K\) 颗球,每颗球带一点随机初速度(采样的控制扰动),让它们在这张地形上各滚一段,然后看哪些球滚到了低处(低代价轨迹),按"滚得越低权重越大"把名义路径往那些球的方向挪一点,再重复。

像的地方:都是用"撒一批探针、看谁到了低处、往低处挪"的方式找路,不需要知道地形的解析梯度。不像的地方(边界):真实的球滚动靠重力(梯度)驱动,会自动滚向局部最低——而 MPPI 的"球"靠随机扰动驱动、靠加权平均挪动,它**不依赖梯度**,所以能"跳过"小山头(局部极小)去找更低的洼地,这正是它比"真球滚动"(梯度下降)强的地方。另一个不像:真球滚动是连续的物理过程,MPPI 是离散的迭代——每轮所有球重新从当前名义路径附近撒起,而不是让球一直滚。别把这个类比延伸到"MPPI 在做物理仿真的滚球"——它做的是统计加权的迭代优化,"滚球"只是帮你理解"撒探针找低处"这个核心动作。

深一层:代价地图的分辨率、范围、计算量——一个三角权衡

代价地图看似只是"一张栅格图",但它的**分辨率**(每格多大)和**范围**(覆盖多大区域)是要权衡的设计参数,值得展开一层,因为这直接影响 MPPI 能不能实时跑、避障准不准。

三者构成一个**三角权衡**:分辨率越高(格子越小),障碍刻画越精细、避障越准,但同样范围下格子数越多(格子数随分辨率平方增长)、查询和维护越慢;范围越大(覆盖越远),全局信息越多、越不容易"看不到远处",但同样分辨率下格子数也越多、越慢;而计算量(格子数)是有实时预算上限的。所以你不能同时要"高分辨率 + 大范围 + 实时"——必须取舍。

实践中的常见折中:用一张高分辨率的局部代价地图(小范围、精细,给 MPPI 实时避障用)+ 一张低分辨率的全局代价地图(大范围、粗,给全局规划器用)。这正好和 §8.3 的"全局规划器 + 局部 MPPI"分层对应——局部地图精细管避障、全局地图粗管拓扑,各自的分辨率/范围匹配各自的职责。这也解释了为什么 Nav2 里有 local_costmapglobal_costmap 两张图、各自配置不同的分辨率和范围。

这个权衡对 MPPI 还有个间接影响:代价地图分辨率应和 MPPI 的 rollout 步长匹配。如果 rollout 每步移动的距离远大于格子尺寸,轨迹可能"跨过"一个本该撞上的薄障碍而没查到它(采样点之间的空隙漏掉了障碍);反之分辨率远高于步长则浪费。所以调代价地图分辨率时,要和 MPPI 的速度 × 步长(每步位移)对照,保证 rollout 不会"跳过"障碍。

本质洞察:代价地图的分辨率-范围-计算量三角,是机器人感知表示里一个普遍的权衡——任何空间表示都要在"精细度""覆盖范围""计算/存储成本"之间取舍,不存在三者全优。应对之道往往是"分层多分辨率"(近处精细、远处粗略),这和人眼(中央凹高分辨率、周边低分辨率)、图形学的 LOD(细节层次)、甚至本章的"全局粗 + 局部精"分层是同一智慧。理解这个,你设计任何空间表示(地图、体素、点云)时都会问:我能不能按"近精远粗"分层,把有限的计算预算花在最需要精度的地方?这比"全局都用最高分辨率"(撑不住)或"全局都用低分辨率"(不够用)都聪明。

深一层:2D 地面导航 vs 3D 无人机——代价表示的维度跃迁

本章把"代价地图"主要当成 2D 栅格讲(地面机器人),但无人机在 3D 空间飞,它的代价表示要跃迁到三维,展开这一层能让你看清"地面"和"空中"两类系统在代价侧的根本差异。

2D(地面):地面机器人活动在一个平面上(高度基本固定),代价地图是 2D 栅格——\(O(n^2)\) 个格子,查询、维护都轻。这是 §8.1/§8.3 的设定。

3D(无人机):无人机在三维空间飞,"哪能飞、哪有障碍"是个 3D 问题。最直接的表示是 3D 体素栅格(voxel grid),但格子数随分辨率 \(O(n^3)\) 增长——同样分辨率下比 2D 多一个数量级,查询和存储成本陡增(§8.1 深一层的"分辨率-范围-计算量三角"在 3D 下更尖锐)。所以无人机的代价/障碍表示常用更省的结构:Octree(八叉树,如 OctoMap)——按需细分,空旷大块用粗格子、障碍附近才细分,大幅省存储;或 ESDF(欧氏距离场)——每个体素存"到最近障碍的距离",天然提供"离障碍多近"的平滑梯度信息(对避障代价友好);或直接在**深度图/点云**上算代价。

这个维度跃迁对采样 MPC 意味着什么?其一,rollout 和代价查询的维度都升一维——无人机的状态/控制更高维(位置 3D + 姿态),代价查询是 3D 的,计算更重(这也是无人机 MPPI 更依赖 GPU、更在意样本效率的原因之一)。其二,§8.1 的核心命题在 3D 下依然成立且更突出——3D 体素/Octree/ESDF 代价同样是离散、来自感知、不可微的,采样法"只查不求导"的优势在 3D 下更值钱(3D 代价求导更难、可微化更贵)。

本质洞察:从 2D 地面到 3D 空中,变的是**代价/状态空间的维度**(以及随之而来的表示选择和计算成本),不变的是**采样 MPC 的适用逻辑**——代价来自感知、不可微,采样只查表打分。这再次印证全章母题:采样内核的适用性跨维度、跨领域稳定,落地的功夫在于针对具体问题(这里是 3D)选对感知表示(体素/Octree/ESDF)、配够算力。理解这点,你从地面导航转去做无人机时,不会觉得是从头学一套新东西——内核和判据都一样,主要是把代价表示从 2D 栅格换成 3D 的合适结构、把运动模型换成无人机的、并因维度升高更重视算力和样本效率。会一类(地面),举一反三到另一类(空中),靠的正是抓住"什么不变"(内核与判据)而非死记"什么变了"(具体表示)。

深一层:代价整形(shaping)——demo 里那个"逐步趋目标"项在干什么

细心的你会注意到,本章好几个 demo 的代价里除了"终端到达目标",还有一个小小的"每步都罚离目标的距离"项(如 §8.1 demo 里的 0.01 * ((p-GOAL)**2))。展开一层讲清它在干什么——这叫**代价整形(cost shaping)**,是采样 MPC 实战里一个朴素但重要的技巧。

问题背景:如果代价**只有**终端项("最后一步离目标多远"),那么对很多条候选轨迹来说,它们的代价可能差不多(都没到目标、终端距离相近),MPPI 的加权就没什么区分度——它"看不出"哪条轨迹更有前途,收敛慢、容易在原地附近徘徊(尤其目标很远、单段 rollout 还到不了时)。这有点像"只在终点设奖励"的稀疏奖励问题(强化学习里的老难题)。

**整形项的作用**就是给代价加一个"沿途的、稠密的"引导信号——每步都罚一点离目标的距离,于是"朝目标走的轨迹"每一步都比"乱走的"代价低一点,MPPI 立刻就有了区分度、知道往哪个方向挪。它把"稀疏的终端目标"变成了"稠密的、每步都有的引导",大大改善收敛。这正是 §8.1 demo 加那个 0.01×趋目标 项的原因,也是 Nav2 的 GoalCritic/PathFollowCritic 等"沿途"critic 在做的事。

但整形要**克制**(注意 demo 里权重只有 0.01,远小于避障的 200):整形项权重太大,会喧宾夺主——MPPI 为了"每步都更靠近目标"而忽略避障或不愿绕路(绕路意味着暂时远离目标、整形代价上升),结果直直撞向障碍。所以整形项是"轻轻地引导方向",不能盖过避障、终端等主代价。

本质洞察:代价整形揭示了一个采样/优化方法的通用技巧——当目标信号太"稀疏"(只在终点/远处才有区分度)时,加一个稠密的、沿途的引导信号能极大改善搜索/收敛,因为它让优化器每一步都"看得出"哪个方向更好。这和强化学习的奖励整形(reward shaping)、启发式搜索的启发函数是同一智慧——给一个"局部就能感知的、指向目标的"信号。但它也共享同一个陷阱:整形信号若设计不当(权重过大或方向有偏),会把优化器引向错误的局部最优(如为了贪图"每步更近目标"而撞障碍)。所以整形是把双刃剑:用对了加速收敛,用过了制造新的局部最优。实战中的原则是"整形项轻、主目标重"——让整形引导方向,让主代价(避障、到达)拍板。理解这点,你设计任何采样 MPC 的代价时,都会有意识地加适度的稠密引导、又不让它盖过硬性目标。

易错点:代价地图查询要可批量、要处理边界

把 MPPI 接到代价地图上,有个工程细节新手常踩:代价地图查询必须是可批量的张量操作。MPPI 一个周期要对 \(K \times H\) 个轨迹点查地图(\(K\) 条轨迹 × \(H\) 个时间步),\(K=1000\)\(H=60\) 就是 6 万次查询。如果你写成对每个点的 Python 循环逐个查,这一步会成为瓶颈,把 MPPI 的实时性吃掉(和第 7 章 §7.2 张量化反例的道理一样)。正确做法是把所有轨迹点的坐标组织成一个 \((K, H, 2)\) 的张量,用一次向量化的索引操作(如 NumPy 的高级索引或 PyTorch 的 grid_sample)把全部查询并行算完。

另一个易遗漏点:坐标越界要妥善处理。rollout 出的轨迹点可能跑出地图边界(尤其早期迭代控制还很乱时),直接用越界坐标索引会报错或取到错误值。标准做法是把坐标 clip 到地图范围内,并给地图外区域设一个合理的高代价(让 MPPI 学会"别往地图外跑")。下面的 demo 里 lookup_costnp.clip 正是做这件事。

一个能跑的对照:MPPI 在代价地图上绕过墙找到缺口

把上面的论点跑出来。我们设一个最小但有代表性的导航任务:一台差速小车要从墙的一侧到另一侧,墙是代价地图上的一道高代价横条,墙上有个缺口。MPPI 不知道缺口在哪——它只会查代价地图、给轨迹打分。先定义代价地图和"查表"操作:

import numpy as np
rng = np.random.default_rng(0)

W = 100
costmap = np.zeros((W, W))
costmap[40:60, 20:80] = 1.0          # 一道横墙(可通行性差)
costmap[40:60, 45:55] = 0.0          # 墙上留个缺口
GOAL = np.array([90.0, 50.0])        # 目标在墙另一侧
START = np.array([10.0, 50.0])

def lookup_cost(xy):                  # 查代价地图: 连续坐标→栅格代价(可批量)
    ix = np.clip(xy[..., 0].astype(int), 0, W - 1)   # clip 处理越界
    iy = np.clip(xy[..., 1].astype(int), 0, W - 1)
    return costmap[ix, iy]

然后是 rollout(差速车运动学 + 沿途查图)和 MPPI 主循环——注意整段代码对代价地图只有"查"、没有任何求导:

def rollout_cost(U, p0, dt=1.0, v=1.4):
    """差速小车 rollout: U 是每步航向角 (..., H)。代价=地图代价+趋目标。"""
    p = np.broadcast_to(p0, U.shape[:-1] + (2,)).copy().astype(float)
    cost = np.zeros(U.shape[:-1])
    for t in range(U.shape[-1]):
        th = U[..., t]
        p = p + dt * v * np.stack([np.cos(th), np.sin(th)], axis=-1)
        cost += 200.0 * lookup_cost(p)             # 撞墙重罚(只查表,不求导)
        cost += 0.01 * ((p - GOAL) ** 2).sum(-1)   # 逐步趋目标(shaping)
    cost += 2.0 * ((p - GOAL) ** 2).sum(-1)        # 终端到达
    return cost

def mppi_nav(K=400, H=70, iters=30, lam=1.0, sigma=0.4, v=1.4):
    U = np.zeros(H)                                 # 名义航向: 朝 +x
    for _ in range(iters):
        eps = rng.normal(0, sigma, size=(K, H))     # 采样航向扰动
        J = rollout_cost(U + eps, START, v=v)        # K 条轨迹各打分
        w = np.exp(-(J - J.min()) / lam); w /= w.sum()
        U = U + (w[:, None] * eps).sum(0)            # 加权更新
    p = START.astype(float).copy(); hit = 0.0
    for t in range(H):
        p = p + v * np.array([np.cos(U[t]), np.sin(U[t])]); hit += lookup_cost(p)
    return p, float(np.linalg.norm(p - GOAL)), float(hit)

p_end, dist, hit = mppi_nav()
print(f"终点 ({p_end[0]:.0f},{p_end[1]:.0f}), 离目标 {dist:.1f}, 撞墙格点 {hit:.0f}")
# 典型输出:
# 终点 (86,50), 离目标 3.6, 撞墙格点 0   —— 绕过墙、穿过缺口、到达目标

跑出来:小车终点 \((86, 50)\),离目标 \(3.6\)经过的高代价格点数为 0——它自己找到了墙上的缺口、穿了过去、到达目标。这里没有任何人告诉它缺口在 \(y\in[45,55]\),它只是查代价地图、给 \(K=400\) 条候选轨迹打分、把名义路径往低代价方向挪——缺口是从"哪条轨迹代价最低"里**涌现**出来的。把这个 1D 航向序列换成真实车的转向+油门,把这张玩具代价地图换成相机/激光雷达实时生成的可通行性地图,逻辑完全一样——这就是 AutoRally(§8.2)和 Nav2 MPPI(§8.3)在做的事的微缩版。注意这和第 7 章 §7.1 "接触序列从代价涌现"是同一个母题的另一面:那里是接触序列涌现,这里是绕障路径涌现,共同点是"该走哪条路/做什么动作"不是被规定的,而是从对一批候选打分里自然产生的。

再快速看两个变体,能看清 MPPI 的能力与边界。变体一:把缺口堵死(删掉留缺口那行,墙变成完整一道)。MPPI 没有可行解,结果终点卡在墙前(离目标约 8.8、撞墙格点 15)——它"想穿但穿不过",在墙前持续尝试。这告诉你 MPPI 在无解时的行为是"挤在最接近目标的障碍处",而非崩溃或乱跑。变体二:把缺口移到很偏的位置(如 costmap[40:60,70:80]=0,缺口远离起点-目标的直线)。这时 MPPI 反而**找不到缺口、卡在墙边**(终距约 7.3、撞墙 15),而且把采样方差调大也无济于事(方差太大反而让控制乱晃、跑得更偏)。为什么居中的缺口找得到、偏远的找不到?因为 MPPI 是**贪心的局部规划器**——它的采样集中在"朝目标的直线"附近,居中缺口正好在这条线上、容易被采到;偏远缺口则要大幅绕行才能到,超出了贪心采样的"视野",于是它宁可挤在直线对应的墙段(局部最优)也想不到去绕远。这正是 §8.1 陷阱 3 和 §8.6 GP-MPPI 要解决的问题的活样本:局部规划器需要全局引导(全局路径或子目标)才能应对"解在远处"的情形——你在这个玩具 demo 上就能亲手触到这个边界。

⚠️ 常见陷阱

陷阱 1:代价地图查询写成逐点 Python 循环,吃掉实时性 ⚠️ 编程陷阱 - 错误做法:对 \(K\times H\) 个轨迹点用 for 循环逐个 costmap[x][y] 查询。 - 现象/后果:\(K=1000\)\(H=60\) 就是 6 万次 Python 级查询,每个控制周期都要重算,MPPI 从几十赫兹掉到几赫兹,实时性崩溃;在车上跑会直接导致控制跟不上、撞墙。 - 根本原因:Python 循环的逐元素开销极大,而代价地图查询本质是可以一次性批量索引的张量操作(和第 7 章 §7.2 "批量维 K 必须并行"同理)。 - 正确做法:把所有轨迹点坐标组织成 \((K,H,2)\) 张量,用一次向量化索引(NumPy 高级索引、或 PyTorch 的 F.grid_sample 做双线性采样)并行查完。自检:把 \(K\) 翻倍,单周期耗时应近似线性增长且仍在毫秒级;若耗时爆炸或卡顿,多半是写成了 Python 循环。

陷阱 2:轨迹点越界不处理,索引报错或取到错误代价 ⚠️ 编程陷阱 - 错误做法:直接用 rollout 出的坐标索引代价地图,不检查是否在地图范围内。 - 现象/后果:早期迭代控制还很乱,轨迹点常跑出地图边界,直接索引要么抛 IndexError 崩溃,要么(用了环绕索引时)取到地图另一侧的错误代价,导致 MPPI 学到莫名其妙的行为。 - 根本原因:rollout 不保证轨迹留在地图内,而数组索引对越界要么报错要么环绕,都不是你想要的。 - 正确做法:查询前把坐标 clip 到地图范围,并给地图外区域设一个合理的高代价(鼓励 MPPI 别往外跑)。自检:故意从地图边缘附近起步跑 MPPI,不应崩溃,且轨迹应被"推"回地图内。

陷阱 3:以为采样式导航能彻底替代全局规划器 ⚠️ 概念误区 - 错误想法:"MPPI 能绕过障碍找到路,那导航就只要 MPPI 一个局部规划器就够了,不需要全局路径规划(A*/Dijkstra)。" - 现象/后果:在大型或复杂环境里,MPPI 只看有限时域(如 \(H=60\) 步),看不到远处——遇到死胡同、需要大幅绕行的拓扑结构时,它会陷在局部、来回打转,到不了目标。 - 根本原因:MPPI 是**局部**规划器,它的"视野"只有 rollout 时域那么长。全局拓扑(怎么绕过一栋楼、走哪条走廊)超出它的视野,需要全局规划器(在全局地图上搜出一条粗路径)来提供远程引导。 - 正确做法:标准架构是**全局规划器(A*/Dijkstra 出粗路径)+ 局部规划器(MPPI 跟踪粗路径并实时避障)**。Nav2 正是这么分层的(§8.3),MPPI 的代价里有一项"路径跟随 critic"就是跟着全局路径走。§8.6 的 GP-MPPI 则是另一条思路——用 GP 推子目标给 MPPI 当局部引导。自检:在有死胡同的地图里只跑 MPPI,若它陷住,说明你缺全局引导。

陷阱 4:把"代价地图导航"误当成"和动力学无关的纯几何寻路" ⚠️ 思维陷阱 - 错误想法:"导航不就是在代价地图上找一条低代价的路吗?那是个几何/图搜索问题,跟车怎么动(运动学/动力学)没关系。" - 现象/后果:你用纯几何方法(如 A* 在栅格上)规划出一条"最短低代价路径",但这条路径有急转弯、原地掉头,真实的车(尤其阿克曼转向的,不能原地转)根本走不出来,或走出来后剧烈抖动。 - 根本原因:纯几何寻路忽略了车辆的运动学约束(差速车不能横移、阿克曼车有最小转弯半径、还有速度/加速度限制)。MPPI 导航之所以强,恰恰是因为它在 rollout 里**用真实运动学模型**前向积分——它采样的是控制、生成的是动力学可行的轨迹,所以产出的路径天然满足运动学约束(§8.4 会详讲)。 - 正确做法:把导航理解为"在运动学约束下、在代价地图上找一条动力学可行的低代价轨迹",而非纯几何寻路。用 MPPI 这类基于模型 rollout 的方法,让运动学约束自动融入。自检:检查 MPPI 产出的轨迹——它应该处处满足车辆运动学(差速车无横移、阿克曼车转弯不超曲率上限);若你的方法产出了运动学不可行的路径,说明它没考虑动力学。

练习

  1. (复现核心 demo,⭐⭐) 把本节的代价地图导航 demo 敲一遍跑通,复现"穿过缺口、撞墙格点 0"的结果。然后实验:(a) 把缺口移到 costmap[40:60, 70:80](靠右),MPPI 还能找到吗?(b) 把缺口完全堵上(删掉那行),MPPI 的终点会停在哪、代价如何?这说明 MPPI 在"无解"时的行为是什么。
  2. (动手·批量查图,⭐⭐⭐) 把 demo 里的 lookup_cost 改成用 PyTorch 的 F.grid_sample 做双线性插值查询(而非最近邻),对比两种查询方式产出的轨迹平滑度差异。思考:双线性插值为什么能让代价梯度"看起来"更连续,这对采样法有用吗(提示:采样法不求导,但更平滑的代价地图会让 rollout 打分更稳定)?
  3. (动手·加全局引导,⭐⭐⭐) 给 demo 制造一个死胡同地图(如把目标放在一个 U 形墙里、开口背对起点),确认纯 MPPI 会陷住。然后手动给一个中间子目标(U 形开口处),把代价改成"先到子目标再到终点",看 MPPI 是否就能绕出来。这道题让你亲手体会"局部规划器需要全局引导"(陷阱 3)。
  4. (思考·迁移判断,⭐⭐⭐) 第 7 章说接触不可微让采样法成为腿足主场,本节说代价不可微让采样法成为导航主场。请用"求值 vs 求导"这把尺子,判断下面三个问题各自适不适合采样法、为什么:(a) 一个动力学光滑、代价是简单二次型的线性系统镇定问题;(b) 一个动力学光滑、但代价来自一个不可微的神经网络评分器的问题;(c) 一个动力学含接触、代价也来自感知地图的腿足-导航联合问题。

§8.2 AutoRally:第一个真车采样 MPC 闭环 ⭐⭐⭐

动机

§8.1 论证了采样法适合"代价来自感知"的导航/自驾。但论证归论证,采样式 MPC 能不能真的在一台真车上跑通一个完整闭环、做出梯度法做不到的事? 2016 年,Williams 等人用 AutoRally 给出了肯定的、惊艳的答案:一台 1/5 比例的越野车,在土路赛道上以接近失控的姿态高速漂移过弯,全程靠 MPPI 实时控制。这是采样式 MPC 第一次在真实硬件上闭环、第一次在公众面前证明自己。本节复盘这个系统——不是因为它"新"(它是 2016 年的),而是因为它**确立了一个模板**:后来所有真实世界的采样 MPC 系统(包括工业级的 Nav2,§8.3)都在沿用它"采样控制 + 感知代价 + 状态估计"的三柱式闭环结构。读懂 AutoRally,你就读懂了一个真车采样 MPC 系统的骨架。

如果不这样做会怎样:只有一个 MPPI 算法,跑不起一辆车

设想你只学了 MPPI 算法本身(第 2–3 章),现在要让一台真车动起来。你会立刻发现 MPPI 只是闭环里的一环,缺了另外两环它什么都做不了。其一,MPPI 要知道"车现在在哪、多快、什么姿态"才能 rollout——这个状态从哪来?真车的相机/GPS/IMU 各有噪声和延迟,你需要一个**状态估计**模块把它们融合成可靠的状态。其二,MPPI 的代价里"这片地形能不能开"从哪来?你需要一个**感知**模块把相机图像变成可通行性代价。其三,rollout 要一个**车辆动力学模型**——越野车高速过弯时轮胎打滑,动力学高度非线性,模型不对 rollout 就是错的。只给一个 MPPI 算法、不配齐这三样,车就是一堆不会动的硬件。AutoRally 的贡献正是把这三样和 MPPI 串成了一个能在真车上实时跑的闭环。

历史:土路上的漂移与信息论推导

2016 年 ICRA,Williams、Drews、Goldfain、Rehg、Theodorou 的 "Aggressive Driving with Model Predictive Path Integral Control" 是 MPPI 的奠基性真车应用。它的理论基础是**信息论的随机最优控制框架**——用自由能(free energy)与相对熵(relative entropy / KL 散度)之间的对偶关系,推导出最优控制具有"路径积分"的形式,再用重要性采样近似(这套推导第 2 章 §2.1 讲过,是 MPPI 指数加权的理论来源)。2018 年他们在 IEEE T-RO 发表期刊扩展版 "Information-Theoretic Model Predictive Control: Theory and Applications to Autonomous Driving",把理论和自驾应用讲完整。工程上,AutoRally 是一个**完整开源平台**:硬件是 1/5 比例的 R/C 越野卡车底盘改装(电动驱动),车上装一台 Mini-ITX 工控机(四核 i7 + NVIDIA GTX 750 Ti 显卡 + 32 GB 内存),由 Georgia Tech 的一支六车队在赛道上采集真实驾驶数据,代码在 GitHub 的 AutoRally/autorally 仓库(MPPI 在 autorally_control 里)。这个"硬件 + 软件 + 数据"全开源的平台,让采样式自驾从一篇论文变成了可复现、可扩展的研究基础设施。

理论与设计:三柱式闭环

AutoRally 的闭环可以拆成三根柱子,MPPI 只是其中一根。把它画出来:

相机 + GPS + IMU
[柱一] iSAM2 状态估计 (GTSAM)  ──→  状态 x = (位置, 速度, 航向, ...)
      │                                    │
      ▼                                    ▼
[柱二] CNN 代价地图          [柱三] MPPI (GPU) rollout
   (地形可通行性)                  ├─ 采样 K 条 控制序列(转向+油门)
      │                            ├─ 车辆动力学模型前向 rollout
      └────────代价地图────────────┤   (轮胎打滑等非线性动力学)
                                   ├─ 沿轨迹查代价地图 + 赛道代价打分
                                   └─ 指数加权更新 → u₀(转向,油门) @ 50 Hz

柱一·状态估计(iSAM2 / GTSAM):MPPI 的 rollout 必须从一个准确的当前状态出发。AutoRally 用 iSAM2(增量平滑与建图,GTSAM 库的核心算法)融合相机、GPS、IMU,实时估计车的位置、速度、航向等状态。这是"我在哪"的来源——估计不准,rollout 的起点就错,后面再准的优化也白搭。

柱二·CNN 代价地图:一个卷积神经网络从相机图像预测**地形可通行性**——哪里是赛道、哪里是草地/边界、哪里能高速通过。输出是一张代价地图(和 §8.1 的代价地图同构)。这是"代价从哪来"的答案,也正是 §8.1 强调的"代价来自感知、不可微"——这张 CNN 生成的地图你没法对它求导,但 MPPI 只需要查它。

柱三·MPPI(GPU):内核就是第 2–3 章的 MPPI。它采样 \(K\) 条控制序列(每条是一段转向 + 油门指令),用**车辆动力学模型**把每条序列前向 rollout 成一条轨迹,沿轨迹查 CNN 代价地图、加上赛道边界等代价打分,指数加权更新名义控制,执行第一步(转向 + 油门)。整个过程在 GPU 上以约 50 Hz 实时跑。

这三柱缺一不可,而且 MPPI 这一柱用的就是你已经会的内核——AutoRally 的"新"不在算法,在于**把三柱组装成一个能在真车上闭环实时跑的系统**。下面这个结构骨架能帮你看清三柱怎么接(它复用 §8.1 的代价地图 rollout,只是把"代价从哪来"换成 CNN 输出、把状态换成估计器输出):

class AutoRallyClosedLoop:
    """AutoRally 三柱式闭环的结构骨架(示意, 非完整实现)。"""
    def __init__(self, estimator, cnn_costmap, vehicle_model, K=1000, H=60):
        self.estimator = estimator        # 柱一: iSAM2/GTSAM 状态估计
        self.cnn = cnn_costmap            # 柱二: CNN 地形可通行性
        self.model = vehicle_model        # 柱三的 rollout 模型(轮胎打滑等)
        self.K, self.H = K, H
        self.U = None                     # 名义控制序列(转向,油门), warm-start

    def control_step(self, sensors):
        x0 = self.estimator.update(sensors)         # 柱一: 融合相机/GPS/IMU→状态
        costmap = self.cnn(sensors['image'])         # 柱二: 图像→可通行性代价地图
        # 柱三: MPPI 在 GPU 上采样-rollout-加权(内核同第2-3章, 代价查 costmap)
        self.U = mppi_update(self.U, x0, self.model, costmap, self.K, self.H)
        return self.U[0]                             # 执行第一步: (转向, 油门)

深一层:CNN 代价地图怎么来——自监督地从驾驶数据学

柱二的 CNN 代价地图也值得展开一层:这个"从图像预测可通行性"的网络是怎么训练的?答案揭示了一个很巧的数据获取思路。

朴素想法是人工标注——让人在大量图像上画出"哪里能开、哪里不能开"。但这极费人力、且主观。AutoRally 这类系统更倾向**自监督(self-supervised):让车(用其它方式,如人工遥控或保守的初始控制器)实际去开,**车实际开过且没出事的地方,就是可通行的;车避开的、或开过去出问题的地方,就是不可通行的。于是驾驶数据本身——车的轨迹 + 同步的图像——就自动提供了标签:图像里对应"车安全开过"的区域标为低代价,对应"障碍/边界"的区域标为高代价。这样不需要人工标注,开得越多、数据越多、CNN 学得越好(AutoRally 用真车队采集大量驾驶数据,正是为此)。

这个思路有两个值得品的点。其一,它和 §8.2 车辆模型"从数据辨识"、§8.6 动力学"从数据学残差"是同一种**数据驱动**精神——真车系统的很多模块(感知代价、动力学、甚至控制)都可以"从开车这件事本身产生的数据里学",而非全靠人工设计或标注。其二,它体现了机器人学习里一个重要的"标签来自交互"思想:不必额外标注,让机器人与世界交互的结果(开过/没开过、成功/失败)自动成为监督信号——这和强化学习"用奖励信号学"、模仿学习"用专家轨迹学"是同一谱系的不同形态。

理解这层,你对 AutoRally 三柱的认识就更完整了:三柱里不只控制(MPPI)用了第 2–3 章的内核,感知(CNN 代价地图)和模型(车辆动力学)这两柱都是"从驾驶数据学出来的"——整个系统是"经典优化内核 + 数据驱动的感知与模型"的结合。这也预告了一个趋势:随着学习方法成熟,真车采样 MPC 系统里"从数据学"的成分会越来越多(感知、模型、采样分布,如 §8.6 讲的),而采样内核"只求值不求导"的特性,让这些学习成分都能顺滑地接进来。

深一层:为什么 AutoRally 用 GPU,而 MJPC 用 CPU

第 7 章 §7.3 讲过 MJPC 刻意选多核 CPU,本章 AutoRally 却用 GPU(GTX 750 Ti)。同样是采样 MPC,为什么平台选择相反?这正好印证第 7 章那条判据——按 rollout 的计算特性选硬件

AutoRally 的 rollout 是**车辆动力学积分**:给定转向 + 油门,用车辆模型(含轮胎力、滑移等)一步步前向积分出轨迹。这个计算虽然非线性,但**没有接触求解那种多分支、迭代不定的逻辑**——它是一串相对规整的浮点运算(算轮胎力、更新状态),而且要采几千条轨迹才够在高速驾驶里覆盖控制空间。规整 + 大批量,正是 GPU 的主场(SIMT 并行成千上万条整齐的 rollout)。反观 MJPC 的腿足 rollout 含 MuJoCo 接触求解(多分支、迭代不定),样本数适中,CPU 多核处理这种不规整负载更自如。

本质洞察:AutoRally(GPU)和 MJPC(CPU)的相反选择,不是谁对谁错,而是**rollout 长什么样决定了用什么硬件**。车辆动力学 rollout 规整、要大批量 → GPU;腿足接触 rollout 不规整、样本适中 → CPU。这条"按负载特性选硬件"的判据(第 7 章 §7.3 深一层)在这里再次应验。它给你一个实用的预判:拿到一个采样 MPC 任务,先看 rollout 重不重接触/分支、要多少样本——车辆/无人机这类"光滑动力学 + 大批量"的,默认 GPU;腿足/操作这类"接触求解 + 中等样本"的,CPU 往往更省事。别一上来就假定某种硬件,让 rollout 的特性替你做决定。

深一层:rollout 的车辆模型从哪来——又一次"采样不挑模型"

柱三的 rollout 需要一个车辆动力学模型。越野车高速过弯时轮胎打滑、侧偏,动力学高度非线性,简单的自行车模型不够准。AutoRally 用的是**辨识/学习得到的车辆模型**(从真车采集的大量真实数据拟合轮胎和底盘动力学)。这里又一次体现了采样法相对梯度法的自由:MPPI 对这个模型唯一的要求是"能前向积分"——它是解析的自行车模型、还是数据拟合的神经网络、还是查表的经验模型,MPPI 一概不挑,因为它只用模型做 rollout、从不对模型求导(呼应 §8.1 本质洞察、第 6 章学习模型、以及本章后面 §8.6 的 Neural-MPPI)。如果用梯度式 MPC,你得让这个车辆模型可微才能传梯度——而一个数据拟合的复杂轮胎模型可微化既麻烦又可能损失精度。采样法直接拿来 rollout,省去这一层。这也预告了 §8.6:用神经网络学车辆/无人机的动力学残差,再嵌进 MPPI,是一条自然的增强路线。

深一层:iSAM2 为什么用"因子图 + 增量"做状态估计

柱一的 iSAM2 值得单独讲一层,因为状态估计是真车采样 MPC 最容易被算法人忽视、却最常成为瓶颈的一环(§8.2 陷阱 2 会再点)。为什么 AutoRally 用 iSAM2(来自 GTSAM 库),而不是一个简单的卡尔曼滤波?

先说问题。真车要融合多种传感器——相机(视觉里程计/特征)、GPS(全局位置,但低频且有噪声)、IMU(高频但会漂移)。这些传感器频率不同、噪声特性不同、有的还有延迟。要从它们融合出一个准确、一致的状态估计(位置、速度、姿态),本质是一个**非线性最小二乘**问题:找一组状态轨迹,使其与所有传感器测量最吻合。

为什么用因子图(factor graph)。 GTSAM 把这个问题表示成一张因子图——节点是待估的状态(各时刻的位姿等),因子是测量约束("这两帧之间视觉里程计说位移是 X""这一时刻 GPS 说在 Y""IMU 预积分说速度变化是 Z")。因子图的好处是**统一、模块化地表达异构测量**:每种传感器就是一类因子,加一种传感器就是加一类因子,和 Nav2 的 critic 插件、采样 MPC 的代价模块化是同一种"把复杂目标拆成可加模块"的智慧(这个模式在本书里第三次出现了——critic、cost buffer、因子图)。

为什么要"增量"(incremental,iSAM2 的 i)。 朴素地解这个最小二乘,每来一个新测量就把整张图从头重解一遍——但图随时间越来越大(状态越来越多),从头解会越来越慢,撑不住实时。iSAM2 的关键是**增量式更新**:新测量到来时,它只更新受影响的那部分(利用问题的稀疏结构和矩阵分解的增量更新),而非重解全图。这让状态估计能在车上实时跑。这个"只更新变化的部分、复用之前的计算"思想,和采样 MPC 的 warm-start(复用上周期的解、§7 章)异曲同工——都是**用增量复用对抗"每次从零"的低效**。

本质洞察:AutoRally 三柱里,"控制"(MPPI)往往是算法人最关注的,但**状态估计这一柱的质量,常常才是真车表现的真正上限**——再好的 MPPI,喂给它一个漂移的、延迟的状态估计,规划就是错的(垃圾进、垃圾出)。这揭示了一个做真机系统的常见认知偏差:人们倾向于把注意力投在自己熟悉/感兴趣的那一柱(通常是控制算法),而真正的瓶颈常在不那么"性感"的另一柱(估计、感知、标定)。 这和第 7 章"瓶颈常在环境侧/模型侧"是同一类教训的不同侧面。做真车系统,要克制"只盯着控制算法调"的冲动,用系统视角找出真正最弱的那一柱——很多时候,把状态估计或感知做好带来的提升,远大于在 MPPI 参数上反复折腾。

深一层:信息论推导给了 MPPI 指数加权的"理由"

§8.2 历史里提到 AutoRally 的理论基础是"信息论的随机最优控制框架"。这一层值得稍展开,因为它回答了一个你可能一直含糊带过的问题:MPPI 那个 \(w_k\propto\exp(-\frac{1}{\lambda}J_k)\) 的指数加权,到底是怎么来的、为什么是指数而不是别的?

第 2 章讲过 MPPI 的更新公式,但 Williams 等人的信息论推导给了它一个干净的"出身证明"。核心是**自由能(free energy)与相对熵(KL 散度)的对偶关系**。简化地说:把"找最优控制分布"表述成一个优化问题——在所有控制分布里,找一个使"期望代价 + 与某基准分布的 KL 距离"最小的分布。这个问题有**闭式解**,而那个最优分布恰好是**对基准分布按 \(\exp(-\frac{1}{\lambda}\,\text{代价})\) 重新加权**的形式。换句话说,指数加权不是拍脑袋选的启发式,而是"最小化期望代价、同时别离基准分布太远"这个有原则的优化问题的**精确解**。\(\lambda\)(温度)在这里的角色也清楚了:它是 KL 正则项的权重——\(\lambda\) 大则更看重"别离基准太远"(保守、加权平缓),\(\lambda\) 小则更看重"压低期望代价"(贪心、几乎只取最优样本)。

理解这层推导有两个实际好处。其一,它让你明白**MPPI 的指数加权和"软最优"是有理论保证的**,不是数值技巧——这在你向人解释或改进 MPPI 时是底气。其二,它把 MPPI 和一大类方法(路径积分控制、KL 控制、最大熵 RL 里的软更新)连在了同一个信息论框架下——你以后看到别处出现 \(\exp(-\text{代价}/\text{温度})\) 的加权(如最大熵 RL、扩散模型的某些采样),能立刻认出这是同一个"代价-KL 对偶"在起作用。本章不展开完整推导(第 2 章已给,想深挖看 Williams 的 T-RO 2018),但你要记住:MPPI 的指数加权有信息论的"理由",温度 \(\lambda\) 是代价与 KL 正则之间的权衡旋钮。

多视角:闭环像一支三人接力队

给三柱式闭环配个直觉。把它想象成一支**三人接力队**:估计员(iSAM2)先跑——他负责搞清"我们现在在哪、多快",把准确的状态交棒给制图员;制图员(CNN)接棒——他负责看清"前方地形哪能开哪不能开",把代价地图交棒给驾驶员;驾驶员(MPPI)最后跑——他拿着状态和地图,想象一千种开法、挑最好的那种、打方向踩油门。每 20 毫秒(50 Hz)这支队伍跑完一棒接力,周而复始。

像的地方:三个角色职责清晰、按顺序交棒、缺一棒整队就垮(估计错则地图和控制都错,地图错则控制错),这准确反映了三柱的依赖关系。不像的地方(边界):真实接力是一次性的线性传递,而这个闭环是**高频循环**的——每个周期三柱都重跑一遍,且 MPPI 的 warm-start 让这一周期的解依赖上一周期(不是每次从零)。另外,三柱其实有部分并行(估计和感知可以同时算),不像接力那样严格串行。别把这个类比延伸到"三柱必须严格串行各跑一次"——它们在一个高频循环里协同,估计-感知-控制的边界也没有接力棒那么硬。

深一层:sim-to-real——仿真里调好的车,为什么上真车会变样

§8.2 反复强调车辆模型保真度,这里展开一个所有真车/真机系统都绕不开的问题:sim-to-real gap(仿真到现实的差距),理解它能让你对"为什么仿真完美、真车拉胯"有框架。

差距从哪来?把它拆成几类:动力学差距——仿真的车辆模型(哪怕是辨识的)和真车总有差异,尤其轮胎-地形交互(不同路面摩擦、温度、磨损)、电机的非线性、机械间隙等,仿真难完全复现。感知差距——仿真里代价地图"干净",真车上 CNN 受光照、天气、运动模糊影响,代价地图质量波动。延迟差距——仿真常忽略或低估传感器-计算-执行的延迟(§8.2 陷阱 1),真车延迟实打实存在。噪声差距——仿真的传感器噪声模型往往比真实噪声"温柔"。这些差距叠加,就是"仿真神、真车萎"的根源。

应对 sim-to-real 有几条思路,都和本章内容相关:其一,提高模型保真度(§8.2、§8.4)——用真车数据辨识/学习模型(包括残差,§8.6),缩小动力学差距。其二,加鲁棒化层(§8.5)——用 L1 这类在线补偿吸收"仿真没建的那部分"(差距的一部分本质就是"未建模动态",正是 L1 擅长补的)。其三,域随机化(domain randomization)——训练/调参时在仿真里随机化参数(摩擦、质量、延迟),让控制器对参数变化鲁棒,这样真车参数落在随机化范围内时也能工作。其四,闭环重规划(§8.1 深一层)——MPPI 的高频重规划本身就持续修正"规划与现实的偏差",是对抗 sim-to-real 的天然机制。

本质洞察:sim-to-real gap 是一切"在仿真里开发、到现实里部署"的系统的根本挑战,而本章给的多个工具其实都在从不同角度**缩小或吸收这个差距**——更准的模型缩小它、L1 外环吸收它、域随机化让控制器容忍它、闭环重规划持续修正它。这揭示了一个重要的真机开发心法:不要指望消灭 sim-to-real gap(不可能),而要用一组互补的手段把它缩小到"可吸收"、再用鲁棒化和闭环把残余的吸收掉。这也解释了为什么纯仿真的论文结果要谨慎看待(§8.5 提到 L1-MPPI 止步仿真)——仿真里 work 离真车 work 之间,隔着的正是这道 gap,跨过它往往比算法本身更难、更值得尊重。做真机的人都知道:让东西在真实世界里真的转起来,是一种独立于"算法对不对"的、极其硬核的能力。

深一层:三柱式"模块化"vs 端到端——一个仍在演进的取舍

AutoRally 的三柱(估计、感知、控制)是**模块化**的——每柱职责清晰、接口明确。但近年自动驾驶有一股"端到端"潮流(一个大网络直接从传感器输入到控制输出)。展开一层对比这两条路线,能帮你理解 AutoRally 模块化设计的价值与时代局限。

模块化(AutoRally 路线)的优点:每柱可单独开发、测试、调试、替换(§8.2 陷阱 2 的"逐柱诊断"正依赖此);接口清晰、可解释(出了问题能定位到哪柱);每柱可用最合适的方法(估计用 iSAM2、控制用 MPPI)。缺点:柱间接口是人为设计的(如"感知输出代价地图"),可能丢失信息(感知的某些有用信号没传给控制);各柱单独最优不一定全局最优。

端到端的优点:联合优化、理论上无接口信息损失、能学到人想不到的特征。缺点:是个黑箱(难解释、难定位问题)、要海量数据、难保证安全(一个网络兜全部,没有清晰的安全边界)、难调试(不能逐柱诊断)。

现实是这条取舍**仍在演进、未有定论**:纯端到端在某些任务上展现潜力,但工业部署(尤其安全关键)仍大量依赖模块化或"模块化为主 + 局部端到端"的混合——因为可解释、可验证、可逐模块保证安全,对量产太重要。AutoRally 的三柱模块化,正是这种"可解释、可逐柱保证"的工程稳健性的代表。

本质洞察:模块化 vs 端到端,是"可解释/可验证/可调试"与"联合最优/无接口损失"之间的根本取舍,没有放之四海的答案,取决于你多需要可解释性与安全保证、有多少数据。安全关键、要可验证、数据有限 → 倾向模块化;任务复杂到难分解、可解释性要求低、数据海量 → 端到端更有戏。这个取舍远超自驾——任何"感知-决策-执行"的 AI 系统都面临它(机器人、对话系统、推荐系统)。本章教的采样 MPC 天然站在"模块化"一侧(控制是一个清晰、可解释、可单独保证的模块),这正是它在安全关键的真实机器人上至今广泛部署的原因之一:当你需要"这个控制器在干什么、为什么、安不安全"说得清楚时,一个清晰的模块(如 MPPI)比一个黑箱网络可靠得多。 理解这个取舍,你就不会盲目追"端到端最先进",而会按"这个系统多需要可解释与安全保证"来判断该用模块化还是端到端、还是两者混合。

易错点:状态估计的延迟会让控制"踩空"

AutoRally 这类真车闭环有个容易被算法初学者忽略的工程现实:状态估计有延迟。从传感器采集到 iSAM2 输出一个状态估计,要花若干毫秒;等 MPPI 算完控制、指令到达执行器,又花若干毫秒。这意味着 MPPI 拿到的状态其实是"几毫秒前的车在哪",而它算出的控制要"几毫秒后才执行"——在 50 Hz、高速驾驶下,这段延迟里车已经移动了不小的距离。如果不管延迟、直接拿旧状态规划、把控制延迟执行,控制就会系统性地"踩空"(总是在纠正车几毫秒前的位置,而车早已不在那)。处理办法是**状态预测补偿**:用模型把估计的状态往前推算到"控制实际执行的时刻",再从这个预测状态开始 rollout(这和第 7 章 §7.5 提到的、采样 MPC 处理"重规划耗时"的状态预测是同一招)。这个细节在纯算法教学里常被略过,但在真车上不处理就会出问题。

⚠️ 常见陷阱

陷阱 1:拿状态估计的"旧"输出直接规划,不补偿延迟 ⚠️ 编程陷阱 - 错误做法:每个控制周期直接用估计器当前输出的状态当 rollout 起点,算完控制立刻下发,不考虑传感器-估计-执行链路的延迟。 - 现象/后果:在高速/高频场景下,控制总在纠正车几毫秒前的位置,表现为轨迹跟踪有系统性滞后、过弯切线晚、甚至高速下因"踩空"而失稳冲出赛道。 - 根本原因:传感器采集、状态估计、控制计算、指令下发都有延迟,MPPI 拿到的状态和它的控制实际生效的时刻之间隔着这段延迟,车在这段时间已经移动。 - 正确做法:做**状态预测补偿**——用车辆模型把估计状态前向推算到"控制预计执行的时刻",从预测状态开始 rollout(同 §7.5 处理重规划耗时的思路)。自检:测量你的端到端延迟(采集到执行),在仿真里人为加这段延迟,对比有无预测补偿的跟踪误差,应明显改善。

陷阱 2:以为"MPPI 算法"就是 AutoRally 的全部 ⚠️ 概念误区 - 错误想法:"AutoRally 能漂移过弯是因为 MPPI 厉害,把 MPPI 抄过来我也能让车这么开。" - 现象/后果:你实现了 MPPI,但状态估计用了个简陋的滤波、代价地图随便糊一个、车辆模型用最简单的自行车模型——结果车要么乱开、要么慢吞吞、要么一快就失控,完全复现不出论文效果。 - 根本原因:AutoRally 是三柱式闭环,MPPI 只是一柱。它的成功同等依赖另两柱——准确的状态估计(iSAM2)和靠谱的感知代价地图(CNN)、以及一个能反映轮胎打滑的车辆模型。任何一柱拉胯,整个闭环就拉胯。 - 正确做法:把 AutoRally 理解为"估计 + 感知 + 控制"的系统工程,而非单个算法。复现时三柱都要认真做,尤其状态估计和车辆模型的质量往往是真车表现的瓶颈(呼应第 7 章 §7.3 "瓶颈常在环境侧/模型侧")。自检:分别评估三柱——估计精度、代价地图质量、模型预测误差,找出最弱的一柱优先改进。

陷阱 3:把 AutoRally 当"2016 年的老古董"而不屑于学 ⚠️ 思维陷阱 - 错误想法:"这是 2016 年的系统了,现在都 2025 年了,肯定过时了,学最新的就行。" - 现象/后果:你跳过 AutoRally 直接学最新方法,却发现看不懂它们为什么都长成"采样控制 + 感知代价 + 状态估计"的样子,遇到真车系统的工程问题(延迟、估计、模型)时没有框架,反复踩 AutoRally 早就解决过的坑。 - 根本原因:AutoRally 的价值不在于它用的具体算法版本,而在于它**确立的闭环模板**——这个"三柱式"结构是后来几乎所有真实世界采样 MPC 系统(包括工业级 Nav2)的骨架,是经得起时间考验的系统级洞察。 - 正确做法:学 AutoRally 学的是它的**系统架构和工程经验**(三柱依赖、延迟补偿、模型从数据来、按负载选硬件),而非它 2016 年用的 CNN 或显卡型号。这些架构级的东西不过时。自检:学完后你应该能用"三柱"框架去拆解任何一个真车/真机的采样 MPC 系统,一眼看出它的估计、感知、控制分别是什么、哪一柱是瓶颈。

练习

  1. (复现·闭环结构,⭐⭐) 基于 §8.1 的代价地图 demo,把它包成本节 AutoRallyClosedLoop 的结构:写一个假的 estimator(给真实状态加点噪声当估计)、一个静态 costmap 当 CNN 输出、一个差速/自行车模型当 vehicle_model,跑一个多周期闭环(每周期重规划、执行第一步、更新状态)。验证闭环能把车开到目标。
  2. (动手·延迟补偿,⭐⭐⭐) 在上题闭环里人为加入"状态估计延迟"——让 MPPI 拿到的状态是 \(d\) 个周期之前的。先观察 \(d=2,3\) 时跟踪如何变差(陷阱 1);再加状态预测补偿(用模型把旧状态往前推 \(d\) 步),看跟踪是否恢复。这道题让你亲手体会真车闭环的延迟问题。
  3. (思考·三柱瓶颈,⭐⭐⭐) 假设你复现 AutoRally,发现车在直道还行、一过弯就冲出赛道。请用"三柱"框架列出至少三种可能的根因(分别落在估计/感知/模型/控制哪一柱),并为每种给出一个诊断方法。这道题训练你用系统视角而非"怪 MPPI"去排查真车问题。
  4. (综合·联系全章,⭐⭐⭐) AutoRally 用 GPU、MJPC(第 7 章)用 CPU。请综合两章说明:(a) 这个选择差异的根本原因是什么(rollout 特性);(b) 如果把 AutoRally 的车辆模型换成一个含复杂接触求解的模型(比如要精确仿真轮胎-碎石接触),硬件选择是否可能改变、为什么;(c) 这对你"给一个新采样 MPC 任务选硬件"有什么一般性启示。
  5. (思辨·算力门槛,⭐⭐⭐) AutoRally(2016 年)用的是一块当年入门级的 NVIDIA GTX 750 Ti 显卡,就让 MPPI 在真车上跑到约 50 Hz;Nav2 MPPI 更是在普通 i5 CPU 上跑 50+ Hz(§8.3)。请论证:(a) 这说明采样式 MPC 的"算力门槛"其实并不高,背后的原因是什么(提示:rollout 可大规模并行、样本数和时域可调);(b) 这对"采样 MPC 能否下沉到低成本/嵌入式平台"(前沿三)意味着什么;(c) 结合第 7 章腿足趋向"几十样本"的趋势,说说"降低样本数"和"提升硬件"这两条路对普及采样 MPC 各自的作用。

§8.3 Nav2 MPPI:工业级生产部署与 critic 调优 ⭐⭐⭐

动机

AutoRally(§8.2)证明了采样 MPC 能在真车上闭环,但它是个研究平台。如果你今天要给一台仓库 AGV、配送机器人、清洁机器人做导航,你会用什么? 大概率是 ROS2 的 Nav2 导航栈,而 Nav2 里有一个生产级的局部规划器叫 MPPI Controller——它把采样式 MPC 从论文做成了插件化、可配置、被千万台机器人部署的工业组件。本节讲它,因为它是你最可能真正动手部署和调试的采样 MPC 系统,而"调它"——尤其是调它的 critic 权重——是一项有方法、可习得的真实工程技能。这一节是全章最"接地气"的一节:你学完不是会推公式,而是会真的把一个采样 MPC 调到能在窄通道里把机器人开过去。

如果不这样做会怎样:把多目标代价写成一坨

导航要同时满足一堆目标:跟着全局路径走、别撞障碍、对准目标朝向、到点停下、尽量别倒车、别超速。设想你把这些揉进**一个庞大的代价函数**,所有项硬编码、权重写死在代码里。会怎样?其一,没法单独调——你想"更在乎避障一点",得在那坨代码里翻出避障项、改它、还担心牵动别的项。其二,没法消融——某天机器人行为不对,你想知道是哪个目标项的锅,但代价是一坨,没法逐项验证。其三,没法复用/增删——换个机器人要加"别压黄线"目标,你得动那坨代码而非加个模块。Nav2 不这么干:它把每个目标做成一个独立的 critic 插件,各自算分、各自带权重、可插拔可增删。这就是第 4 章 §4.5 和第 7 章 §7.2(STORM cost buffer)讲过的"代价模块化"工程智慧,Nav2 把它做到了生产级。

历史与背景:从 ROSCon 2023 到千万台机器人

Nav2 MPPI Controller 由 Aleksei Budyakov 原创,后由 Steve Macenski(Nav2 的核心维护者)移植并发展进 Nav2,在 ROSCon 2023 正式介绍。它实现了 nav2_core::Controller 接口,作为局部轨迹规划器嵌在 Nav2 的 controller server 里,可以无缝替换 Nav2 早期默认的 DWB(动态窗口)控制器。一个关键的工程事实:它在一台普通的 Intel 处理器(第 4 代 i5)上就能跑到 50+ Hz——这说明经过精心的 C++ 工程优化,采样式 MPC 完全能在没有 GPU 的普通机器人计算平台上实时跑(呼应第 7 章 §7.3 MJPC 的 CPU 路线、本章 §8.2 AutoRally 用 GPU 的对比——又一次印证"按 rollout 特性和样本数选硬件")。它支持差速(Differential)、全向(Omnidirectional)、阿克曼(Ackermann)三种运动模型(§8.4 详讲),是少数"开箱即用、还能扩展支持研究变体"的生产级采样 MPC 实现。

理论与设计:critic 架构与关键参数

Nav2 MPPI 的内核仍是第 2–3 章的 MPPI(采样控制序列、用运动模型 rollout、加权更新),它的特色全在**代价怎么组织**——critic 架构。一条候选轨迹的总代价是若干 critic 分项的加权和:

\[J_k = \sum_{i \in \text{critics}} w_i \cdot \big(c_i(\tau_k)\big)^{p_i}\]

其中每个 critic \(i\) 对轨迹 \(\tau_k\) 算一个分项代价 \(c_i\),乘上权重 \(w_i\)(参数 cost_weight)、取幂 \(p_i\)(参数 cost_power,控制该项代价随偏差增长的快慢)。Nav2 默认启用八个 critic,各管一个目标:

Critic 管什么目标 默认权重 cost_weight
ConstraintCritic 速度/约束限制(别超出运动学限制) 4.0
CostCritic 避障(查代价地图, 别撞障碍)
GoalCritic 到达目标位置 5.0
GoalAngleCritic 到达目标朝向 3.0
PathAlignCritic 与全局路径对齐
PathFollowCritic 跟随全局路径前进
PathAngleCritic 与路径方向对齐
PreferForwardCritic 偏好前进(少倒车)

注意 CostCritic(避障,查代价地图)正是 §8.1/§8.2 那个"代价来自感知"的项,PathFollowCritic/PathAlignCritic 则是"跟着全局规划器给的粗路径走"——后者正好回应了 §8.1 陷阱 3:"MPPI 是局部规划器,需要全局路径引导"。除 critic 外,几个关键的全局参数(这些是你部署时要懂的):

参数 默认值 含义
batch_size 1000(示例配置常用 2000) 采样轨迹数 \(K\)——越大越稳但越慢
time_steps 56 rollout 步数 \(H\)——时域长度
model_dt 0.05 每步时长(秒),\(H \times\) model_dt = 预测时域秒数
temperature 0.3 MPPI 的 \(\lambda\)——越小越"贪心"(只信最好的少数样本)
iteration_count 1 每周期 MPPI 迭代次数——官方建议保持 1,宁可加 batch_size
motion_model DiffDrive 运动模型: DiffDrive / Omni / Ackermann

iteration_count 默认 1 且官方建议别加,是个值得注意的工程取舍:与其在一个周期内迭代多次(增加延迟),不如一次采更多样本(batch_size)——因为采样可以并行、迭代必须串行(呼应第 7 章 §7.2"批量维并行、时域/迭代串行")。下面的骨架展示 critic 栈怎么组织(结构同 §7.2 cost buffer,每个 critic 是对 \((K,H)\) 批量轨迹的一次打分):

class NavCriticStack:
    """Nav2 风格 critic 栈骨架: 每个 critic 对 (K,H) 批量轨迹打分, 加权求和。"""
    def __init__(self, costmap, global_path, weights):
        self.costmap = costmap            # 感知代价地图(给 CostCritic 用)
        self.path = global_path           # 全局规划器给的粗路径
        self.w = weights                  # 各 critic 的 cost_weight

    def score(self, traj):                # traj: (K, H, 3) 每条轨迹的 (x,y,θ)
        import numpy as np
        K = traj.shape[0]; cost = np.zeros(K)
        xy = traj[..., :2]
        # CostCritic: 沿轨迹查代价地图(避障)——"代价来自感知", 只查不求导
        cost += self.w['obstacle'] * lookup_cost(xy).sum(-1)
        # GoalCritic: 终端离目标距离
        cost += self.w['goal'] * ((xy[:, -1] - self.path[-1]) ** 2).sum(-1)
        # PathFollowCritic: 每条轨迹到全局路径的贴合程度(简化: 到最近路径点距离)
        d2path = ((xy[:, :, None, :] - self.path[None, None]) ** 2).sum(-1).min(-1)
        cost += self.w['path'] * d2path.sum(-1)
        # PreferForwardCritic: 惩罚倒车(航向与运动方向相反)
        cost += self.w['forward'] * (traj[..., 0] < 0).sum(-1)  # 示意
        return cost                       # (K,) 总代价, 交给 MPPI 指数加权

深一层:调 critic 权重——这才是 Nav2 MPPI 部署的核心技能

学会 critic 架构只是基础,真正的工程功夫是**根据机器人的"症状"调对应 critic 的权重**。这是一项有章法的技能,不是瞎试。核心思路:每个 critic 管一个目标,机器人出什么毛病,就调对应那个目标的 critic 权重。下面这张"症状 → 调哪个 critic"的对照表,是把这项技能显式化(也是真实部署里最常用的诊断逻辑):

机器人症状 大概率原因 调整方向
离障碍太近/蹭墙 避障权重不足 调高 CostCriticcost_weight
太胆小、离障碍老远绕大圈 避障权重过高 调低 CostCriticcost_weight
偏离全局路径、自己乱走 路径跟随权重不足 调高 PathFollowCritic/PathAlignCritic
到点了不停、冲过目标 目标吸引不足 调高 GoalCritic
到点了朝向不对 朝向权重不足 调高 GoalAngleCritic
老爱倒车/掉头 前进偏好不足 调高 PreferForwardCritic
窄通道过不去、来回试探 避障太重 + 路径跟随太轻 CostCritic、升 PathFollowCritic,必要时降采样方差

本质洞察:critic 架构最大的工程价值,是它把"调一个复杂控制器"从"在一坨代码里大海捞针"变成了**"看症状、对症调那一味药"。因为每个 critic 职责单一、权重独立,机器人的每种不良行为都能对应到某个 critic 的权重上——这是模块化设计(高内聚、低耦合,第 7 章 §7.2 讲过)带来的可调试性红利。这也揭示了一个更普遍的工程道理:**好的系统设计,会让"调试/调参"这件事变得可定位、可对症;坏的设计(一坨代价)让调参变成玄学。所以你评价任何一个多目标控制器的设计好不好,一个关键标准就是"它的行为问题能不能被定位到某个可独立调整的旋钮上"。Nav2 critic 架构在这点上是范本——这也是为什么它能被千万台机器人部署:因为普通工程师能调得动它。

深一层:全局 + 局部的分层——MPPI 不是一个人在战斗

§8.1 陷阱 3 埋了个伏笔:"MPPI 是局部规划器,需要全局引导。"Nav2 的架构正是这个分层的标准答案,值得讲清。Nav2 导航是**两层规划**:全局规划器(planner server,跑 A*/Dijkstra/Smac 等)在全局代价地图上搜出一条从当前位置到目标的**粗路径**(它有全局视野,能绕过整栋楼、选对走廊);局部规划器/控制器(controller server,这里就是 MPPI Controller)负责**跟踪这条粗路径并实时避开全局规划时不知道的障碍**(动态障碍、新出现的东西),同时把粗路径变成机器人能执行的、满足运动学的平滑控制。MPPI 的 PathFollowCritic/PathAlignCritic 就是"跟着全局粗路径走"这件事的代价化体现。

这个分层回答了"MPPI 视野有限怎么办":全局规划器提供远程视野(拓扑、绕行),MPPI 提供近程的动力学可行控制和实时避障,各司其职。理解这个分层,你就不会犯"想用一个 MPPI 包打天下"的错(§8.1 陷阱 3),也能理解为什么 Nav2 的 critic 里有那么多"路径相关"的项——因为 MPPI 在这个架构里的角色不是"自己找路去目标",而是"忠实又灵活地跟着全局路径走、顺便避开突发障碍"。

深一层:全局规划器有哪些选择,MPPI 怎么消费它的路径

上面说"全局规划器出粗路径、MPPI 跟踪",但全局规划器本身也有讲究,值得展开一层。Nav2 的 planner server 支持多种全局规划算法,各有取舍:

  • Dijkstra / A*:在全局代价地图(栅格)上搜最短/最低代价路径。A* 用启发式加速,是最经典的选择。产出的路径是栅格化的折线,可能不平滑、不考虑车的运动学。
  • Smac Planner(混合 A* / State Lattice):考虑车辆运动学的全局规划——混合 A*(Hybrid-A*)在连续位姿空间搜,产出**运动学可行**的平滑路径,特别适合阿克曼车(它会避免规划出原地掉头这种阿克曼做不到的路径)。State Lattice 用预计算的运动基元搜索。
  • Theta* / 平滑变体:在 A* 基础上做视线优化,产出更直、转折更少的路径。

选哪个取决于车型和环境:差速/全向车用 A*/Dijkstra 出粗路径就够(MPPI 会处理运动学);阿克曼车在紧凑环境(如泊车)里,全局就用 Smac 的混合 A*,让全局路径本身就运动学可行,MPPI 跟起来更顺。

MPPI 怎么"消费"全局路径——这是关键的接口细节。全局规划器发布的路径是一串路点。MPPI 不是死板地逐点跟踪,而是通过几个 critic 柔性地**贴合它:PathAlignCritic(轨迹整体与路径对齐)、PathFollowCritic(朝路径前方推进、别原地不动)、PathAngleCritic(朝向与路径方向一致)。这种"用代价柔性贴合"而非"硬性逐点跟踪"的好处是:MPPI 可以为了避开一个全局规划时不知道的动态障碍,**临时偏离**全局路径一点,绕过障碍后再贴回去——它在"跟随全局意图"和"实时避障"之间自动权衡。这正是"全局管拓扑、局部管避障"分层的精髓:全局路径是**建议**而非**命令,MPPI 有偏离它的自由(去避障),但有贴回它的倾向(由 path critic 提供)。理解这点,你就明白为什么 Nav2 的 path 类 critic 有好几个、且各管一个细分(对齐、推进、朝向)——它们共同把"柔性跟随全局路径"这件事刻画清楚。

深一层:一个窄通道调参的完整走查(从撞墙到通过)

把"调 critic 权重"这项技能用一个**完整的走查**演示一遍——这是认知科学里的"渐隐示例",让你从"看懂"过渡到"会做"。场景:机器人要穿过一个略宽于自身的窄门,默认参数下它在门前来回试探、过不去。我们一步步诊断、调参:

第 1 步:观察症状、定位问题。 机器人到门口减速、左右晃、最终绕开门去别处或卡住。用"症状→critic"表(§8.3)判断:过不了窄通道,典型是"避障太重 + 路径跟随太轻"——避障 critic 把窄门两侧的墙看得太"危险",让机器人不敢进;路径跟随又不够强,没能拽着它沿全局路径穿门。

第 2 步:先动一个变量——降避障权重。 遵守"一次只改一个"(§8.3 陷阱 1),先把 CostCriticcost_weight 调低一档(如从默认降 30%)。观察:机器人是不是敢往门里走一点了?如果是,说明方向对(避障确实过重);如果开始蹭墙,说明降过头了,回调一点。

第 3 步:再动第二个变量——升路径跟随。 避障调到"敢进但不蹭墙"后,再把 PathFollowCritic/PathAlignCritic 权重升一档,让全局路径更强地"拽"机器人沿门中线穿过。观察:机器人是不是更果断地沿路径穿门了?

第 4 步:若仍在门口抖动——查采样方差。 如果权重调好了机器人还在门口犹豫、轨迹抖,可能是采样方差(vx_std/wz_std)太大,让候选轨迹太发散、在窄空间里"看起来"很多都撞墙、加权出的动作不果断。适当降采样方差,让候选轨迹更集中,有助于在窄通道里收敛出果断的穿门动作(这呼应第 7 章 §7.4 退火——窄通道是强非凸,方差策略很关键)。

第 5 步:验证泛化。 在不同宽度的门、不同入射角度下测试,确认调好的参数不是只对这一个门有效(§8.3 陷阱 3)。往往要在"敢穿窄门"和"宽阔处不蹭墙"之间找一个对各场景都稳的折中。

这个走查的价值不在具体数字(数字随机器人和环境变),而在**方法**:症状→定位到 critic→一次一个变量→观察因果→再下一个→最后验证泛化。掌握这套方法,你面对任何 Nav2 调参问题都有章可循,而不是瞎试。把这个走查和后面的练习 1(实际去 Gazebo 调窄通道)结合,你就能把"调 critic 权重"从知识变成技能。

多视角:critic 栈像一个评委打分团

给 critic 架构配个直觉。把每条候选轨迹想象成一位选秀选手,critic 栈是一个**评委打分团**:避障评委(CostCritic)给"安不安全"打分,路径评委(PathFollowCritic)给"跟没跟着路线"打分,目标评委(GoalCritic)给"离终点近不近"打分……每位评委按自己关心的维度独立打分,最后按权重(cost_weight,相当于每位评委话语权的大小)汇总成总分。MPPI 则像节目导演,看谁总分高(代价低),就往那个方向调整选手阵容(名义控制)。

像的地方:多位评委各打各的、按话语权加权汇总,准确反映了 critic 各算分项、加权求和的机制;调 cost_weight 就像调整某位评委的话语权。不像的地方(边界):真实评委的打分是主观的、一次性的,而 critic 的打分是确定的数值函数、每周期对所有候选重算;而且 critic 之间完全独立(不像评委可能互相影响)。别把类比延伸到"评委会商量"——critic 之间零耦合,各算各的,这种独立性正是它可单独调权重的前提。

深一层:MPPI 卡住了怎么办——恢复行为与"非这一层"的职责

部署 Nav2 你迟早会遇到:机器人卡住了——被临时障碍堵死、定位丢了、或 MPPI 实在找不到可行轨迹。这时该怎么办?答案揭示了一个重要的系统分层观:"卡住了怎么恢复"不是 MPPI 这一层的职责,而是更上层(Nav2 的 behavior tree / recovery server)的事。

Nav2 的导航是一棵**行为树(behavior tree)驱动的:正常情况下走"全局规划 + MPPI 局部控制"这条主干;但当主干失败(MPPI 多次算不出可行解、或机器人长时间没进展),行为树会触发**恢复行为(recovery behaviors)——比如原地旋转一圈(重新感知周围、更新代价地图)、清除代价地图里可能过时的障碍、后退一点再试、或最终求助人工。MPPI 本身不管这些,它只负责"给定当前状态和代价地图,尽力规划一段好轨迹";"反复失败后该做什么"由上层决策。

这个分层很重要,因为它告诉你**遇到"机器人卡住"问题时该去哪一层找答案**:不是去调 MPPI 参数(MPPI 在它的职责内可能已经尽力了——确实没有可行解),而是去配置恢复行为、或排查为什么会陷入需要恢复的境地(代价地图过时?定位丢失?全局路径不可行?)。把"局部规划"和"失败恢复"分成两层,各自的问题就不会混在一起。

本质洞察:一个健壮的机器人系统是**分层**的——底层(MPPI)管"在正常情况下做好局部决策",上层(行为树/恢复)管"异常情况下怎么办"。新手常犯的错是想让一层包打天下(试图调 MPPI 让它"永不卡住"),但有些情况(真的没可行解、定位丢失)本就超出了局部规划器的职责范围,强行让它处理只会让它变复杂还做不好。正确的系统设计是明确每层的职责边界,让异常处理在它该在的层。这和 §8.5"规划-估计分离"、§8.3"全局-局部分层"是同一种分层智慧的不同应用——复杂系统的鲁棒性来自清晰的分层与职责划分,而非某一层的全能。

深一层:控制频率、代价地图更新率与"信息新鲜度"

§8.3 提到 Nav2 MPPI 在普通 i5 上跑 50+ Hz,但"控制频率"这件事比一个数字更值得展开一层,因为它牵连着整个闭环的"信息新鲜度"。

闭环里有几个不同的频率:控制频率(controller_frequency,MPPI 多久重规划一次,如 30–50 Hz)、代价地图更新率(感知多久刷新一次障碍,可能只有 5–10 Hz)、定位更新率(状态估计多久出一次,可能又不同)。这些频率往往**不一致**,而闭环的反应能力受限于其中**最慢且最相关**的那个。

举例(呼应前沿二):就算 MPPI 以 50 Hz 重规划,如果代价地图只有 5 Hz 更新,那么对一个突然出现/快速移动的障碍,MPPI 在代价地图刷新前的那 200 毫秒里,规划用的都是"还没有这个障碍"的旧地图——它再高频重规划也避不开一个它"还看不见"的障碍。所以**提高控制频率不一定提高反应能力**,如果瓶颈在代价地图更新率。

这给你两个实践判断。其一,诊断"反应慢"问题要看对频率:机器人对动态障碍反应慢,先查代价地图更新率而非一味提高控制频率——前者可能才是瓶颈。其二,各频率要匹配:控制频率远高于代价地图更新率是浪费(重规划很多次但信息没变);代价地图更新率远低于障碍移动速度则危险(信息过时)。理解"闭环的反应能力受限于最慢的相关信息流",比单看"MPPI 跑多少 Hz"更能抓住真实系统的性能瓶颈(呼应 §8.2 状态估计延迟、第 7 章"瓶颈常在意想不到处")。

深一层:调试 MPPI 的利器——把它"在想什么"可视化出来

调 Nav2 MPPI 时,光看机器人最终行为去猜哪里不对,效率很低。展开一层讲一个实战调试利器:把 MPPI 的内部状态可视化出来——这能让"调参"从盲猜变成"看着改"。

MPPI 每周期采样 \(K\) 条候选轨迹、算它们的代价、加权出一条名义轨迹。这些内部量都可以画出来:Nav2 的 TrajectoryVisualizer(配置里的 visualize: true)能把采样的候选轨迹束、以及最终选出的轨迹发布到 RViz 里。看这幅图,很多问题一眼可辨:

  • 候选轨迹束太发散(撒得到处都是)→ 采样方差太大,难收敛,应降 vx_std/wz_std
  • 候选轨迹束太集中(几乎重合)→ 方差太小、探索不足,窄通道/绕障时找不到出路,应升方差或加退火。
  • 候选都"贴着"障碍/全局路径偏一侧 → 某个 critic 权重失衡,把轨迹束整体"推"偏了,对照"症状→critic"表定位。
  • 最终轨迹和候选束的"重心"明显不一致 → 可能是 temperature\(\lambda\))太小(过度贪心,只被极少数样本主导)或代价里有数值问题。

这个"可视化内部状态"的调试法,价值远超 MPPI——它体现了一个通用的调试智慧:调试一个黑箱式的优化器,最有效的办法往往是把它的中间产物(这里是候选轨迹和代价分布)暴露出来看,而非只盯着最终输出猜。同理,调 L1 时画 \(\hat\sigma\) 收敛曲线(§8.5 练习 2)、调学习模型时画预测 vs 真实轨迹(§8.4 陷阱 3 自检),都是"把内部状态可视化"的应用。

本质洞察:能不能高效调试一个系统,很大程度取决于它的内部状态**可不可观测**——把关键中间量暴露、可视化出来,调试就从"对着最终行为猜原因"变成"看着内部状态定位问题"。这是为什么成熟的工具(Nav2、各类优化器)都提供丰富的可视化/日志:可观测性(observability)是可调试性的前提。你自己实现采样 MPC(或任何复杂系统)时,从一开始就把关键内部量(候选轨迹、代价分布、各 critic 的分项值、收敛曲线)做成可输出/可视化的,会让后续调试事半功倍——这点小投入在 debug 时会千百倍地回报你。反过来,一个"什么内部状态都看不到"的黑箱系统,调起来就是噩梦。设计系统时就为"将来要调试它"做准备,是工程成熟度的标志。

易错点:cost_power 不是越大越好,权重要先归一化量纲

调 Nav2 时有两个易踩的点。其一,cost_power(代价的幂次)不是越大越好cost_power 控制代价随偏差增长的快慢——cost_power=1 是线性,cost_power=2 是平方(偏差大时惩罚急剧上升)。把它调大能让某个目标"在偏差大时被强烈抑制",但调过头会让代价地形变得极端陡峭、采样难以平滑收敛,反而抖动。多数 critic 默认 cost_power=1 就够,只在确有需要(如希望"绝对不能靠近障碍"时给避障项更陡的惩罚)才谨慎调高。其二,不同 critic 的分项代价量纲不同(距离是米、朝向是弧度、约束违反是无量纲),调权重前要意识到这点——你调的 cost_weight 实际上同时在"表达偏好"和"抵消量纲差异"。Nav2 的默认权重已经隐含了一套量纲平衡,所以**改权重时建议在默认值附近按比例微调,而非凭空设一个数**(呼应第 7 章 §7.2 陷阱"量纲归一化再谈权重")。一上来就把某个权重设成 100 或 0.01,往往是量纲没搞清的表现。

⚠️ 常见陷阱

陷阱 1:调参时一次改一大把 critic 权重,出了效果不知道是谁的功劳 ⚠️ 编程陷阱 - 错误做法:机器人行为不对,一口气把五六个 critic 的权重都改了,然后看效果。 - 现象/后果:行为变了(或没变),但你完全不知道是哪个权重起的作用,下次遇到类似问题还是只能瞎试;更糟的是几个改动互相抵消/叠加,让你对 critic 的作用建立错误认知。 - 根本原因:多个变量同时改,无法归因——这是调参的大忌,违背"一次只动一个变量"的基本实验纪律。critic 架构的可定位性优势(每个 critic 独立)正是被这种乱改浪费掉的。 - 正确做法:一次只调一个 critic 的权重,观察机器人行为的变化,确认这个 critic 的作用方向后再调下一个(用上面"症状→critic"表定位先调哪个)。自检:记录每次只改一个权重前后的行为差异,建立"这个 critic 调高/调低 → 机器人会怎样"的因果认知;若你说不清某次行为变化是哪个权重导致的,说明你一次改太多了。

陷阱 2:以为 Nav2 MPPI 能脱离全局规划器单独工作 ⚠️ 概念误区 - 错误想法:"MPPI 这么强,能避障能到目标,那我把全局规划器关了,只用 MPPI Controller 导航就行。" - 现象/后果:在大环境或有复杂拓扑(绕楼、死胡同、长走廊)的地方,机器人陷在局部、来回打转、到不了目标——因为 MPPI 只看 time_steps × model_dt 那么长的时域(默认约 2.8 秒),看不到远处。 - 根本原因:MPPI 是**局部**规划器,视野只有 rollout 时域那么长。全局拓扑超出它的视野,必须靠全局规划器提供粗路径(§8.1 陷阱 3、本节"全局+局部分层")。Nav2 的 PathFollowCritic 等就是设计来"跟着全局路径走"的——关掉全局规划器,这些 critic 就没路可跟。 - 正确做法:始终用"全局规划器 + MPPI 局部控制器"的标准 Nav2 分层。MPPI 的职责是跟踪全局路径 + 实时避障,不是自己找全局路。自检:在有死胡同的地图里测试,确认全局规划器在工作(能看到全局路径发布);若机器人在复杂拓扑里陷住,先查全局规划器而非调 MPPI。

陷阱 3:把"调到 demo 能跑"当成"调好了",忽视鲁棒性与场景覆盖 ⚠️ 思维陷阱 - 错误想法:"我在空旷场地调得机器人能到目标了,参数就调好了,可以部署了。" - 现象/后果:一到真实场景——窄通道、动态行人、传感器噪声、不同光照下的代价地图质量波动——机器人就各种出问题(蹭墙、卡住、急停),因为参数只在那个简单 demo 场景下调过。 - 根本原因:导航参数的好坏是**场景相关**的,单一简单场景调出的参数不能代表它在多样真实场景下的表现(呼应第 7 章 §7.3"演示里惊艳≠真机鲁棒")。一组参数在空场好,不等于在窄通道、动态环境、噪声感知下都好。 - 正确做法:在**多样化的代表性场景**里测试和调参——窄通道、开阔区、动态障碍、不同起止配置,确认参数在这些场景下都可接受(往往要在"激进/高效"和"保守/安全"之间取一个对各场景都稳的折中)。自检:列出你的部署环境会遇到的典型场景,逐个测试当前参数;只在一个场景测过就说"调好了"是危险的。

练习

  1. (动手·部署,⭐⭐⭐,环境题) 在 Gazebo + TurtleBot3 上部署 Nav2 MPPI Controller,替换默认控制器,先用默认参数跑通点到点导航。然后制造一个窄通道(两堵墙留一个略宽于机器人的缝),观察默认参数能否通过;若不能,用"症状→critic"表诊断并调参(提示:多半要降 CostCritic、升 PathFollowCritic)。记录你每次只改一个权重的过程和效果。
  2. (动手·消融,⭐⭐⭐) 在仿真里,把 PreferForwardCritic 的权重设为 0(关掉前进偏好),观察机器人是否开始频繁倒车/掉头;再恢复,观察行为变化。用这个消融建立"这个 critic 到底在干什么"的直观认知。对 GoalAngleCritic 做同样的消融(关掉后到点朝向是否变乱)。
  3. (思考·参数权衡,⭐⭐⭐) Nav2 官方建议 iteration_count 保持 1、宁可加 batch_size。请结合第 7 章 §7.2"批量维并行、迭代串行"解释:(a) 为什么加 batch_size(采样数)比加 iteration_count(迭代次数)对实时性更友好;(b) 什么情况下你可能仍需要 iteration_count>1(提示:单次迭代的探索不足以收敛的强非凸场景);(c) temperature\(\lambda\))调小会让 MPPI 更"贪心",这在什么场景下有益、什么场景下危险。
  4. (综合·联系 §4.5/§7.2,⭐⭐⭐) Nav2 critic 栈、第 7 章 STORM cost buffer、第 4 章 §4.5 的 critic 架构,是同一个"代价模块化"思想的三个实例。请对比它们:(a) 三者在"用什么实现模块化"上的异同(C++ pluginlib vs PyTorch 张量 vs ...);(b) 这个模块化思想带来的三个共同工程红利(可测试、可调、可复用)分别在 Nav2 调参里如何体现;(c) 为什么说"代价模块化"是采样 MPC 工程落地的关键模式之一。

§8.4 运动模型:差速、全向、阿克曼 ⭐⭐⭐

动机

§8.3 提到 Nav2 MPPI 支持差速、全向、阿克曼三种 motion_model。这不是可有可无的配置项——用错运动模型,MPPI 规划出的轨迹机器人根本走不出来。一台阿克曼转向的车(像真实汽车,靠前轮转向)不能原地掉头、有最小转弯半径;一台差速车(靠左右轮差速,像扫地机器人)能原地旋转;一台全向车(麦克纳姆轮)能朝任意方向平移。同一个导航任务,这三种车能做的动作天差地别。本节讲清这三种运动学模型的差异,以及一个漂亮的事实:MPPI 适配不同车,只需要换 rollout 里的运动模型函数,采样、打分、加权的内核一行都不用动。

如果不这样做会怎样:给阿克曼车规划差速车的路径

设想你给一台阿克曼转向的配送车做导航,但偷懒用了差速车的运动模型来 rollout。MPPI 会规划出"原地转 90° 再直行""贴着障碍急转弯"这类差速车能做、但阿克曼车做不到的动作(阿克曼车不能原地转、转弯有最小半径)。结果:规划的轨迹下发到真车,车要么转不过来、卡住,要么强行转弯时轨迹严重偏离规划、撞上障碍。反过来,给差速车用阿克曼模型,则会让 MPPI 过度保守——明明能原地转的车,却被规划成大半径绕弯,效率低下。运动模型必须匹配真车的真实运动学,否则规划与执行脱节。这正是 §8.1 陷阱 4("导航不是纯几何寻路")的具体化:路径必须是该车动力学可行的。

历史与理论:三种运动学模型

这三种模型是移动机器人运动学的经典内容。它们的状态都可取 \((x, y, \theta)\)(位置 + 航向),区别在**控制量**和**约束**:

差速驱动(Differential Drive):控制量是线速度 \(v\) 和角速度 \(\omega\),运动学为 $\(\dot x = v\cos\theta,\quad \dot y = v\sin\theta,\quad \dot\theta = \omega\)$ 关键特性:\(\omega\) 独立于 \(v\)——可以 \(v=0\)\(\omega\neq 0\)原地旋转),也可以任意紧的转弯。但它**不能横向平移**(没有 \(\dot y\) 方向的独立控制,这叫非完整约束 / nonholonomic)。扫地机器人、TurtleBot 都是差速车。

全向(Omnidirectional):控制量是 \(v_x, v_y, \omega\) 三个独立量,运动学为 $\(\dot x = v_x\cos\theta - v_y\sin\theta,\quad \dot y = v_x\sin\theta + v_y\cos\theta,\quad \dot\theta = \omega\)$ 关键特性:能朝**任意方向平移**(\(v_x, v_y\) 独立),还能同时独立旋转——自由度最高,约束最少。麦克纳姆轮、全向轮平台属此类。

阿克曼(Ackermann):像真实汽车,控制量是速度 \(v\) 和**转向角** \(\delta\),运动学为 $\(\dot x = v\cos\theta,\quad \dot y = v\sin\theta,\quad \dot\theta = \frac{v\tan\delta}{L}\)$ 其中 \(L\) 是轴距。关键特性:转弯率 \(\dot\theta = v\tan\delta / L\) 耦合于速度——\(v=0\)\(\dot\theta=0\)不能原地转!),且转向角有上限 \(|\delta|\le\delta_\max\),导致**最小转弯半径** \(R_\min = L/\tan\delta_\max\)(转弯不能比这更紧)。汽车、阿克曼转向的 AGV 属此类,AutoRally 也是阿克曼。

约束从松到紧:全向(能平移能转)> 差速(能原地转不能平移)> 阿克曼(不能原地转、有最小转弯半径)。约束越紧,可行轨迹空间越小,MPPI 能采到的有效动作就越受限。

一个能跑的对照:同一个 MPPI,差速能到的目标阿克曼到不了

把"换模型内核不动"和"约束自动被尊重"一起跑出来。任务:目标在正左侧 2 米处。到达它需要转弯半径约 1 米,而我们设的阿克曼车最小转弯半径是 1.83 米——它**物理上转不了那么紧**。同一个 MPPI、同一个任务,只换 rollout 模型,看结果。先看两个 rollout 模型(差异只在转弯率怎么算):

import numpy as np
rng = np.random.default_rng(0)
GOAL = np.array([0.0, 2.0])      # 起点(0,0)朝+x, 目标在正左侧
L, DELTA_MAX = 1.0, 0.5          # 轴距、最大转向角 → 最小转弯半径 1.83m

def rollout_diff(U, dt=0.2):
    """差速车: 每步 (v, ω)。ω 独立于 v → 可原地转、任意紧转弯。"""
    B = U.shape[:-2]; x=np.zeros(B); y=np.zeros(B); th=np.zeros(B); cost=np.zeros(B)
    for t in range(U.shape[-2]):
        v = np.clip(U[..., t, 0], 0, 1.5); w = np.clip(U[..., t, 1], -2.5, 2.5)
        x = x+dt*v*np.cos(th); y = y+dt*v*np.sin(th); th = th+dt*w   # ω 独立
        cost += 0.05*((x-GOAL[0])**2+(y-GOAL[1])**2)
    cost += ((x-GOAL[0])**2+(y-GOAL[1])**2); return cost, x, y

def rollout_ackermann(U, dt=0.2):
    """阿克曼车: 每步 (v, δ)。ω=v·tan(δ)/L, |δ|≤δmax → 有最小转弯半径。"""
    B = U.shape[:-2]; x=np.zeros(B); y=np.zeros(B); th=np.zeros(B); cost=np.zeros(B)
    for t in range(U.shape[-2]):
        v = np.clip(U[..., t, 0], 0, 1.5)
        delta = np.clip(U[..., t, 1], -DELTA_MAX, DELTA_MAX)        # 转向角受限
        w = v*np.tan(delta)/L                                        # 转弯率耦合于 v
        x = x+dt*v*np.cos(th); y = y+dt*v*np.sin(th); th = th+dt*w
        cost += 0.05*((x-GOAL[0])**2+(y-GOAL[1])**2)
    cost += ((x-GOAL[0])**2+(y-GOAL[1])**2); return cost, x, y

MPPI 主循环对两个模型**完全一样**(这正是重点)——只是把 rollout 传进去:

def mppi(rollout, H=25, K=600, iters=50, lam=1.0, sigma=0.6):
    U = np.zeros((H, 2)); U[:, 0] = 0.8                  # 名义: 缓速直行
    for _ in range(iters):
        eps = rng.normal(0, sigma, size=(K, H, 2))        # 采样 (v,转向) 扰动
        J, _, _ = rollout(U + eps)                         # 内核不变, 只是模型不同
        wt = np.exp(-(J - J.min()) / lam); wt /= wt.sum()
        U = U + (wt[:, None, None] * eps).sum(0)
    J, xf, yf = rollout(U[None])
    return float(np.sqrt((xf[0]-GOAL[0])**2 + (yf[0]-GOAL[1])**2))

print(f"差速车   终点离目标: {mppi(rollout_diff):.2f} m  (可原地转, 轻松到达)")
print(f"阿克曼车 终点离目标: {mppi(rollout_ackermann):.2f} m  (转不了那么紧, 到不了)")
# 典型输出:
# 差速车   终点离目标: 0.11 m   —— 原地转向再直行, 贴到目标
# 阿克曼车 终点离目标: 2.00 m   —— 最小转弯半径 1.83m > 需要的 1m, 物理上转不过去

结果一目了然:差速车终点离目标 \(0.11\) 米(轻松到达),阿克曼车卡在 \(2.00\) 米(基本没动到目标)。同一个 MPPI、同一个任务、同一套采样和加权,仅仅因为 rollout 模型不同,结果天差地别——阿克曼车的最小转弯半径约束让"到达正左侧近处目标"这件事物理上做不到,而 MPPI 通过在 rollout 里如实使用阿克曼运动学,自动尊重了这个约束(它采出的所有轨迹都满足 \(\dot\theta = v\tan\delta/L\),没有一条能转得比 \(R_\min\) 更紧)。这也再次落实了 §8.1 陷阱 4:MPPI 产出的是动力学可行的轨迹,不是纯几何路径。

补上第三种车——全向车(控制量 \(v_x, v_y\),能朝任意方向平移),把 rollout_ackermann 换成全向 rollout(x += dt*vx; y += dt*vy),同样用这个 mppi 函数跑 \((0,2)\) 目标。结果:全向车终点离目标约 \(0.11\) 米——它**直接横着平移**过去,路径几乎是一条直接指向目标的侧移线,比差速车(要先转向再前进)还省事。三种车放一起看就完整了:全向车(终距 0.11,直接横移)、差速车(终距 0.11,先转后行)、阿克曼车(终距 2.00,转不过去)——同一个目标、同一个 MPPI 内核,三种车因运动学约束从松到紧,表现从"轻松直达"到"根本到不了"。这就是 §8.4 开篇说的"约束越紧、可行轨迹空间越小、MPPI 能采到的有效动作越受限"的最直观证据,也再次印证了"换车只换 rollout 模型、内核不动"——上面三个结果用的是**同一个 mppi 函数**,只换了传进去的 rollout。

深一层:换车,内核为什么一行不用动

上面 demo 里 mppi 函数对差速和阿克曼**完全一样**,这不是巧合,是采样法架构的必然。回想 MPPI 的内核:采样控制扰动 → rollout 打分 → 指数加权更新。这三步里,只有 "rollout" 一步涉及"车怎么动",其余两步(采样、加权)只跟控制序列的数值有关、跟车的运动学无关。所以**换一种车,只需要换 rollout 里那个把控制序列变成轨迹的运动模型函数**,采样和加权的代码原封不动。

这正是第 7 章反复出现的"换领域内核不动"母题在导航内部的又一次体现——第 7 章是换应用领域(腿足→操作),§8.1 是换代价来源(解析→感知),这里是换车型(差速→阿克曼)。

本质洞察:采样式 MPC 的代码可以清晰地分成"领域无关的内核"和"领域相关的插件"两部分——采样器、加权更新、滚动时域是内核(写一次、到处用),运动模型、代价函数是插件(按问题替换)。换车型只动运动模型插件,换代价来源只动代价插件,换应用领域可能两个都动一点,但内核永远不动。这种"内核-插件"的清晰分离,是采样法能横跨这么多领域、又能做成 Nav2 那样可配置生产组件的根本(Nav2 的 motion_model 参数和 critic 插件正是这种分离的工程化——换车改一个参数、换目标加一个 critic)。理解了这一点,你看任何采样 MPC 系统都能一眼分出"哪部分是它和别人共享的内核、哪部分是它针对自己问题的插件"——而你自己搭系统时,也应该照这个分离来组织代码:把内核写成与运动模型/代价无关的通用循环,把车型和目标做成可替换的模块。

深一层:阿克曼模型背后的"自行车模型"近似

上面阿克曼的运动学 \(\dot\theta = v\tan\delta/L\) 是哪来的?它来自一个经典近似——自行车模型(bicycle model),值得展开一层,因为它是几乎所有车辆运动规划的基础。

真实汽车有四个轮子、前轮转向(左右前轮转角还略有不同,即阿克曼几何)。完整建模很复杂。自行车模型做一个简化:把左右两个前轮合并成一个虚拟前轮、左右两个后轮合并成一个虚拟后轮,于是车变成一辆"自行车"——一个前轮(可转向 \(\delta\))、一个后轮(不转向)、轴距 \(L\)。这个简化抓住了车辆转向的核心几何:后轮沿车身方向滚动,前轮沿转向方向滚动,车绕一个瞬时中心转动。几何推导给出转弯率 \(\dot\theta = v\tan\delta/L\)\(v\) 是后轮速度)——这正是阿克曼 rollout 用的式子。

自行车模型的**适用边界**要清楚(这是 R7 语言精确性要求的"标注边界"):它假设**低速、无轮胎侧滑**——轮子纯滚动、不打滑。在城市低速导航、泊车这类场景,这个假设很好,自行车模型够用。但在**高速过弯**(轮胎侧向力大、开始打滑)时,纯运动学的自行车模型就不准了,需要**动力学自行车模型**(引入轮胎侧偏角、侧向力)甚至更复杂的轮胎模型——这正是 §8.2 AutoRally 高速激进驾驶时,为什么不能用简单自行车模型、而要从数据辨识一个含轮胎打滑的车辆模型的原因。

本质洞察:自行车模型是"用一个抓住主要几何的简化,换可解析、可计算"的典范——它丢掉了四轮的细节,但保住了"转向如何改变航向"这个对规划最重要的关系。这种"抓主要矛盾的简化建模"思想贯穿机器人学(第 7 章腿足的单刚体模型 SRBD 也是同类——丢掉腿的细节、保住质心动力学)。但每个简化模型都有适用边界(自行车模型:低速无滑),用之前必须知道它在什么条件下成立、什么条件下失效——高速时自行车模型失效、要换动力学模型,正如腿足里某些动作下 SRBD 失效、要换全身模型。会用简化模型的人多,知道它何时失效的人少,后者才是真正的判断力。

深一层:采样式局部规划 vs 搜索式(DWB)——同一个问题的两条路

Nav2 早期默认的局部规划器是 DWB(Dynamic Window Approach 的 Nav2 实现),现在 MPPI 是热门替代。两者都是局部规划器、都要"在运动学约束下找一段好的控制",但思路不同,对比它们能加深你对采样法的理解。

DWB(动态窗口)的思路:在**速度空间**里离散采样。它考虑机器人当前速度、加速度限制,圈定一个"下一步可达的速度窗口"(动态窗口),在这个窗口里**离散地枚举**一组 \((v,\omega)\) 候选,对每个候选**前向模拟一小段**轨迹、用一组评价函数打分,选分最高的。

MPPI 的思路:在**控制序列空间**里**随机采样**。它对整段时域的控制序列加高斯噪声采样出 \(K\) 条,rollout 打分,指数加权**融合**(而非选单个最优)。

把两者并排,区别就清楚了:

维度 DWB(搜索式) MPPI(采样式)
采样空间 当前一步的速度窗口 整段时域的控制序列
采样方式 规则网格离散枚举 随机高斯采样
决策 选单个最高分候选 指数加权融合所有候选
时域 通常较短(看一小步) 较长(整段序列)
维度承受 速度维度低时好,高维枚举爆炸 高维下随机采样更可扩展
代价灵活性 评价函数,相对受限 任意可打分代价(含感知地图)

取舍:DWB 在低维(差速车的 \((v,\omega)\) 二维)、短时域、规则环境里简单可靠,枚举几十个候选就够。但它的离散枚举在**高维**(如要同时优化更多控制量)会组合爆炸,且只看一小步、容易短视。MPPI 用随机采样替代规则枚举,天然适应高维(随机采样不随维度组合爆炸,只是需要更多样本,呼应第 7 章维度灾难那套讨论),且看整段序列、不那么短视,代价也更灵活(能直接吃感知地图)。代价是 MPPI 需要更多样本、结果有随机性。

本质洞察:DWB 和 MPPI 是"搜索 vs 采样"这对更普遍范式在局部规划上的体现——搜索(规则枚举)在低维、要确定性、要最优性时占优;采样(随机撒点)在高维、代价复杂、可接受随机性时占优。这和你在第 1 章看到的"采样式 vs 梯度式/搜索式 MPC"的取舍是同一个谱系。所以"DWB 还是 MPPI"不是"老的还是新的"之争,而是"你的问题是低维规则、还是高维复杂"之选——低维规则的差速导航 DWB 至今够用,高维或代价复杂(要吃 CNN 地图、要长时域)则 MPPI 更合适。能按问题特性而非新旧来选,才是判断力。

多视角:运动模型像"给同一个大脑换不同的身体"

给"换模型内核不动"配个直觉。把 MPPI 内核想象成一个**通用的运动规划大脑**——它会"想象很多种动作、挑最好的",但它本身不知道自己控制的是什么身体。运动模型就是给这个大脑接上的**具体身体**:接上差速车的身体,大脑想象的动作就是"原地转 + 直行";接上阿克曼车的身体,大脑想象的动作就受限于"只能弧线转弯";接上全向车的身体,大脑就能想象"朝任意方向平移"。大脑(采样 + 加权逻辑)不变,换身体(运动模型)就让同一个大脑去驾驭不同的机器人。

像的地方:大脑(内核)和身体(模型)解耦,换身体不用换大脑——准确反映了内核与运动模型插件的分离。不像的地方(边界):真实的大脑-身体需要适应期(学习用新身体),而 MPPI 换运动模型是即时的、无需"学习"——只要模型写对,内核立刻就能正确驾驭新车。另外,这个类比不要延伸到"大脑有自主意识"——MPPI 内核只是机械地采样和加权,"想象动作"是拟人化的说法。

深一层:控制的"量"还能再选——速度级 vs 加速度级,与轨迹平滑性

§8.4 把控制量定为速度 \((v,\omega)\)\((v,\delta)\),但"控制哪个量"本身还有一层选择,关系到轨迹的平滑性,值得展开——这其实是第 7 章 §7.5"动作空间"母题在导航里的又一个面。

速度级 vs 加速度级控制。 上面 demo 把**速度**当控制量(每步直接给 \(v,\omega\))。但你也可以把**加速度**(或角加速度、jerk)当控制量,速度由加速度积分得到。区别在哪?直接控速度,相邻两步的速度可以突变(这一步 \(v=1\)、下一步 \(v=2\)),对应**加速度无穷大**——真实电机做不到,且乘客/货物会被"顿"一下。控加速度(限幅)则保证速度连续变化、加速度有界,轨迹更平滑、更舒适、更可执行。代价是状态维度增加(要把速度也纳入状态),rollout 多积分一层。

这对采样有什么影响。 控加速度时,采样的是加速度扰动,rollout 多一层积分——这天然让产出的速度/位置轨迹更平滑(积分是低通的,把采样噪声滤平了)。所以"控制量选在哪一层"也是一个影响轨迹平滑性的旋钮:要更平滑、更舒适,把控制量往"更高阶导数"(加速度、jerk)选,让积分帮你平滑;要更直接、响应更快,把控制量选在速度级。Nav2 的 ConstraintCritic 和速度/加速度限制参数,正是在管这件事——约束速度和加速度别超限,间接保证平滑可执行。

曲率连续性(对阿克曼车尤其重要):阿克曼车的转向角 \(\delta\) 不能瞬变(方向盘转动有速度上限),对应**曲率不能瞬变**。如果只控 \(\delta\) 且允许它突变,规划的轨迹会有曲率跳变,真车方向盘跟不上。更讲究的做法是把**转向角速率**(方向盘转多快)也限幅或当控制量,保证曲率连续——这是高速自驾里轨迹平滑性的关键细节。

本质洞察:从 §7.5(力矩 vs 位置目标)到这里(速度级 vs 加速度级),你应该看出一个统一的认识——"控制量选在系统的哪一个导数层次上"是一个普遍的设计旋钮,它在"表现力/响应速度"和"平滑性/可执行性"之间权衡。选在低阶(位置、速度)直接、响应快但可能不平滑;选在高阶(加速度、jerk)平滑、舒适但响应慢、状态维度高。这个"导数层次选择"贯穿所有控制问题(腿足的力矩 vs 位置、导航的速度 vs 加速度),是动作空间设计的核心维度之一。理解它,你给任何系统设计控制量时,都会多问一句:我该把控制量选在哪个导数层次上,这个选择在响应性和平滑性上意味着什么?

深一层:为什么差速车是导航的"默认",以及阿克曼车的泊车难题

三种运动模型里,差速车在室内服务机器人领域是事实上的"默认",阿克曼车则在某些场景(尤其泊车)格外棘手。展开这一层,能帮你理解运动模型选择背后的工程权衡。

为什么差速车最常见:它在"能力"和"机械简单"间平衡得好——能原地转(机动灵活,窄空间里能转身)、机械结构简单(左右两个驱动轮 + 万向轮,无需复杂转向机构)、运动学简单(控制直接是 \(v,\omega\),好建模好控制)。对室内服务机器人(扫地、配送、迎宾),这些优点正中需求,所以 TurtleBot、大量商用服务机器人都是差速底盘。全向车机动性更强但机械复杂、成本高(麦克纳姆轮维护贵、效率低),只在特定场景(仓库 AGV 要任意方向移动)才值得。

阿克曼车的泊车难题:阿克曼车不能原地转、有最小转弯半径,这让它在**紧凑空间的机动**(如泊车、掉头)变得困难——它可能需要"揉库"(前进-后退多次,像人类泊车),而这种含**反向(reversing)的复杂机动,简单的几何规划做不出来。这正是 §8.3 深一层提到的 **Smac Hybrid-A* 全局规划器的用武之地:它在连续位姿空间搜索、显式考虑最小转弯半径约束和车辆完整足迹、且支持反向(Dubins/Reeds-Shepp 运动模型),能规划出阿克曼车真正可执行的泊车/掉头路径。然后 MPPI 用阿克曼模型跟踪这条已经运动学可行的路径。所以阿克曼车的典型架构是 Smac Hybrid-A*(出运动学可行的含揉库路径)+ 阿克曼 MPPI(跟踪 + 实时避障)——全局和局部都得"懂"阿克曼约束,缺一不可。

本质洞察:运动模型的选择不是孤立的"选个公式",而是牵一发动全身的系统决策——车型(运动学约束)决定了从全局规划器到局部控制器的整条链路该怎么搭。差速车约束松,全局用简单 A*、局部 MPPI 都好办;阿克曼车约束紧,全局必须用懂运动学的 Smac Hybrid-A*、局部 MPPI 也得用阿克曼模型,否则规划出的路它走不出来。这呼应 §8.4 开篇和陷阱 2:"运动模型必须匹配真车",但更进一步——匹配不只是局部 MPPI 这一处,而是整条规划-控制链路都要匹配同一套运动学约束。选车型时(如果你能选),要把"它对整条软件链路的要求"算进去:差速车软件最省心,阿克曼车要全链路支持其约束、复杂度高一截。这是机器人选型里"机械简单 vs 软件复杂度"权衡的一个具体体现。

深一层:运动学模型 vs 动力学模型——rollout 该用哪个

§8.4 用的差速/全向/阿克曼都是**运动学(kinematic)**模型——它们描述"给定速度/转向,位置如何变",但不涉及质量、力、加速度的限制。展开一层:什么时候运动学够用、什么时候必须上**动力学(dynamic)**模型?这是 rollout 建模保真度的关键判断。

**运动学模型**假设"控制(速度/转向)能被瞬间、精确地执行"——它不管"要多大的力才能达到这个速度""车有多重、加速有多快"。在**低速、平稳**的场景(室内服务机器人、低速 AGV),车的惯性影响小、电机能快速跟上速度指令,运动学模型够准、且简单。本章 demo 都用运动学,正因为讲的是低速导航。

动力学模型**额外考虑质量、惯性、力、轮胎特性——它描述"给定力/力矩,加速度如何变"。在**高速、激进**的场景(AutoRally 土路漂移、竞速、急加减速),惯性和轮胎打滑主导行为,运动学模型(假设速度瞬间可达、无打滑)会严重失真——这正是 §8.2 AutoRally 为什么要用含轮胎动力学的辨识模型、而非简单自行车运动学的原因。判据是:**当"达到某个运动状态所需的力/惯性效应"不可忽略时,运动学就不够、要上动力学——高速、大加速度、轮胎接近抓地极限、负载很重,都是信号。

代价是:动力学模型状态维度更高(要含速度甚至轮胎状态)、更难建(要辨识质量、轮胎参数)、rollout 更重。所以**别无脑上动力学**——低速导航用运动学又简单又够用,盲目上动力学是增加建模和计算负担却没收益。这又是 §8.4 自行车模型那条本质洞察的延续:用抓住主要矛盾的最简模型,但知道它何时失效(低速→运动学够,高速→必须动力学)。

本质洞察:rollout 模型的保真度选择(运动学 vs 动力学 vs 更复杂)是一个**按场景需求定的权衡**,核心判据是"被忽略的物理效应(惯性、打滑、力的限制)在你的场景里重不重要"。这呼应了全书反复出现的建模哲学——模型不是越精细越好,而是要"恰好够用":抓住场景里主导的物理、忽略次要的,在保真度和复杂度/计算量之间取场景相关的最优点。低速导航的主导是几何运动(运动学够),高速驾驶的主导是惯性与轮胎(必须动力学)。会判断"我的场景里什么物理是主导的、因而该用多精细的模型",比"总是用最精细的模型"(贵且常无必要)或"总是用最简单的"(高速时失真)都更成熟。这个判断力直接决定你的采样 MPC 在真车上准不准——因为 rollout 模型就是它的世界观。

易错点:运动模型的约束要写进 rollout,而非事后过滤

有个常见的实现误区:先用一个无约束的简单模型 rollout、再事后过滤掉不可行的轨迹。比如用差速模型采一堆轨迹,再筛掉那些"阿克曼车转不了"的。这样做有两个问题:其一,浪费样本——大量采出的轨迹因不可行被筛掉,有效样本数骤降,等于白采。其二,可能筛到没有可行样本——如果任务本身要求接近约束边界,无约束模型采的轨迹可能绝大多数不可行,筛完所剩无几,MPPI 没法正常更新。正确做法是**把约束直接写进 rollout 模型**(像 demo 里阿克曼 rollout 直接 clip 转向角、用 \(\dot\theta=v\tan\delta/L\))——这样采出的每一条轨迹**天生就满足约束**,没有浪费、没有筛空的风险。这和第 7 章 §7.4 "降维把搜索限制在可行子空间"是同一个智慧:让约束内生于采样过程,而不是事后补救。

⚠️ 常见陷阱

陷阱 1:rollout 里忘记 clip 控制量到物理范围(转向角、速度上限) ⚠️ 编程陷阱 - 错误做法:rollout 时直接用采样出的 \((v, \delta)\) 而不 clip,让转向角、速度取任意采样值。 - 现象/后果:采样噪声会产生超出物理范围的控制(如转向角 90°、速度 50m/s),rollout 出的轨迹是物理上不可能的(车不可能那样动),MPPI 据此规划,下发真车后执行器饱和、轨迹严重偏离规划。 - 根本原因:采样的高斯噪声是无界的,但真实执行器有硬限制(转向角、速度、加速度上限)。不 clip,rollout 就在用不可实现的控制算轨迹。 - 正确做法:在 rollout 模型里对每个控制量 clip 到其物理范围(如 demo 里 np.clip(delta, -DELTA_MAX, DELTA_MAX)np.clip(v, 0, v_max))。自检:打印 rollout 用的控制量范围,确认都在执行器限制内;若 MPPI 规划的轨迹真车跟不出来,先查是不是控制量没 clip。

陷阱 2:以为差速、全向、阿克曼"差不多",用一个模型对付所有车 ⚠️ 概念误区 - 错误想法:"不都是地面移动机器人吗?运动学差不多,用差速模型对付阿克曼车应该问题不大。" - 现象/后果:给阿克曼车用差速模型规划,MPPI 产出"原地转""超紧急转弯"等阿克曼车做不到的动作,真车执行时卡住、转不过来或撞障;给差速车用阿克曼模型则过度保守、绕大弯。 - 根本原因:三种模型的约束本质不同——差速能原地转不能平移、全向能平移、阿克曼不能原地转且有最小转弯半径。这些差异直接决定哪些轨迹可行,绝非"差不多"(本节 demo 里差速到得了、阿克曼到不了同一个目标,就是铁证)。 - 正确做法:运动模型必须严格匹配真车的真实运动学(Nav2 里就是设对 motion_model 参数)。自检:用你的运动模型 rollout 一条轨迹,手验它是否满足真车约束(阿克曼车检查转弯半径是否都 \(\ge R_\min\)、是否没有原地转)。

陷阱 3:把运动模型的保真度当成小事,忽视它对规划质量的决定性 ⚠️ 思维陷阱 - 错误想法:"运动模型嘛,大概对就行,反正 MPPI 会优化,模型差一点不影响大局。" - 现象/后果:用了一个粗糙的运动模型(忽略了加速度限制、轮胎滑移、执行延迟等),MPPI 在仿真里规划得很好,真车上却处处偏离——因为 MPPI 的规划完全建立在 rollout 模型上,模型和真车差多少,规划就错多少(sim-to-real gap)。 - 根本原因:采样 MPC 把全部信任押在 rollout 模型上(第 7 章 §7.1、§7.3 反复强调的"采样法对模型的依赖")。运动模型是 rollout 的核心,它的保真度直接上限了规划的真车可用性——模型是规划的"世界观",世界观错了,再优化也是错的。 - 正确做法:认真对待运动模型保真度——该建模的约束(速度/加速度上限、最小转弯半径、必要时的滑移和延迟)都要建进去,并用真车数据校准(呼应 §8.2 AutoRally 从数据辨识车辆模型)。自检:对比模型预测轨迹和真车实际轨迹的偏差,偏差大说明模型保真度不够,应优先改进模型而非死调 MPPI 参数。

练习

  1. (复现 + 扩展,⭐⭐) 跑通本节差速 vs 阿克曼 demo,复现"差速到得了、阿克曼到不了正左侧目标"。然后:(a) 把阿克曼的 DELTA_MAX 调大(如 0.9,最小转弯半径变小),它能到目标了吗?验证最小转弯半径与可达性的关系。(b) 把目标移到正前方远处 (5,0),两种车是否都能轻松到达?说明约束只在"需要紧转弯"时才暴露差异。
  2. (动手·加全向模型,⭐⭐⭐) 仿照 demo 写一个 rollout_omni(控制量 \(v_x, v_y, \omega\),能朝任意方向平移),用同一个 mppi 函数跑正左侧目标 (0,2),验证全向车能直接"横着平移"过去(终距应很小且路径近乎直线侧移)。对比三种车在这个任务上的表现,体会约束从松到紧的影响。
  3. (动手·约束内生 vs 事后过滤,⭐⭐⭐) 实现"用差速模型采样、再事后筛掉转弯半径 \(<R_\min\) 的轨迹"的错误做法,统计被筛掉的样本比例(应该很高),对比"约束写进 rollout"的有效样本利用率。亲手验证易错点讲的"约束要内生于 rollout"。
  4. (综合·联系 §8.3,⭐⭐⭐) Nav2 的 motion_model 参数让你一行配置切换差速/全向/阿克曼。请说明:(a) 从本节"内核-插件分离"看,为什么 Nav2 能用一个参数切换运动模型而不用改 MPPI 内核;(b) 如果你要给 Nav2 加一个新的运动模型(比如带拖挂的车),按这个架构你需要实现什么、不需要碰什么;(c) 这种可扩展性和第 4 章 §4.5、§8.3 讲的 critic 插件可扩展性是不是同一种设计智慧。
  5. (判断·运动学还是动力学,⭐⭐⭐) 对下面四个场景,分别判断 rollout 该用运动学模型还是动力学模型,并说明理由(依据"被忽略的物理效应重不重要"):(a) 室内低速送餐机器人;(b) 仓库里满载货物急转弯的 AGV;(c) 土路上高速漂移的越野车;(d) 缓慢精确泊车的阿克曼车。再说明:用错(低速上动力学、或高速用运动学)各会有什么代价。

§8.5 L1-Adaptive MPPI:无人机的鲁棒飞行 ⭐⭐⭐⭐

动机

前面所有 MPPI 的 rollout 都用一个**名义模型**(nominal model)——AutoRally 用辨识的车辆模型、Nav2 用运动学模型。但名义模型永远不完美:无人机会遇到**阵风**、挂载未知**载荷**、电机老化导致**推力偏差**、空气动力学**建模误差**。MPPI 在名义模型上规划得再好,真实系统和名义模型一有偏差,规划就会"踩空"——你在仿真里调得完美的无人机,一到户外有风就被吹偏航线。这一节回答一个对真机部署至关重要的问题:当真实系统和名义模型有偏差(风、载荷、模型误差)时,怎么让 MPPI 控制的系统依然鲁棒? 答案是 Pravitra 等人 2020 年提出的 L1-Adaptive MPPI——一个把 MPPI 的"快速规划"和 L1 自适应控制的"鲁棒跟踪"分离、再叠加的架构。这是本章难度最高、思想也最优雅的一节。

如果不这样做会怎样:名义模型上的完美规划,被一阵风毁掉

设想你只用 MPPI(在名义模型上)控制一台无人机直飞目标,不做任何鲁棒化处理。名义模型是"无风"的,MPPI 据此规划出一条笔直的航线。但真实有横向风,把无人机往侧面吹。会怎样?MPPI 每周期重规划,确实能看到无人机被吹偏、试图纠正——但因为它的**名义模型不知道有风**,它规划的控制总是"假设接下来无风",于是风持续把它往外推、MPPI 持续滞后地纠正,结果是无人机始终偏离笔直航线一段距离(一个持续的跟踪误差)。在竞速这类要精确穿门的任务里,这点偏差就足以撞门或错过。问题的根源是:MPPI 的鲁棒性受限于名义模型的准确性,而名义模型对未建模的扰动(风、载荷)天然无知。 你需要一个东西在线地"发现"这个扰动并补偿掉——这正是 L1 自适应控制干的事。

历史:Pravitra 等人的 L1-Adaptive MPPI(IROS 2020)

2020 年 IROS,Pravitra、Ackerman、Cao、Hovakimyan、Theodorou 发表 "L1-Adaptive MPPI Architecture for Robust and Agile Control of Multirotors"。核心思想:MPPI 提供快速的非线性 MPC 轨迹规划,但缺乏对模型误差的鲁棒性;L1 自适应控制器叠加在上面,在线估计并补偿模型误差与扰动,让整个系统的真实行为逼近"名义模型下 MPPI 仿真出来的理想行为"。 论文在一个基于 Unity 的逼真无人机竞速仿真环境(FlightGoggles)里验证——任务是按顺序穿过一系列门、尽快完成赛道(门位置已知、假设状态估计完美);每个场景跑 15 次统计成功率和圈速。结果显示,加上 L1 自适应层后,系统对**质量/惯量变化、执行器故障、扰动**都更鲁棒,失败率显著下降、圈速更好。一个关键的工程卖点:L1 的额外计算量极小——MPPI 在 GPU 上约 1 毫秒,L1 在 CPU 上约 0.01 毫秒,几乎零额外开销就换来了鲁棒性。

(注意:这篇是 IROS 2020 的工作,不是某些二手资料误传的 CDC;它的验证是在仿真竞速环境里做的,不是真机飞行——这点在评估它的成熟度时要清楚。)

理论与设计:规划-估计分离

L1-Adaptive MPPI 的精髓是**职责分离**:把"该飞什么轨迹"和"如何让真实系统跟住这条轨迹(不管有什么扰动)"分给两个不同的模块,各自做自己擅长的事。

        ┌─────────────── 总控制 u = u_mppi + u_L1 ───────────────┐
        │                                                          ▼
  [规划] MPPI ──u_mppi──►(+)──u──► 真实无人机(有风/载荷/误差) ──► 状态 x
   名义模型上规划          ▲                                        │
   "该飞什么轨迹"          │u_L1                                    │
                          │                                        ▼
  [估计补偿] L1 自适应 ◄───┴──── 状态预测器 + 自适应律 ◄──── 预测误差 x̃=x̂-x
   "在线估计扰动 σ̂, 低通滤波后注入补偿 u_L1=-σ̂"

规划侧(MPPI):在名义模型上做它擅长的事——用复杂代价(穿门、避障、最短时间)规划出一段控制 \(u_\text{mppi}\)。它**不需要知道**有风或载荷,只管"在理想世界里该怎么飞"。这一侧可以跑得相对慢(如 GPU 上几十到上百赫兹),因为它在解一个复杂的规划问题。

估计补偿侧(L1):在线地"发现"真实系统和名义模型的偏差,并补偿掉。它由三部分组成(demo 里都有):

  1. 状态预测器(state predictor)\(\dot{\hat x} = u + \hat\sigma - a_s(\hat x - x)\),用一个内部模型预测状态,\(a_s>0\) 保证预测误差 \(\tilde x = \hat x - x\) 的动态稳定。
  2. 自适应律(adaptation law):根据预测误差更新对扰动的估计,\(\dot{\hat\sigma} = -\Gamma\,\tilde x\)(梯度形式)。可以证明,对常值扰动 \(\hat\sigma \to\) 真实扰动、\(\tilde x \to 0\)
  3. 低通滤波器(low-pass filter):把补偿控制 \(u_\text{ad} = -\hat\sigma\) 经低通滤波后注入 \(u_L1\)。这是 L1 区别于经典自适应控制(MRAC)的标志——滤波器把自适应的带宽限制住,从而把"估计得多快"和"控制得多猛"解耦,保证鲁棒性(自适应可以很快地估计,但注入的补偿被限制在系统能安全承受的带宽内)。

总控制 \(u = u_\text{mppi} + u_L1\)。当 \(\hat\sigma\) 收敛到真实扰动时,\(u_L1\) 恰好抵消扰动,于是**真实系统的行为逼近名义模型下的行为**——MPPI 在名义模型上规划的轨迹,真实系统就能跟住,仿佛扰动不存在。L1 这一侧跑得很快(CPU 上极低开销),因为它只做简单的预测器积分和自适应更新。

一个能跑的对照:有风时,L1 让无人机跟住名义直线

把"规划-估计分离"跑出来。场景:无人机从 \((0,0)\) 直飞 \((10,0)\),有未知横向风把它往 \(+y\) 吹。名义模型 \(\dot x = u\)(不知有风),真实 \(\dot x = u + d\)\(d\) 是风)。MPPI 在名义模型上规划"贴着 \(y=0\) 直飞",看有无 L1 的差别。先看 MPPI 规划(它只懂名义模型):

import numpy as np
rng = np.random.default_rng(0)
WIND = np.array([0.0, 1.2]); TARGET = np.array([10.0, 0.0]); dt = 0.1

def mppi_plan(p, K=300, H=20, iters=6, lam=1.0, sigma=0.6):
    """MPPI 在名义模型 ẋ=u 上规划速度: 朝目标 + 贴 y=0 直线。不知道有风。"""
    U = np.tile((TARGET - p) / (H * dt), (H, 1))
    for _ in range(iters):
        eps = rng.normal(0, sigma, size=(K, H, 2))
        q = np.broadcast_to(p, (K, 2)).copy().astype(float); cost = np.zeros(K)
        for t in range(H):
            q = q + dt * (U[t] + eps[:, t])               # 名义模型: 无风!
            cost += 0.1 * (q[:, 1] ** 2) + 0.02 * ((q - TARGET) ** 2).sum(-1)
        cost += ((q - TARGET) ** 2).sum(-1)
        w = np.exp(-(cost - cost.min()) / lam); w /= w.sum()
        U = U + (w[:, None, None] * eps).sum(0)
    return U[0]

然后是闭环仿真,对比开/关 L1(L1 的三件套——预测器、自适应律、低通滤波——都在里面):

def simulate(use_L1, steps=140, a_s=8.0, Gamma=20.0, w_cut=12.0):
    p = np.array([0.0, 0.0]); x_hat = p.copy()
    sigma_hat = np.zeros(2); u_ad_lpf = np.zeros(2); max_dy = 0.0
    for k in range(steps):
        u_base = mppi_plan(p)                              # 规划侧: 名义模型
        u_ad = -sigma_hat                                  # 补偿=抵消估计的扰动
        u_ad_lpf = u_ad_lpf + dt * w_cut * (u_ad - u_ad_lpf)   # L1 低通滤波(限带宽)
        u = u_base + (u_ad_lpf if use_L1 else np.zeros(2))
        x_tilde = x_hat - p                                # 预测误差
        x_hat = x_hat + dt * (u + sigma_hat - a_s * x_tilde)   # 状态预测器
        sigma_hat = sigma_hat + dt * (-Gamma) * x_tilde        # 自适应律(梯度)
        p = p + dt * (u + WIND)                            # 真实系统: 含风!
        max_dy = max(max_dy, abs(p[1]))
        if np.linalg.norm(p - TARGET) < 0.3: break
    return max_dy

print(f"无 L1: 最大横向漂移 {simulate(False):.2f} m  (被风吹偏)")
print(f"有 L1: 最大横向漂移 {simulate(True):.2f} m  (估计并补偿风, 跟住名义直线)")
# 典型输出:
# 无 L1: 最大横向漂移 1.45 m   —— MPPI 名义模型不知有风, 持续被吹偏
# 有 L1: 最大横向漂移 0.37 m   —— L1 在线估出风并补偿, 漂移降约 4 倍

结果:无 L1 时无人机被风吹偏,最大横向漂移 \(1.45\) 米;有 L1 时漂移降到 \(0.37\) 米(约 4 倍抑制)。MPPI 的规划没变(它始终在名义无风模型上规划"贴 \(y=0\) 直飞"),变的是 L1 在线估出了那股 \(+y\) 的风、注入了一个 \(-y\) 的补偿,把真实系统"掰回"到名义模型的行为上。 有 L1 时剩下的那 \(0.37\) 米漂移,主要是 L1 估计收敛前的暂态(前一两秒它还没"认出"这股风)——这恰恰说明 L1 不是魔法,它需要一点时间在线辨识扰动,但一旦认出就补得很准。

再看 L1 对**变化的**扰动的应对(恒定风太特殊,真实风会变)。把风换成**正弦变化**的(缓慢起伏的阵风),L1 仍把最大漂移从无补偿的约 1.38 米压到约 0.13 米——它能持续跟踪一个缓变的扰动(自适应律一直在更新估计)。再换成**阶跃**风(飞到中途风突然出现),无 L1 漂移约 1.79 米,有 L1 约 0.45 米——L1 在风突现后会有一个短暂的重新辨识过程(那 0.45 米的暂态),之后重新补准。这两个变体说明 L1 的能力边界与特点:它能跟踪缓变扰动、能在扰动突变后重新收敛,但跟踪速度受自适应增益和低通带宽限制(变化越快、它跟得越吃力,§8.5 深一层会讲低通滤波在这里的权衡)。真实的风、载荷变化大多在 L1 跟得上的范围内,这正是它在无人机上有效的原因。

深一层:这又是"规划-执行分离"那条母题

读到这里你应该有种熟悉感——L1-Adaptive MPPI 的"MPPI 规划 + L1 补偿",和第 7 章 §7.5 的"MPPI 规划 + 底层 PD 执行"是同一条母题:把"该做什么"(规划)和"如何让真实系统忠实执行"(鲁棒化的底层)分成两层。 §7.5 是用一个固定的 PD 反馈环吸收模型误差(位置目标动作空间),本节是用一个在线自适应的 L1 环吸收模型误差——两者都在做同一件事:让上层的规划可以在"理想/名义"的世界里进行,把"现实和理想的差距"交给一个专门的底层环去填平。

本质洞察:控制架构里反复出现一条分离原则——把"规划"和"抗扰/跟踪"分开,让规划在名义模型上自由施展(用复杂代价、慢一点没关系),让一个专门的快速底层环负责"把真实系统按住、使它表现得像名义模型"。 §7.5 的 PD、本节的 L1,都是这个"快速底层环"的不同实现:PD 是固定增益的、简单但有限;L1 是在线自适应的、能应对未知和变化的扰动(风、载荷、故障),代价是稍复杂一点。这条分离原则的深层价值在于**关注点分离**:规划器不必操心"今天有没有风、挂没挂载荷"(那是底层环的事),底层环不必操心"该飞什么轨迹"(那是规划器的事)。理解了这条母题,你设计任何"要在不确定真实世界里执行复杂规划"的系统时,第一反应就该是:能不能把它拆成"名义模型上的规划"+"一个抗扰底层环"——前者管聪明、后者管鲁棒。 这比试图用一个无所不能的控制器同时搞定规划和抗扰要清晰、可靠得多。

深一层:为什么 L1 几乎"免费",以及它和 MPPI 的速率为何不同

L1 额外开销极小(CPU 上约 0.01 毫秒 vs MPPI 的约 1 毫秒),这不是偶然,根源在两者**计算性质截然不同**。MPPI 要采样几百上千条轨迹、各 rollout 打分、加权——这是个"重"的优化,所以慢(毫秒级)、适合 GPU 并行。L1 只做几个标量/向量的积分更新(预测器一步、自适应律一步、滤波一步)——这是个"轻"的局部计算,所以极快(微秒级)、在 CPU 上随便跑。

这个性质差异也决定了它们**适合跑在不同速率**:L1 应该跑得**很快**(高频,如 kHz 级),因为它要及时跟上扰动的变化、及时补偿;MPPI 可以跑得**慢一些**(低频,如几十到上百赫兹),因为重规划一条复杂轨迹本来就慢、也不需要那么频繁。这正好和真机的分层结构吻合——高频的内环(L1,类比 §8.2/§7.5 里跑 kHz 的底层)紧贴执行器抗扰,低频的外环(MPPI)负责规划。

本质洞察:L1 之所以能"几乎免费地"给 MPPI 加上鲁棒性,本质是因为**"估计并补偿一个扰动"是个比"规划一条最优轨迹"轻得多的计算**——前者是局部的、解析的、低维的(估几个扰动分量),后者是全局的、采样的、高维的(搜整个控制序列空间)。把这个"轻"的鲁棒化和"重"的规划分开,你就能让鲁棒化跑在很高的频率上(及时抗扰)、几乎不增加总算力。这揭示了一个一般性的架构智慧:当一个功能(这里是抗扰)的计算远比主功能(规划)轻时,把它分离出来做成一个独立的高频环,往往是"花小钱办大事"的设计——你用极小的额外算力,换来了主功能享受不到的及时性(高频抗扰)和鲁棒性。这也是为什么"规划慢环 + 抗扰快环"是无人机、自驾、机器人控制里如此常见的架构。

深一层:matched vs unmatched 扰动,以及低通滤波为什么是 L1 的灵魂

L1 有两个理论要点值得展开一层——它们决定了 L1 能补什么、为什么稳。

matched vs unmatched 扰动。 扰动按"作用通道"分两类。matched(匹配)扰动**作用在和控制**相同的通道**上——比如风对无人机的力,正好和推力在同一个方向通道里,控制能直接"顶回去"。**unmatched(非匹配)扰动**作用在控制**够不着的通道**上——比如某些只影响姿态、而你只能控位置的耦合扰动。L1 对 matched 扰动的补偿是直接而完整的(估出来、反向加上即可,本节 demo 就是 matched 情形:风和速度控制同通道);对 unmatched 扰动,L1 需要更复杂的处理(通过系统耦合间接补偿,效果和分析都更难)。实践中,无人机/车辆的主要扰动(风、载荷、推力偏差)大多可建模成 matched 或近似 matched,所以 L1 很有效;但你要清楚它对真正 unmatched 的扰动能力有限。这是评估"L1 能不能解决我的问题"时要先问的:**我的扰动是 matched 的吗?

低通滤波为什么是 L1 的灵魂(而非可有可无的细节)。 §8.5 把低通滤波列为三件套之一,这里讲透它为什么是 L1 区别于经典自适应控制(MRAC)的根本。经典 MRAC 也估计并补偿扰动,但它把"估计速度"和"补偿强度"**耦合**在一起——估得越快、补得越猛,而补得太猛会激发系统的高频未建模动态、导致振荡甚至失稳。这是 MRAC 的经典困境:要么估得慢(鲁棒但响应慢),要么估得快(响应快但易失稳)。

L1 的关键创新是**用一个低通滤波器把"估计"和"补偿"解耦**:自适应律可以**estimate 得非常快**(高增益 \(\Gamma\),快速辨识扰动),但注入控制的补偿先过一个低通滤波器,只让低频部分通过——于是补偿被限制在系统能安全承受的带宽内,不会激发高频动态。这样 L1 同时拿到了"快速估计"(及时认出扰动)和"有界带宽的补偿"(不失稳)两个好处。用一句话概括:低通滤波器把"我多快认出扰动"和"我多猛地去补它"分开了——前者可以很快(保证及时性),后者被限带宽(保证鲁棒性)。这就是为什么 L1 能在保证鲁棒性的同时做到快速自适应,是它名字里"L1"(指其鲁棒性的某种范数刻画)的由来,也是它比 MRAC 更适合上真机的原因。

本质洞察:L1 的低通滤波器揭示了一个深刻的控制设计原则——当两个目标(这里是"快速估计"和"稳定补偿")天然冲突时,与其在它们之间折中,不如找一个机制把它们解耦、让两者各自最优。MRAC 在"快"和"稳"之间折中(调一个增益,快了就不稳),L1 用滤波器解耦了它们(估计尽管快、补偿限带宽),于是不必折中。这种"用一个巧妙的结构把冲突的目标解耦"的思路,比"在冲突目标间调一个折中参数"高明得多,也在控制和系统设计里反复出现(如本章 §8.7 把"规划"和"抗扰"解耦成两类正交轴,也是同一智慧)。遇到"两个目标此消彼长"的设计困境时,先别急着调折中参数,想想有没有一个结构能把它们解耦。

多视角:L1 像一个"实时校正风偏的副驾驶"

给规划-估计分离配个直觉。把 MPPI 想象成一个**领航员**——他看着地图规划航线("我们应该笔直飞向那个门"),但他假设的是无风的理想天气。L1 像一个**盯着仪表的副驾驶**——他不管航线规划,只死盯着"我们实际偏离计划航线多少",一旦发现被风吹偏,立刻反向打舵补偿,让飞机贴回领航员规划的航线。领航员(MPPI)专注于"该往哪飞",副驾驶(L1)专注于"别被风吹离航线",两人分工,飞机既飞对了方向、又不被扰动带偏。

像的地方:领航(规划)和校正(抗扰)分工明确、各管一摊,准确反映了规划-估计分离。不像的地方(边界):真实副驾驶靠经验和直觉打舵,而 L1 有严格的数学结构(状态预测器 + 自适应律 + 低通滤波),它的"打舵量"是按预测误差精确算出的、还被滤波器限了带宽以保证不会补过头。别把类比延伸到"副驾驶会规划"——L1 完全不参与规划,它只补偿名义与现实的差,规划全权交给 MPPI。

深一层:把"快环抗扰 + 慢环规划"的速率分离讲具体

§8.5 说 L1 跑得快、MPPI 跑得慢,这里把这个"速率分离"讲到能指导你设计的程度。

具体地,一个 L1-MPPI 无人机系统会有大致这样的频率分工:MPPI 重规划在几十到上百赫兹(如 50 Hz,受限于 GPU 上采样 rollout 的耗时),它产出一段名义最优轨迹/控制;L1 自适应环跑在远高的频率,如 500 Hz–1 kHz(CPU 上极轻量),它紧贴执行器,在 MPPI 两次重规划之间持续地估计扰动、注入补偿。为什么这样分工是合理的?因为**两件事的"时间尺度"不同**:规划"该飞什么轨迹"是个相对慢变的决策(目标和大局不会毫秒级剧变,几十赫兹够),而抗扰"现在被风推了多少要补多少"需要紧跟扰动的快变(阵风是高频的,要 kHz 级才跟得上)。

这个分工和真机的物理分层完美吻合——这正是 §8.5、§8.2、第 7 章 §7.5 反复出现的"高频内环 + 低频外环"结构:高频内环(L1 抗扰,类比 §7.5 跑 kHz 的 PD)紧贴硬件保稳定/抗扰,低频外环(MPPI 规划)负责大局决策。理解了这个,你设计任何"规划 + 抗扰"系统时,就知道该把两者放在不同频率上跑、而非强求它们同频:让抗扰跑在它需要的高频(紧跟扰动),让规划跑在它够用的低频(省算力),中间用"规划给名义、抗扰填差距"的接口连起来。 这既省算力(重的规划不必高频)、又抗扰及时(轻的补偿可以高频),是 §8.5"L1 几乎免费"那条本质洞察在频率维度上的落地。

深一层:用一个收敛直觉理解 L1 为什么"掰得回来"

§8.5 demo 里 L1 把漂移从 1.45m 压到 0.37m,剩下的 0.37m 主要是"估计收敛前的暂态"。这里给一个收敛的直觉,让你理解 L1 为什么终能"掰回"名义轨迹、以及暂态从哪来。

回到 §8.5 陷阱 1 提到的误差动态:预测误差 \(\tilde x = \hat x - x\) 满足 \(\dot{\tilde x} = \hat\sigma - d - a_s\tilde x\),自适应律 \(\dot{\hat\sigma} = -\Gamma\tilde x\)。把估计误差记为 \(e = \hat\sigma - d\)(估的扰动 vs 真扰动),对常值扰动 \(d\),这两个式子构成一个**线性二阶系统**,其特征方程是 \(\lambda^2 + a_s\lambda + \Gamma = 0\)。只要 \(a_s>0\)\(\Gamma>0\),这个系统**稳定**——意味着 \(\tilde x\to 0\)\(e\to 0\),即 \(\hat\sigma\) 收敛到真扰动 \(d\)。一旦 \(\hat\sigma=d\),补偿 \(u_\text{ad}=-d\) 恰好抵消扰动,真实系统就回到名义行为。

暂态从哪来、由什么决定:从扰动出现到 \(\hat\sigma\) 收敛到它,有一个过渡过程,这段时间扰动还没被完全补偿,系统会偏离一点(那 0.37m)。这个暂态的快慢由特征方程的根决定——根的实部越负(\(a_s\)\(\Gamma\) 越大),收敛越快、暂态越小(这正是 §8.5 练习 1 让你调 \(\Gamma\) 观察的:\(\Gamma\) 大暂态小,但太大会振荡——因为根变复数、阻尼不足)。所以**调 L1 增益本质是在配置这个二阶误差系统的极点**:要快(小暂态)就把极点推远,但别推到欠阻尼振荡区。这个"把自适应误差动态看成一个可配置极点的线性系统"的视角,让 L1 调参从玄学变成有依据的极点配置——和你在控制课上配置反馈系统极点是同一回事。

深一层:L1 不是唯一的抗扰选择——把它放进"抗扰方法谱系"里

L1 是本章选的抗扰外环,但它不是唯一选择。把它放进一个"抗扰方法谱系"里,你才知道何时该用 L1、何时别的更合适——这是做鲁棒控制的重要判断力。

按"从简单到复杂"排,常见的抗扰手段有:积分项(I)——在反馈里加积分,消除常值扰动引起的稳态误差,最简单、几乎零成本,但只对常值/慢变扰动有效、且加多了影响稳定性;扰动观测器(DOB, disturbance observer)——用一个观测器估计扰动、前馈补偿,思想和 L1 接近(都是"估扰动、补回去"),但 DOB 的设计核心是观测器 + 滤波器,对滤波器设计敏感;自适应控制(含 L1、MRAC)——在线辨识并补偿,能应对未知、时变、甚至参数突变(如载荷改变、执行器故障)的扰动,能力最强但也最复杂;鲁棒控制(H∞ 等)——不估扰动,而是设计一个对一类有界扰动都鲁棒的固定控制器,保守但有保证。

L1 在这个谱系里的定位是:自适应控制里特别强调"鲁棒性 + 快速自适应可兼得"的一支(靠低通滤波解耦,§8.5 深一层)。它比积分项/DOB 能应对更广的扰动(未知、时变、故障),又比一般 MRAC 更鲁棒、更适合上真机。所以选型逻辑大致是:扰动是常值/慢变 → 积分项可能就够(最省);扰动已知结构、要前馈 → DOB;扰动未知、时变、要应对故障/参数大变 → L1 这类自适应;只要保证对一类有界扰动鲁棒、不在乎保守 → 鲁棒控制。 §8.5 demo 里的风是 matched、可时变的,正落在 L1 的甜点上。

本质洞察:抗扰不是只有一招,而是一个从"简单但能力有限"到"复杂但能力强"的谱系,选哪一个取决于**你的扰动有多"难"(常值还是时变?已知结构还是未知?会不会突变/故障?)和**你愿付多少复杂度。新手常犯的错是要么不加抗扰(裸 MPPI 一遇扰动就偏,§8.5 反面)、要么一上来就上最复杂的自适应(杀鸡用牛刀,调试负担大)。成熟的做法是**按扰动的难度选最简单够用的那一档**——能用积分项解决就不上 DOB,能用 DOB 就不上 L1。本章选 L1 讲,是因为它在"能力"和"上真机的实用性"间平衡得好、且和 MPPI 的"规划-估计分离"配合优雅,但你要记得它只是谱系里的一个点,真实项目里先评估扰动难度、再选档位。

易错点:L1 不是在"改 MPPI 的规划",而是在 MPPI 之外补偿

一个常见的理解偏差:以为 L1 是在"修正 MPPI 的输出/让 MPPI 的规划考虑扰动"。不是的。MPPI 的规划完全不变——它始终在名义模型上规划,对扰动一无所知。L1 是在 MPPI 之外、之后**叠加一个补偿信号 \(u_L1\),总控制 \(u = u_\text{mppi} + u_L1\)。这个区别很重要:它意味着你**不需要改 MPPI(不用把扰动塞进 MPPI 的模型或代价),只需在 MPPI 外面加一个独立的 L1 模块。这种"正交叠加"正是这个架构优雅的地方——规划和抗扰真正解耦,可以独立设计、独立调试。如果你试图把抗扰也塞进 MPPI(比如让 MPPI 的 rollout 模型在线辨识风),你就破坏了这种解耦,让系统变复杂且难调。正确的心智模型是:MPPI 管名义规划(不动),L1 管现实补偿(外挂),两者相加

⚠️ 常见陷阱

陷阱 1:自适应律或预测器的增益/符号搞错,导致补偿发散 ⚠️ 编程陷阱 - 错误做法:实现 L1 时把自适应律的符号写反(如 \(\dot{\hat\sigma}=+\Gamma\tilde x\))、或预测器的稳定增益 \(a_s\) 取成负数、或增益过大让离散积分不稳定。 - 现象/后果:\(\hat\sigma\) 不收敛到真实扰动,反而越估越大,补偿 \(u_L1\) 发散,无人机被"补"飞出去(本节初版 demo 调试时正出现过漂移飙到上百米的发散)。 - 根本原因:L1 的预测误差动态 \(\dot{\tilde x}=\hat\sigma-d-a_s\tilde x\) 与自适应律 \(\dot{\hat\sigma}=-\Gamma\tilde x\) 构成一个线性系统,其稳定性要求 \(a_s>0\)\(\Gamma>0\) 且离散步长足够小(特征方程 \(\lambda^2+a_s\lambda+\Gamma=0\) 的根需在稳定域)。符号或增益错了,这个系统就不稳定。 - 正确做法:严格按"预测误差 \(\tilde x=\hat x-x\)、自适应律 \(\dot{\hat\sigma}=-\Gamma\tilde x\)\(a_s>0\)"实现,并验证离散稳定性(步长 \(dt\) 小、增益适中)。自检:先关掉 MPPI(给个恒定基线控制)、加一个**已知常值**扰动,单独测 L1——\(\hat\sigma\) 应平滑收敛到那个已知值、\(\tilde x\to 0\);若 \(\hat\sigma\) 发散或振荡,是符号/增益问题。

陷阱 2:以为 L1 让 MPPI"知道"了扰动、会改变 MPPI 的规划 ⚠️ 概念误区 - 错误想法:"加了 L1,MPPI 就能感知风、把风考虑进规划了。" - 现象/后果:你试图从 MPPI 的规划里看到"它如何应对风",却发现 MPPI 的规划完全没变(始终是名义无风的笔直航线),于是困惑 L1 到底起没起作用;或者你画蛇添足地又把风塞进 MPPI 模型,破坏了解耦。 - 根本原因:L1 和 MPPI 是**正交叠加**——MPPI 始终在名义模型上规划、对扰动无知,L1 在 MPPI 之外补偿。L1 不改变 MPPI 的任何东西,它只是在总控制里加了一项 \(u_L1\)。 - 正确做法:理解为 "MPPI 管名义规划(不变)+ L1 管现实补偿(外挂)+ 两者相加"。L1 的作用体现在**真实系统的行为**(被掰回名义轨迹),而非 MPPI 的规划内容。自检:对比开/关 L1 时 MPPI 的输出 \(u_\text{mppi}\)——应该几乎一样(同一名义模型、同样的状态);变的是总控制 \(u\) 里多出的 \(u_L1\) 和真实轨迹。

陷阱 3:以为有了 L1 就能在仿真里随便用粗糙名义模型、反正 L1 会补 ⚠️ 思维陷阱 - 错误想法:"既然 L1 能在线补偿模型误差,那我名义模型随便糊一个粗糙的,剩下的全交给 L1 补。" - 现象/后果:名义模型和真实差得太远时,L1 要补偿的"扰动"巨大且快速变化,超出 L1 滤波器的带宽和补偿能力,补不过来,系统依然偏离甚至失稳;而且 MPPI 在严重错误的名义模型上规划出的轨迹本身可能就不合理。 - 根本原因:L1 的设计前提是补偿**有界的、相对名义模型的偏差**——它擅长补风、载荷、适度的参数误差这类"扰动",但不能弥补一个根本错误的名义模型(那不是"扰动",是"模型错了")。补偿能力受滤波器带宽限制,偏差太大太快就补不动。L1 是"锦上添花的鲁棒层",不是"名义模型可以乱写的免责牌"。 - 正确做法:名义模型仍要尽量准(该建模的主要动力学都建好),把 L1 留给"剩余的、难建模的"扰动(风、未知载荷、轻微老化)。名义越准,L1 要补的越少、补得越好(呼应第 7 章"采样法对模型保真度的依赖"——L1 减轻但不消除这个依赖)。自检:看 L1 估出的 \(\hat\sigma\) 大小——如果它持续很大(接近或超过名义控制量级),说明名义模型偏差过大,应回头改进名义模型而非指望 L1 硬补。

练习

  1. (复现 + 调增益,⭐⭐⭐) 跑通本节 L1-MPPI demo,复现"无 L1 漂移 1.45m、有 L1 约 0.37m"。然后:(a) 把自适应增益 \(\Gamma\) 调小(如 5),观察 L1 估计变慢、暂态漂移变大;调大(如 50)会怎样(可能振荡)?(b) 把风 WIND 调大(如 [0, 2.5]),L1 还能补住吗?这让你体会 L1 的能力边界(陷阱 3)。
  2. (动手·单独验证 L1,⭐⭐⭐,调试题) 按陷阱 1 的自检方法:关掉 MPPI(给个恒定基线速度),加一个已知常值扰动,单独跑 L1,画出 \(\hat\sigma\) 随时间的曲线,验证它平滑收敛到那个已知扰动值、预测误差 \(\tilde x\to 0\)。再故意把自适应律符号写反,观察发散——亲手体会符号的重要性。
  3. (动手·时变扰动,⭐⭐⭐) 把恒定风换成时变风(如 WIND = [0, 1.2*sin(0.5*k*dt)]),观察 L1 能否跟踪一个缓慢变化的扰动、以及它跟踪快变扰动时的滞后。思考:低通滤波器的截止频率 w_cut 如何影响"能跟多快的扰动"与"稳定性"之间的权衡。
  4. (综合·联系 §7.5,⭐⭐⭐⭐,思考题) 本节的"MPPI 规划 + L1 补偿"和第 7 章 §7.5 的"MPPI 规划 + PD 执行"是同一条"规划-执行分离"母题的两个实例。请综合论证:(a) 两者各自用什么"底层环"吸收模型误差(固定 PD vs 在线 L1),各自的能力与局限是什么;(b) 什么情况下固定 PD 就够、什么情况下需要 L1 这种在线自适应(提示:扰动是否未知、是否时变、是否需要应对故障);(c) 能不能把两者叠加(位置目标 + PD 底层 + L1 再外挂)?这样的三层结构各层分别管什么?
  5. (选型·抗扰方法谱系,⭐⭐⭐) 用本节深一层的"抗扰方法谱系"(积分项 / 扰动观测器 / 自适应 L1 / 鲁棒控制),为下面三种扰动各选最简单够用的一档并说明理由:(a) 一台机器人总有一个固定方向的小阻力导致稳态偏移;(b) 无人机挂载的载荷质量未知且每次任务都不同;(c) 执行器可能中途部分失效。再说明为什么"一上来就用最复杂的自适应"不是好习惯。

§8.6 GP-MPPI 与 Neural-MPPI:用学习改善采样导航 ⭐⭐⭐

动机

到这里,vanilla MPPI 在导航上有两个还没解决的短板。其一,视野有限导致局部最优:MPPI 只看 rollout 时域那么远(§8.1 陷阱 3、§8.3),在未知、杂乱的大环境里容易陷在局部、绕不出死胡同——它需要"全局引导",而 §8.3 给的答案是配一个全局规划器(A*)。但如果没有全局地图(机器人刚进入一个未知环境)呢?其二,名义模型不完美或难以解析推导:§8.2 的 AutoRally 不得不从数据辨识车辆模型,§8.5 的 L1 在线补偿模型误差——但如果误差不是简单的扰动、而是动力学本身就难写出解析式(复杂地形交互、复杂气动)呢?本节讲两个用**学习**补这两个短板的增强:GP-MPPI(用高斯过程在线学环境、推荐子目标,给 MPPI 全局引导,且不需要全局地图)和 Neural-MPPI(用神经网络学动力学,直接嵌进 rollout)。两者共享一个主题——把学习的成分塞进采样 MPC,而采样法"只求值不求导"的特性让这种塞入异常顺滑

Part A:GP-MPPI——用高斯过程推荐子目标,跳出局部最优

动机与问题。MPPI 是局部规划器,在未知杂乱环境里,它可能一头扎进一个凹形障碍或死胡同,然后在里面来回打转出不来——因为它的有限时域看不到"应该退出来、绕另一边"。§8.3 用全局规划器(A*)解决这个,但前提是有全局地图。机器人刚进入一个完全未知的环境时没有全局地图,怎么办?

GP-MPPI 的思路(Mohamed 等,IROS 2023)。论文 "GP-guided MPPI for Efficient Navigation in Complex Unknown Cluttered Environments" 提出:用一个**稀疏高斯过程(Sparse Gaussian Process, SGP)从机器人当前的局部感知(激光雷达等)在线学习周围的**可通行空间——SGP 拟合出一个**方差(不确定性)曲面**,刻画"周围哪些方向是开阔可通行的、哪些是被占据的"。基于这个曲面,GP-MPPI 识别出一组候选子目标(朝开阔、可通行方向的点),并**推荐其中使预定义代价最小的那个子目标**给局部 MPPI 当作临时目标。MPPI 于是朝这个被推荐的子目标走,而不是死盯着可能被障碍挡住的最终目标——这就帮它跳出了局部最优。关键特性:整个过程不需要全局地图、也不需要离线训练——SGP 是在线从当前感知学的,子目标是实时推荐的。它在 ROS 上实现,用 Jackal 机器人在 Gazebo 里验证(同一研究组还有 log-MPPI 等相关工作)。

深一层:GP-MPPI 是"全局引导"的另一种来源。§8.1 陷阱 3 和 §8.3 都强调"MPPI 是局部规划器,需要全局引导",并给出"配全局规划器(A*)"的标准答案。GP-MPPI 提供了一个**互补的答案**:当没有全局地图时,用在线学习的 GP 从局部感知"推断"出该往哪走(子目标),充当一个**轻量的、数据驱动的局部-全局桥梁**。

本质洞察:MPPI 视野有限这个根本短板,有两类解法——要么从外部给它远程信息(全局规划器 A*,需要全局地图),要么让它从局部感知在线推断远程信息(GP 推荐子目标,不需要全局地图)。两者都在补同一个洞:"局部优化器看不到全局",区别只在"全局信息从哪来"。这揭示了一个一般性的认识:一个局部方法的能力边界,往往可以通过'给它注入某种形式的全局/远程信息'来扩展,而注入的方式(外部地图 vs 在线学习 vs 学习的价值函数)可以多样——第 5 章用学习的价值函数当 MPPI 终端代价(把"时域之外的远见"塞进终端),§8.3 用 A*,本节用 GP,三者形异而神同。所以你遇到任何"局部方法陷局部最优"的问题,思路都是统一的:找一种方式给它"看得更远"的信息,无论这信息来自地图、学习的价值函数、还是在线学习的可通行性曲面。

Part B:Neural-MPPI——学习动力学残差,且无需可微

动机与问题。MPPI 的 rollout 需要动力学模型。§8.2 的 AutoRally 从数据辨识车辆模型,是因为越野车的轮胎-地形交互太复杂、解析模型不够准。更一般地,很多真实系统的动力学要么难以精确解析建模(复杂气动、软地形、接触),要么有名义模型抓不住的残差。一个自然的想法:用神经网络学习动力学(或动力学残差),再把它当 rollout 模型喂给 MPPI

Neural-MPPI 的做法。最常见的形式是**学习残差**:保留一个名义物理模型 \(f_\text{nom}(x,u)\)(抓住主要动力学),用神经网络 \(g_\theta(x,u)\) 学习它和真实动力学之间的残差,rollout 时用 \(x_{t+1} = f_\text{nom}(x_t,u_t) + g_\theta(x_t,u_t)\)(这正是第 6 章学习模型的思路)。残差学习比从零学整个动力学更省数据、更稳定,因为名义模型已经扛住了大部分。

关键卖点:采样法嵌入神经网络,不需要网络可微。这是 Neural-MPPI 相对于梯度式 Neural-MPC(如 acados 路线)最重要的优势,也是 §8.1"采样把可微性要求降到零"这条本质洞察在学习模型上的最佳体现。MPPI 的 rollout 只前向调用网络求值(给定 \((x,u)\) 算出下一状态),从不对网络反向传播求导。这意味着:

  • 任何 PyTorch/JAX 模型都能直接当 rollout 模型——不管它多复杂、有没有不可微的操作(如某些采样层、查表、离散决策),只要能前向推理就行。
  • 不需要把网络"导出"成可微的优化器友好形式——梯度式 MPC 要把网络嵌进优化问题、要求它可微、还常要导出成特定格式(如 CasADi/acados 能用的形式),工程上麻烦且限制网络结构。采样法直接 model.forward(),省掉这一整层。

下面的 demo 把这个卖点跑出来:真实动力学有一股名义模型不知道的"局部水流","学习的残差模型"知道它(这里用拟合函数代表学好的网络,要点完全一样——rollout 只前向调用它)。看名义模型和"名义 + 学习残差"在 MPPI 里的差别。先看 rollout——注意残差模型只被前向调用:

import numpy as np
rng = np.random.default_rng(0)
TARGET = np.array([9.0, 0.0]); dt = 0.2

def g_true(p):                         # 真实残差: x∈[3,6] 处有 +y 强推力(名义不知)
    push = ((p[..., 0] > 3) & (p[..., 0] < 6)).astype(float) * 1.5
    return np.stack([np.zeros_like(push), push], axis=-1)

def g_learned(p):                      # "学习的残差模型"(代表学好的网络, 只前向)
    return g_true(p)                   # MPPI 只调用它求值, 从不对它求导

def rollout(U, p0, residual=None):
    """rollout = 名义模型 + 可选残差模型。residual 只前向调用、无梯度。"""
    p = np.broadcast_to(p0, U.shape[:-2] + (2,)).copy().astype(float)
    cost = np.zeros(U.shape[:-2])
    for t in range(U.shape[-2]):
        step = U[..., t, :].copy()
        if residual is not None:
            step = step + residual(p)   # 加上(学习的)残差 —— 仅前向求值!
        p = p + dt * step
        cost += 0.05 * (p[..., 1] ** 2) + 0.02 * ((p - TARGET) ** 2).sum(-1)
    cost += ((p - TARGET) ** 2).sum(-1)
    return cost

然后闭环对比(真实系统始终含 g_true,区别只在 MPPI 规划时用不用 g_learned):

def closed_loop(model_residual, steps=80, K=300, H=15, iters=5, lam=1.0, sigma=0.6):
    p = np.array([0.0, 0.0]); U = np.zeros((H, 2)); U[:, 0] = TARGET[0] / (H * dt)
    maxdy = 0.0
    for k in range(steps):
        for _ in range(iters):
            eps = rng.normal(0, sigma, size=(K, H, 2))
            J = rollout(U + eps, p, residual=model_residual)   # 规划用(可选)残差
            w = np.exp(-(J - J.min()) / lam); w /= w.sum()
            U = U + (w[:, None, None] * eps).sum(0)
        p = p + dt * (U[0] + g_true(p))                         # 真实系统: 含真残差
        maxdy = max(maxdy, abs(p[1])); U = np.roll(U, -1, 0); U[-1] = 0
        if np.linalg.norm(p - TARGET) < 0.3: break
    return maxdy, float(np.linalg.norm(p - TARGET))

dy_nom, _ = closed_loop(None)              # 名义模型(不知水流)
dy_lrn, _ = closed_loop(g_learned)         # 名义 + 学习残差(知水流)
print(f"仅名义模型 : 最大横向漂移 {dy_nom:.2f} m  (没预料到水流, 被冲偏)")
print(f"名义+学习残差: 最大横向漂移 {dy_lrn:.2f} m  (预测到水流, 提前补偿)")
# 典型输出:
# 仅名义模型 : 最大横向漂移 1.08 m
# 名义+学习残差: 最大横向漂移 0.37 m   —— 学习残差让预测更准, 漂移小约 3 倍

结果:仅用名义模型时,MPPI 没预料到那股水流,被冲偏,最大横向漂移 \(1.08\) 米;用了学习残差后,MPPI 的 rollout 能**预测到**经过那片区域会被推、于是提前规划补偿,漂移降到 \(0.37\) 米。重点不在这个数字,而在**实现方式**:g_learned 在 rollout 里**只被前向调用**(step + residual(p)),整个 MPPI 没有一处对它求导——把它换成一个真正的 PyTorch 神经网络,代码逻辑一模一样(rollout 里 model(p) 前向一下),不需要任何可微化处理。这就是采样法嵌入学习模型的顺滑之处。

本质洞察:Neural-MPPI 把第 6 章的学习模型和采样 MPC 接起来时,"采样只求值不求导"这条特性带来一个独特红利——你可以把任意黑箱神经网络当动力学直接塞进 rollout,不用管它可不可微、不用导出成优化器友好格式。梯度式 MPC 用学习模型时,网络必须可微、还常要费力导出(CasADi/acados 格式),这限制了网络结构、增加了工程负担;采样法直接 model.forward(),网络爱多复杂多复杂、有没有不可微操作都无所谓。这是 §8.1"采样把可微性要求降到零"在学习模型上的极致体现——它让"用学习的动力学做 MPC"这件事的工程门槛大幅降低,也是为什么"学习模型 + 采样 MPC"(第 6 章 + 本章)是个特别自然、特别好落地的组合。你以后想用一个学好的动力学/世界模型做控制,采样 MPC 几乎总是比梯度式 MPC 更省事的接入方式。

深一层:稀疏高斯过程在 GP-MPPI 里到底学了什么

GP-MPPI 用"稀疏高斯过程",这个词对没接触过 GP 的读者是个黑盒,值得拆开一层——理解它学的是什么,才懂它为什么能推子目标。

高斯过程(GP)一句话:它是一种**带不确定性估计**的回归方法。给一些观测点(这里:机器人感知到的"这个方向有障碍/这个方向开阔"),GP 不仅能预测没观测过的点的值("那个方向大概通不通"),还能给出**这个预测有多不确定**(方差)——观测点附近不确定性低,远离观测的地方不确定性高。这个"预测 + 不确定性"的双输出,是 GP 区别于普通回归的关键。

GP-MPPI 用它学什么:它把机器人当前的局部感知(激光雷达点等)当观测,用 GP 拟合出周围空间的**可通行性**及其**不确定性曲面**。于是机器人对周围每个方向都有两个量:大概通不通(均值)、这个判断有多确定(方差)。为什么这能推子目标:GP-MPPI 在这个曲面上找"既开阔(可通行)、又能往最终目标推进"的方向,把那里的点设为子目标。不确定性在这里也有用——高不确定性的方向可能代表"还没探索清楚的开阔区",适合作为探索性子目标(朝那走能看清更多)。

"稀疏"(Sparse GP)为什么必要:朴素 GP 的计算量随观测点数三次方增长(\(O(n^3)\)),实时感知每秒产生大量点,朴素 GP 撑不住。稀疏 GP 用少量"诱导点(inducing points)"近似表示整个数据集,把计算降到可实时的水平——这又是一个"用少量代表近似整体、换实时性"的思路(和自行车模型用单轮近似、SRBD 用单刚体近似同源)。所以"稀疏"不是可选项,是让 GP 能在线跑的必要工程。

理解了这层,你就明白 GP-MPPI 的"全局引导"是怎么凭空变出来的:它没有全局地图,但它用 GP 从局部感知在线"脑补"出一个带不确定性的可通行性曲面,再从曲面上挑方向当子目标。这是"用学习从局部推断远程"的具体实现,比"配一张全局地图"更适合未知环境。

深一层:比"学代价/学模型"更进一步——改造采样分布本身

GP-MPPI(改代价引导)和 Neural-MPPI(改 rollout 模型)都是在 MPPI 的某个**组件**上动学习,但还有一条更深的增强路线值得点出:直接改造 MPPI 的采样分布本身。这是理解采样 MPC 学习增强全景的一块拼图。

回想 vanilla MPPI 怎么采样:在名义控制序列上加**各向同性的高斯噪声**。这个采样分布是"盲目"的——它不知道哪些方向的样本更可能好,均匀地往各方向撒。如果能让采样"更聪明地"撒——把样本多花在更可能产生低代价轨迹的区域——就能用更少的样本达到同样的搜索质量(直接缓解维度灾难)。

沿这个思路有几类工作。其一,log-MPPI(GP-MPPI 同一研究组,Mohamed 等,RA-L 2022):把采样分布从纯高斯换成**正态-对数正态(normal log-normal, NLN)混合分布**,让样本在杂乱环境里覆盖得更有效、减少"撞墙"的无效轨迹、提升轨迹可行性。其二,更广义地,用学习的提议分布——比如用一个学好的策略或扩散模型来"提议"控制样本(而非纯高斯),把第 5 章扩散启发采样、第 6 章学习模型的思路推进到"学习怎么采样"。

本质洞察:采样 MPC 的学习增强可以发生在三个层次,由浅入深——改代价(GP 推子目标,告诉它"往哪走更好")、改模型(Neural-MPPI,告诉它"会发生什么")、改采样分布(log-MPPI / 学习提议,告诉它"该试哪些动作")。前两者是给采样过程提供更好的"输入信息",第三者是改造采样过程的"核心机制"。三个层次正交、可叠加(你可以同时学代价、学模型、学采样)。理解这个三层框架,你看任何"学习 + 采样 MPC"的新工作,都能立刻定位它在改哪一层——这比记住一个个具体方法名有用得多。而它们能这么自由地叠加,根源还是采样法"只求值不求导"的开放性:每一层的学习成分都只需"能前向产出(代价值/下一状态/控制样本)",不需要可微,所以想塞什么学习模块都顺滑。

深一层:GP-MPPI 为什么选"在线学习"而非"离线训练"

§8.6 强调 GP-MPPI"不需要离线训练",这个选择值得展开一层——在线学习 vs 离线训练是机器人学习的一个根本分野,理解它能让你判断一个学习方法适合什么场景。

离线训练(offline):先收集大量数据、训练好模型、再部署(部署时模型固定)。Neural-MPPI 的残差网络通常这么做(先采驾驶数据训网络)。优点是训练时有全部数据、可反复优化、模型质量可控;缺点是要求"训练分布覆盖部署会遇到的情况"——遇到训练没见过的新环境就抓瞎(§8.6 陷阱 1 的域外问题)。

在线学习(online):部署时实时从当前数据学、模型持续更新。GP-MPPI 的稀疏 GP 这么做(从当前激光雷达实时拟合可通行性)。优点是**天然适应新环境**(进入一个全新场景,它就地学当前场景,不依赖任何先验数据或地图);缺点是只能用"此刻看到的"有限数据、学得"浅"、且要求学习算法足够轻能实时跑(这正是为什么用稀疏 GP 而非深度网络——GP 能从少量数据即时拟合且给不确定性,深度网络需要大量数据和训练步)。

为什么 GP-MPPI 选在线:它要解决的问题是"未知、无全局地图的环境导航"——这种场景下,离线训练的前提(有覆盖性的先验数据)根本不成立(你不知道会进入什么环境)。所以它必须在线、就地学。这是一个由"问题特性"决定"学习范式"的清晰例子:面对未知、无先验数据的场景,在线学习是必然选择;面对可以离线收集代表性数据的场景,离线训练通常质量更高。 你判断一个学习增强该用在线还是离线,第一问就是:"我有没有覆盖部署场景的离线数据?"——没有就得在线。

本质洞察:在线学习和离线训练不是"谁先进",而是"在不在线学"对应着两种根本不同的问题设定——离线训练假设"世界可以提前采样、部署时不变",在线学习假设"世界部署时才遇到、必须就地适应"。GP-MPPI(在线、无地图、就地学)和 Neural-MPPI(离线、训好残差、部署)的对比,正是这两种设定的体现。它们甚至可以结合——离线训一个先验、在线再适应(这是机器人持续学习的前沿)。理解这个分野,你看任何"带学习的机器人系统",都能先问"它的学习是在线还是离线、为什么",从而判断它适合什么场景、有什么固有局限。

深一层:GP 的"不确定性"不只是副产品,它是探索的指南针

§8.6 提到 GP 给出"方差(不确定性)曲面",这里展开一层讲清不确定性的妙用——它不是可有可无的副产品,而是 GP 方法相对普通回归的核心价值,尤其在探索未知环境时。

回想 GP 的双输出:预测均值(这个方向大概通不通)+ 预测方差(这个判断有多确定)。普通回归只给均值,GP 多给的这个方差有什么用?在 GP-MPPI 的导航里,方差直接指导**探索 vs 利用**的权衡:

  • **方差低**的方向(已经感知清楚、确定可通行)→ 适合"利用",可放心地朝那走。
  • 方差高**的方向(还没看清、不确定)→ 代表"信息缺口",朝那走能**看清更多(减少不确定性)。在需要探索的场景(环境未知、当前路被堵要找新出路),高方差方向反而是好的探索性子目标——去那里能获取新信息、可能发现新的可通行路径。

这让 GP-MPPI 的子目标推荐有了"信息论"的味道:它不只看"哪通",还看"哪不确定、值得去看看",在"朝目标推进(利用)"和"探索未知区域(探索)"之间权衡。这正是 GP 这类**贝叶斯方法**的招牌能力——用不确定性量化"我对哪里无知",从而智能地决定去哪获取信息,而非盲目探索或只顾眼前。

本质洞察:不确定性估计是贝叶斯方法(GP 是其一)给决策带来的独特价值——知道"自己不知道什么",比只知道"最可能的答案"强大得多。普通回归/网络只给"最可能的预测",但不知道这个预测可不可信;GP 额外给出不确定性,让系统能区分"我确信的"和"我没把握的",并据此智能行动(确信处利用、无把握处探索或谨慎)。这个能力在本章是 GP-MPPI 推探索性子目标,在更广处是主动学习(去标注最不确定的样本)、安全探索(不确定处更保守)、风险敏感决策(这也正是后续"不确定性规划"方向的核心)。所以"模型要不要给不确定性"是个有深远影响的设计选择——给了,系统就能"知道自己的无知"并据此聪明地行动;不给,它就只能对所有预测一视同仁地信任,在未知环境里要么鲁莽要么寸步难行。

多视角:GP 子目标像"给近视的人配一个远眺的向导"

给 GP-MPPI 配个直觉。把 MPPI 想象成一个**近视的徒步者**——他只能看清脚下几步(有限时域),在杂乱地形里容易走进死胡同。GP 像一个**站在高处远眺、不断给他指路的向导**——向导根据看到的开阔地形,隔一段就喊一句"往那个方向走,那边通"(推荐子目标),徒步者朝向导指的方向走,就不容易困在局部。像的地方:向导提供徒步者自己看不到的远程信息,准确反映了 GP 给 MPPI 补全局视野。不像的地方(边界):真实向导站在高处有全局视野,而 GP 其实也只看到机器人周围的局部感知——它是从**局部**感知**推断**出"哪个方向更可能通"(基于可通行性的不确定性),不是真有上帝视角。所以更准确地说,GP 像一个"善于从眼前线索推断远处的向导",而非"真的看到了全局的向导"。别把类比延伸到"GP 有全局地图"——它恰恰是为"没有全局地图"的场景设计的。

深一层:为什么"学残差"比"学整个动力学"省数据得多

§8.6 提到 Neural-MPPI 常用"学残差"而非"学整个动力学",这里展开一层讲清为什么——这是个对所有"学习 + 物理先验"组合都通用的重要判断。

设想两种学法。学整个动力学:用网络从零拟合 \(x_{t+1} = g_\theta(x_t, u_t)\),网络要自己学出全部物理规律(惯性、重力、空气动力学、地形交互……)。学残差:保留一个名义物理模型 \(f_\text{nom}\)(已经抓住了惯性、重力等主要动力学),只用网络学它和真实的**差** \(g_\theta \approx f_\text{true} - f_\text{nom}\)

学残差省数据,根源在**网络要学的东西少了一大截**。名义模型已经免费提供了大部分动力学(物理定律是先验知识,不用数据学),网络只需补"名义模型没抓住的那部分"(如复杂气动、地形交互的修正项)——这部分通常**小、且结构简单**(是对主体的微调,不是主体本身)。学一个"小修正"比学"整个动力学"需要的数据少得多、也更不容易过拟合。这是"物理先验 + 数据补残差"范式的核心优势:用已知的物理省下学习的负担,把宝贵的数据花在"物理模型抓不住的、真正需要从数据学的"那部分。

这个判断有几个延伸。其一,它解释了为什么 §8.2 AutoRally 也是"在物理底盘模型基础上辨识参数/残差"而非纯黑箱学车。其二,它和第 6 章学习世界模型的讨论一脉相承——有物理先验时,残差学习几乎总是优于从零学。其三,它给你一个一般性的建模决策:面对一个"部分可建模"的系统,先用物理建到能建的程度(名义模型),再用数据补残差,而不是一上来就上黑箱网络从零学——后者浪费了你本可免费利用的物理知识、要更多数据、还更难调。只有在系统**几乎无法物理建模**(如纯像素输入的复杂场景)时,从零学整个(世界)模型才更合适。

本质洞察:学残差 vs 学整体,体现了机器学习与领域知识结合的一条黄金原则——已知的部分用知识(物理模型),未知的部分才用学习;让数据只为"你确实不知道的东西"买单。这条原则远超动力学建模:任何"部分可建模"的问题(控制、预测、估计),都应该先榨干可用的结构化知识、再用学习补差,而非用一个大模型从零吞掉一切。数据是昂贵的、学习是有过拟合风险的,把它们省着用在"非学不可"的地方,是数据高效的关键。这也是为什么"物理先验 + 学习残差"在机器人学里如此流行、如此有效——它在"纯物理(不够准)"和"纯学习(费数据、难泛化)"之间,找到了一个用知识为学习减负的甜点。

深一层:多步 rollout 的误差累积——学习模型的长程预测为什么更难

Neural-MPPI 把学习模型用于 rollout,但有个常被低估的问题值得展开一层:rollout 是多步的,单步的小误差会沿时域累积成大误差——这对学习模型尤其要命。

回想 MPPI 的 rollout:从当前状态出发,用模型一步步往前推 \(H\) 步。每一步模型都有预测误差(学习模型尤甚),而且关键在于——这一步的输出是下一步的输入。所以第 1 步的小误差会让第 2 步从一个略偏的状态出发、产生略大的误差,第 3 步更偏……误差像滚雪球一样沿 rollout 累积,到第 \(H\) 步可能已经偏得离谱。这就是"复合误差(compounding error)"问题——在模仿学习、世界模型里都是核心难题(第 6 章讨论过)。

学习模型在这里比解析模型更脆弱,原因有二。其一,学习模型的单步误差通常比精心建的解析模型大(尤其域外,§8.6 陷阱 1)。其二,更隐蔽——学习模型的训练通常是"单步预测"(给真实的 \((x_t,u_t)\) 预测 \(x_{t+1}\)),但 rollout 用的是"多步自回归"(用自己预测的 \(\hat x_t\) 再预测 \(\hat x_{t+1}\)。训练和使用的分布不匹配:训练时输入是真实状态,使用时输入是自己(带误差)的预测——这个分布偏移让误差累积雪上加霜。

应对这个问题的思路(都和本章/第 6 章相关):残差学习(名义模型扛大头,学习只补小残差,单步误差小、累积也慢,§8.6 深一层);多步训练(训练时就用多步 rollout 的损失,让模型学会"在自己的预测上还能预测得准",缓解分布偏移);短时域(H 别太长,从根上限制累积的步数——这也是为什么导航 MPPI 的时域往往只有几十步、对应几秒);不确定性感知(模型给出预测不确定性,rollout 越往后不确定性越大,MPPI 据此对长程预测降权)。

本质洞察:多步 rollout 的误差累积揭示了"用模型做规划/预测"的一个根本张力——模型越用于长程预测,单步误差的累积放大就越严重,而学习模型因单步误差大、且有训练-使用的分布偏移,长程更不可靠。这解释了几件事:为什么 MPPI 的时域不能无限长(越长累积误差越主导,规划反而变差)、为什么"短时域 MPPI + 学习的终端价值函数"(第 5 章)是个好组合(用短 rollout 避开累积、用学习的终端代价补远见,各取所长)、为什么世界模型做长程想象这么难(第 6 章)。一般性的教训是:对任何"靠模型多步推演"的方法,都要警惕误差累积——它让"看得更远"和"看得更准"成为一对矛盾,而化解之道往往不是把模型做得无限准(不可能),而是用残差/短时域/终端价值/不确定性这些手段管住累积、或绕开对长程精度的依赖。

易错点:学习模型的适用域,以及子目标不是终目标

两个易踩点。其一(Neural-MPPI):学习的动力学模型只在训练数据覆盖的状态-控制域内可信。MPPI 在 rollout 时会探索各种控制,可能把状态推到训练数据没覆盖的区域——那里神经网络的预测是**外推**,可能离谱(神经网络外推不可靠是老问题)。如果 MPPI 据此规划,会基于错误的预测做出错误决策。处理办法:限制采样范围别推到域外、或给模型配一个不确定性估计(域外不确定性高时不信它)、或保证名义模型在域外仍兜底(残差学习的好处——域外残差预测不准时,名义模型至少给个合理基线)。其二(GP-MPPI):子目标是手段、不是目的。GP 推荐的子目标只是帮 MPPI 跳出局部的临时引导点,机器人最终要到的还是真正的目标。实现时别把子目标当成终目标(到了子目标就停),而要"朝子目标走、但持续朝最终目标推进、到了开阔处再更新子目标"。把临时引导误当最终目标,机器人会停在半路。

⚠️ 常见陷阱

陷阱 1:把学习的动力学模型用到训练域之外,rollout 预测离谱 ⚠️ 编程陷阱 - 错误做法:训练好残差网络后,直接在 MPPI 里用,不限制采样范围、不管状态是否推到了训练数据覆盖的域外。 - 现象/后果:MPPI 采样探索时把状态推到训练没见过的区域,网络在那里外推出离谱的预测(如预测出巨大的不存在的力),MPPI 据此规划出诡异甚至危险的动作;表现为机器人偶发性地做出莫名其妙的剧烈动作。 - 根本原因:神经网络在训练数据域内插值可靠,但**域外外推不可靠**——而 MPPI 的采样探索天然会触及域外。模型在域外给的预测没有依据。 - 正确做法:(a) 用残差形式(域外时残差预测不准,但名义模型仍兜底给合理基线);(b) 给模型配不确定性估计(如集成、MC dropout),域外不确定性高时降低对它的信任;(c) 适当限制采样方差别把状态推太远。自检:记录 rollout 中状态是否频繁超出训练数据范围;若 MPPI 偶发剧烈异常动作,查是不是模型在域外乱预测。

陷阱 2:以为 Neural-MPPI 要求网络可微(套用了梯度式 MPC 的思维) ⚠️ 概念误区 - 错误想法:"要把神经网络用进 MPC,网络当然得可微、得能导出成优化器能用的格式,不然怎么优化?" - 现象/后果:你花大力气把动力学网络做成可微的、导出成 CasADi/acados 格式,或避开用了不可微操作的网络结构——做了大量对采样法**完全不必要**的工作,还限制了能用的网络。 - 根本原因:把梯度式 MPC 的要求(网络必须可微、可导出)错误地套到了采样法上。采样 MPC 的 rollout 只前向求值、不反向求导,对网络可微性零要求(§8.1 本质洞察)。 - 正确做法:采样法里直接 model.forward() 用网络,不管它可不可微、有没有不可微操作、是什么框架。把可微化/导出的功夫省下来。自检:检查你的 MPPI rollout 代码——它应该只调用网络的前向推理,没有任何 .backward()、没有梯度、没有对网络的导出处理;若你在为网络可微性发愁,说明你误用了梯度法的思维。

陷阱 3:以为"加了学习就一定更好",忽视学习带来的新风险 ⚠️ 思维陷阱 - 错误想法:"用神经网络学动力学/用 GP 推子目标,肯定比手写模型/纯 MPPI 强,加上就对了。" - 现象/后果:盲目加学习成分,结果引入新问题——学习的动力学在域外乱预测(陷阱 1)、GP 在感知差时推荐错误子目标把机器人带沟里、训练数据偏差导致模型有系统性错误——这些新风险可能比原来的短板更难排查。 - 根本原因:学习不是免费的——它把"手写模型不准/视野有限"的旧问题,换成了"学习模型的泛化、数据覆盖、不确定性、训练-部署分布偏移"等新问题。这些新问题有时更隐蔽、更难调(呼应第 6 章学习模型的种种陷阱)。 - 正确做法:把学习当成"有代价的工具"而非"万能升级"——只在手写方法确实不够时引入,引入后认真对待它的新风险(域覆盖、不确定性、分布偏移),并保留名义模型/全局规划器等"非学习的兜底"。自检:问自己"不加这个学习成分,用更简单的方法(更好的名义模型、A* 全局规划)能不能解决?"——能的话优先用简单方法;加了学习后,专门测试它的失败模式(域外、感知差、分布偏移)。

练习

  1. (复现 + 探究,⭐⭐⭐) 跑通本节 Neural-MPPI demo,复现"仅名义 1.08m、名义+学习残差 0.37m"。然后:(a) 把"学习残差" g_learned 改成一个**有误差**的版本(如 g_true 的 0.7 倍,模拟没学准),看漂移介于两者之间——体会模型准度对效果的影响。(b) 把 g_learned 改成在 \(x\in[3,6]\) 外也乱给值(模拟域外外推错误),看会不会反而变差——体会陷阱 1。
  2. (动手·真神经网络,⭐⭐⭐) 把 demo 里的 g_learned 换成一个真正的小 PyTorch MLP:先采一批 \((p, g_\text{true}(p))\) 数据训练这个 MLP,再把它(前向)嵌进 rollout。验证:(a) MPPI rollout 里只用了 mlp(p) 前向、没有任何 .backward();(b) 效果和用解析 g_learned 接近。亲手体会"采样法嵌入神经网络无需可微"(陷阱 2)。
  3. (动手·GP 子目标思想,⭐⭐⭐) 用 §8.1 的代价地图 demo 造一个凹形障碍(U 形墙,开口背对起点),确认纯 MPPI 陷在里面。然后模拟 GP-MPPI 的子目标机制:每隔几步,在机器人周围找一个"朝开阔方向、且离最终目标更近"的点当子目标,让 MPPI 朝子目标走。观察机器人是否能被一串子目标引导着绕出 U 形。这道题让你亲手实现"子目标引导跳出局部最优"的核心思想。
  4. (综合·联系第 5/6 章,⭐⭐⭐⭐,思考题) 本节两个增强都在"给 MPPI 注入学习"。请综合论证:(a) GP-MPPI 推荐子目标、第 5 章用学习价值函数当终端代价、§8.3 用 A* 全局规划,三者都在补 MPPI"视野有限"的短板——它们注入的"远程信息"分别是什么形式,各适合什么场景(有无全局地图、有无离线训练);(b) Neural-MPPI 用学习的动力学、第 6 章的学习世界模型,二者关系如何,"采样只求值不求导"在其中扮演什么角色;(c) 为什么说"学习模型 + 采样 MPC"是个特别自然的组合,而"学习模型 + 梯度式 MPC"工程上更费劲。
  5. (动手·误差累积,⭐⭐⭐) 基于本节 Neural-MPPI demo,把时域 H 从 15 逐步加大到 30、50,并给"学习残差"加一点噪声(模拟单步误差),观察随 H 增大、终距和漂移如何变化——亲手看到多步 rollout 的误差累积(本节深一层)。再验证:用残差形式(名义模型兜底)时,误差累积是否比"纯学习整个动力学"更慢。思考这对"该把 MPPI 时域设多长"的启示。

§8.7 累积项目实战:给统一规划器接上"代价来源"维度 ⭐⭐⭐

动机

我们的累积项目 Mini-MPPI(UnifiedSamplingPlanner)一路走来已经长出了好几根可插拔的"维度旋钮":第 4 章给了它权重 / 噪声 / 协方差(weight/noise/cov)、第 5 章给了它退火日程(schedule)、第 6 章给了它模型来源与终端代价(model_source/terminal)、第 7 章给了它动作空间(action_space)。但有一根关键的旋钮一直缺着——代价从哪来。前面这些章节里,代价基本都是手写的解析函数(到目标的二次型 + 控制能量)。可本章反复讲:真实导航/自驾的代价来自**感知代价地图**(AutoRally 的 CNN、§8.1/§8.2)或**critic 栈**(Nav2,§8.3)。本节给累积项目接上这根旋钮——cost_source(取值 'analytic'/'costmap'/'critics'),让同一个 MPPI 内核能无缝切换"代价来自手写函数 / 感知地图 / critic 栈",把本章学的 AutoRally 代价地图和 Nav2 critic 栈统一进项目。

设计:cost_source 作为一根正交旋钮

延续累积项目"内核-插件分离"的一贯架构(§8.4 本质洞察):MPPI 内核(采样、rollout、加权)不动,cost_source 只决定 _cost 方法**怎么给一批轨迹打分**。三种取值对应本章三种代价来源:

  • 'analytic':手写解析代价——只有"到目标"的二次型(前几章一直用的)。没有障碍意识。
  • 'costmap':查感知代价地图——在解析的趋目标项之外,加一项"沿轨迹查代价地图"(AutoRally 路线,§8.2)。有避障意识。
  • 'critics':critic 栈加权求和——把代价拆成若干 critic(目标 critic、避障 critic、路径 critic……),各自打分、按权重汇总(Nav2 路线,§8.3)。有避障意识,且可逐项调权重。

关键在于:这根旋钮和之前所有旋钮**正交**——你可以任意组合"cost_source='critics' + schedule='diffusion'(第 5 章退火)+ model_source='learned'(第 6 章学习模型)+ action_space='velocity'(第 7 章动作空间)"。每根旋钮管一件正交的事,这正是累积项目想教的核心工程能力:把一个复杂控制器拆成一组正交、可独立替换的设计选择。 先看类的结构(cost_source 只影响 _cost):

import numpy as np

W = 100
costmap = np.zeros((W, W)); costmap[40:60, 20:80] = 1.0; costmap[40:60, 45:55] = 0.0
GOAL = np.array([90.0, 50.0]); START = np.array([10.0, 50.0])

def lookup(xy):                                          # 查代价地图(可批量)
    ix = np.clip(xy[..., 0].astype(int), 0, W - 1)
    iy = np.clip(xy[..., 1].astype(int), 0, W - 1)
    return costmap[ix, iy]

class UnifiedSamplingPlanner:
    """累积项目: MPPI 内核 + 可插拔维度。本章新增 cost_source。"""
    def __init__(self, cost_source='analytic', critic_weights=None):
        self.cost_source = cost_source                   # 本章新增的正交旋钮
        self.cw = critic_weights or {'goal': 1.0, 'obs': 200.0}

    def _cost(self, traj):                               # traj: (K,H,2), 只有这里随 cost_source 变
        xy = traj; goal_term = 2.0 * ((xy[:, -1] - GOAL) ** 2).sum(-1)
        if self.cost_source == 'analytic':               # 手写解析: 只趋目标, 无障碍意识
            return goal_term + 0.01 * ((xy - GOAL) ** 2).sum(-1).sum(-1)
        elif self.cost_source == 'costmap':              # 查感知代价地图(AutoRally 路线)
            return goal_term + 200.0 * lookup(xy).sum(-1) + 0.01*((xy-GOAL)**2).sum(-1).sum(-1)
        elif self.cost_source == 'critics':              # critic 栈加权(Nav2 路线)
            c = self.cw['goal'] * goal_term
            c = c + self.cw['obs'] * lookup(xy).sum(-1)   # 避障 critic
            c = c + 0.01 * ((xy - GOAL) ** 2).sum(-1).sum(-1)
            return c

MPPI 主循环(plan)对三种 cost_source 完全一样——它只管采样、rollout、调 _cost 打分、加权,不关心代价从哪来:

    def plan(self, K=400, H=70, iters=30, lam=1.0, sigma=0.4, v=1.4, seed=0):
        rng = np.random.default_rng(seed); U = np.zeros(H)
        for _ in range(iters):
            eps = rng.normal(0, sigma, size=(K, H))
            th = U + eps; p = np.broadcast_to(START, (K, 2)).copy().astype(float)
            traj = np.zeros((K, H, 2))
            for t in range(H):                            # rollout: 差速车运动学
                p = p + v * np.stack([np.cos(th[:, t]), np.sin(th[:, t])], -1); traj[:, t] = p
            J = self._cost(traj)                          # ← 唯一与 cost_source 相关处
            w = np.exp(-(J - J.min()) / lam); w /= w.sum(); U = U + (w[:, None] * eps).sum(0)
        p = START.astype(float).copy(); hit = 0.0
        for t in range(H):
            p = p + v * np.array([np.cos(U[t]), np.sin(U[t])]); hit += lookup(p)
        return float(np.linalg.norm(p - GOAL)), float(hit)

for cs in ['analytic', 'costmap', 'critics']:
    d, hit = UnifiedSamplingPlanner(cost_source=cs).plan()
    print(f"cost_source={cs:8s}: 终距 {d:.1f}, 撞墙格点 {hit:.0f}")
# 典型输出(带墙导航任务):
# cost_source=analytic: 终距 4.1, 撞墙格点 8    —— 无障碍意识, 直穿墙
# cost_source=costmap : 终距 3.6, 撞墙格点 0    —— 查地图避障, 绕墙穿缺口
# cost_source=critics : 终距 3.6, 撞墙格点 0    —— critic 栈避障, 同样绕墙

跑出来一目了然:在带墙的导航任务上,'analytic'(只有趋目标代价、没有障碍意识)让车**直直穿过墙**(撞墙格点 8)——它根本不知道墙的存在;'costmap''critics'(都含避障代价)则让车**绕墙、穿过缺口**到达目标(撞墙格点 0)。同一个 MPPI 内核、同一个任务,仅仅切换 cost_source,行为天差地别——这正是本章 §8.1 的核心命题(代价来自感知)在累积项目里的落地:"代价从哪来"是和"怎么优化"完全正交的一根旋钮,而真实导航的避障能力,正是从把代价来源从'解析'换成'感知地图/critic'来的。

深一层:L1 这类鲁棒化层,是叠在内核之外的又一根正交轴

§8.5 的 L1-Adaptive 给了我们另一个正交维度,但它和 cost_source 这种"内部旋钮"不同——L1 是叠在整个规划器之外的一层。回想 §8.5:MPPI(管名义规划)的输出 \(u_\text{mppi}\) 之外,L1 加一个补偿 \(u_L1\),总控制 \(u = u_\text{mppi} + u_L1\)。所以从累积项目的视角看,L1 不是 _cost 里的一个分支、也不是换 model_sourceaction_space,而是**把整个 plan() 的输出再过一层在线补偿**。结构上,它像给规划器套了个外壳:

   ┌──────────────────────────────────────────┐
   │  UnifiedSamplingPlanner (内核 + 内部旋钮)   │
   │   cost_source / schedule / model_source /  │──u_mppi──►(+)──u──► 真实系统
   │   action_space / weight / noise / cov ...  │           ▲
   └──────────────────────────────────────────┘           │u_L1
                                          [外壳] L1 自适应 ─┘ (§8.5)

这揭示了累积项目里两种正交性的区别:cost_source/schedule/model_source/action_space 是"内核内部的、关于怎么规划"的正交旋钮;而 L1 这类鲁棒化层是"内核外部的、关于怎么让真实系统跟住规划"的正交外壳(呼应 §8.5 的"规划-执行分离"母题)。

本质洞察:一个成熟的采样 MPC 系统,它的设计空间不是一团乱麻,而是可以分解成**两类正交轴**——"规划怎么做"的内部轴(代价来源、退火、模型、动作空间……)和"如何鲁棒执行"的外部轴(PD 底层、L1 补偿……)。内部轴决定"在名义世界里规划出什么",外部轴决定"如何让真实世界跟住它"。把任何复杂控制器这样分解,你就能像调音台一样**独立地推每一根滑块**——想加避障就换 cost_source、想抗风就加 L1 外壳、想用学习模型就换 model_source,彼此不打架。这种"正交分解"的能力,是从"会用某个具体控制器"上升到"会设计控制器架构"的关键一跃,也是这个贯穿八章的累积项目最想留给你的东西:面对一个新需求,先问它落在哪根轴上、能不能独立加一根旋钮/一层外壳,而不是去改动内核的其它部分。

深一层:把八根旋钮画成一张"设计空间决策地图"

累积项目到本章已有八根维度旋钮,光知道它们的名字不够,值得展开一层,给你一张"遇到需求该拨哪根"的决策地图——这才是把它们用起来的关键。

按"你想改变什么"来组织这八根旋钮:

你想改变/解决的 拨哪根旋钮 来自
多个目标的相对重要性 weight(各代价项权重) 第 4 章
探索得不够 / 容易陷局部 noise/cov(采样幅度形状)+ schedule(退火) 第 4、5 章
看得不够远 / 时域太短的短视 terminal(终端代价,如学习的价值函数) 第 6 章
rollout 模型不准 / 难解析 model_source(解析 / 学习 / 黑箱仿真) 第 6 章
控制的"抽象层次"(力矩 vs 位置目标) action_space 第 7 章
代价从哪来(手写 / 感知 / critic) cost_source 第 8 章
真实系统有扰动 / 模型误差 L1 等鲁棒化外壳(外部轴) 第 8 章

用法:拿到一个新需求,先把它翻译成"我想改变上表左列的哪一项",再拨对应旋钮,而不是去改内核或乱试。比如"机器人在窄通道陷住"→ 翻译成"探索不够 + 代价权重失衡"→ 拨 schedule(退火探索)+ weight(调避障/路径权重);"无人机有风偏航"→ 翻译成"真实系统有扰动"→ 加 L1 外壳。

本质洞察:这张决策地图的真正价值,是它把"控制器调试/扩展"从**"在代码里到处试"变成了"先定位需求落在哪根正交轴上、再拨那一根"。一个复杂控制器之所以难搞,往往是因为人们没有这张地图——改一个地方牵动一堆,不知道该动哪。而正交分解 + 一张"需求→旋钮"的地图,让每个需求都有明确的着手点、且改一根不污染其它。这种能力——**把一个复杂系统的设计空间显式地组织成一张"症状/需求 → 可独立调整的旋钮"的地图——是高级工程师和初级工程师的分水岭,也是本累积项目八章下来最想沉淀给你的元能力。你以后接手任何复杂系统(不止采样 MPC),第一件事都该是尝试画出它的这张地图。

深一层:什么时候该停止加旋钮——别让设计空间无限膨胀

累积项目一路加旋钮,但这里要展开一个相反方向的判断力:什么时候不该再加,以及旋钮多了的代价。这对你做真实系统很重要,否则你会陷入"什么都想要、把系统堆成无法维护的怪物"。

每加一根旋钮,确实多一分灵活,但也有**代价**:其一,调参空间爆炸——旋钮越多,组合越多,调起来越难(八根旋钮的组合已经很大了),且旋钮间可能有微妙的交互(虽然设计上正交,实际可能耦合)。其二,认知负担——用的人要理解每根旋钮、知道何时拨它,旋钮太多就没人搞得懂、最后都用默认值(等于白加)。其三,维护成本——每根旋钮都是代码里要测试、要维护的分支。

所以**加旋钮要克制**,判断标准是:这个灵活性**有没有真实、反复出现的需求**?如果只是"理论上可能有用",多半不值得加(YAGNI 原则——You Aren't Gonna Need It)。Nav2 之所以成功,部分正因为它在"够灵活"和"别太复杂"之间拿捏得好——critic 可插拔(够灵活),但默认给一套能用的配置(多数人不用动)。这呼应第 7 章 §7.2 的工程智慧:好的模块化不是"把一切都做成可配置",而是"该可配置的可配置、该有好默认的有好默认"。

本质洞察:设计能力不只是"会加抽象/加旋钮",更是**"知道何时不加"——克制地保持系统的简单,往往比堆砌灵活性更难也更有价值。一个常见的工程误区是把"灵活/可配置"当成无条件的优点,于是给一切加开关,最后系统复杂到没人会用、没人敢改。真正成熟的设计是在"灵活性"和"简单性"之间按**真实需求**取舍:有反复出现的真实需求才加旋钮,否则用一个好的默认/约定。这条"克制的判断力"和前面所有"加能力"的内容是一体两面——会加、也知道何时不加,才是完整的设计判断力。本累积项目教你拆出正交旋钮,但也希望你带走这条相反的智慧:**别为了灵活而灵活,简单是一种需要主动捍卫的美德。

深一层:回看八章——累积项目其实是一座"学习脚手架"

到本章,累积项目走完了它的八章旅程,值得跳出来展开一层,反思它作为**学习设计**的用意——这能帮你把八章的知识真正内化成能力,而非记住一堆旋钮名。

这个项目从第 4 章一个只会"在解析代价上跑 MPPI"的玩具开始,每章加一根维度旋钮,到本章长成一个"维度相当完整的采样 MPC 沙盘"。这种"一个贯穿始终、逐章生长的项目"是有意的学习设计(教育学里叫 spiral curriculum / 累积式项目)。它的好处有三层。其一,每个新概念都落到同一个具体载体上——你学 cost_source 不是抽象地学,而是看它怎么插进你已经熟悉的那个 UnifiedSamplingPlanner,新旧知识立刻建立连接。其二,正交性在"加旋钮"的过程中被反复体认——每加一根旋钮都不动内核、不碰其它旋钮,你在动手中一次次体会到"正交分解"这件事,比读十遍定义都深。其三,最终你拥有的不是八个孤立知识点,而是一个有机的、自己搭起来的系统——你知道每根旋钮为什么在那、管什么、和别的什么关系,这种"整体把握"是孤立学习给不了的。

本质洞察:这个累积项目想教给你的,表面是一个采样 MPC 规划器,里子是一种"把复杂能力拆成正交模块、逐步组装"的工程思维方式。它反复演示的那件事——面对新需求,先看它落在哪根正交轴上、能不能独立加一根旋钮/一层外壳,而不去搅动内核——是可迁移到任何复杂软件/系统设计的元能力。所以学完这八章,如果你只记住了"MPPI 有 weight/schedule/cost_source 等参数",那是捡了芝麻;如果你内化了"复杂系统应当被分解成正交可组合的模块、新需求应当映射到对应模块上增量满足"这种思维,那才是丢了西瓜还捡回来了——这个思维会在你做任何复杂系统时反复增值,远超采样 MPC 这一个领域。一个好的累积项目,教的从来不只是它表面那个东西,而是借它沉淀一种可迁移的思维方式。 这也是为什么本书要费力维护这样一个贯穿八章的项目,而非每章给一堆孤立例子。

深一层:把八根旋钮写进一个统一接口长什么样

光说"八根正交旋钮"还是抽象,展开一层看它们写进一个统一接口的样子——这能让你直观感受"正交分解"在代码里如何落地,也是累积项目八章的一个收束快照。一个把全部维度暴露成构造参数的 UnifiedSamplingPlanner 大致长这样:

class UnifiedSamplingPlanner:
    """累积八章的统一采样 MPC 规划器: 每个参数是一根正交旋钮。"""
    def __init__(self,
                 # —— 内部规划轴(决定"在名义世界里怎么规划") ——
                 weight=None,            # 第4章: 各代价项权重
                 noise=0.4, cov=None,    # 第4章: 采样幅度/协方差形状
                 schedule='const',       # 第5章: 退火日程(noise 随迭代怎么降)
                 model_source='analytic',# 第6章: rollout 模型来源(解析/学习/黑箱)
                 terminal=None,          # 第6章: 终端代价(如学习的价值函数)
                 action_space='velocity',# 第7章: 动作空间(力矩/位置目标/速度...)
                 cost_source='analytic', # 第8章: 代价来源(解析/代价地图/critic栈)
                 # —— 外部鲁棒化轴(决定"如何让真实系统跟住规划") ——
                 outer_layer=None):      # 第8章: 鲁棒化外壳(如 L1 自适应), None=无
        ...                             # 各旋钮只影响对应的一处, 内核循环不变

    def plan(self, state):
        u = self._mppi_core(state)      # 内核: 采样-rollout-加权(永不变)
        if self.outer_layer is not None:
            u = self.outer_layer(u, state)   # 外壳: 在内核输出上再补偿
        return u

注意这个接口的两个设计要点。其一,所有旋钮都是构造参数、互相独立——你改 cost_source 不碰 action_space,改 schedule 不碰 model_source,每根旋钮在 _mppi_core 里只影响对应的一处(代价怎么算、噪声怎么降、模型怎么 rollout……),内核的主循环骨架(采样、加权、滚动)对所有旋钮组合都一样。其二,内部轴和外部轴的结构差异在代码里清清楚楚——内部旋钮(cost_source 等)影响 _mppi_core 的内部,外部 outer_layer 则在 _mppi_core **之外**包一层(u = outer_layer(u, state)),正是 §8.7"两类正交轴"的代码化身。

本质洞察:这个接口直观地展示了"正交分解"落到代码上的样子——一组互相独立的构造参数 + 一个对所有参数组合都不变的核心循环 + 一个可选的外层包装。它的美在于:加一个新能力(比如未来某章的新维度),多半就是"加一个构造参数 + 在核心循环的对应处加一个分支(或加一层外壳)",而不需要重写核心、不影响已有的旋钮。这种"开放扩展、封闭修改"(面向对象设计的开闭原则)正是好架构的标志,也是 Nav2 能做成可插拔生产组件、能让社区不断贡献新 critic/规划器而不破坏既有功能的根本。当你自己设计一个会长期演进、不断加功能的系统时,照这个样子组织——稳定的核心 + 正交的可配置维度 + 可叠的外层——能让它在加了几十个功能后依然清晰可维护,而不是变成一团没人敢动的意大利面。

深一层:外部鲁棒化轴上还能放什么——通往后续方向的接口

§8.7 把 L1 当作"外部鲁棒化轴"的代表,但这根轴上能放的不止 L1。展开一层看它还能放什么,正好为你接续后续方向铺路——因为后续几个方向的不少方法,本质都是"在采样规划器外面套一层"。

这根"外部轴"(在 _mppi_core 输出之上再处理)能挂的层,按职责大致有几类:抗扰层(L1、扰动观测器——把真实系统掰回名义,§8.5);硬安全层(控制屏障函数 CBF、安全滤波器——对 MPPI 的输出做安全投影,否决会碰撞/越界的控制,把"绝不违反安全约束"做成硬保证,§8.7 陷阱 3 埋的伏笔);监督/切换层(一个监督器监控 MPPI 是否健康,异常时切换到一个保守的备用控制器或触发恢复,呼应 §8.3 的恢复行为);滤波/平滑层(对输出控制做低通/限幅,保证平滑可执行)。这些层都共享同一个结构特征——它们不改 MPPI 的内部规划,而是在它输出之上做一层处理,因而都和内部规划轴正交、可独立叠加。

理解这根外部轴的开放性,你就有了一张接续后续方向的地图:后续的"安全滤波"方向,本质是给这根轴加一个硬安全层;"不确定性规划"里的一些方法,是在规划层或这根轴上注入风险敏感的处理;"博弈规划"则是把"其它智能体的反应"建进规划层。 它们不是要推翻你这八章建立的采样 MPC 系统,而大多是在这个"内核 + 内部轴 + 外部轴"的框架上,往某根轴或某一层加东西。

本质洞察:一个设计良好的系统框架,它的价值不只在于"满足当前需求",更在于**它为未来的扩展预留了清晰的接口**——你这八章搭起的"稳定内核 + 正交内部轴 + 可叠外部轴"框架,恰好为后续方向(安全、不确定性、博弈)准备好了挂载点:要硬安全就在外部轴加安全层、要风险敏感就在规划层调代价、要应对对手就把博弈建进规划。这就是为什么"先把框架的正交结构想清楚"如此重要——好的框架让新需求"有地方放",而不是每来一个新需求就要大改。这也是本累积项目和本章留给你的最后一层意思:你带走的不该只是一个能跑的导航规划器,而是一个**可以继续往上长**的框架和"在哪根轴上加什么"的判断力——后续方向的学习,会是在这个框架上继续生长,而非另起炉灶。

易错点:换 cost_source 时别忘了量纲与权重要重新平衡

一个换代价来源时的常见疏忽:不同 cost_source 的代价量纲和尺度不同,权重不能直接照搬'analytic' 只有趋目标项(量纲是距离平方),'costmap' 多了一项"查地图累加"(量纲是无量纲的可通行性 × 步数),'critics' 则是多项不同量纲的加权和。从 'analytic' 切到 'costmap',那项避障代价的权重(demo 里的 200.0)要和趋目标项**量纲平衡**——设太小则避障无力(还是会撞墙),设太大则过度保守(绕大圈甚至不敢靠近缺口)。这和 §8.3 调 Nav2 critic 权重、第 7 章 §7.2 "量纲归一化再谈权重" 是同一个道理。所以换 cost_source 不是改个枚举值就完事,往往要跟着重新平衡一次权重。自检:换完 cost_source 后,单独看每个代价分项在典型轨迹上的数值量级,确认它们在同一量级、没有哪一项把别的项淹没。

⚠️ 常见陷阱

陷阱 1:换 cost_source 后沿用旧权重,导致避障无效或过度保守 ⚠️ 编程陷阱 - 错误做法:从 cost_source='analytic' 切到 'costmap'/'critics' 时,避障项随手给个权重(或沿用别处抄来的数),不做量纲平衡。 - 现象/后果:权重太小,车照样穿墙(避障形同虚设);权重太大,车过度保守地绕大圈、甚至不敢进缺口,到不了目标。两种都让"加了避障代价"白费。 - 根本原因:不同代价分项量纲不同(距离平方 vs 无量纲可通行性 × 步数),权重既要表达偏好、又要抵消量纲差异(§8.3、§7.2 同理)。随手设的权重大概率没在正确量级。 - 正确做法:换 cost_source 后,打印各代价分项在典型轨迹上的数值量级,调权重让它们同量级、再按偏好微调(参照 demo 里 200.0 与趋目标项的平衡)。自检:避障项权重对了,车应该既绕开墙、又敢穿缺口直奔目标;若穿墙或绕大圈,多半是权重没平衡。

陷阱 2:以为换 cost_source 要改 MPPI 内核 ⚠️ 概念误区 - 错误想法:"代价来源变了,采样、加权这些是不是也得跟着改?" - 现象/后果:你去动 plan() 里的采样或加权逻辑,把本该正交的两件事耦合起来,结果改出 bug,且以后换别的 cost_source 又要再改一遍内核。 - 根本原因:MPPI 内核(采样、rollout、指数加权)只依赖"每条轨迹的总代价是个数",不关心这个数怎么算出来的。代价来源只影响 _cost 这一处(§8.4 "内核-插件分离")。 - 正确做法:换 cost_source 只改 _cost 方法,内核 plan() 一行不动。自检:你的 plan() 里应该只有一句 J = self._cost(traj) 与代价来源相关;若你为了换代价来源去改采样/加权,说明耦合错了。

陷阱 3:把"加了避障代价"等同于"安全",忽视代价 ≠ 硬约束 ⚠️ 思维陷阱 - 错误想法:"我在 cost_source 里加了避障代价,车就安全了,不会撞障碍。" - 现象/后果:避障是个**软代价**(撞障碍只是代价高、不是绝对禁止),在权衡里它可能被别的项盖过——比如目标吸引很强、通道很窄时,MPPI 可能"算出"轻微擦墙到达比绕远更划算,于是擦着障碍过去;或采样运气差时偶尔采出穿障碍的轨迹被选中。代价高 ≠ 绝不发生。 - 根本原因:MPPI 优化的是**期望代价的加权平均**,避障作为软代价项只是让撞障碍"不划算",不是硬性禁止(这是采样 MPC 处理约束的固有特性——多数实现把约束软化成高代价,而非硬约束)。 - 正确做法:安全关键场景下,避障不能只靠软代价——要么把障碍代价设得极高(接近硬约束,但仍非绝对)、要么在 MPPI 外面加一层硬安全检查/安全滤波(如 §后续章节会讲的安全滤波器,或简单的"否决会碰撞的控制"逻辑),把"绝不碰撞"做成硬保证。自检:在窄通道、强目标吸引下测试,看车会不会为了到达而擦障碍;安全关键应用别把软避障代价当成安全保证。

练习

  1. (复现 + 调权重,⭐⭐⭐) 跑通本节 cost_source demo,复现"analytic 撞墙 8 格、costmap/critics 撞墙 0 格"。然后针对 'costmap':把避障权重 200.0 调到 20.0(太小)和 2000.0(太大),分别观察"穿墙"和"过度保守绕大圈/不敢进缺口",亲手体会陷阱 1 的量纲平衡。
  2. (动手·组合正交旋钮,⭐⭐⭐)UnifiedSamplingPlanner 同时挂上本章 cost_source='critics' 和第 5 章的退火 schedule(让 sigma 随迭代从大到小)。在一个更难的双障碍地图上,验证"critics 避障 + 退火搜索"是否比单用其一更可靠(呼应第 7 章 §7.4 钥匙叠加)。这道题让你体会各维度旋钮的正交组合。
  3. (动手·加 L1 外壳,⭐⭐⭐⭐) 把 §8.5 的 L1 补偿做成 UnifiedSamplingPlanner 的一层"外壳":给规划器的输出再过一遍 L1(在一个带未知扰动的导航任务上)。验证 L1 外壳与 cost_source 内部旋钮互不干扰(换 cost_source 不影响 L1、加 L1 不影响 cost_source)。亲手实现本节"两类正交轴"的结构。
  4. (综合·梳理八章累积,⭐⭐⭐) 列出 Mini-MPPI 累积项目到本章为止的所有维度旋钮(weight/noise/cov/schedule/model_source/terminal/action_space/cost_source)及各自来自哪一章、管什么正交的事。然后把它们分成"内部规划轴"和"外部鲁棒化轴"两类(L1 属后者)。这道题帮你建立对整个采样 MPC 设计空间的结构化地图。

采样 MPC 落地的统一流程:把全章串成一套方法

学完七节,值得把它们收拢成一套**可操作的落地流程**——当你真要在一个新的导航/自驾/无人机问题上部署采样 MPC 时,按这个顺序走,本章的知识就从"一堆零件"变成"一条流水线"。

第 1 步:确认采样法适合这个问题。 用 §8.1 的"求值 vs 求导"尺子先判断——你的代价是否来自感知(代价地图、可通行性)、是否不可微?动力学是否含接触或难解析?只要"代价或动力学里有能算不好导的地方",采样法就合适。若代价光滑可微、要极高频和最优性保证,先考虑梯度式(第 1 章取舍)。

第 2 步:搭好三柱——估计、感知、控制。 按 §8.2 的 AutoRally 模板,先确认三柱都有着落:状态估计(机器人在哪、多快——别让控制建在漂移的状态上)、感知代价(障碍/可通行性从哪来——代价地图或 critic)、控制(MPPI 内核)。记住瓶颈常在估计/感知侧,别只盯控制。

第 3 步:选对运动模型。 按 §8.4,确认 rollout 用的运动模型匹配真车(差速/全向/阿克曼),把运动学约束(最小转弯半径、速度/加速度上限)和控制量范围(clip)写进 rollout,保证采出的轨迹真车走得出来。

第 4 步:组织代价——用模块化的 critic / 代价来源。 按 §8.3 和 §8.7,把多目标代价拆成可插拔、可加权的 critic(或选 cost_source),别揉成一坨。这一步决定了后面调参能不能"对症"。

第 5 步:接全局引导。 按 §8.3,MPPI 是局部规划器,给它配全局规划器(A*/Smac 出粗路径)或——在无全局地图时——GP 子目标(§8.6)提供远程引导,否则它在大环境/死胡同里会陷局部(§8.1 陷阱 3)。

第 6 步:调参——看症状、对症调权重。 按 §8.3 的"症状→critic"表和窄通道走查,一次只改一个权重,在多样场景测试。注意量纲平衡(§8.7、§7.2)。这是落地里花时间最多、最考验功夫的一步。

第 7 步:按需加鲁棒化与学习增强。 评估是否需要:鲁棒化外环(有显著扰动/模型误差就加 L1,§8.5,几乎免费)、学习成分(动力学难建模就学残差,§8.6;视野/局部最优问题就上 GP 子目标)。记住学习有新风险(§8.6 陷阱),且加旋钮要克制(§8.7 深一层)。

第 8 步:兜住安全。 按 §8.7 陷阱 3,避障是软代价不是硬保证,安全关键场景在 MPPI 外加一层硬安全检查/滤波,并配好上层的失败恢复行为(§8.3 深一层)。

这套流程的内在逻辑,正是本章的主线:先判断适配性(第 1 步)→ 搭起感知-估计-控制的系统骨架(第 2 步)→ 让它在具体车上动起来(第 3 步)→ 把代价组织好、接上全局引导、调到能用(第 4–6 步)→ 按需加鲁棒和学习、兜住安全(第 7–8 步)。 注意这个顺序不是随意的——它从"地基"往"增强"走,前面的步骤是后面的前提(运动模型不对,调参白搭;估计不准,啥都白搭)。新手常犯的错是跳过前面直奔调参或加花哨的学习增强,结果地基不稳、事倍功半。按这个顺序走,把每一步做扎实再进下一步,是落地采样 MPC 最稳的路径。 你可以把这八步当成一张部署检查清单(配合前面的"动手前 checklist"一起用)。


本章横向对比:四个系统/方向放在一起看

本章四个工作分别在采样 MPC 落地的不同维度上发力。把它们放一张表里对照,能看清各自的定位和共同的骨架:

维度 AutoRally (§8.2) Nav2 MPPI (§8.3) L1-Adaptive MPPI (§8.5) GP / Neural-MPPI (§8.6)
领域 自驾(越野激进驾驶) 地面导航(AGV/服务机器人) 无人机(竞速/敏捷飞行) 导航(未知杂乱环境)
解决的核心问题 能不能用:真车闭环 好不好用:工业部署+调参 准不准/稳不稳:抗模型误差 聪不聪明:跳局部+学动力学
代价来源 CNN 代价地图(感知) critic 栈(8 个可插拔) 复杂代价(穿门/避障) MPPI 代价 + GP 子目标引导
动作空间 转向 + 油门 速度(差速/全向/阿克曼) 无人机控制(力/姿态) 速度 / 控制序列
硬件 GPU(GTX 750 Ti) 多核 CPU(普通 i5 即可) GPU(MPPI) + CPU(L1) 取决于实现
状态估计 iSAM2(GTSAM) 显式一柱 由 Nav2 定位栈提供 假设完美(仿真) 由 ROS 栈提供
关键创新 三柱式真车闭环模板 插件化 critic + 生产级工程 规划-估计分离(L1 外环) GP 推子目标 / 学习残差
成熟度 研究平台(开源) 工业生产级(千万部署) 仿真竞速验证 研究(开源 ROS 包)
验证规模 真车(Georgia Tech 车队) 大规模真实部署 仿真(FlightGoggles) Gazebo + Jackal 真机

几个值得品的对照。硬件那一行:AutoRally 用 GPU、Nav2 用普通 CPU、L1-MPPI 是 GPU(规划) + CPU(抗扰)——印证了第 7 章和本章反复讲的"按 rollout 特性和样本数选硬件",没有放之四海皆准的硬件答案。代价来源那一行:从 AutoRally 的 CNN 代价地图到 Nav2 的 critic 栈,都是"代价来自感知/工程化模块",正是 §8.1 的核心命题;而 GP-MPPI 在代价之外又加了"子目标引导"来补视野。成熟度那一行:注意 Nav2 是唯一真正工业大规模部署的,L1-MPPI 还停在仿真——读论文时分清"真机验证"和"仿真验证"很重要(§8.5 特意点过)。

但更重要的是它们的**共同骨架**:四个系统全都跑第 2–3 章那个 MPPI 内核(采样-rollout-加权),区别只在外围——代价从哪来、用什么运动模型、要不要加抗扰外环、要不要学习增强。这再次印证全书的母题:采样 MPC 的内核是稳定的、领域无关的,落地的全部功夫在于把内核接到具体问题的感知、模型、约束、鲁棒性需求上。

从方法选择的角度,给一个"我该用哪个/学哪个"的速查:

你的情况 参考谁 为什么
要给地面机器人做导航、想直接用现成的 Nav2 MPPI(§8.3) 生产级、开箱即用、社区支持好
想理解一个完整真车系统怎么搭 AutoRally(§8.2) 三柱式闭环模板,经典且开源
无人机/系统有显著模型误差或扰动 L1-Adaptive(§8.5) 在线补偿,规划-估计分离,几乎免费
环境未知、MPPI 容易陷局部 GP-MPPI(§8.6) 在线学可通行性推子目标,免全局地图
动力学难解析建模、有数据 Neural-MPPI(§8.6) 学残差嵌 rollout,无需可微

前沿与开放问题

采样 MPC 在导航/自驾/无人机上虽已大规模落地,但仍有不少活跃的前沿和悬而未决的问题。

开放问题一:感知-控制的紧耦合与端到端不确定性传播。 本章把感知(代价地图、可通行性)当成 MPPI 的输入,但感知本身有不确定性(CNN 的误检、激光雷达的噪声、动态物体的预测误差)。当前多数系统把感知输出当确定的代价地图用,没有把感知的不确定性系统地传播到控制决策里。如何让 MPPI"知道"代价地图哪里可信、哪里存疑,并据此做风险敏感的决策(不确定处更保守),是把采样 MPC 推向开放、动态真实环境的关键一环——这也和后续"不确定性规划"方向(风险敏感、机会约束)直接相关。

开放问题二:动态环境中的反应速度受限于感知更新率。 本章骨架点名的开放问题:Nav2 MPPI 在动态环境中的反应速度,受限于代价地图的更新率——代价地图每隔一段才更新一次,快速移动的障碍(行人、其它车)可能在两次更新之间就逼近了,MPPI 据旧地图规划就来不及反应。前沿方向是把**预测模型**融进来——不只看障碍现在在哪,还预测它接下来去哪(行人轨迹预测、车辆意图预测),让 MPPI 对"将要发生的"而非"已发生的"做规划。这把单纯的避障推向了"多智能体交互预测",又和后续"博弈规划"方向相接。

前沿一:把感知直接做进可微/可采样的代价。 当前感知和控制常是分离的两个模块(感知出地图、控制查地图)。一个前沿方向是让感知输出更适合采样 MPC 直接消费的形式——比如让 CNN 直接输出"对每条候选轨迹打分"的可学习代价(端到端学代价函数),或用神经隐式表示(如神经辐射场类的占据表示)让代价查询更连续、更信息丰富。这能让感知和控制的接口更紧、信息损失更少。

前沿二:学习引导的采样——用数据先验加速搜索。 §8.6 的 GP 推子目标是一个例子,但更广义的方向是用学习的先验改造采样分布本身——比如用一个学好的策略/扩散模型来"提议"好的控制样本(而非纯高斯采样),让 MPPI 把样本花在更可能好的区域。这延续第 5 章扩散启发采样、第 6 章学习模型的思路,把"学习"从改善代价/模型推进到改善采样过程本身。它的吸引力在于:导航/自驾有海量数据,用数据先验缩小搜索能直接提升采样效率。

前沿三:算力下沉与嵌入式部署。 Nav2 MPPI 已经证明采样 MPC 能在普通 CPU 上跑(§8.3)。前沿在于进一步下沉到**算力更受限的嵌入式平台**(小型无人机的飞控、低成本 AGV 的 MCU),让采样 MPC 在瓦特级功耗下实时跑。这需要在样本效率(用更少样本,呼应第 7 章腿足里"几十样本"的趋势)、定点运算、专用加速等方面继续突破。算力下沉一分,采样 MPC 能进入的设备就多一类。

前沿四:与安全保证的结合。 §8.7 陷阱 3 点出,采样 MPC 的避障是软代价、不是硬保证。前沿方向是把采样 MPC 和**形式化安全保证**结合——在 MPPI 外面套一层控制屏障函数(CBF)或安全滤波器,对 MPPI 的输出做硬性安全投影(否决会导致碰撞/越界的控制),既享受采样 MPC 处理复杂代价的灵活,又获得"绝不违反安全约束"的硬保证。这正是后续章节(安全滤波、不确定性规划)要展开的,本章的软避障是它的铺垫。

把这些放一起看,会发现一条清晰的演进方向:采样 MPC 在导航/自驾/无人机上的下一步,是从"会避静态障碍"走向"在不确定、动态、有他车交互的真实开放环境里,既灵活又安全地行动"——而这恰好把本方向(采样 MPC)和后续的不确定性规划、博弈规划、安全滤波几个方向串了起来。本章是这条路的起点:把内核接进真实系统、让它鲁棒、让它聪明;后面几个方向接着回答"让它在不确定和有对手的世界里安全"。


本章常见误解汇总

把全章散落在各节的易错点和陷阱里最容易误解的,集中成一张"误解 → 正解"对照表,供快速纠偏:

常见误解 正确理解 出处
采样 MPC 适合导航是因为动力学不可微 是因为**代价**不可微(来自感知地图);动力学侧不可微是腿足的理由,代价侧不可微才是导航的理由 §8.1
导航就是在代价地图上找低代价路径(纯几何寻路) 是在**运动学约束下**找动力学可行的低代价轨迹;MPPI 在 rollout 用真实运动模型,产出的是车走得出来的轨迹 §8.1、§8.4
MPPI 能避障,导航就只要它一个局部规划器 MPPI 是**局部**规划器,视野有限,需要全局规划器(A*)或 GP 子目标提供远程引导 §8.1、§8.3、§8.6
AutoRally 厉害是因为 MPPI 算法厉害 是**三柱式闭环**(估计 iSAM2 + 感知 CNN + 控制 MPPI)的系统工程,MPPI 只是一柱 §8.2
AutoRally 是 2016 年的,过时了不用学 它确立的"三柱式闭环"模板至今是所有真车采样 MPC 系统的骨架,架构级洞察不过时 §8.2
调 Nav2 就是瞎试参数 是**看症状、对症调对应 critic 权重**的有章法技能;critic 的模块化让行为问题可定位到具体旋钮 §8.3
差速/全向/阿克曼运动学差不多,一个模型通用 约束本质不同(能否原地转、有无最小转弯半径),直接决定哪些轨迹可行;用错模型规划出真车走不出的路 §8.4
加了 L1,MPPI 就能感知并应对扰动了 L1 与 MPPI 正交叠加;MPPI 始终在名义模型上规划、对扰动无知,L1 在它之外补偿,两者相加 §8.5
有了 L1 名义模型就可以随便糊 L1 只补偿**有界的、相对名义的偏差**;名义模型根本错了 L1 补不动,名义越准 L1 越有效 §8.5
用神经网络做 MPC,网络得可微、得导出 采样 MPC 的 rollout 只前向求值、不求导,任何 PyTorch/JAX 网络直接嵌入,无需可微化 §8.6
加了避障代价,车就安全了 避障是**软代价**、不是硬约束,可能被强目标吸引盖过;安全关键场景需硬安全滤波 §8.7

这张表的一个共同主题:很多误解都源于"只看算法、不看系统"或"把某一层的特性错配到另一层"。采样 MPC 落地是系统工程——内核之外的估计、感知、模型、运动学、鲁棒化、安全,每一层都有自己的角色和边界,搞混它们就会产生上面这些误解。


本章小结

术语速查表

本章引入或重点使用的术语(中英对照 + 一句话定义):

术语 英文 一句话定义
代价地图 costmap 把环境可通行性编码成栅格代价的地图,障碍处高、空地处低,常由感知实时生成
可通行性 traversability 某处地形/空间能否(以及多容易)通过,是导航代价的核心
AutoRally Georgia Tech 的 1/5 比例越野车开源平台,第一个真车 MPPI 闭环
信息论 MPC information-theoretic MPC 用自由能-相对熵对偶推导 MPPI 的理论框架(Williams 等)
状态估计 state estimation 从带噪声的传感器(相机/GPS/IMU)融合出机器人状态
iSAM2 incremental smoothing and mapping GTSAM 库的增量式状态估计/SLAM 算法,AutoRally 用它
critic critic Nav2 里的一个可插拔代价评价器,对一条轨迹的某个目标打分
critic 权重 cost_weight 某个 critic 在总代价里的权重,调它是 Nav2 部署核心技能
差速驱动 differential drive 控制量是线速度+角速度,能原地转、不能横移的运动模型
全向 omnidirectional 能朝任意方向平移的运动模型(麦克纳姆轮等)
阿克曼 Ackermann 像汽车的转向模型,不能原地转、有最小转弯半径
最小转弯半径 minimum turning radius 阿克曼车能转的最紧圆弧半径,\(R_\min = L/\tan\delta_\max\)
非完整约束 nonholonomic constraint 对速度方向的约束(如差速车不能横移),不可积分为位形约束
L1 自适应控制 L1 adaptive control 在线估计并补偿模型误差/扰动、用低通滤波限带宽保证鲁棒的自适应控制
状态预测器 state predictor L1 里用内部模型预测状态、产生预测误差驱动自适应的部件
matched 扰动 matched disturbance 作用在和控制相同通道上的扰动,L1 能直接补偿
规划-估计分离 planning-estimation separation MPPI 管名义规划、L1 管在线补偿,两者正交叠加
稀疏高斯过程 sparse Gaussian process (SGP) GP-MPPI 用来在线学可通行性曲面、推子目标的轻量 GP
子目标 subgoal 帮局部规划器跳出局部最优的临时引导目标
学习残差 learned residual 用网络学名义模型与真实动力学之差,加进 rollout
代价来源 cost_source 累积项目本章新增维度:代价来自解析函数/代价地图/critic 栈

知识点总表

# 知识点 核心要点 对应节 难度
1 导航为何是采样主场 代价来自感知、不可微,采样只查表打分、不求导 §8.1 ⭐⭐
2 可微性要求降到零 采样对动力学和代价都不要求可微,腿足(动力学侧)和导航(代价侧)同理 §8.1 ⭐⭐
3 AutoRally 三柱闭环 状态估计(iSAM2) + 感知(CNN 代价地图) + 控制(MPPI GPU),缺一不可 §8.2 ⭐⭐⭐
4 按 rollout 特性选硬件 规整大批量(车/无人机)→GPU;含接触中等样本(腿足)→CPU §8.2 ⭐⭐⭐
5 Nav2 critic 架构 多目标代价拆成可插拔、可加权的 critic 插件 §8.3 ⭐⭐⭐
6 调 critic 权重 看症状对症调对应 critic 权重,模块化让问题可定位 §8.3 ⭐⭐⭐
7 全局+局部分层 全局规划器(A*)给远程粗路径,MPPI 跟踪并实时避障 §8.3 ⭐⭐⭐
8 三种运动模型 差速(能原地转)/全向(能平移)/阿克曼(有最小转弯半径) §8.4 ⭐⭐⭐
9 换车内核不动 只换 rollout 运动模型,采样/加权内核不变;内核-插件分离 §8.4 ⭐⭐⭐
10 约束内生于 rollout 把运动学约束写进 rollout,采出的轨迹天生可行,不事后过滤 §8.4 ⭐⭐⭐
11 L1 规划-估计分离 MPPI 名义规划 + L1 在线补偿,正交叠加,真实系统逼近名义 §8.5 ⭐⭐⭐⭐
12 L1 三件套 状态预测器 + 自适应律 + 低通滤波(限带宽保鲁棒) §8.5 ⭐⭐⭐⭐
13 L1 几乎免费 估计补偿扰动比规划轨迹轻得多,可高频跑、近零额外算力 §8.5 ⭐⭐⭐⭐
14 GP 推子目标 稀疏 GP 在线学可通行性、推子目标补 MPPI 视野,免全局地图 §8.6 ⭐⭐⭐
15 Neural-MPPI 免可微 学习残差嵌 rollout,只前向不求导,任何网络直接用 §8.6 ⭐⭐⭐
16 cost_source 维度 累积项目正交旋钮:解析/代价地图/critic,决定有无避障意识 §8.7 ⭐⭐⭐
17 两类正交轴 内部规划轴(代价/退火/模型/动作) + 外部鲁棒化轴(PD/L1) §8.7 ⭐⭐⭐

一句话速记

  • §8.1:导航偏爱采样,是因为代价(来自感知)不可微——采样只查表打分,不求导。
  • §8.2:AutoRally = 状态估计 + 感知代价 + MPPI 控制,三柱缺一不可;它是真车采样 MPC 的模板。
  • §8.3:Nav2 把多目标代价拆成可插拔的 critic,"看症状调对应 critic 权重"是真实工程技能。
  • §8.4:换车只换 rollout 运动模型,内核不动;阿克曼的最小转弯半径靠在 rollout 用真实运动学自动尊重。
  • §8.5:MPPI 在名义模型上规划,L1 在外面在线补偿模型误差,让真实系统表现得像名义——规划-估计分离。
  • §8.6:GP 推子目标补视野、网络学残差补模型;采样只求值不求导,让嵌入任何学习模型都顺滑。
  • §8.7:"代价从哪来"是和"怎么优化"正交的一根旋钮;复杂控制器 = 内部规划轴 + 外部鲁棒化轴。

学完自检

读完本章,检查自己能不能回答这些(答不出的回对应节):

  1. 为什么说导航适合采样 MPC 的理由和腿足不同?两种"不可微"分别在哪?(§8.1)
  2. AutoRally 的三柱是什么?为什么说只学 MPPI 算法复现不出它?(§8.2)
  3. 机器人"蹭墙"和"绕大圈不敢靠近障碍"分别该调哪个 critic、往哪个方向调?(§8.3)
  4. 为什么差速车能到的正侧方近处目标,阿克曼车可能到不了?(§8.4)
  5. L1 和 MPPI 是什么关系?L1 改变 MPPI 的规划吗?L1 的三件套是什么?(§8.5)
  6. 为什么"用神经网络做采样 MPC"不需要网络可微,而梯度式 MPC 需要?(§8.6)
  7. 累积项目的 cost_source 有哪三个取值?它和 L1 外壳分属哪两类正交轴?(§8.7)

承上启下

承上:本章把前七章打磨的采样 MPC 内核(第 2–3 章 MPPI、第 4 章 critic、第 5 章退火、第 6 章学习模型、第 7 章动作空间与分层)接到了导航/自驾/无人机的真实感知-控制闭环里。你看到内核一行不用动,落地的功夫全在外围——代价来自感知(§8.1)、三柱式闭环(§8.2)、critic 调参(§8.3)、运动模型适配(§8.4)、鲁棒化外环(§8.5)、学习增强(§8.6)。

启下:本章默认环境基本已知、障碍基本静态、世界里只有"我"一个智能体。但真实世界充满**不确定性**(感知有噪声、障碍会动、模型不准)和**其它智能体**(别的车、行人,它们有自己的意图、会和我交互)。本章的软避障(§8.7 陷阱 3)、对感知不确定性的忽略(前沿一)、对动态障碍的滞后反应(前沿二),都在呼唤后续方向:不确定性规划(风险敏感、机会约束、信念空间规划——如何在不确定下既高效又安全)和**博弈规划**(多智能体交互——如何在有对手/伙伴的世界里行动)。本章是采样 MPC 落地的"单智能体、确定性"起点,后面几个方向接着把它推向"多智能体、不确定"的真实开放世界。


本章的累积项目:Mini-MPPI 第八次扩展

延续贯穿全书的累积项目 UnifiedSamplingPlanner(Mini-MPPI),本章新增一根维度旋钮,并明确了一类新的"外壳"结构。

本章新增: - cost_source 维度(§8.7):取值 'analytic'(手写解析代价)/ 'costmap'(查感知代价地图,AutoRally 路线)/ 'critics'(critic 栈加权,Nav2 路线)。它决定 MPPI 的代价从哪来,是和优化过程正交的一根内部旋钮,给项目接上了"避障意识"。 - 外部鲁棒化外壳的概念(§8.5、§8.7):L1-Adaptive 这类层不是内核内部的旋钮,而是套在整个规划器外面的一层在线补偿(总控制 = 规划器输出 + L1 补偿)。它明确了累积项目的"两类正交轴"——内部规划轴与外部鲁棒化轴。

累积至今的全部维度(八章):

维度旋钮 来自 管什么(正交的事) 类别
weight / noise / cov 第 4 章 代价权重、采样噪声、协方差形状 内部
schedule 第 5 章 退火日程(噪声随迭代怎么降) 内部
model_source / terminal 第 6 章 rollout 模型来源、终端代价(远见) 内部
action_space 第 7 章 力矩 / 位置目标等动作空间 内部
cost_source 第 8 章 代价来源(解析/代价地图/critic) 内部
L1 等鲁棒化外壳 第 8 章 在线补偿模型误差/扰动 外部

到本章为止,Mini-MPPI 已经是一个**维度相当完整的采样 MPC 设计沙盘**——你可以在它上面独立地拨动每一根内部旋钮、叠加外部外壳,组合出从"玩具解析任务"到"感知驱动的鲁棒导航"的各种配置。这套"正交分解"的设计观,是累积项目最想沉淀给你的工程能力。


延伸阅读

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

AutoRally 与信息论 MPC(§8.2) - Williams 等, "Aggressive Driving with Model Predictive Path Integral Control", ICRA 2016 —— ⭐⭐ MPPI 真车应用的奠基论文,必读。 - Williams 等, "Information-Theoretic Model Predictive Control: Theory and Applications to Autonomous Driving", IEEE T-RO 2018 —— ⭐⭐⭐ 上文的期刊扩展,理论讲完整,想深挖信息论推导看这篇。 - Goldfain 等, "AutoRally: An Open Platform for Aggressive Autonomous Driving", IEEE Control Systems Magazine 2019(39(1):26-55)—— ⭐⭐ 完整的硬件 + 软件平台论文,想自己复现真车系统(底盘、计算盒、传感器)从这篇起步。 - AutoRally/autorally GitHub 仓库(MPPI 在 autorally_control/src/path_integral/)—— ⭐⭐⭐ 原始 CUDA MPPI 实现,想看真车代码精读这里。

Nav2 MPPI 与工程部署(§8.3、§8.4) - Nav2 官方文档 MPPI Controller 配置页(nav2_mppi_controller)—— ⭐ 部署必查,所有参数和 critic 的权威说明。 - Macenski 等关于 Nav2 的 ROSCon 2023 报告与 Nav2 总论文 —— ⭐⭐ 理解 Nav2 架构与 MPPI 在其中的定位。 - nav2_mppi_controller/ 源码全目录 —— ⭐⭐⭐ 生产级 C++ 采样 MPC 实现,critic 插件机制的范本。

L1-Adaptive 与鲁棒控制(§8.5) - Pravitra 等, "L1-Adaptive MPPI Architecture for Robust and Agile Control of Multirotors", IROS 2020 —— ⭐⭐⭐ 规划-估计分离的代表作。 - Hovakimyan & Cao, L1 Adaptive Control Theory —— ⭐⭐⭐ L1 自适应控制的系统理论来源,想吃透状态预测器/自适应律/滤波器看这本。

学习增强采样导航(§8.6) - Mohamed, Ali, Liu, "GP-guided MPPI for Efficient Navigation in Complex Unknown Cluttered Environments", IROS 2023 —— ⭐⭐⭐ GP 推子目标的代表作,有开源 ROS 包。 - 同组的 log-MPPI(Mohamed 等, RA-L 2022, arXiv 2203.16599)—— ⭐⭐⭐ 用正态-对数正态混合分布改造采样、提升杂乱环境轨迹可行性。 - 第 6 章延伸阅读里的学习动力学/世界模型文献 —— ⭐⭐ Neural-MPPI 的模型学习基础。

综述与背景 - 路径积分控制的综述(如 Kazim 等关于 path integral control 的 overview)—— ⭐⭐⭐ 把本章各工作放进 MPPI 大家族的坐标系。 - ROS2 / Nav2 导航栈的官方教程 —— ⭐ 想动手部署导航的工程入门。


本章与后续章节的关系

本章是采样 MPC 方向的"落地应用"章,它和后续方向的衔接如下:

后续方向/章节 关系 本章埋下的铺垫
对比与实战(本方向收尾章) 把采样式与梯度式 MPC 做系统对比、做综合实战 本章的真实系统(AutoRally/Nav2)是对比的素材;运动模型、critic 调参是实战基础
不确定性规划方向 处理感知噪声、动态障碍、模型不准下的"既高效又安全" §8.7 软避障 ≠ 安全、前沿一感知不确定性、前沿二动态障碍——都呼唤风险敏感/机会约束规划
博弈规划方向 处理多智能体交互(他车、行人有自己的意图) 前沿二"预测障碍意图"把单纯避障推向多智能体交互预测
安全滤波相关 给控制套硬安全保证(CBF/安全滤波器) §8.7 陷阱 3 指出避障是软代价,硬安全需外加滤波——这正是安全滤波要补的

一句话定位:本章把采样 MPC 在"单智能体、环境基本已知、障碍基本静态"的设定下落了地;后续方向接着把这些假设一个个放开——环境不确定(不确定性规划)、有其它智能体(博弈规划)、要硬安全(安全滤波)。 你在本章建立的"内核 + 感知代价 + 鲁棒外环 + 学习增强"的系统观,是理解后续所有"更难设定"的基础——因为它们大多还是在这套骨架上,往某个方向加东西。


🔧 故障排查手册

导航/自驾/无人机采样 MPC 在真实部署里最常见的几类故障,按"症状 → 可能原因 → 排查步骤 → 相关节"组织。这些来自本章各系统的真实工程经验。

症状 可能原因 排查步骤 相关节
机器人撞墙/蹭障碍 (1) 避障 critic 权重太低 (2) 代价地图没更新/没接上 (3) 避障是软代价被强目标盖过 1.确认代价地图在实时更新且 MPPI 查的是它 2.调高 CostCritic 权重,一次只改这一个 3.窄通道强目标场景下考虑加硬安全滤波 §8.3、§8.7
机器人绕大圈、不敢靠近障碍/进缺口 避障 critic 权重过高,过度保守 1.调低 CostCritic 权重 2.检查避障项与趋目标项的量纲是否失衡 3.适当升 PathFollowCritic 让它敢跟路径 §8.3、§8.7
机器人在大环境/死胡同里来回打转、到不了目标 MPPI 视野有限陷局部,缺全局引导 1.确认全局规划器在工作(能看到全局路径发布)2.检查 PathFollow/PathAlign critic 是否启用 3.无全局地图场景考虑 GP 子目标 §8.1、§8.3、§8.6
规划的轨迹真车走不出来/执行时剧烈偏离 (1) 运动模型与真车不匹配 (2) 控制量没 clip 到物理范围 (3) 模型保真度不够 1.确认 motion_model 设对(差速/全向/阿克曼)2.检查 rollout 是否 clip 了转向角/速度上限 3.对比模型预测与真车实际轨迹的偏差 §8.4
高速/高频下跟踪有系统性滞后、过弯踩空 状态估计延迟没补偿,规划用了旧状态 1.测量端到端延迟(采集到执行)2.加状态预测补偿(用模型把状态前推到控制生效时刻)3.仿真里人为加延迟验证补偿效果 §8.2
无人机有风/载荷时偏离航线、抖动 名义模型不含扰动,无鲁棒化外环 1.确认是否有抗扰层(L1/积分)2.若用 L1,单独验证它(已知扰动下 \(\hat\sigma\) 是否收敛)3.检查名义模型是否偏差过大(L1 补不动) §8.5
加了 L1 后系统发散/被"补"飞 L1 自适应律符号/增益错,或离散步长过大 1.检查自适应律是 \(\dot{\hat\sigma}=-\Gamma\tilde x\)\(a_s>0\) 2.关掉 MPPI 用恒定基线单独测 L1 3.减小步长或增益至稳定域 §8.5
用学习动力学后偶发诡异剧烈动作 网络在训练域外外推出离谱预测 1.记录 rollout 状态是否超出训练数据范围 2.改用残差形式让名义模型兜底 3.给模型加不确定性估计、域外降权 §8.6
MPPI 实时性差、控制频率上不去 (1) 代价地图查询写成 Python 循环 (2) 样本数/时域过大 (3) 迭代次数过多 1.确认代价查询是批量张量操作 2.适当降 batch_size/time_steps 3.iteration_count 保持 1、宁可加 batch §8.1、§8.3
阿克曼车规划出原地掉头/急转,真车走不出 运动模型用错(用了差速)或全局规划器没考虑运动学 1.确认局部 motion_model 设为 Ackermann 2.阿克曼车全局用 Smac Hybrid-A*(懂最小转弯半径)3.检查转向角是否 clip §8.4
学习增强后效果反而变差/不稳定 (1) 模型在训练域外乱预测 (2) 长程 rollout 误差累积 (3) 子目标把机器人带偏 1.缩短时域限制累积 2.残差形式 + 不确定性兜底 3.确认子目标是临时引导而非终目标 §8.6
机器人对动态障碍反应慢、险些撞上 代价地图更新率/感知频率成瓶颈(非控制频率) 1.查代价地图更新率(可能远低于控制频率)2.提高感知/地图刷新 3.考虑加预测模型(前沿二) §8.3

排查的一个通用心法(呼应第 7 章"瓶颈常在环境侧"):遇到导航/自驾问题,先别急着怪 MPPI 算法本身——大概率出在外围:代价地图对不对、运动模型匹不匹配、状态估计准不准、权重平不平衡、有没有抗扰层。用"三柱 + 外围"的系统视角逐层排查,比死调 MPPI 内核参数高效得多。


概念与 API 速查表

Nav2 MPPI 关键参数(§8.3,部署时查):

参数 默认 作用 调整提示
batch_size 1000(示例 2000) 采样轨迹数 \(K\) 不够稳就加,吃算力;优先于加 iteration
time_steps 56 rollout 步数 \(H\) × model_dt = 预测时域秒数
model_dt 0.05 每步时长(秒) 与控制频率匹配
temperature 0.3 MPPI 的 \(\lambda\) 调小更贪心(只信最优少数样本),强非凸慎用
iteration_count 1 每周期迭代次数 官方建议保持 1,宁可加 batch
motion_model DiffDrive 运动模型 DiffDrive / Omni / Ackermann,必须匹配真车
vx_max/wz_max 任务相关 速度/角速度上限 设成真车执行器实际限制

Nav2 八个默认 critic(§8.3):ConstraintCritic(约束/限速,权重 4.0)、CostCritic(避障)、GoalCritic(到目标,5.0)、GoalAngleCritic(到朝向,3.0)、PathAlignCritic(对齐路径)、PathFollowCritic(跟随路径)、PathAngleCritic(路径方向)、PreferForwardCritic(少倒车)。每个有 cost_weightcost_power

运动模型公式(§8.4,状态 \((x,y,\theta)\)):

模型 控制量 运动学 关键约束
差速 \(v, \omega\) \(\dot x=v\cos\theta,\ \dot y=v\sin\theta,\ \dot\theta=\omega\) \(\omega\) 独立于 \(v\),能原地转,不能横移
全向 \(v_x, v_y, \omega\) \(\dot x=v_x\cos\theta-v_y\sin\theta,\ \dot y=v_x\sin\theta+v_y\cos\theta,\ \dot\theta=\omega\) 能任意方向平移
阿克曼 \(v, \delta\) \(\dot x=v\cos\theta,\ \dot y=v\sin\theta,\ \dot\theta=v\tan\delta/L\) \(v{=}0\) 不能转,最小转弯半径 \(L/\tan\delta_\max\)

L1-Adaptive 三件套(§8.5,简化形式):

部件 公式 作用
状态预测器 \(\dot{\hat x}=u+\hat\sigma-a_s(\hat x-x)\) 用内部模型预测状态,\(a_s>0\) 保证预测误差稳定
自适应律 \(\dot{\hat\sigma}=-\Gamma(\hat x-x)\) 由预测误差更新对扰动的估计,\(\Gamma>0\)
低通滤波 \(u_\text{ad}=\text{LPF}(-\hat\sigma)\) 限制补偿带宽,解耦"估多快"与"补多猛",保鲁棒

总控制 \(u=u_\text{mppi}+u_\text{ad}\)\(\hat\sigma\) 收敛到真实扰动时,真实系统逼近名义。

累积项目 UnifiedSamplingPlanner 本章接口(§8.7):

# cost_source: 'analytic' | 'costmap' | 'critics' —— 决定代价从哪来
planner = UnifiedSamplingPlanner(cost_source='critics',
                                 critic_weights={'goal': 1.0, 'obs': 200.0})
dist, hit = planner.plan(K=400, H=70, iters=30, sigma=0.4)
# 内核 plan() 与 cost_source 无关; 换代价来源只改 _cost(), 内核不动

本章 demo 与可复现结果速查

本章五个核心 demo 都是可独立运行、结果可复现的最小实验。把它们集中成一张表,方便你按需复现、或作为自己实验的起点:

demo 验证什么命题 设置要点 关键结果
代价地图导航(§8.1/§8.2) 采样法只查地图打分、不求导,就能绕障穿缺口 100×100 栅格,中间横墙留缺口,差速车 START(10,50)→GOAL(90,50),K=400,H=70 终点(86,50),撞墙格点 0;缺口偏远或堵死时卡墙边(局部最优)
运动模型对照(§8.4) 换车只换 rollout 模型、内核不动;约束自动被尊重 目标正左侧(0,2),差速/全向/阿克曼,阿克曼 \(R_\min\)=1.83m 差速 0.11m、全向 0.11m 到达;阿克曼 2.00m 到不了(转不够紧)
L1-Adaptive(§8.5) 名义规划 + L1 在线补偿,真实系统逼近名义 2D 无人机直飞,横向风 1.2,L1 三件套 \(a_s{=}8,\Gamma{=}20,w_{cut}{=}12\) 无 L1 漂移 1.45m → 有 L1 0.37m;时变/阶跃风也能补
Neural-MPPI(§8.6) 学习残差嵌 rollout、只前向不求导 2D 到(9,0),途经 +y 水流区,名义模型 vs 名义+学习残差 仅名义漂移 1.08m → 名义+残差 0.37m
cost_source(§8.7) 代价来源是和优化正交的一根旋钮 带墙导航,analytic / costmap / critics 三种代价来源 analytic 撞墙 8 格(无障碍意识);costmap/critics 撞墙 0 格

几个复现提示:所有 demo 都用 numpydefault_rng(seed) 固定随机种子,结果可复现(不同 NumPy 版本下数值可能有末位差异,但定性结论稳定)。这些 demo 刻意做成"最小但有代表性"——它们用玩具规模(小栅格、低维、短时域)让你能秒级跑完、看清机制,但每一个的逻辑都和真实系统(AutoRally、Nav2、L1-MPPI)一致,只是规模和保真度的差别。建议的学习方式:先原样跑通复现结果,再按各节练习里的变体改一改(移缺口、调增益、换模型),亲手观察机制如何随之变化——改一个变量、观察结果、理解因果,这是把"看懂 demo"变成"理解方法"的关键。


研究与实践建议

按"用 / 调 / 研究"三个层次给建议:

如果你只是想用(做一个能导航的机器人): - 直接上 Nav2 MPPI Controller,别自己从零写。先用默认参数跑通,再按"症状→critic"表(§8.3)针对性调。 - 运动模型一定设对(§8.4),这是最容易忽略又最致命的配置。 - 在多样场景测试(窄通道、动态障碍、不同起止),别只在空场调出参数就部署(§8.3 陷阱 3)。

如果你要调/改(定制导航行为或扩展功能): - 调参遵守"一次只改一个权重"的纪律(§8.3 陷阱 1),建立每个 critic 的因果认知。 - 要加新目标就加新 critic(插件化),别去改 MPPI 内核(§8.4、§8.7)——内核-插件分离是 Nav2 可扩展的根基。 - 有显著模型误差/扰动时,考虑加 L1 这类抗扰外环(§8.5),它和你的规划器正交,几乎免费。 - 换代价来源/加学习成分时,先想清楚量纲平衡(§8.7)和学习的新风险(§8.6 陷阱 3)。

如果你要做研究(推进采样导航前沿): - 前沿与开放问题那节列的方向都值得深挖,尤其:感知不确定性传播到控制(连接不确定性规划)、动态障碍的预测式规划(连接博弈规划)、采样 MPC + 硬安全保证(连接安全滤波)。 - 做无人机/真车实验时,分清"仿真验证"和"真机验证"——很多采样 MPC 工作止步仿真(如 L1-MPPI),真机的状态估计、延迟、模型误差才是硬骨头(§8.2、§8.5)。 - 用"内部规划轴 + 外部鲁棒化轴"(§8.7)的框架审视你的新方法:它在改哪根轴?能不能做成正交模块、不污染内核?


动手前 checklist

真正去部署/实现导航采样 MPC 前,过一遍这张清单:

  • 代价地图:是否实时更新?MPPI 查的就是它?查询是批量张量操作(非 Python 循环)?坐标越界 clip 了吗?(§8.1)
  • 运动模型:是否匹配真车(差速/全向/阿克曼)?控制量 clip 到物理范围(转向角、速度上限)了吗?约束写进 rollout 了吗?(§8.4)
  • 全局引导:有全局规划器提供粗路径吗?PathFollow 类 critic 启用了吗?(无全局地图才考虑 GP 子目标)(§8.3、§8.6)
  • 状态估计:估计准吗?延迟测过吗?高速场景做状态预测补偿了吗?(§8.2)
  • critic 权重:在默认值附近按比例调吗?一次只改一个吗?各分项量纲平衡吗?(§8.3)
  • 鲁棒性:有显著扰动/模型误差吗?需要 L1 这类抗扰外环吗?若用 L1,单独验证过它收敛吗?(§8.5)
  • 学习成分(若用):训练域覆盖够吗?域外有兜底(残差形式/不确定性)吗?rollout 只前向调用网络吗?(§8.6)
  • 安全:避障是软代价,安全关键场景加硬安全检查了吗?(§8.7)
  • 测试覆盖:在多样代表性场景(窄通道、动态、噪声)测过吗,而非只在简单 demo?(§8.3)

版本信息速查

本章涉及的工作与工具的关键版本/出处信息(截至成文):

工作/工具 出处/版本 备注
Aggressive Driving with MPPI Williams 等, ICRA 2016, pp.1433-1440 MPPI 真车奠基;作者 Williams/Drews/Goldfain/Rehg/Theodorou
Information-Theoretic MPC Williams 等, IEEE T-RO 2018, 34(6):1603-1622 上文期刊扩展
AutoRally 平台 AutoRally/autorally (GitHub) 1/5 比例 R/C 越野卡车底盘(电动), Mini-ITX 四核 i7 + GTX 750 Ti + 32GB
Nav2 MPPI Controller Budyakov 原创,Macenski 移植,ROSCon 2023 nav2_mppi_controller;普通 i5 上 50+ Hz
L1-Adaptive MPPI Pravitra 等, IROS 2020, pp.7661-7666 仿真竞速验证(FlightGoggles);注意是 IROS 非 CDC
GP-MPPI Mohamed/Ali/Liu, IROS 2023 稀疏 GP 推子目标;开源 ROS 包,Jackal+Gazebo
ROS 2 / Nav2 持续演进 参数默认值可能随版本微调,以官方文档为准

注:Nav2 的参数默认值(如 batch_size、critic 权重)可能随 ROS 2 发行版微调,部署时以你所用版本的官方文档为准。本章所列为成文时的代表性默认值。


本章关键事实与数字速查

把全章散落的、容易记混的关键事实和数字集中一处,便于查证和复习(也是自检"读对了没"的对照表):

事实/数字 出处/备注
AutoRally MPPI 论文 Williams 等, ICRA 2016 非 NeurIPS/CoRL;期刊扩展是 T-RO 2018
AutoRally 车型/算力 1/5 比例 R/C 越野卡车, GPU(GTX 750 Ti) 状态估计 iSAM2(GTSAM), 控制 ~50 Hz
L1-Adaptive MPPI 论文 Pravitra 等, IROS 2020 注意是 IROS 不是 CDC;仿真竞速验证
GP-MPPI / log-MPPI Mohamed 等, IROS 2023 / arXiv 2203.16599 GP 推子目标 / 正态-对数正态混合采样
Nav2 MPPI 作者 Budyakov 原创, Macenski 移植 ROSCon 2023;普通 i5 上 50+ Hz
Nav2 batch_size 默认 1000(示例配置常用 2000) 采样数 K
Nav2 time_steps 56 rollout 步数 H
Nav2 temperature 0.3 MPPI 的 \(\lambda\)
Nav2 iteration_count 1 官方建议保持 1, 宁可加 batch
Nav2 默认 critic 数 8 个 Constraint/Cost/Goal/GoalAngle/PathAlign/PathFollow/PathAngle/PreferForward
Nav2 全局规划器(阿克曼) Smac Hybrid-A* 考虑最小转弯半径 + 完整足迹, 支持反向
阿克曼最小转弯半径 \(R_\min = L/\tan\delta_\max\) demo 里 \(L{=}1,\ \delta_\max{=}0.5\) → 1.83 m
L1 demo 抑制效果 漂移 1.45m → 0.37m 时变风 1.38→0.13, 阶跃风 1.79→0.45
运动模型 demo 差速 0.11m / 全向 0.11m 到达, 阿克曼 2.00m 到不了 目标正左侧(0,2), 需转弯半径 1m
cost_source demo analytic 撞墙 8 / costmap·critics 撞墙 0 带墙导航
Neural-MPPI demo 仅名义 1.08m → 名义+残差 0.37m 途经水流区

这张表里**最容易记错、最该牢记**的是:L1-Adaptive MPPI 是 IROS 2020(不是 CDC),且是仿真验证——这是引用时最常出错的地方。其次是 Nav2 的几个默认参数(time_steps=56temperature=0.3iteration_count=1),调参时要以你所用 ROS 2 版本的官方文档为准。

本章本质洞察一览

本章在各节给出的"本质洞察"(用 > 标出的那些)是全章最该带走的思想。集中成一份索引,方便你回顾、也方便你检验自己是否抓住了重点:

  • 可微性要求降到零(§8.1):采样法对动力学和代价都不要求可微,腿足(动力学侧不可微)和导航(代价侧不可微)同属"求值可行、求导困难"的主场。
  • 按 rollout 特性选硬件(§8.2):规整大批量(车/无人机)→GPU,含接触中等样本(腿足)→CPU;让 rollout 特性替你决定。
  • 瓶颈常在不性感的那一柱(§8.2):真车表现的上限常是状态估计/感知,而非控制算法;克制"只盯控制调"的冲动。
  • 调试可定位 = 好设计(§8.3):模块化(critic)让每种不良行为对应一个可独立调的旋钮;这是评价多目标控制器设计好坏的通用标准。
  • 复杂系统靠分层而非全能(§8.3):全局/局部、正常/恢复各司其职;别让一层包打天下。
  • 内核-插件分离(§8.4):采样器/加权是内核(写一次到处用),运动模型/代价是插件(按问题换);换车只动插件。
  • 导数层次是设计旋钮(§8.4):控制量选在位置/速度/加速度哪一层,在响应性和平滑性间权衡。
  • 规划-执行分离(§8.5):让规划在名义模型上自由施展,让一个专门的快速底层环(PD/L1)负责把真实系统按住、表现得像名义。
  • 轻功能分离出去做高频环(§8.5):抗扰比规划轻得多,分离成高频环可"花小钱办大事"(及时性 + 鲁棒性,近零额外算力)。
  • 用结构解耦冲突目标(§8.5):L1 用低通滤波把"快速估计"和"稳定补偿"解耦,不必在二者间折中。
  • 局部方法靠注入全局信息扩展能力(§8.6):A* 地图、学习的价值函数、GP 子目标,都是给"看不到全局"的局部优化器注入远程信息,形异神同。
  • 采样只求值不求导 → 嵌任意学习模型都顺滑(§8.6):黑箱网络直接 forward() 当动力学,无需可微/导出。
  • 已知用知识、未知才用学习(§8.6):残差学习让数据只为"你确实不知道的"买单。
  • 不确定性让系统"知道自己的无知"(§8.6):贝叶斯方法(GP)给的方差,使系统能确信处利用、无把握处探索/谨慎。
  • 设计空间 = 内部规划轴 + 外部鲁棒化轴(§8.7):把复杂控制器分解成正交的两类轴,按需独立拨动。
  • 简单是需要主动捍卫的美德(§8.7):会加旋钮,也要知道何时不加;别为灵活而灵活。

把这些串起来,它们其实在反复讲同几件事:采样法的开放性(不挑模型、不挑代价)源于"只求值不求导";复杂系统的掌控靠"正交分解 + 清晰分层";以及面对不确定/未知,用反馈重规划、用学习补差、用不确定性指导决策。 这些思想远超采样 MPC 一个领域——它们是你做任何复杂机器人/系统时都用得上的"道"。


跨章综合应用

把本章和前面几章的知识串成一个完整的综合场景,看它们如何协同。

场景:给一台户外配送机器人(阿克曼底盘)做一套鲁棒导航。 它要在半已知的园区里送货,会遇到行人、有上下坡和风、园区地图只有粗略版本。用本书学到的东西,你会这样搭:

  1. 内核(第 2–3 章):MPPI 主循环——采样控制序列、rollout 打分、指数加权、滚动时域、warm-start。这是不变的地基。
  2. 运动模型(§8.4):用阿克曼模型 rollout(配送机器人是阿克曼底盘),把最小转弯半径、速度上限写进 rollout,保证规划的路真车走得出来。
  3. 代价来源(§8.1、§8.3、§8.7)cost_source='critics'——避障 critic 查实时代价地图(感知园区障碍和行人)、路径 critic 跟随粗全局路径、目标/朝向 critic 管到达。按"症状→critic"调权重。
  4. 全局引导(§8.3):用粗园区地图跑全局规划器出粗路径,MPPI 跟踪它并实时避开行人——全局管拓扑、局部管避障。
  5. 退火搜索(第 5 章):园区里有窄通道(如门、栅栏口),用退火日程(schedule)让 MPPI 在强非凸处先大方差探索、再小方差精修,提升穿窄通道的成功率(呼应第 7 章钥匙叠加)。
  6. 学习模型(第 6 章、§8.6):上下坡导致的动力学变化用学习残差补——model_source 接一个学好的残差网络(只前向、不求导),让 rollout 预测更准。
  7. 鲁棒外环(§8.5):户外有风,套一层 L1 自适应外壳在线补偿风扰,让真实轨迹贴住 MPPI 在名义模型上规划的路。
  8. 硬安全(§8.7 陷阱 3、后续方向):行人安全关键,避障软代价之外加一层硬安全检查/滤波,否决任何会碰到行人的控制。

你看——这台机器人的导航系统,几乎用上了全书每一章的一个零件,而它们各管一根正交的轴、彼此不打架:内核管优化、运动模型管"车怎么动"、cost_source 管"代价从哪来"、全局规划管"远程拓扑"、退火管"搜得动"、学习残差管"模型准"、L1 管"抗扰"、安全滤波管"硬安全"。这就是采样 MPC 工程落地的全貌——不是某个神奇算法,而是一组正交能力的有机组装。 能把这张图在脑子里画出来、知道每个需求该挂在哪根轴上,你就真正掌握了这个方向。

换一个差异很大的场景——室内竞速无人机——再演示一遍,看同一套思维如何适配截然不同的需求。 这台无人机要在布满障碍门的室内赛道上尽快穿门飞完,对手是时间(要激进、贴极限)而非其它智能体,且室内有气流扰动、无人机动力学快而非线性。用本书的零件:

  1. 内核(第 2–3 章):还是 MPPI 主循环,但因无人机动力学快、要高频,规划频率要更高、时域可短一些。
  2. 动作空间(第 7 章 §7.5):无人机用力/姿态级控制(要敏捷,位置目标太"钝"),表现力优先——这和配送机器人偏好平稳的速度级控制相反,因为竞速场景把"敏捷/极限"排在"平稳"之前。
  3. 代价来源(§8.1、§8.7)cost_source 这里偏向解析 + 已知门位置(竞速门位置已知,§8.5 论文设定),代价是"按顺序穿门 + 最短时间 + 不撞门框"——和配送机器人"查实时感知地图避行人"不同,因为赛道是已知结构化的。
  4. 远见(第 5/6 章):要"提前为下一个门摆好姿态",终端代价/价值函数(第 5 章)帮它不只顾眼前这个门、还考虑下一个门的入射——这是竞速里"过弯走线"的关键。
  5. 鲁棒外环(§8.5):室内气流扰动 + 激进飞行放大模型误差,L1 自适应外环在这里**比配送场景更关键**——因为越激进、模型误差影响越大,L1 把真实飞行掰回名义规划,正是 §8.5 论文要解决的核心(竞速正是 L1-MPPI 的验证场景)。
  6. 学习增强(§8.6):无人机高速气动难精确建模,可用学习残差补——但要注意激进飞行常把状态推到训练域外(§8.6 陷阱 1),残差形式的域外兜底在这里尤其重要。

对比这两个场景(配送机器人 vs 竞速无人机),你会发现**同一套"内核 + 正交轴"的思维框架,能适配需求截然相反的两个系统**——配送要平稳安全(位置级控制、实时感知避障、软硬安全兜底),竞速要激进极限(力级控制、已知赛道、L1 抗扰保激进下的鲁棒)。变的是每根轴的**取值**(控制量层次、代价来源、要不要 L1),不变的是**分解和组装的方法**。这正是本章(和全书)最想给你的:不是一套固定配方,而是一种"按需求在正交轴上做选择、再组装"的设计能力——同一框架,你既能搭出沉稳的配送机器人,也能搭出疯狂的竞速无人机。能对这两个差异巨大的场景都说清"每根轴该怎么选、为什么",你对采样 MPC 的掌握就不是纸上的了。


跨章综合思考题

这几道题需要综合本章和前面 2–3 章的知识,目的是把知识连成网而非孤岛:

  1. (采样 vs 梯度,跨第 1/8 章) 第 1 章讲采样式 vs 梯度式 MPC 的根本取舍。请用本章的具体系统论证:(a) AutoRally 为什么用采样而非梯度式(代价来自 CNN、轮胎动力学非线性——梯度式会卡在哪);(b) Neural-MPPI"网络无需可微"相对梯度式 Neural-MPC 的优势,本质是第 1 章哪个取舍的体现;(c) 是否存在导航场景反而梯度式更合适(提示:代价光滑可微、要极高频、要最优性保证时)。
  2. (代价模块化的三个化身,跨第 4/7/8 章) 第 4 章 §4.5 的 Nav2 critic、第 7 章 §7.2 的 STORM cost buffer、本章 §8.3 的 critic 栈,是"代价模块化"的三个实例。请综合论证:(a) 三者在实现机制上的异同;(b) 这个模块化思想带来的可测试/可调/可复用三个红利,分别在"调 Nav2 让机器人过窄通道"这个具体任务里如何体现;(c) 把累积项目的 cost_source='critics' 和这三者联系起来——它们是不是同一种设计观。
  3. (远见的三种注入,跨第 5/6/8 章) MPPI 视野有限,本书给了三种补法:第 5 章用学习的价值函数当终端代价、第 6 章学习世界模型做长程 rollout、本章 §8.6 用 GP 推子目标 / §8.3 用 A* 全局规划。请论证:(a) 这四种"注入远见"的方式各自把什么形式的远程信息喂给了 MPPI;(b) 它们各自需要什么前提(离线训练?全局地图?在线感知?);(c) 给定"机器人进入一个完全未知、无地图、无法离线训练的新环境",哪种最合适、为什么。
  4. (规划-执行分离的谱系,跨第 7/8 章) 第 7 章 §7.5(位置目标 + PD 底层)和本章 §8.5(MPPI + L1 外环)都是"规划-执行分离"。请综合论证:(a) 两者用什么底层环吸收模型误差,各自能力与局限;(b) 把它们和"力矩级直接控制"(无分离)放在一个谱系上,从"表现力强但脆弱"到"鲁棒但需分层"如何排列;(c) 设计一个三层结构(MPPI 规划 + 位置目标/PD + L1 再外挂)各层分别管什么,这样叠加有没有意义。
  5. (钥匙叠加的迁移,跨第 7/8 章) 第 7 章 §7.4 讲腿足全身 MPPI 靠"降维 + 退火 + warm-start"三把钥匙治维度灾难。请论证:(a) 这三把钥匙在本章导航场景里还用得上吗,分别对应什么(提示:控制序列参数化、退火穿窄通道、滚动 warm-start);(b) 导航相比腿足,维度灾难是更轻还是更重,为什么;(c) 由此说明"采样 MPC 的核心技巧是跨领域可迁移的"这个判断。
  6. (误差累积与远见,跨第 5/6/8 章) 本章 §8.6 深一层讲了多步 rollout 的误差累积,第 5 章讲了用学习的终端价值函数补远见。请综合论证:(a) 为什么"短时域 rollout + 学习的终端价值函数"能同时缓解误差累积、又保住远见,是个比"一味加长时域"更好的组合;(b) 这个组合里,采样内核、学习的价值函数(第 5 章)、学习的动力学(第 6 章)各扮演什么角色;(c) 它和 §8.6 的 GP 推子目标都在"补 MPPI 的视野",二者的适用前提(有无离线训练、有无地图)有何不同。
  7. (硬件-算法的协同设计,跨第 7/8 章) §8.2 讲 AutoRally 用 GPU、Nav2 用 CPU,第 7 章讲 MJPC 用 CPU、腿足趋向少样本。请综合论证:(a) "用多少样本""rollout 重不重接触/分支""要不要长序列"如何共同决定硬件选择;(b) 为什么"提高样本效率(用更少样本达到同等质量)"能改变硬件选择的格局,甚至让采样 MPC 下沉到嵌入式平台(前沿三);(c) 给定一个新任务,你会按什么步骤决定它的硬件和样本预算。

扩展综合练习库

除每节末的练习外,这里再给一组**跨节、贴近真实工程**的综合练习,难度偏高,适合想把本章学深、学透的读者。它们大多没有唯一答案,重在训练你综合运用本章知识、做工程判断。

  1. (系统设计·室内配送,⭐⭐⭐) 为一台差速底盘的室内配送机器人设计完整导航方案:会遇到行人、玻璃门(激光雷达可能测不到)、电梯。请按"采样 MPC 落地统一流程"(本章那一节)逐步给出你的设计——三柱怎么搭、运动模型选什么、代价怎么组织(哪些 critic)、全局引导用什么、哪些场景需要特殊处理(玻璃门?电梯?)、要不要鲁棒外环/学习成分。重点说明每个选择的理由。

  2. (诊断·真车排错,⭐⭐⭐) 你部署的 Nav2 MPPI 机器人出现以下症状,请分别用本章知识给出至少两种可能根因和诊断步骤:(a) 在空旷处正常,一进窄走廊就来回晃、过不去;(b) 直道稳,过弯时轨迹明显"切内道"蹭到弯道内侧;(c) 平时好,某些时段(如阳光强烈时)突然频繁急停或乱走;(d) 机器人卡在一个早已搬走的"幽灵障碍"前不动。

  3. (对比·选型决策,⭐⭐⭐) 给定三个项目,分别论证你会更靠近本章哪个系统/方法的思路,以及为什么:(a) 一家仓储公司要给 500 台同款 AGV 部署导航,要求稳定、可维护、招得到会调的工程师;(b) 一个研究组要让四旋翼在有阵风的室外做敏捷穿越;(c) 一台机器人要进入完全未知、无地图的灾后建筑探索导航。

  4. (实现·正交组合,⭐⭐⭐⭐) 在累积项目 UnifiedSamplingPlanner 上,实现"cost_source='critics' + 第 5 章退火 schedule + 一层 L1 外壳"的三重组合,在一个带未知扰动 + 双障碍的导航任务上跑。验证三者正交(关掉任一个不影响另外两个的接口),并对比"全开"vs"逐个关掉"的效果,说明每个组件各自贡献了什么。

  5. (思辨·软硬约束,⭐⭐⭐) §8.7 陷阱 3 说避障是软代价、非硬约束。请论证:(a) 为什么采样 MPC 天然倾向把约束软化成高代价,而非硬约束(提示:采样和加权的机制);(b) 软代价避障在什么情况下会"失手"让机器人碰障;(c) 设计一个最简单的"硬安全检查"层套在 MPPI 外(不需要复杂的 CBF,就用否决逻辑),描述它怎么工作、和软代价如何配合。

  6. (迁移·跨领域,⭐⭐⭐⭐) 本章把第 7 章腿足的采样 MPC 内核迁移到了导航/自驾/无人机。请反向思考:本章学到的导航专属经验里,哪些能反过来用于腿足或机械臂?具体论证至少两条(提示:代价来自感知地图的思路用于机械臂的避障、L1 抗扰外环用于腿足应对未知负载、运动学约束内生于 rollout 用于带约束的机械臂)。

  7. (开放·前沿设想,⭐⭐⭐⭐) 从本章"前沿与开放问题"里挑一个(如动态环境的预测式规划、感知不确定性传播、采样 MPC + 硬安全),设想一个具体的技术方案:它会在本章哪个系统/方法基础上改、改哪一部分、用什么新技术、可能遇到什么困难。这道题为你接续后续方向(不确定性规划、博弈规划、安全滤波)做准备。

这些题的共同特点是**没有标准答案、需要权衡、贴近真实工程决策**——这正是从"学过采样 MPC"到"能用采样 MPC 解决真实问题"要跨过的鸿沟。建议挑两三道认真写出你的完整思路(而非想个大概),写的过程会逼你把本章知识真正串起来、查漏补缺。


本章给你的可迁移判断力

抛开 AutoRally、Nav2 这些具体系统,本章真正想沉淀给你的是几条**跨系统、跨领域可迁移的判断力**——它们在你以后遇到任何采样控制/机器人系统问题时都用得上:

  • 判断一个问题适不适合采样法:看它的动力学和代价里,有没有哪一处是"能算出来、但不好求导"的(接触、感知地图、黑箱网络)。只要有,采样法就值得一试,且往往最省事(§8.1)。这把"求值 vs 求导"的尺子,比记住"腿足用采样、导航用采样"这些结论有用得多。
  • 拆解一个真实机器人系统:用"三柱 + 外围"的系统视角——状态估计、感知(代价)、控制(内核)三柱,加上运动模型、鲁棒外环、安全等外围。遇到问题先定位是哪一柱/哪一层,而非笼统怪"算法不行"(§8.2、故障排查)。
  • 按负载特性选硬件:看 rollout 重不重接触/分支、要多少样本——规整大批量(车/无人机)默认 GPU,含接触中等样本(腿足)CPU 往往更优。别预设硬件,让 rollout 特性替你决定(§8.2)。
  • 评价一个多目标控制器的设计:好设计让"调试/调参"可定位、可对症(每个行为问题对应一个可独立调的旋钮);坏设计(一坨代价)让调参变玄学。这条标准(§8.3)适用于任何多目标系统,不止 Nav2。
  • 设计"在不确定真实世界里执行复杂规划"的系统:第一反应是拆成"名义模型上的规划(管聪明)+ 抗扰底层环(管鲁棒)",而非用一个无所不能的控制器硬扛。规划-估计/执行分离是反复奏效的架构(§8.5、§7.5)。
  • 正交分解一个复杂控制器:把设计空间拆成"内部规划轴(代价/退火/模型/动作)"和"外部鲁棒化轴(PD/L1/安全)",面对新需求先问它落在哪根轴、能不能独立加一根旋钮/一层外壳,而非改动内核其它部分(§8.7)。
  • 审慎对待"加学习":学习不是免费升级,它把旧问题换成"泛化、数据覆盖、不确定性、分布偏移"等新问题。只在简单方法不够时引入,引入后认真测它的失败模式,并保留非学习的兜底(§8.6)。

这些判断力是本章的"道",AutoRally/Nav2/L1/GP 是"术"。术会过时(具体系统会被新的取代),道不会——它们是你面对下一个没见过的采样控制问题时,真正能依靠的东西。


收束语

本章把采样式 MPC 从前七章的"内核与前沿"带到了"工程落地"——让它在真实的车、真实的无人机、真实的导航栈里跑起来。我们从一个问题出发:当代价不再来自手写函数、而来自感知,当系统要面对真实世界的风、载荷、模型误差和未知环境时,采样 MPC 该怎么落地?

回答这个问题的过程,串起了一条清晰的线:采样法适配导航,是因为代价(来自感知)不可微、而它只查表不求导(§8.1,把第 7 章"动力学不可微"的主场论证扩展到了"代价不可微");AutoRally 用三柱式闭环证明了采样 MPC 能在真车上跑,并立下了至今通用的系统模板(§8.2);Nav2 把它做成插件化、可调、生产级的工业组件,"调 critic 权重"成了一项真实可习得的技能(§8.3);换不同的车只需换 rollout 运动模型,内核一行不动(§8.4,内核-插件分离);L1 自适应外环用"规划-估计分离"让 MPPI 在有扰动的真实世界里鲁棒,几乎不花额外算力(§8.5,呼应第 7 章规划-执行分离母题);GP 推子目标补视野、网络学残差补模型,而采样"只求值不求导"让嵌入任何学习模型都顺滑(§8.6);最后**累积项目接上"代价来源"维度,并看清复杂控制器可分解为内部规划轴与外部鲁棒化轴两类正交轴**(§8.7)。

如果要把本章浓缩成一句话,那就是:采样式 MPC 的内核是稳定的、领域无关的;它在导航/自驾/无人机上落地的全部功夫,在于把这个内核接到具体问题的感知(代价从哪来)、模型(车怎么动、动力学准不准)、约束(运动学、安全)和鲁棒性需求(抗扰外环)上——这不是某个神奇算法,而是一组正交能力的有机组装。 你看到的 AutoRally、Nav2、L1-MPPI、GP-MPPI,都是这同一个内核接上了不同外围零件的产物。

到这里,采样 MPC 这个方向的内核(第 1–6 章)、最难的战场(第 7 章腿足)和最广的落地(第 8 章导航/自驾/无人机)都走过了。但你也看到,本章一直默认着一个被简化的世界:环境基本已知、障碍基本静态、世界里只有"我"一个智能体。真实世界远比这复杂——感知有噪声、障碍会动、还有别的车和行人带着自己的意图与你交互。本章末尾埋下的那些伏笔(软避障不等于安全、感知不确定性、动态障碍的滞后反应),正是通往后续方向的门:如何在不确定的世界里既高效又安全(不确定性规划),如何在有对手与伙伴的世界里行动(博弈规划),如何给控制套上硬安全的保证(安全滤波)。 你在这八章里建立的采样 MPC 系统观——内核 + 感知代价 + 鲁棒外环 + 学习增强、内部轴与外部轴的正交分解——将是你理解那些"更难设定"的坚实地基。带着它,继续往前走。

最后,给读到这里的你一个学习上的提醒。本章(和全书)给了你很多具体知识——AutoRally 的三柱、Nav2 的八个 critic、L1 的三件套、阿克曼的最小转弯半径——但这些"术"不是重点,会随时间更新(具体系统会被新的取代、参数会变)。真正该带走的是那些反复出现的"道":用"求值 vs 求导"判断采样法适不适合;用"三柱 + 外围"的系统视角拆解真机问题;用"内核-插件分离""正交分解""规划-执行分离"组织复杂系统;用"已知用知识、未知用学习""不确定性指导决策"驾驭学习与不确定性。这些思想在本章是导航/自驾/无人机的样貌,但它们会在你做任何复杂机器人/控制系统时反复增值。学完一章,不妨合上书问自己:这章哪几条思想,是我换一个领域也能用的? 能答上来,你就把这章真正学进去了——而不只是记住了几个系统的名字和参数。这也是本书每章都配"可迁移判断力"那一节的用意:让你带走的是判断力,而非待查的事实。 愿你在后续方向里,既长出新本领,也不断印证、深化这些贯穿始终的思想。