前阵子帮一个做无人车路径跟踪的团队看控制方案,他们用的还是经典的"在每个工作点线性化+MPC"那套,结果遇到一个头疼的问题:系统状态离标定点一远,线性化模型就失真,预测出来的轨迹完全不能用,又不敢把MPC预测时域加长,因为非线性在线求解压根算不动。后来我把Koopman算子那套理论搬过去,用Matlab做了个快速验证,效果出乎意料地好——预测模型是全局线性的,控制律求解退化成标准二次规划,计算速度快了两个数量级,而且非线性特征保留得相当不错。这篇文章就把这个思路完整拆开讲清楚。
标题里说的"Koopman模型预测MPC",本质上是用Koopman算子理论把非线性受控动力系统"提升"到一个高维线性空间,然后在这个线性空间里做模型预测控制,省掉传统非线性MPC最头疼的在线优化负担。文章适合这几类读者:正在被非线性MPC求解速度折磨的工程师,想给系统加上先进控制却不知道怎么绕过非线性优化的研究生,以及想弄清楚Koopman算子到底怎么落地而不是停留在理论推导的科研人员。我会把数据准备、字典函数选取、Koopman矩阵拟合、MPC控制器搭建、状态估计配合这一整条链路都走一遍,代码用Matlab实现,最后拿一个倒立摆系统做完整验证。
1. 为什么会盯上Koopman算子:非线性MPC的计算困境
1.1 非线性MPC的"老三难"
做过非线性模型预测控制的人都清楚,真正落地的时候会撞上三堵墙。
第一堵墙是在线求解速度。非线性MPC每个控制周期都要解一个非线性规划(NLP)问题,常用的求解器是IPOPT、fmincon这类,内部要做大量Hessian矩阵计算和迭代收敛,在嵌入式控制器上经常跑不完一个周期。我见过一个工程案例,预测时域设成10步,系统有3个状态2个输入,NLP求解时间就到了200毫秒以上,对控制周期50毫秒的机械系统来说这直接宣判了死刑。
第二堵墙是局部最优问题。非线性规划是非凸的,求解器只能保证找到局部最优解,初始猜测给不好就收敛到某个不可用的解上。这个坑特别隐蔽,因为它不是每次都出错,而是系统运行到某个状态区域才触发,排查起来非常痛苦。
第三堵墙是标定参数多。非线性MPC的代价函数、约束、终端惩罚项里全是权重,每个权重对闭环性能的影响又高度耦合,调参基本靠经验加蒙特卡洛试错。
所以工程上有个很现实的需求:能不能找一个预测模型,它既是全局的而非局部线性化、又让在线优化变成凸问题、还能保留非线性系统的动态特征?Koopman算子理论恰好同时满足这三条。
1.2 Koopman思想的精髓:把非线性问题"提升"到线性空间
Koopman算子的核心思想听起来有点绕,但可以用一句话概括:在原状态空间里是非线性的系统,在某个"观测函数空间"里是线性的。
严格地说,对离散系统 (x_{k+1}=f(x_k)),Koopman算子 (\mathcal{K}) 作用在观测函数 (g) 上满足:
[
\mathcal{K} g(x) = g(f(x))
]
也就是说,(\mathcal{K}) 把"观测当前状态"这件事映射到"观测下一时刻状态",这个算子在函数空间里是线性的。如果我们找到一组坐标变换 (\varphi(x)),使得 (\varphi(x_{k+1}) = K \varphi(x_k)),那么非线性系统在新的坐标下就变成了线性系统 (z_{k+1} = K z_k),其中 (z = \varphi(x))。
这里的关键点是:Koopman算子本身是无穷维的,实际使用时必须做有限维近似。有控制的系统形式稍微改一下,变成:
[
z_{k+1} = A z_k + B u_k
]
这是最常用的受控Koopman线性模型,虽然为了简化丢掉了一些输入和状态之间的耦合项,但在很多实际系统中已经足够用。更高的精度可以考虑双线性形式 (z_{k+1} = A z_k + B (u_k \otimes z_k)),但MPC求解就会从QP变成双线性优化,计算优势就没了,所以工程实现通常还是用线性形式。
1.3 它跟常见的"线性化+MPC"有什么本质差别
很多人会问:这不就是换个方式做线性化吗?跟在每个工作点做泰勒展开有什么区别?
区别在"全局性"。传统的线性化是在某个工作点附近做一阶展开,只在该点附近有效,离开一段距离误差就爆炸。Koopman提升则是找一个全局的坐标变换,把整个感兴趣的状态空间区域映射到高维空间,在这个高维空间里动态是线性的。理论上说,字典函数够多、提升维数够高,近似精度可以无限逼近原非线性系统。
打个生活化的比方:地球表面是非线性的曲面,你直接在球面上规划航线非常麻烦;但如果你先把经纬度坐标提升到三维直角坐标,地面上任意两点间的大圆航线就变成三维空间里的简单几何计算。Koopman做的事情就是这个——找一个合适的"坐标系",让原来复杂的问题变简单,而且这个坐标变换是全局有效的。
这也是为什么我在实际项目里宁可多花时间在字典函数设计上,也不愿意回去调非线性MPC——Koopman模型一旦训好,在线部分就只剩一个二次规划,稳定性、实时性、可维护性全都上一个台阶。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从高维空间回到现实中:有限维近似与EDMD
2.1 字典函数选取——决定模型精度的"隐形手"
Koopman理论看起来漂亮,落地时第一个要解决的问题就是:(\varphi(x)) 到底取什么?这个选择直接决定模型精度,比后续任何一步都重要。
我的经验是,字典函数要从系统非线性特征反推。比如倒立摆系统里有 (\sin\theta) 和 (\cos\theta) 项,字典里就必须包含这两个函数;如果系统有速度相关的阻尼非线性,那就要考虑多项式项 (x_1 x_2) 或高次项。
常用的字典函数有这几类:
- 基本状态本身:(x_1, x_2, \dots, x_n),保证线性部分被覆盖
- 多项式项:一直到3阶或4阶,覆盖主要的非线性特征
- 三角函数项:(\sin, \cos),对摆动、旋转系统至关重要
- 高斯基函数:覆盖局部非线性突变,比如摩擦、间隙
- 输入相关的交叉项:(x_1 u_1, x_2 u_2) 等,能够捕捉输入与状态耦合
实际选取时有个实用技巧:先用包含大量函数项的字典训练模型,然后用稀疏回归(类似SINDy的思路)把系数接近零的项去掉,这样既避免了手工筛选的盲目性,又能得到一个紧凑的模型。在Matlab里这个操作很好做,OLS求解之后直接看系数矩阵,阈值以下的项置零再重新拟合。
2.2 数据采集:激励信号设计和快照对构建
Koopman模型是数据驱动的,数据质量决定了模型质量,这一环不能马虎。
第一步是设计激励信号。训练输入 (u) 必须充分激励系统的各个模态,只在平衡点附近小幅抖动的数据学不到全局非线性特征。我的做法是叠加伪随机二值序列(PRBS)和扫频信号(Chirp),PRBS负责激发宽频动态,Chirp负责跑遍工作区间。幅度要覆盖系统实际运行的所有工况,但也要注意别把系统推到不安全的区域——我吃过一次亏,信号幅度太大直接把实验台架的保护机构触发了。
第二步是生成训练快照对。对离散系统,我们需要的数据形式是:
[
X = [x_1, x_2, \dots, x_N], \quad X' = [x_2, x_3, \dots, x_{N+1}], \quad U = [u_1, u_2, \dots, u_N]
]
把状态和输入代入字典函数,得到提升状态矩阵:
[
\Phi_X = [\varphi(x_1), \dots, \varphi(x_N)], \quad \Phi_{X'} = [\varphi(x_2), \dots, \varphi(x_{N+1})]
]
这里有个细节:如果需要用控制项,通常输入不需要提升,直接保留原始值 (u_k) 就行,因为控制输入往往只以线性方式进入模型。
2.3 最小二乘拟合Koopman矩阵:公式与Matlab实现细节
有了快照对,拟合Koopman矩阵就是一个标准的最小二乘问题。模型形式是:
[
z_{k+1} = A z_k + B u_k
]
令 (K = [A \quad B]),数据矩阵 (\Gamma = [\Phi_X; U]),目标就是最小化 (|\Phi_{X'} - K \Gamma|_F^2)。解析解是:
[
K = \Phi_{X'} \Gamma^\dagger
]
其中 (\Gamma^\dagger) 是伪逆。在Matlab里,这行代码就够了:
matlab复制Gamma = [Phi_X; U_train];
K = Phi_X_next / Gamma; % 相当于 Phi_X_next * pinv(Gamma)
A = K(:, 1:Nz);
B = K(:, Nz+1:end);
这里强烈建议用 mrdivide(即 /)而不是显式调用 pinv,因为斜杠运算会根据矩阵的秩自动选择最小二乘算法,数值稳定性更好。另一个细节是数据归一化。如果状态变量量纲差异很大,比如位移是0.01量级、速度是10量级,直接拟合会导致大数值变量主导损失函数,Koopman矩阵的病态程度很高。先把数据归一化到[-1,1]区间再拟合,训完保存归一化参数,在线推理时再处理一遍。这个步骤能解决绝大多数拟合不稳定的问题,属于典型的花小钱办大事。
训练完成之后一定要做验证:用单独的验证数据集跑一遍开环预测,对比真实状态和提升状态逆映射后的预测值。如果验证集误差明显高于训练集,说明过拟合了,减少字典函数数量或者增加数据量。
3. 模型预测控制器的搭建:如何在提升空间里做最优化
3.1 提升状态与控制量的约束表达
拿到线性模型 (z_{k+1} = A z_k + B u_k) 之后,MPC的设计就变得非常清爽了,因为预测模型是线性的,约束是线性的,代价是二次的,整个最优化问题是一个标准的凸二次规划(QP),全局最优解有保证。
不过这里有个绕不开的细节: (z = \varphi(x)) 是高维空间里的坐标,物理意义不在原始状态空间,约束怎么加?
输出约束要回到物理量上。假设我们有映射关系 (y = C z),把提升状态映射回原始物理输出。一个简单的做法是在训练数据上求解最小二乘:
matlab复制C = Y_train / Phi_X;
其中 (Y_train) 是训练时记录的物理输出序列。有了 (C),输出约束就可以安全地写成:
[
y_{\min} \le C z_j \le y_{\max}
]
对输入约束,因为 (u) 没有提升,直接写成上下界约束就是:
[
u_{\min} \le u_j \le u_{\max}
]
关于状态的约束也是同理,物理状态 (x) 如果作为字典的一部分包含在提升状态里,可以直接对相应的坐标分量加约束;如果字典里没有显式的物理状态分量,就得靠 (C) 矩阵把那部分映射出来再加约束。这么处理之后就完全绕开了"高维状态没有物理意义"的坑。
3.2 代价函数设计与权重标定
代价函数这一块是新手最容易懵的,因为提升状态的各个维度没有任何物理含义,直接对 (z) 写二次型权重根本没有直觉。
我的做法是:不在 (z) 上直接设权重,而是把代价映射回物理输出 (y = C z)。假设我们关心的是输出误差 (y - y_{\text{ref}}),代价函数写成:
[
J = \sum_{j=0}^{N-1} \left[ (y_j - y_{\text{ref},j})^T Q_y (y_j - y_{\text{ref},j}) + u_j^T R u_j \right]
]
把 (y_j = C z_j) 代进去,展开之后就变成关于 (z_j) 的二次型,加权矩阵是 (C^T Q_y C)。这样操作的好处是权重矩阵 (Q_y) 和 (R) 仍然对着物理量,比如"角度误差权重是5,角速度误差权重是0.1",调参直觉完全保留。
还要注意终端代价。MPC的预测时域有限,如果没有终端代价,控制序列倾向于"只管前面几步、不管末端状态",导致闭环性能下降甚至稳定性问题。常见做法是训练完成后解一个黎卡提方程得到终端加权矩阵 (P),或者在仿真里微调一个对角矩阵让终端状态尽快归零。工程上我通常先这么做:预测时域内代价正常设计,终端代价 (z_N^T P z_N) 里 (P) 取小幅值对角阵,然后看闭环曲线微调。
3.3 QP求解器选型:quadprog还是OSQP
模型已经变成线性,在线求解就是凸QP,这给了我们很大的选择权。
Matlab自带的 quadprog 是最省事的方案,适合快速验证。它对中小规模问题(决策变量几百维)求解速度很快,而且接口稳定,不用额外装包。把MPC问题整理成标准形式传给 quadprog 就行,注意要把多步的状态变量和控制变量全部写成一个大决策向量。
如果要在仿真里模拟嵌入式实时场景,或者预测时域较长导致QP规模变大,我建议用OSQP,它专门为嵌入式QP求解设计,利用交替方向乘子法,求解速度比 quadprog 快很多倍。Matlab里可以调用OSQP的Mex接口。它在求解小规模问题上一般几十毫秒内能完成几百次迭代。
我的经验是:先拿 quadprog 把控制方案跑通,验证Koopman模型精度和控制效果,再考虑换OSQP做实时化。不要一开始就上高速求解器,否则问题没想清楚,调试成本还翻几倍。
3.4 闭环控制流程与时域结构
Koopman MPC的闭环控制流程跟标准MPC完全一致:
- 在当前时刻 (k) 获取系统状态 (x_k)
- 计算提升状态 (z_k = \varphi(x_k))
- 求解QP,得到最优控制序列 ([u_k, u_{k+1}, \dots, u_{k+N-1}])
- 只将第一个控制量 (u_k) 作用于系统
- 进入下一时刻,重复上述过程
这个流程里有两个实践上的关键点。
第一个是预测时域 (N) 的选择。Koopman模型虽然是线性的,但它毕竟是有限维近似,预测步数越多误差累积就越大。不能因为求解快了就无限加大预测时域。我一般从 (N=10) 开始试,观察闭环性能和计算耗时,逐步增加,直到性能没有明显提升为止。
第二个是控制量直接作用于真实非线性系统。注意Koopman模型只是预测模型,实际系统还是原来的非线性动力学,MPC算出来的控制量会送到真实系统。这意味着如果Koopman模型精度不够,闭环照样会出问题。所以在搭建MPC之前,先确保开环预测精度过得去,否则后续所有调参都是在错误模型上浪费时间。
4. 状态估计怎么配合:从量测到提升状态的一跳
4.1 为什么标题里还带着"状态估计"三个字
Koopman MPC跟别的MPC有个显著区别:标准MPC直接基于物理状态 (x) 做预测,而Koopman MPC的预测基于提升状态 (z),所以要求我们在每个控制周期都能拿到当前的 (z_k)。
如果系统状态可以直接测量,那就简单了:测到 (x_k),直接算 (\varphi(x_k)) 就完事。但实际系统往往只能测到部分输出 (y_k),全状态可能是缺失的、带噪声的,甚至某些状态没有传感器。这时候就需要一个状态估计环节,从带噪的局部量测中恢复出提升状态向量。
更隐蔽的问题是:提升状态 (z) 是虚拟的,没有直接传感器,它必须通过映射关系从量测中重构出来。这个映射关系如果设计得不好,状态估计误差会直接污染MPC的预测,导致控制精度还不如直接用局部线性模型。
4.2 直接映射法:训练一个lifting encoder
最简单粗暴也经常有效的办法是:在训练Koopman模型时,同时训练一个从量测输出 (y) 到提升状态 (z) 的映射。
具体做法是,数据采集时同时记录输出序列 (Y) 和对应的完整状态 (X)(仿真里当然全状态已知,这没问题;实验上可以通过更多的传感器或离线辨识获得)。然后求解:
[
E = \Phi_X / Y
]
这样就得到了一个线性映射 (z = E y)。在线运行时,我们只需要测到 (y_k),就能快速算出 (z_k)。
这个方法的好处是极其轻量,在线计算就是一次矩阵乘法,特别适合实时性要求高的场景。它的前提是输出 (y) 要包含足够的信息来重构提升状态。如果量测太少,比如只有单个标量输出,直接线性映射的信息量可能不够,估计精度就会崩。
如果输出与提升状态之间关系是非线性的,单层线性映射不够,我的做法是训练一个前馈神经网络,输入 (y) 输出 (z),在Matlab的Deep Learning Toolbox里非常好实现。不过做之前先试线性映射,很多时候线性映射的效果完全够用,用神经网络属于过度设计。
4.3 在线估计法:用Kalman滤波处理噪声和部分观测
如果系统量测噪声比较大,或者量测输出和提升状态的映射关系无法离线学准,那就得上在线状态估计。
我们已经有Koopman线性模型:
[
z_{k+1} = A z_k + B u_k + w_k
]
[
y_k = H z_k + v_k
]
其中 (H = C) 是量测矩阵,(w_k) 和 (v_k) 分别是过程噪声和量测噪声。因为系统是线性的,可以直接设计线性卡尔曼滤波器,每个控制周期先做一步预测,再用新量测更新:
matlab复制% 预测
z_pred = A * z_est + B * u_prev;
P_pred = A * P * A' + Q_kf;
% 更新
K_kf = P_pred * H' / (H * P_pred * H' + R_kf);
z_est = z_pred + K_kf * (y_meas - H * z_pred);
P = (eye(Nz) - K_kf * H) * P_pred;
这里 (Q_{kf}) 和 (R_{kf}) 的取值需要根据噪声特性调。有个实用技巧:先用开环仿真数据做一次最大似然估计得到噪声协方差的初值,再在闭环中微调。我在实际调参中发现,(R_{kf}) 稍微给大一点可以让滤波更平滑,但反应会迟钝;给小了则跟随快但噪声大,需要根据控制效果做权衡。
使用Kalman滤波器时,量测矩阵 (H) 可能是非方阵,但 (H P H' + R) 只要满足可观测条件就能保证可逆。如果可观测性不强,建议回到直接映射法,用更多的量测信息来补充。
5. Matlab代码实现:一个倒立摆案例的完整拆解
5.1 系统模型与仿真参数
理论扯了这么多,还是得落到代码上才能看到真实效果。我拿一个典型的非线性倒立摆系统做演示,它的动力学方程为:
[
\ddot{\theta} = \frac{g}{l} \sin\theta - \frac{b}{m l^2} \dot{\theta} + \frac{1}{m l^2} \tau
]
参数取 (g=9.81),(l=0.5),(m=0.2),(b=0.1)。状态 (x = [\theta, \dot{\theta}]^T),控制量 (\tau) 是施加在关节上的力矩,范围限制在 ([-2, 2]) N·m。目标是把摆从某个初始角度摆到竖直向上((\theta=0))的稳定平衡点。这个系统的非线性主要在 (\sin\theta) 和 (\dot{\theta}^2) 的隐含耦合上,传统局部线性化在小角度尚可,大角度完全不可用,用来验证Koopman MPC再合适不过。
仿真步长取 (T_s = 0.02) 秒,训练数据用四阶Runge-Kutta积分生成,共跑5000步。
5.2 数据生成与Koopman模型的离线训练
数据生成阶段,用Chirp激励信号加PRBS信号叠加:
matlab复制% 生成激励信号:chirp + prbs
t_vec = 0:T_s:(N_train*T_s - T_s);
u_train = 0.8 * chirp(t_vec, 0.1, t_vec(end), 2, 'quadratic') + ...
0.5 * idinput(N_train, 'prbs', [0 0.5], [-1 1])';
% 用四阶Runge-Kutta积分非线性系统
x = [-0.5; 0.2]; % 初始状态
X = zeros(2, N_train);
X(:,1) = x;
for k = 1:N_train-1
x_next = rk4_step(x, u_train(k), Ts, dynamics);
X(:, k+1) = x_next;
x = x_next;
end
字典函数我选择 ([x_1; x_2; \sin(x_1); \cos(x_1)-1; x_1^2; x_1 x_2; x_2^2]),共7个基函数,提升维数 (N_z = 7)。注意这里把 (\cos(x_1)-1) 而不是 (\cos(x_1)) 放进字典,是因为 (\cos(0)=1),减掉1之后在平衡点处的值是0,方便线性部分正常工作。
训练Koopman矩阵的核心代码:
matlab复制% 构建字典矩阵
Phi_X = build_dictionary(X(:, 1:end-1));
Phi_X_next = build_dictionary(X(:, 2:end));
U_train = u_train(:, 1:end-1);
% 最小二乘拟合
Gamma = [Phi_X; U_train];
K = Phi_X_next * pinv(Gamma);
A = K(:, 1:Nz);
B = K(:, Nz+1:end);
% 输出映射矩阵(把提升状态映射回物理输出)
Y_train = X(:, 1:end-1); % 这里关注角度和角速度
C_mat = Y_train * pinv(Phi_X);
训练完检查一下特征值,确保 (A) 的特征值都在单位圆内或者附近(因为倒立摆不施加控制时是不稳定的,所以特征值可能在单位圆外,这是正常的)。关键是确认 (\sin) 和 (\cos) 相关的字典分量确实在重塑非线性动态。
5.3 MPC主循环实现
MPC部分我用 quadprog 求解。预测时域 (N=15),采样周期 (T_s=0.02) 秒,所以预测总时长是0.3秒。代价权重:输出角度权重 (Q_\theta=100),角速度权重 (Q_{\dot{\theta}}=1),控制权重 (R=0.1)。
将MPC问题整理成QP标准形式:
matlab复制% QP变量: [z_1; z_2; ...; z_N; u_0; u_1; ...; u_{N-1}]
% 构造预测模型矩阵
[Az_all, Bz_all] = build_prediction_matrices(A, B, N);
% 代价矩阵
Qz = C_mat' * diag([100, 1]) * C_mat; % 提升状态的代价权重
Q_bar = kron(eye(N), Qz);
R_bar = kron(eye(N), 0.1);
H = blkdiag(Q_bar, R_bar); % 需要再拼上终端代价
主循环:
matlab复制x_current = x0;
for k = 1:N_sim
% 计算当前提升状态
z_current = build_dictionary(x_current);
% 构建QP的约束矩阵(状态递推等式约束 + 输入界限)
Aeq = build_equality_constraints(A, B, N, Nz);
beq = [A * z_current; zeros((N-1)*Nz, 1)];
% 输入约束
lb = [-inf(Nz*N,1); -2*ones(N,1)];
ub = [ inf(Nz*N,1); 2*ones(N,1)];
% 求解QP
options = optimoptions('quadprog', 'Display', 'off', 'Algorithm', 'interior-point-convex');
[w_opt, fval] = quadprog(H, f_vec, [], [], Aeq, beq, lb, ub, [], options);
% 将第一个控制量作用于非线性系统
u_applied = w_opt(Nz*N + 1);
x_next = rk4_step(x_current, u_applied, Ts, dynamics);
x_current = x_next;
% 记录状态用于绘图
x_history(:, k+1) = x_current;
end
这里有个容易出错的细节: f_vec 是线性项,它是由参考轨迹产生的。如果参考平衡点是 (\theta=0),而提升状态里有 (\cos) 项,在平衡点处 (\cos(0)-1=0),所以线性项基本为0;但如果参考点不在字典函数的零点,需要把参考提升状态也算出来,然后 (f = -H z_{\text{ref}})。
5.4 我踩过的几个坑
这个案例跑通之前我踩了好几个坑,每个都值得单独提出来说一下。
第一个坑是Q矩阵的单位不一致导致控制发散。最初我直接在提升状态上设对角权重,但对角线各维度的数值量级差异巨大—— (x_1) 在0.1量级,而 (x_1 x_2) 在0.01量级,0.01量级的代价几乎被忽略,导致MPC根本不约束这个维度,预测模型中的这些项就起不到应有作用。改成先对物理输出设权重再通过 (C^T Q_y C) 映射之后,问题立刻消失。所以再次强调:不要在提升状态上直接写权重。
第二个坑是激励信号幅度不足。一开始我用小幅随机信号训练出来的模型,验证时发现大角度区域的预测误差特别大,MPC控制大角度初始状态时完全失效。原因是训练数据根本没去过那些状态区域。后来把Chirp幅度调大、并把它从一个平衡点附近扩展到整个摆角范围,重新训练后模型精度大幅改善。训练数据覆盖范围决定了模型适用范围,这比任何算法技巧都重要。
第三个坑是求解QP时约束矩阵的维度搞混。构建多步预测约束矩阵时,状态维数、提升维数、控制时域三者容易对不上,Matlab会报维度错误或者悄悄算出一个错误的结果。我的建议是先用一个小规模测试(比如 (N=3))手工展开约束矩阵验证正确性,再放大到正式预测时域,省得在15步的矩阵里找bug。
5.5 结果与对比
仿真初始状态设为 (\theta_0 = 1.2) 弧度(约68.8度),这已经远超传统小角度线性化模型的有效范围。Koopman MPC的闭环响应表现很好:系统在约1.8秒内从初始角度收敛到竖直位置,没有明显超调,控制量在约束范围内平顺变化。
作为对比,我同时实现了基于局部线性化(在 (\theta=0) 处线性化)的线性MPC。从同一初始状态出发,线性MPC序列在一开始就给出了错误的控制方向,系统非但没有回到竖直位置,反而被推出了稳定范围。这个对比结果非常有说服力:同样的MPC框架,仅仅是预测模型从局部线性换成了Koopman提升模型,可工作的状态范围完全不同。
计算耗时方面,Koopman MPC单步QP求解平均耗时约3毫秒,而如果用matlab内置的 fmincon 直接做非线性MPC,同样的预测时域平均耗时在400毫秒以上,差了不止两个数量级。这也是我在工程推荐里最有底气的一组数据。
6. 适用边界与工程建议:什么时候该用Koopman MPC
6.1 模型精度与计算效率的权衡
每次做完一个Koopman MPC项目,我都会提醒自己:这不是银弹,它有明确的适用边界。
最适合Koopman MPC的系统特征是"非线性强但结构相对平滑"。倒立摆、机械臂、无人机、移动机器人、化学反应器,这些系统的非线性主要来自三角项、多项式项和耦合项,字典函数能够较好地覆盖;动态本身在状态空间中的变化是连续的,没有突变。这类系统用Koopman提升之后,线性模型精度通常很好。
反过来,如果系统有强不连续性,比如摩擦死区、齿轮间隙、碰撞接触,这些局部突变在有限维字典下很难精确表达。Koopman模型的预测在这些区域会出现明显偏差,MPC的闭环性能就会打折扣。这类系统要么增加局部字典函数,要么考虑别的方案。
还有一个务实的判断标准:预测时域内Koopman模型的开环预测误差是否能控制在可接受范围。如果训练出的模型开环预测三步就发散,那后面MPC调得再好也白搭。碰到这种情况,先回头优化字典和数据,而不是贸然上线。
6.2 容易翻车的情况
我总结了几种特别容易翻车的工况,大家在评估方案时可以直接对照检查。
训练数据与在线工况不一致会导致"实验室效果好、实际一用就废"。如果训练时只在平衡点附近采集数据,在线却有大幅度机动需求,Koopman模型没见过这些状态区域,预测自然不准。对策是训练数据务必覆盖在线可能到达的所有状态空间,留出20%的边界余量。
状态估计环节引入的误差被MPC放大。Koopman MPC对状态估计误差的敏感度通常比线性MPC更高,因为提升状态里的非线性项(比如 (\sin\theta) 和高阶项)即使原始状态只有微小误差,经过非线性变换后误差也会被放大。如果发现闭环抖动厉害,先看状态估计是否收敛,再检查MPC权重,顺序不要颠倒。
字典函数过拟合。字典包含大量函数项时,训练集上拟合误差很小,但验证集上预测误差很大。这是因为高维线性近似把噪声也一并拟合了。解决方法是减少字典规模、增加数据量,或者像前面说的那样做稀疏化。
预测时域太长也会出问题。Koopman模型是有限维近似,预测误差会随时间累积,预测步数越多后期预测越不可靠。我见过有人因为QP求解快就把预测时域从20加到50,结果控制效果反而变差,就是被长时域累积误差坑了。务必用验证数据测试不同预测时域的闭环表现,找到拐点。
6.3 向鲁棒/随机MPC扩展的方向
Koopman MPC最大的价值不仅在于比非线性MPC快,更在于它把非线性控制问题重新拉回到线性系统理论的框架内,这意味着大量线性控制理论工具都可以直接迁移过来。
一个自然的扩展方向是鲁棒MPC。既然模型是线性的,系统不确定性和模型失配可以用有界集来描述,然后设计min-max鲁棒MPC或者tube-based MPC。这些都是线性框架下成熟的方法,直接叠加到Koopman模型上即可,不需要重新发明轮子。
另一个方向是随机MPC。如果系统受随机扰动,可以在Koopman线性模型上设计带机会约束的随机MPC,用终端代价和协方差传播处理概率约束。这类方法在传统非线性系统上实现极其困难,但在Koopman线性框架下就是一个带矩阵不等式约束的优化问题。
我在最近的工作里还尝试了模型预测路径积分(MPPI)与Koopman模型的组合——用Koopman模型做快速采样评估,MPPI负责在采样空间里优化控制序列。效果也相当不错。这说明Koopman MPC的生态远不止"把非线性MPC变快"这一个点,它是一个打通数据、模型与控制设计的新基础框架。
回到最开始那个无人车团队的问题,最终方案就是训练一个Koopman提升模型做路径跟踪MPC,预测模型全局线性,QP求解稳定可靠,计算耗时从数百毫秒降到了个位数毫秒。如果你手头的项目也正被非线性MPC的计算负担卡住,我的建议是先花一周时间把Koopman MPC的流程跑通——数据生成、字典设计、模型训练、MPC闭环,这四步在Matlab里都有清晰的实现路径,跑完你就会发现,非线性控制绕开非线性优化,这条路不仅可行,而且比你想象的宽得多。
