前阵子调一个轮式平台的预测控制器,仿真里预测轨迹画得漂漂亮亮,一上真机就眼看着实际轨迹越偏越远。后来把整套逻辑拆开才发现,根子不在控制器本身,而在“预测出来之后拿什么数学去纠”——也就是标题里说的 Rollout Correction Math。Rollout 这个词,在控制、强化学习、运动规划里说的是同一件事:拿着当前状态和一套动力学模型,把未来若干步的状态先“滚”一遍,得到一条预测轨迹。可预测毕竟是预测,模型有偏差、初值有误差、环境有扰动,滚出来的东西一定会偏。真正决定一个系统好不好用的,往往不是预测本身有多准,而是修正预测那一步的数学。下面把整理过的误差分解、反馈修正、统计修正,加一套能直接抄走的数值算例,一次讲清楚。刚碰 MPC 的读者拿它可以少走弯路;做强化学习评估时被 off-policy 偏差坑过的人,也能在里面找到对应的修正思路。
1. 先把 Rollout 定义清楚:一条预测轨迹是怎么算出来的
1.1 从连续模型到离散递推
先统一符号,后面所有推导都基于这套记号。连续系统的状态方程通常写成ẋ = f(x, u),x 是状态,u 是控制输入。计算机没法解析地把微分方程解到未来,所以第一步一定是离散化。最简单的做法是欧拉法:
x_{k+1} = x_k + dt * f(x_k, u_k)
更稳一点用 RK4,差别只在单步精度。无论用哪种,最终得到的都是一个离散映射g(x, u)。Rollout 就是从当前状态x̂_0出发,把一段控制序列u_0, u_1, ..., u_{H-1}依次代进映射:
x̂_1 = g(x̂_0, u_0)x̂_2 = g(x̂_1, u_1)
…x̂_H = g(x̂_{H-1}, u_{H-1})
这段递推在数学上就是函数的复合。注意这里的 x̂ 是“模型状态”,不是实测状态。整条轨迹完全由(x̂_0, u_0..H-1)决定,中间没有任何新信息注入,所以本质上是开环预测。
注意:哪怕用的是 MPC,每次优化迭代内部生成的那条轨迹也是开环 rollout;“闭环”来自每个控制周期用实测状态重新滚动,而不是轨迹本身是闭环的。这一点想清楚,后面看修正就好理解了。
我把预测误差统一记成δ_k = x_k(actual) − x̂_k。后面所有修正方法,目标都是把 δ 压到一个可接受的范围。
1.2 预测为什么一定会偏
很多第一次写 rollout 代码的人有个错觉:只要模型足够准,轨迹就足够真。实际有三个误差源永远躲不掉:
- 初值误差。x̂_0 来自状态估计,滤波必然有滞后,融合出来的位置不可能绝对等于真值。
- 模型失配。摩擦力、风阻、电压下降、负载变化,真实 f 和代码里的 f 永远有距离。
- 过程噪声。随机扰动,路面颠簸也好、气流也好,这一项是统计意义上的,不是确定性偏差。
用最简单的线性标量系统x_{k+1} = a x_k + b u_k看误差传播:δ_{k+1} = a δ_k + (a_real − a_model) x_k + ε_k。如果 |a| > 1,哪怕初值误差只有一点点,也会指数放大;如果 a = 1,也就是系统里带着积分器,误差永远不会自己衰减;只有 0 < a < 1 的稳定系统,初值误差才会逐渐消散。这和天气预报是同一个道理:初始场差一点点,几天后整条预报轨迹就走样了。控制里更常见的是模型失配这一项,因为被控对象里往往天然带着积分器或者不稳定模态。所以“预测漂了”不是算法没写好,而是误差机制决定的;能改的只有修正这一步。
2. 误差的定量拆解:三类误差各自怎么长
2.1 线性误差递推的推导
在设计修正器之前,先把误差拆开,才知道力气该往哪儿使。假设已经沿名义轨迹 x̂ 把 rollout 滚完,真实系统满足:
x_{k+1} = f_real(x_k, u_k) + ε_k
模型输出是:
x̂_{k+1} = f_model(x̂_k, u_k)
在名义轨迹附近对真实系统做一阶展开,得到x_{k+1} ≈ f_real(x̂_k, u_k) + A_k δ_k + ε_k,其中A_k = ∂f_real/∂x在(x̂_k, u_k)处取值。于是误差递推:
δ_{k+1} ≈ A_k δ_k + b_k + ε_kb_k = f_real(x̂_k, u_k) − f_model(x̂_k, u_k)
这里 b_k 是“在名义轨迹附近,真实模型和所用模型的输出差”,它会随轨迹不同而变化,是模型失配在当前点的局部体现。
把递推一路展开到 H 步之后,就是一条完整的预测误差公式:
δ_H = Φ_{H,0} δ_0 + Σ_{i=0}^{H-1} Φ_{H,i+1} b_i + Σ_{i=0}^{H-1} Φ_{H,i+1} ε_i
其中 Φ 是状态转移矩阵的链式乘积,Φ_{j,i} = A_{j-1} A_{j-2} ... A_i,表示从第 i 步到第 j 步的误差放大。翻译成人话:最终预测误差 = 初值误差被动力系统放大后的残留 + 每一段模型失配沿未来轨迹的累积 + 每一段过程噪声的累积。
2.2 从公式里找修正抓手
这条公式最大的价值不是用来算,而是用来判断:误差是哪一项主导。
| 主导项 | 特征 | 优先处理手段 |
|---|---|---|
| 初值误差 δ_0 | 轨迹整体平移、起点就偏 | 改进状态估计(滤波) |
| 模型失配 b_k | 误差有方向性、逐段累积 | 离线辨识参数,或加反馈修正 |
| 过程噪声 ε_k | 轨迹抖动、重复试验结果分散 | 滤波平滑,反馈增益宜温和 |
模型失配是大多数人第一个想到的,但它在三类里往往不是最优投入点。我在某项目里测过一种很典型的现象:预测轨迹画出来很平滑,真机轨迹却一抖一抖的。一开始我拼命调模型参数,后来把噪声项用滑动平均估出来才发现,误差里随机成分占了大半,钱应该花在滤波和增益上,而不是花在模型辨识上。
判断误差类型的土办法:同一个初始条件、同一条控制序列,多跑几次。如果误差方向和大小基本一致,偏的主要是模型失配;如果结果五花八门,噪声和扰动占大头。这个判断先做了,后面选修正策略才不会跑偏。
3. 反馈修正的数学:闭环预测怎么打断误差累积
3.1 周期重规划:重置初值就是一个修正器
最简单也最常用的修正手段,是每个控制周期都测一次真实状态,把 x̂_0 换成实测值,然后重新滚一条轨迹,只发送第一个控制量。这就是滚动时域(receding horizon)的含义。数学上看,它做的事情是频繁地把 δ_0 强行置零。哪怕模型失配项 b_k 还在,误差也只在单个周期内累积一小段,不会被一路放大到 H 步。
用一个标量例子就能说明差异。假设模型是x̂_{k+1} = 0.9 x̂_k + u_k,真实系统是x_{k+1} = 1.1 x_k + u_k,初始状态 x = 1,目标 0。如果开环一次性算出控制序列 u_0 = -0.9 之后全填 0,模型认为自己一步归零,真实轨迹却从 0.2 开始按 1.1 倍往上长。如果每步实测状态、用同一个错误模型算u_k = -0.9 x_k,真实递推就变成x_{k+1} = 1.1 x_k - 0.9 x_k = 0.2 x_k,误差指数衰减。同一个失配模型,加不加“重置初值”,结果是天壤之别。
代价也明确:每个周期都要重新优化,算力就是硬约束。于是就有了更便宜的折中——固定增益反馈。
3.2 沿名义轨迹的 LQR 反馈增益推导
假设已经有一条名义 rollout(x̂_k, û_k),想在它附近求一个线性反馈修正。偏差记成δx = x − x̂,δu = u − û,代价函数写成:
J = δx_H^T S_N δx_H + Σ_{k=0}^{H-1} (δx_k^T Q δx_k + δu_k^T R δu_k)
离散 LQR 的标准结果是末端反推 Riccati 递推:
S_N = Q_NS_k = Q + A_k^T S_{k+1} A_k − A_k^T S_{k+1} B_k (R + B_k^T S_{k+1} B_k)^{-1} B_k^T S_{k+1} A_kK_k = −(R + B_k^T S_{k+1} B_k)^{-1} B_k^T S_{k+1} A_k
最终控制修正为u_k = û_k + K_k (x_k − x̂_k)。K_k 的含义非常直白:实测状态偏离名义轨迹多少,就额外施加多大的控制修正。R 越大,说明越舍不得用控制量去纠偏,增益越温和;Q 越大,说明越在乎轨迹精度,增益越激进。整个过程就是 iLQR / DDP 的标准结构:正向 rollout 生成名义轨迹,反向扫一遍算出 K_k,再用修正后的控制序列重新 rollout。所谓“校正”,数学上就是这组时变增益。
对线性时不变系统,S 最终收敛为常数解,K 也变成常数,就是我们熟悉的定常 LQR。对时变系统,每个时刻的增益都不同,调试时注意别把某一时刻的 K 拿来给别的时刻用。
4. 蒙特卡洛 Rollout 的统计修正:重要性采样与截断
4.1 偏差从哪来
Rollout 不只出现在控制里。强化学习做策略评估时,常用做法是拿当前策略 π_old 去采样若干条轨迹,把累积回报平均,作为价值估计。问题在于,很多场景下手里只有旧策略采的轨迹,却想估新策略 π_new 的价值——直接平均就是有偏的。
这里说的“修正”,就是重要性采样。轨迹在策略 π 下的概率写成:
P_π(τ) = P(s_0) Π_t P(s_{t+1}|s_t, a_t) π(a_t|s_t)
同一套环境动力学下,新老策略轨迹概率的比值,状态转移项会全部约掉,只剩:
P_π_new(τ) / P_π_old(τ) = Π_t π_new(a_t|s_t) / π_old(a_t|s_t) = Π_t ρ_t
于是得到统计意义上的 rollout correction:
E_{π_new}[G] = E_{π_old}[Π_t ρ_t · G]
每条旧样本乘一个权重,把采样分布拉回目标策略的分布。这条恒等式没有任何近似,是精确的,问题出在实操。
4.2 权重爆炸与 V-trace 截断
数学上无懈可击,实际用起来最大的敌人是方差。如果某一时刻 π_old(a|s) 很小而 π_new(a|s) 不小,ρ_t 会非常大,一条样本就可能把估计值带飞。所以实际系统几乎都要做截断:把单步比例限到 c̄(常用 1),再用一个 λ 系数让远期修正衰减,这就是 V-trace 的核心。
V-trace 的目标值可以写成:
v_t = V(s_t) + Σ_{k≥t} γ^{k−t} (Π_{i=t}^{k−1} c_i) ρ_k [r_k + γ V(s_{k+1}) − V(s_t)]c_i = λ min(ρ_i, c̄)
直观理解:ρ_k 决定“这条样本对分布偏差的修正力度”,c_i 决定“这个修正能往前传播多远”。两个系数叠加,就是偏差和方差之间的旋钮。样本量足够大、动作空间不太高维的时候,不做截断也能用;动作空间一高维,ρ_t 的乘积很容易爆炸。我的建议是:先加截断,样本量不够时再考虑自归一化权重。后者会引入一点点统计偏差,但能大幅稳住方差。
有意思的是,第 3 节的反馈修正是把初值拉回实测,第 4 节的重加权是把采样分布拉回目标分布——一个修状态,一个修分布,本质都是“用新信息纠正旧预测”。
5. 完整数值算例:同一个失配模型,三种方案的真实差距
5.1 算例设置与开环结果
设定仿真系统x_{k+1} = 1.1 x_k + u_k,我们手里拿到的模型是x̂_{k+1} = 0.9 x̂_k + u_k,初始状态 x_0 = 1,控制目标是把状态拉回 0。先看模型给出的开环方案:想让模型一步归零,取 u_0 = -0.9,此后 u 全为 0。模型预测 x̂ = [1, 0, 0, …],但真实系统按 1.1 倍演化:
| k | 模型预测 x̂ | 真实状态 x(开环) | 误差 δ |
|---|---|---|---|
| 0 | 1.00 | 1.000 | 0 |
| 1 | 0 | 0.200 | 0.200 |
| 2 | 0 | 0.220 | 0.220 |
| 3 | 0 | 0.242 | 0.242 |
| 4 | 0 | 0.266 | 0.266 |
| 5 | 0 | 0.293 | 0.293 |
开环 rollout 的误差一旦产生就不再回头,反而一路涨。这在预测控制器里尤其要命:控制器完全相信这条预测,输出的整段控制序列都是错的。
5.2 反馈修正与重规划的效果
接着每步实测状态,用同一个模型重算控制u_k = -0.9 x_k,真实递推就变成x_{k+1} = (1.1 − 0.9) x_k = 0.2 x_k。再放一个温和比例反馈u_k = -0.5 x_k作对照,真实递推为x_{k+1} = 0.6 x_k。三种方案逐拍对比:
| k | 开环不修正 x | 每步重算 x | 温和比例反馈 x |
|---|---|---|---|
| 0 | 1.000 | 1.000 | 1.000 |
| 1 | 0.200 | 0.200 | 0.600 |
| 2 | 0.220 | 0.040 | 0.360 |
| 3 | 0.242 | 0.008 | 0.216 |
| 4 | 0.266 | 0.002 | 0.130 |
| 5 | 0.293 | 0.000 | 0.078 |
每步重算对应的闭环极点是 0.2,收敛最快;比例反馈是 0.6,收敛次之;开环是 1.1,不修正。但代价也要看:设测量噪声标准差 σ = 0.01,每步重算的控制量里噪声放大系数是 0.9,比例反馈只有 0.5,灌进系统的噪声方差分别是 0.81σ² 和 0.25σ²。修正越激进,把测量噪声灌进系统也越猛,这是同一枚硬币的两面。这个标量例子里压缩了所有要点:修正的本质,是拿被测状态置换模型状态,通过反馈把闭环极点往里拉;拉得多狠,取决于你对模型失配和测量噪声的相对忍受度。
6. 实操层面的选择:不同误差该用哪种修正
6.1 修正手段的定位对比
把前面几类修正放一张表里,方便直接对照选型:
| 修正手段 | 主战场 | 主要代价 | 常见坑 |
|---|---|---|---|
| 状态估计与滤波 | 初值误差、传感器噪声 | 每个观测周期一次更新 | 无法补偿模型失配 |
| 滚动时域重规划 MPC | 模型失配、大扰动 | 每周期一次完整优化 | 算力不足被迫降频 |
| LQR / iLQR 反馈增益 | 名义轨迹附近的小偏差 | 每步一次矩阵乘 | 偏差太大时线性化失效 |
| 重要性采样 / V-trace | 离策略样本的分布修正 | 每样本一次权重计算 | 不截断则方差爆炸 |
选型顺序我建议固定:先保证滚动时域重规划是正确实现的,再考虑用固定增益反馈给重规划降频,最后才上统计修正。原因很简单,前两层把确定性误差和随机误差分流处理了,统计修正只处理剩余的小尾巴。
6.2 调试中的几个具体坑
预测时域不是越长越好。很长一段时间我也默认 MPC 的 H 越大越“有远见”,但滚动误差公式写得很清楚:模型失配项和噪声项都随 H 累积。在一个失配 10% 的模型上,所谓远见就是远方的幻觉。正确做法是把 H 压到动态性能需要的最短值,或者对远期误差加权打折扣。
别用一个修正器修所有误差。见过有人把反馈增益调得巨大,试图一步干掉模型偏差,结果系统一上真机就抖——原因就是他把噪声当成失配来修了。最好的做法是先做开环 rollout 对比实测:误差有方向性、反复出现,那是模型失配,反馈修正有效;误差杂乱、同场景测几次都不一样,那是噪声和扰动,得上滤波、降增益。
统计修正记得截断。重要性权重不截断的话,实际跑起来动辄上万,一条样本毁掉整批估计。至少加min(ρ, c̄),并且每批训练后统计平均权重:如果长期偏离 1,说明新旧策略差得太远,修正已经在给不合理的估计打补丁。
最后是代码层面的:iLQR 反向递推时,如果B_k^T S_{k+1} B_k + R接近奇异,K_k 会爆炸。我在调一台 2 自由度机械臂时遇到过,处理方式很朴素:给求逆加正则项,或者把 R 的下限卡住,问题立刻消失。这类数值细节不会写在教科书里,但真机调试时比算法选型更容易坑人。
最后分享一条可以直接抄的小经验:新系统上调试 rollout 修正,先什么都不调,只确保每个周期都用实测状态重新滚动,把单步预测误差压到可接受范围;然后才考虑加固定增益反馈给重规划降频;统计修正那套最后再说。这个顺序我反着走了一次,结果是在错误的方向上白调了很久。Rollout 修正的数学不难,难的是先搞清楚误差里哪一部分值得修、哪一部分只能忍——把这件事想清楚,至少能省掉一半的无用功。