在接触非线性控制的时候,我们通常会被迫做出选择:要么用线性化模型牺牲精度换取求解效率,要么用非线性MPC(NMPC)扛着巨大的计算负担在实时性边缘试探。Koopman算子理论提供了一个非常优雅的中间路线——通过把非线性系统“提升”到高维线性空间,将非线性动力学转化为一个高维但线性的预测模型,从而让常规的线性MPC直接落地。这篇文章我会从Koopman模型预测控制的核心原理、数据驱动建模流程,到Matlab完整实现和实测踩坑记录,把这条技术路线从头到尾拆开讲清楚。
很多做控制的人第一次接触Koopman时都会被它的数学形式劝退,但实际上,它的核心思想可以用一句话概括:既然非线性系统难以直接处理,那就把它“嵌入”到一个更高维的空间里,在这个空间里,原本非线性的演化变成了线性演化。这个想法的直接好处是,MPC的在线优化问题从非线性规划(NLP)退化成了二次规划(QP),求解速度和稳定性都有质的飞跃。
这篇文章适合正在做状态估计、轨迹跟踪、非线性MPC方向研究的同学,也想做工程落地的工程师朋友。文章会覆盖Koopman算子理论的最小必要知识、EDMD数据驱动建模的完整流程、基于Koopman模型的线性MPC设计与Matlab实现,以及我实际调试过程中总结的若干关键技巧和坑点。
1. 为什么要用Koopman把非线性系统“线性化”
1.1 直接线性化方案的局限
在传统的控制工程中,处理非线性系统最常用的手段是在工作点附近做泰勒展开,得到一个局部的线性模型,然后基于这个模型设计LQR或者MPC。这套方法在小范围内非常有效,但在系统状态偏离工作点较远时,模型失配会迅速恶化控制性能。比如一个倒立摆,在竖直位置附近线性化效果很好,但摆角超过一定范围后,线性模型的误差就完全不可接受了。
另一种思路是在线实时线性化,也就是在每个采样时刻重新计算Jacobian矩阵。这样做的代价是计算量大,而且在线性化过程中丢失了系统的全局非线性特征,MPC的预测时域只要稍微长一点,线性化误差就会随着预测步数累积,导致控制效果不可靠。
我以前做过一个气悬浮球实验,用局部线性化MPC在高一点的目标位置跟踪效果尚可,但一旦参考轨迹出现大幅跳变,系统就会因为模型失配产生明显的振荡。那个项目让我意识到,如果能在保持线性预测结构的同时捕捉全局非线性动态,会是一个非常理想的状态。
1.2 Koopman算子的核心思想:提升维度的代价换取线性
Koopman算子的出发点不是直接把状态变量当作线性系统来处理,而是寻找一组“观测函数”或者叫“提升函数”,使得系统在这些函数构成的新坐标系下呈现出线性演化行为。
举一个比较形象的类比:在三维空间里,一个螺线管上运动的质点轨迹是曲线,但如果我们把它映射到柱坐标系的某个特定坐标组合里,一个原本看起来复杂的螺旋运动可以被表示为匀速直线运动加上匀速旋转运动——关键是把状态进行了恰当的坐标变换,使得变换后的动态在形式上变得简单。
Koopman算子的核心定理告诉我们,对于任意非线性动力系统,存在一个无限维线性算子作用在观测函数空间上,这个算子完全描述了系统的演化规律。当然,无限维空间在实际计算中不可实现,所以需要用有限维近似——这就是Koopman模型的核心工程折中。
1.3 Koopman模型和传统神经网络的本质区别
很多人会把Koopman模型和深度学习模型混为一谈,因为它们都需要用数据来“训练”。但两者的本质逻辑完全不同。神经网络拟合的是一个输入到输出的映射关系,对于轨迹预测问题,它本质上是在拟合一个序列到序列的映射;而Koopman模型是在寻找一个状态提升映射和一个线性动力学矩阵,使得提升后的状态遵循线性演化。
这个区别带来的实际影响非常直接:Koopman模型天然具有线性系统的全部优秀性质——叠加原理、可控性可观性分析、线性MPC求解。这些性质是普通黑箱神经网络模型不具备的,也是Koopman在控制领域能够得到广泛应用的根本原因。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 如何用EDMD从数据中构建Koopman线性预测模型
2.1 系统描述与数据采集设计
考虑一个离散时间非线性受控系统:
[
x_{k+1} = F(x_k, u_k)
]
其中(x \in \mathbb{R}^n)是系统状态,(u \in \mathbb{R}^m)是控制输入。要构建Koopman模型,我们需要采集系统在不同状态和输入下的转移数据。这里关键点在于数据采集的质量直接决定了模型质量的边界——Koopman模型是一个数据驱动模型,它的泛化能力完全受限于训练数据的覆盖范围。
我在实际代码实现中,通常会采用“随机激励+正弦扫频”的组合方式采集数据。随机激励可以覆盖较大的状态空间区域,正弦扫频则能充分激发系统的动态特性。对于仿真环境,这很容易实现;对于真实平台,需要在安全范围内尽可能充分地激励系统。
需要强调的一点是,数据采集时必须同时记录状态和输入,采样周期还需要和后续MPC的控制周期保持一致。
2.2 提升函数(Lift Functions)的选择策略
提升函数字典的设计是Koopman建模中最关键的环节,直接决定了模型的精度和泛化能力。常用的提升函数包括:
- 原始状态本身
- 状态的二次多项式项
- 径向基函数(RBF)
- 三角函数
- 高斯基函数
在实际工程中,我倾向于优先使用“原始状态+二次多项式”作为基础字典,因为它的实现最简单、计算量可控,而且在很多机械系统中已经能取得不错的效果。如果系统的非线性很强,才考虑引入RBF或更复杂的字典。
提升函数的数量和类型是一个需要权衡的问题。字典太小,线性近似精度不足;字典太大,不仅计算量上升,还容易过拟合训练数据。我在实际项目中有一个经验法则:先从一个较小的字典出发(比如原始状态加二次项),逐步增加字典容量,观察验证集上的预测误差变化,选择误差开始不再明显下降的位置作为字典规模。
2.3 EDMD算法核心步骤:从数据到Koopman矩阵
EDMD(Extended Dynamic Mode Decomposition)是目前最常用的Koopman矩阵辨识方法。它的核心思想非常简单:在提升后的空间中,寻找一个矩阵(K),使得提升状态的演化误差在最小二乘意义下最小。
具体步骤如下:
第一步,构造提升后的数据矩阵。将全部时刻的提升状态(\Psi(x_k))排列成矩阵,同时构造对应的延迟一个时刻的矩阵。
第二步,求解最小二乘问题。Koopman矩阵可以通过一个简单的伪逆运算得到,本质上就是一个线性回归问题。但在实际计算时,我强烈建议使用QR分解或SVD分解来处理,而不是直接算伪逆,数值稳定性会好得多。
第三步,将控制输入纳入模型。对于受控系统,我们需要同时辨识系统矩阵(A)和输入矩阵(B),形成一个标准的线性状态空间模型。实现方式是把提升状态和控制输入拼接成增广回归矩阵,一起做最小二乘。
2.4 模型验证与泛化能力评估
模型训练完成后,验证环节绝对不可跳过。我见过不少同学只看了训练集上的误差就匆匆开始设计控制器,结果一到闭环控制就崩了。这里推荐一个更可靠的验证方法:使用与训练数据不同的验证集数据,将Koopman模型在开环条件下进行多步递推预测,计算预测轨迹与真实轨迹的误差随预测步数的变化曲线。
还有一个在MPC语境下非常重要的验证指标——闭环仿真中的模型可用性。也就是说,即使开环预测误差看起来不小,只要闭环控制性能可接受,这个模型在工程上就是可用的。因为MPC具有滚动优化的反馈补偿特性,对模型误差的容忍度相对于开环预测要高一些。但反过来,如果你发现闭环性能对模型误差异常敏感,则需要回头优化提升字典或增加数据量。
3. Koopman MPC设计:从预测模型到二次规划求解
3.1 Koopman MPC的预测模型结构
有了Koopman线性模型之后,MPC的设计就可以完全沿用线性MPC的标准框架。我们需要定义预测时域(N_p)和控制时域(N_c),在每个采样时刻,基于当前状态,求解一个有限时域最优控制问题,然后将控制序列的第一步作用于系统。
Koopman MPC的预测模型可以写成:
[
z_{k+1} = A_d z_k + B_d u_k
]
其中(z_k = \Psi(x_k))是提升后的状态,(A_d)和(B_d)是经过零阶保持离散化(或者直接对离散系统辨识得到的模型)的离散时间系统矩阵。这里有一个编码时需要特别注意的细节:MPC内部预测的初始状态必须通过提升函数从当前测量得到的原始状态映射而来,也就是(z_0 = \Psi(x_k))。
对于目标状态,同样需要提升到Koopman空间。如果目标是某个平衡点(x_t),对应提升后的状态就是(\Psi(x_t))。有些实现会把原始状态的跟踪误差也加入代价函数,防止提升空间中的误差最小化不能完全反映原始状态的跟踪精度。
3.2 代价函数设计与约束建模
在Koopman MPC中,代价函数通常包含两个部分:跟踪误差代价和控制能量代价。跟踪误差既可以定义在原始状态空间,也可以定义在提升空间,也可以混合两者。
我在代码实现中最常用的一种设计是:
[
J = \sum_{k=0}^{N_p-1} |z_k - z_t|Q^2 + \sum^{N_c-1} |u_k|R^2 + |z - z_t|_P^2
]
其中(Q)、(R)、(P)分别是状态权重矩阵、控制权重矩阵和终端代价权重矩阵。如果更关注原始状态的跟踪精度,可以在代价函数中加入原始状态误差项。
对于约束条件,Koopman MPC的约束通常包含控制输入幅值约束、控制输入变化率约束,以及提升状态空间的约束。由于提升后的状态可能包含多项式和RBF项,对它们施加约束的物理意义需要仔细推敲。
3.3 使用Matlab的quadprog求解QP问题
Matlab中求解二次规划问题最常用的函数是quadprog。Koopman MPC在每一个采样时刻需要求解一次QP问题,因此需要将MPC优化问题转化为quadprog的标准形式:
[
\min_u \frac{1}{2} u^T H u + f^T u
]
subject to矩阵不等式约束。这一步需要做的核心工作是构造预测矩阵,将未来Np步的状态预测表达式展开成当前状态和控制序列的线性函数。
对于一个Koopman线性模型,这一步有非常系统的推导方法:通过递推状态方程,可以得到从初始状态到整个预测时域内所有状态的批量表达形式,然后代入代价函数中,展开整理即可得到Hessian矩阵(H)和线性项系数(f)。对于Matlab中的quadprog,建议使用'interior-point-convex'算法,它在多数情况下收敛稳定。
实际实现中建议把Hessian矩阵的构造放到MPC控制器初始化阶段完成,因为对于线性时不变系统,Hessian矩阵只依赖于模型参数和权重矩阵,与当前状态无关。这样可以避免在控制循环中反复计算矩阵,大幅减少计算延时。
3.4 MPC在线运行流程
Koopman MPC的在线运行流程可以归纳为以下步骤:
第一步,在控制循环开始时,获取当前系统的原始状态测量值(或估计值)。
第二步,应用提升函数,将原始状态映射到Koopman空间,得到当前提升状态。
第三步,将当前提升状态代入MPC优化问题,调用quadprog求解最优控制序列。
第四步,提取控制序列的第一个控制量,作用于被控系统。
第五步,等待下一个采样周期,重复上述过程。
这个流程和标准线性MPC几乎完全一致,区别就在于状态提升这一步。在实际代码中,需要把提升函数封装成一个可复用的函数,便于在训练、验证和MPC运行时统一调用。
4. Matlab代码实现:从训练到闭环仿真
4.1 训练阶段的代码结构
我用一个简单的非线性系统作为示例,来展示Koopman MPC的完整实现流程。假设我们的被控系统是一个一阶非线性系统:
[
x_{k+1} = x_k + T_s \left( \sin(x_k) + u_k \right)
]
其中采样周期(T_s = 0.1s)。这个系统带有正弦非线性项,适合用来演示Koopman建模的效果。
训练阶段的第一步是生成训练数据。代码会生成一组随机的控制序列,作用在系统上,记录下状态和输入的轨迹。这里需要强调的是,训练数据应当覆盖系统可能出现的所有工作区域,否则Koopman模型在未覆盖区域的预测能力会非常差。
第二步是定义提升函数。对于这个系统,我选择提升函数为:
matlab复制function z = liftState(x)
z = [x; x^2; sin(x); cos(x)];
end
这个字典包含了原始状态、平方项、正弦和余弦项,可以比较充分地捕捉这个非线性系统的动力学特征。
第三步是使用EDMD算法辨识系统矩阵A和B。实现时,先将所有训练数据点通过提升函数映射到提升空间,然后组装回归矩阵,用伪逆求解。为了提高数值稳定性,我这里用QR分解来求解最小二乘问题。
4.2 MPC控制器代码实现
MPC控制器的实现核心是预测矩阵的构造和QP问题的定义。我定义了一个setupMPC函数,在初始化阶段就完成Hessian矩阵、线性项矩阵和约束矩阵的构造。预测矩阵的构造思路是通过批量递推将未来Np步的预测状态展开为初始状态和控制序列的函数:
matlab复制function mpc = setupMPC(A, B, Np, Nc, Q, R, umin, umax)
% 构建批量预测矩阵
[nx, nu] = size(B);
% 初始化预测矩阵,维度为 (nx*Np) x (nu*Nc)
Phi = zeros(nx*Np, nu*Nc);
% 构建递推矩阵
for i = 1:Np
for j = 1:min(i, Nc)
if i >= j
Phi((i-1)*nx+1:i*nx, (j-1)*nu+1:j*nu) = A^(i-j) * B;
end
end
end
% 构建状态传播矩阵
Sx = zeros(nx*Np, nx);
for i = 1:Np
if i == 1
Sx(1:nx, :) = A;
else
Sx((i-1)*nx+1:i*nx, :) = A^i;
end
end
% 保存变量结构
mpc.A = A;
mpc.B = B;
mpc.Np = Np;
mpc.Nc = Nc;
mpc.Phi = Phi;
mpc.Sx = Sx;
% 构造QP矩阵
Q_bar = kron(eye(Np), Q);
R_bar = kron(eye(Nc), R);
mpc.H = Phi' * Q_bar * Phi + R_bar;
mpc.Q_bar = Q_bar;
% 约束
mpc.umin = umin;
mpc.umax = umax;
end
在线控制时,只需要调用quadprog求解。需要注意的是,状态提升后的维度可能比原始状态维度高不少,所以Q矩阵的维度要相应地加到提升空间的维度上。
4.3 闭环仿真的完整代码
闭环仿真部分将训练好的Koopman模型、MPC控制器和非线性系统模型连接在一起。在每个控制周期中,系统返回当前状态,MPC计算控制量,被控系统状态更新,循环往复。我还会在仿真中对比Koopman MPC和基于局部线性模型的MPC的控制效果,以便直观感受差异。
为了更直观地展示Koopman MPC的性能,我还建议在仿真中记录每一步的误差、控制量、状态轨迹和计算耗时,为后续的性能分析提供数据支撑。
4.4 与线性MPC的性能对比实验
我设计了一个对比实验:让Koopman MPC和基于工作点线性化的传统MPC跟踪同一条大幅度变化的参考轨迹。实验结果显示,Koopman MPC在整个轨迹段都能保持较好的跟踪性能,而基于工作点线性化的MPC在大偏离工作点的区域出现了明显的跟踪滞后和振荡。
这个实验清晰地展现了Koopman方法的核心价值:在不增加在线计算复杂度的前提下,显著扩展了线性MPC的有效工作范围。
5. 实操中必须关注的细节与避坑指南
5.1 提升函数维度与过拟合问题
提升函数维度的选择是Koopman建模中最需要经验的部分。一个常见的误区是认为提升函数越多越好。确实,增加字典规模可以降低训练集上的拟合误差,但同时也增加了过拟合的风险,导致在验证数据上的预测性能下降。
我在实践中总结的经验是:先从简单的字典开始,逐渐增加复杂度,同时持续监控验证集上的多步预测误差。当误差开始不再明显下降甚至反弹时,就说明模型开始过拟合了。此时应该回溯到之前的字典规模。另一个有效的正则化手段是在EDMD的最小二乘求解中加入L2正则化项。
5.2 数据分布覆盖对模型泛化能力的影响
Koopman模型的泛化能力完全依赖于训练数据的分布覆盖。如果你的训练数据只是在平衡点附近的小范围扰动,那么模型在大范围状态空间中的预测就会严重失真。我在做仿真实验时,会故意让训练数据的参考轨迹覆盖整个允许状态空间的较大范围,确保Koopman模型对这个范围内的动态都有足够的逼近能力。
对于实际平台,数据采集的安全性要放在首位,可以在安全边界内利用椭圆轨迹、扫频信号和随机噪声的组合激励来充分覆盖状态空间,必要时也可以利用仿真数据辅助训练。
5.3 采样周期与预测时域匹配
采样周期的选择直接影响Koopman模型的离散化精度。采样周期过大,离散模型的近似误差增大;采样周期过小,数据量和计算负载都会上升。MPC预测时域的选择也需要和采样周期匹配——预测时域代表控制器“向前看”的物理时间长度,应与被控系统的动态时间尺度相匹配。
一个实用的经验法则是:预测时域应覆盖被控系统主导时间常数的3到5倍,这样MPC才能“看得到”系统的主要动态过程,避免因为预测视野过短而产生过于激进的短期控制行为。
5.4 约束处理中的常见错误
在Koopman MPC中处理约束时,一个容易出错的地方是约束的施加对象。如果控制器的状态变量是提升后的状态(z),那么施加在原始状态上的约束需要被转换为提升空间的约束。但对于类似(\sin(x))这样由提升函数构成的维度,其物理含义已经和原始状态不同,直接对它们施加物理约束需要格外谨慎。
此外,终端约束的施加在Koopman MPC中也有其独特之处。如果希望系统能够稳定收敛到目标状态,最理想的情况是施加终端等式约束把最终状态固定在目标值。但在提升空间中,目标状态的表达依赖于提升函数,如果提升函数不够精确或者字典覆盖不足,终端约束可能会过于严格,导致QP问题无解。我在实际实现中更倾向于使用终端代价(终端权重矩阵)而不是终端等式约束,这样在工程上更稳健。
5.5 数值稳定性与病态矩阵处理
在实际编码中,如果提升函数中包含多项式和指数函数等高阶项,EDMD求解过程中的回归矩阵可能会呈现病态。这会直接导致辨识出的系统矩阵(A)有非常大的元素,进一步引发MPC求解的数值不稳定。
处理这个问题有几个实用手段:一是对训练数据做归一化,让状态和输入都落在一个量级接近的范围内;二是在EDMD的最小二乘求解中使用SVD或者QR分解;三是对小奇异值对应的方向做截断或正则化处理,防止噪声被过度放大。另外,在构造QP问题时,如果Hessian矩阵接近奇异,可以给控制权重矩阵(R)加一个很小的对角扰动量,保证Hessian正定。
6. 从仿真到实际部署的扩展建议
6.1 针对高维系统的拓展思路
文章中的示例系统是一阶非线性系统,但Koopman MPC的适用范围远不止于此。对于更高维的系统,比如四旋翼、机械臂、无人驾驶车辆等,Koopman方法的核心逻辑完全适用,区别主要在于提升字典的设计更加复杂,系统矩阵的维度也更大。
高维系统的Koopman建模,一个比较推荐的思路是使用深度Koopman网络,即用神经网络自动学习提升函数。训练目标就是让提升状态的线性递推误差最小化。神经网络的引入让字典设计从手工设计转变为端到端学习,大幅降低了对研究者经验的依赖,但代价是训练数据和计算资源的消耗成倍增加。
6.2 基于状态估计的Koopman MPC融合
在实际系统中,并不是所有状态都可以直接测量。把Koopman模型和状态估计器(如Kalman滤波器)结合是一个很自然的需求。由于Koopman模型本身是线性的,可以直接套用标准的Kalman滤波框架,只是预测方程需要建立在提升状态空间中。
需要注意的是,过程噪声和测量噪声的建模需要重新定义。在提升空间中,噪声的统计特性和原始空间中的噪声特性并不一致,需要通过实验数据来标定合适的噪声协方差矩阵。这是一个很容易被忽视但影响实际效果的细节。
我个人的建议是,在处理真实平台时,可以先用离线数据辨识Koopman模型,然后设计基于Koopman模型的Kalman滤波器和MPC控制器,形成一个完整的“状态估计+预测控制”闭环系统。这篇文章标题中的“状态估计”正是这个含义——Koopman模型不仅服务于预测控制,同样也可服务于状态观测器的设计。
6.3 关于代码复现和进一步优化的建议
从复现角度看,建议先把基础流程完整跑通,再逐步替换升级模块。把基础流程搭建清晰后,后续替换成深度Koopman网络、加入鲁棒约束、或者替换求解器都是可行的方向。如果要追求极致的实时性,可以考虑使用C代码生成或嵌入式求解器,但前提取决于控制周期和硬件性能。
最后再提一点实际使用中的体会:不要迷信任何建模方法,包括Koopman。Koopman模型在非线性不强或高度周期性的系统中表现惊艳,在强非线性、非光滑或混沌系统中也会力不从心。建议在立项之初就评估目标系统是否适合Koopman建模,从低成本的数据采集和基础的EDMD实验开始,快速判断Koopman方法的适用性,再决定是否投入更多资源开发深度模型和自定义控制器。毕竟,对控制工程师而言,最贵的从来不是模型多漂亮,而是在现场验证时少做无用功。
