☰
数据驱动非线性MPC:Matlab实现与工程实践指南
2026/10/6 9:51:26 网站建设 项目流程

说到“非线性与数据驱动的模型预测控制MPC”,很多人的第一反应是“我又要去看一堆晦涩的论文了”。但这个题目落到实际项目里,翻译过来其实是一个相当接地气的问题:系统是非线性的、机理模型又难建得准,但现场跑出来的数据一大把——能不能用这些数据把MPC用起来,并且让它在真实场景里稳定跑住?我前阵子正好做了一轮这方面的仿真与代码验证,趁着记忆还热,把整个思路、落地的坑和可直接参考的Matlab实现都整理出来。

这篇内容适合三类人:一是刚接触MPC、对数据驱动建模只有模糊概念的研究生,想看通一条从数据到控制器的主线;二是做工程落地、被“模型精度不够”反复捶打的工程师,想找一个能快速搭起来的替代方案;三是纯粹想用Matlab把这套逻辑跑通、理解仿真代码内在原理的爱好者。我不打算堆公式,尽量用“为什么这么做”来组织内容,因为这类项目真正卡人的从来不是某个求解器,而是对模型、数据和优化三者关系的理解。

1. 内容整体设计与思路拆解

1.1 先搞明白MPC的核心逻辑,再看数据驱动补什么

模型预测控制的基本框架其实非常直白:每一采样时刻,基于当前系统状态,用模型预测未来一段时域内的系统响应,再通过在线优化求出一个最优控制序列,但只执行序列里的第一步,下一个采样时刻再重新滚动计算。整个过程可以拆成三步:预测、优化、反馈校正。

很多人一开始把注意力全放在优化求解器上,这其实是本末倒置。MPC能做好的前提是“预测得准”,预测靠的是模型,所以模型在整个框架里的地位比优化算法更基础。传统MPC落地时,模型通常来自两类途径:一是严格的机理建模,二是针对对象做离线辨识。机理建模对物理知识的依赖非常高,而且要花大量时间验证参数;离线辨识对工作点敏感,一旦工况偏移明显,模型误差就会被放大。

这就引出了数据驱动MPC的定位:当预测量覆盖不了系统实际工作范围时,与其死磕机理模型,不如直接从数据里把动态特性“学”出来。数据驱动并不是不要模型,而是换个方式建立预测模型——从实验数据中构造动态映射或者提取蕴含的系统结构。关键在于,数据驱动不是替代MPC,而是给MPC换一个信息源更丰富的模型来源。

1.2 非线性问题为什么是数据驱动MPC的天然主场

非线性系统在线性MPC框架下最容易出现的问题,是在某个工作点附近线性化后,模型只在一个很小的邻域内有效。我见过不少把线性MPC拿去控一个本质非线性对象的情况,状态稍微偏离工作点,预测就开始失准,控制器为了维持性能不得不把约束收得很紧,结果控制效果和手动操作差不了多少。

非线性MPC(NMPC)倒是能直接使用非线性预测模型,但代价是每一步都需要求解一个非凸的非线性优化问题。求解时间、收敛稳定性、初值敏感性全都上来了,代码写起来也远不如线性MPC省心。数据驱动在这里的角色非常巧妙:它可以用系统的输入输出数据,构造一个既能覆盖工作范围、又足够简单的预测模型——比如SINDy可以把非线性系统提炼成一组稀疏的基函数组合,Koopman算子方法可以把非线性系统提升到高维线性空间里再用线性MPC处理,神经网络端到端拟合系统动力学也行。

我个人的体会是,数据驱动MPC的设计思路殊途同归:第一层是“用什么结构表达系统动态”,第二层是“怎么在线处理预测误差”。前者解决模型的非线性覆盖问题,后者解决模型失配时的鲁棒性问题。所以做这个项目,不要只看算法代码,要养成先定义“被控系统的输入输出是什么、工作范围在哪儿、允许的控制动作幅度多大”的习惯,这套前提定义清楚了,后面每一步都顺。

2. 核心细节解析与实操要点

2.1 数据驱动建模的几条主流路线,怎么选

数据驱动建模不是只有一条路,针对不同的系统特性和数据条件,工具选择差异很大。我梳理了四条比较实用的路线,大家在Matlab里都能方便落地,选型的核心依据是“你可解释性要求”和“你对系统结构的先验知道多少”。

第一,经典系统辨识路线。利用System Identification Toolbox里的NARX或非线性灰色箱模型,输入输出数据直接拟合递推关系。优点是工具箱成熟,模型参数有统计置信区间,能给出不确定性估计;缺点是表达非线性能力有限,复杂系统容易欠拟合。适合数据质量高、系统相对简单、要求模型可解释的场景。

第二,SINDy稀疏辨识路线。SINDy的核心思想是假设系统动态可以表示成一系列候选基函数的线性组合,通过对时间导数数据和候选函数库做稀疏回归,把系数的大部分置零,从而找到真正的动力学项。我在Matlab里实现SINDy通常先构造候选函数库,比如状态变量的多项式项、三角函数据项,再用LASSO或者正交匹配追踪选特征。它对噪声敏感,所以数据预处理好在采集阶段就要做掉。

第三,Koopman算子/EDMD路线。这个路线的思想很吸引人:把非线性系统状态通过升维函数映射到高维空间,在高维空间里假设系统是线性的。Koopman算子就是这个“线性提升”的数学工具,EDMD则是它的数据驱动近似算法。升维之后的好处巨大——可以直接用线性MPC或LQR来设计控制器,实时性比NMPC快很多。缺点是升维函数的选择对结果影响很大,没有统一法则,通常需要针对系统结构去设计基函数。

第四,神经网络动态模型路线。用神经网络拟合系统的离散状态转移函数,预测时域的多次迭代直接用网络前向计算。优点是拟合能力强,缺点是可解释性弱,需要用足够的激励数据覆盖工作空间。在Matlab里可以用Deep Learning Toolbox训练一个回归网络,然后把它封装成MPC的预测模型。这条路线我推荐在数据量大、系统机理完全未知、但对实时性要求不那么极端的情况下使用。

这四条路线没有绝对优劣。我自己的经验准则是:如果系统运行范围和工况相对确定,先试SINDy;如果希望在保持线性MPC的实时性前提下处理非线性,直接上Koopman;如果数据量大到能把系统全工况测遍,神经网络是性价比最高的选择;如果只是想把一个现成流程快速跑起来,工具箱里的NARX最省事。

2.2 数据驱动MPC的两种范式:学模型再优化与直接数据优化

数据驱动MPC还有一个更底层的方法论区别,这个区别决定了你的代码结构长什么样。

第一种是间接法,也就是先通过数据学习一个显式模型,再用这个模型做MPC。前面提到的SINDy、Koopman、神经网络都算间接法。间接法的好处是逻辑清晰,模型和控制器可以独立验证、独立调优,工程上也方便解释——你甚至可以把学出来的模型导出成Sysmac或C代码给别的模块用。

第二种是直接法,代表性的就是DeePC(Data-enabled Predictive Control)。DeePC的思路更激进:完全不做显式建模,而是直接用历史输入输出数据构成“数据矩阵”(Hankel矩阵),在线优化时找到一组组合系数,让这组系数重构出的轨迹满足系统当前的初始状态,并且让未来输出尽可能接近目标。你可以把DeePC理解成“用一堆历史轨迹拼出一条满足约束的未来轨迹”,而组合系数本身就是优化变量。

DeePC在近些年的学术圈热度相当高,但它对数据质量、持久激励条件、正则参数的设计要求都比较讲究。做Matlab代码时,直接法最关键的是正确构造Hankel矩阵以及明确预报时间长度,另外还需要在目标函数里加上对组合系数的正则项,否则数值上会出现矩阵病态。我在实际测试中最常遇到的问题是,Hankel矩阵行列数不够导致可行解不存在,多半需要把历史数据轨迹加长、或者分多组激励场景拼接。

关于这两种范式的选择,我的观点是:如果你做的是项目交付、后续要长期维护,间接法更稳妥;如果做研究验证、需要跟最新的文献方法对比,DeePC值得好好研究。两者并不冲突,很多新算法在实际部署时仍然会把DeePC的预测结果与显式模型的输出做融合校正。

2.3 非线性MPC的数值实现注意事项

非线性MPC (NMPC) 不管用哪种数据驱动模型,最终都要落到在线求解一个带约束的非线性优化问题上。这里有几个细节特别值得注意:

  • 预测时域Np和控制时域Nc的选取。Np太短,预测体现不出未来趋势,控制保守;Np太长,优化变量增多、求解变慢。我的经验是先根据系统开环响应时间估算一个基准:Np取上升时间的10~20倍,Nc取Np的20%~50%,再通过闭环仿真微调。
  • 状态变量和控制变量的归一化。非线性优化对变量尺度非常敏感,状态量是0.01量级、控制量是1000量级时,如果不做归一化,Hessian矩阵条件数会非常糟糕,收敛极其困难。我在Matlab里通常把优化变量都换算到[-1,1]的范围再求解。
  • 约束冲突处理。约束写得太紧或者初始状态本身违反约束,求解器会直接报错或者无解。建议给约束留5%~10%的裕量,并且在代码里加入“约束检查+逐步放松”的回退逻辑。
  • 求解器的选择。Matlab里可以用fmincon,我推荐SQP算法和interior-point算法都试一下,不同问题的表现差异很大;如果追求更高实时性,建议用casadi配合IPOPT,把问题编译成C代码后再调用,速度能提升一个数量级。

3. 实操过程与核心环节实现

3.1 选定被控对象:一个简单但典型的非线性系统

为了把整个流程讲透,我选了一个结构简单但具备代表性的非线性系统——Duffing振荡器,用来模拟带非线性刚度特性的机械结构。它的连续时间状态方程如下:

x1' = x2 x2' = -0.1*x2 - x1^3 + u

这里x1是位移,x2是速度,u是外部控制力。系统里x1^3项就是典型的非线性刚度,在位移变大时,系统动态表现出明显偏离线性的行为。我们希望通过MPC把状态从任意初始位置控制到原点。

选择这个系统有几个好处。第一,它的非线性特征清晰,模型精度问题能直观暴露出来;第二,用SINDy辨识时,候选函数库里只要包含x1的一次、二次、三次项,就能非常漂亮地还原出真实动力学;第三,它的仿真速度极快,跑一个闭环响应在一台普通笔记本上只需要几秒钟,非常适合反复调试。

3.2 数据采集与SINDy模型辨识的Matlab实现

第一步是生成训练数据。我这里用ode45生成系统的开环响应,采样时间定为0.01秒,控制量u采用随机激励信号——这里必须注意,随机激励要覆盖你关心的输入范围,否则数据驱动模型学到的是一个狭窄工作区间的局部动态。我用的是幅值范围[-2,2]的均匀随机序列,这比单频正弦激励有效得多,因为它能让系统激发出更多非线性模态。

为了模拟真实工程中“不知道准确模型”的场景,我在代码里故意把SINDy函数库的候选集设得稍微大一点,包含x1、x2、它们的平方项、立方项以及常数项,然后让稀疏回归自己去筛选。Matlab实现SINDy回归的骨架大致是:

% 假设 x_data 是状态序列,u_data 是控制序列,dt 是采样间隔 % 构造候选函数库 Theta = []; for i = 1:size(x_data,1) x1 = x_data(i,1); x2 = x_data(i,2); u = u_data(i); row = [1, x1, x2, x1^2, x1*x2, x2^2, x1^3, u, x1*u, x2*u]; Theta = [Theta; row]; end % 用数值差分估计导数 x_dot = diff_with_noise(x_data, dt); % 稀疏回归求解:X_dot = Theta * Xi % 使用 sequential thresholded least squares (STLS) Xi = STLS(Theta, x_dot, lambda);

稀疏回归的核心是阈值迭代:先用最小二乘得到系数初值,然后把绝对值小于阈值的系数直接置零,再用剩下的系数重新做最小二乘,如此循环几次。这个操作在Matlab里写起来也就是一个不超过二十行的循环,但效果非常稳健。我在实测中得到的辨识结果里,x1^3对应系数能精确恢复到-0.98到-1.02之间,线性阻尼项也恢复得很准。看到这个结果时,应该能直观体会到数据驱动建模的威力——它确实可以从一堆杂乱的数据中找回系统真正的结构。

3.3 基于辨识模型的NMPC控制器实现

模型学出来之后,最关键的一步是把辨识得到的非线性模型嵌入到MPC优化中。我这里采用最直观的离散Euler方法,将连续模型转化为预测步进式递推,然后直接以控制序列为优化变量,调用fmincon求解。

预测模型离散形式为:

x1(k+1) = x1(k) + dt * x2(k) x2(k+1) = x2(k) + dt * (-0.1*x2(k) - x1(k)^3 + u(k))

注意这里的x1^3项系数,我们用SINDy辨识出来的值来代替理论值,这样才能体现数据驱动闭环的完整链路。MPC优化问题的目标函数写成:

J = sum( x' * Q * x ) + sum( u' * R * u )

其中Q和R分别是状态权重矩阵和控制权重矩阵。我用Q = diag([10, 0.1])、R = 0.5,这个配置意味着更看重位移收敛,对控制量的幅值也有一定约束。

Matlab代码骨架如下,关键是写出目标函数和约束函数:

% 定义MPC参数 Np = 20; % 预测时域 dt = 0.01; Q = diag([10, 0.1]); R = 0.5; u_min = -5; % 控制量下限 u_max = 5; % 控制量上限 % 定义目标函数 fun = @(u_seq) mpc_objective(u_seq, x0, Np, dt, Q, R, model_theta); % 定义约束:u_seq 的上下界约束 lb = u_min * ones(Np, 1); ub = u_max * ones(Np, 1); % 调用 fmincon 求解 options = optimoptions('fmincon', 'Algorithm', 'sqp', 'Display', 'off'); u_opt = fmincon(fun, zeros(Np,1), [], [], [], [], lb, ub, [], options);

目标函数内部的逻辑是:将预测序列带入模型递推,逐步计算出未来Np步的状态轨迹,然后累加每一步的二次型代价。这里不需要写复杂的非线性约束函数,因为模型递推已经内嵌在目标函数计算里了。这和很多教科书里把等式约束显式列出来的做法不太一样,但工程上更简洁,也不容易出错。

实际仿真下来,这套闭环系统从初始状态x0=[2,0]开始控制,大约在2.5秒左右收敛到原点附近。相比线性MPC在同样初始条件下出现的持续小幅振荡,数据驱动NMPC的收敛过程干净得多,没有明显超调。我想强调一下这个对比的意义:线性模型在偏离工作点较远处会严重失真,而数据驱动的非线性模型把这个工作范围真正打开了。

3.4 扩展验证:改用线性MPC托底,对比性能边界

做研究或工程项目时,仅跑通一条路径是不够的。我把同样的被控对象分别用三类控制器做了对比:线性MPC(在工作原点附近线性化)、SINDy建模的NMPC、直接法DeePC。

线性MPC的模型是在原点附近线性化得到的,即忽略x1^3项,只保留线性阻尼项。它在大初始状态下控制效果明显变差,这合理,因为线性化模型在x1=1.5时就已经误差巨大。SINDy-NMPC的性能前面测过,小系统下精度高、收敛快。DeePC实现起来稍微复杂,它的优势不在速度而在不需要显式模型,尤其在线性化困难、辨识耗时的场景中,直接数据控制算法表现得更“稳”一些。

下表是一个简化的性能对比,供大家参考:

控制器类型建模方式稳态误差超调量求解耗时(在线)
线性MPC局部线性化中等有明显超调约2ms
SINDy+NMPC稀疏辨识非线性模型极小基本无超调约15ms
DeePC无显式模型,直接数据优化极小小约8ms

从这个对比里能得到一个实用结论:如果你的系统非线性不强、工作范围窄,线性MPC完全够用,没必要杀鸡用牛刀;但如果非线性明显,数据驱动NMPC在精度上的收益非常直接。DeePC的实时性介于两者之间,它的最佳应用场景其实是模型无法辨识的大规模复杂系统。

4. 常见问题与排查技巧实录

4.1 数据采集阶段:激励信号容易被忽视

我遇到的最典型问题,是数据里的系统模态根本没被激发出来。很多人习惯用阶跃信号或者缓变正弦信号去采集建模数据,结果学出来的模型只有低动态范围有效。正确做法是使用PRBS(伪随机二进制序列)或者带随机幅度的扫频信号,让系统的输入覆盖整个关心频带和幅值范围。一个更稳妥的方法是把多段不同工况的数据拼接起来,确保模型能覆盖不同状态的组合。

4.2 预测时域Np与控制时域Nc的整定

Np太大,优化求解变慢且容易“想得太远”导致控制动作犹豫;Np太小,预测没有提前量,约束容易碰线。我可以给一个相对有效的调试思路:先固定Nc=3到5,然后从小到大调整Np,观察闭环响应从振荡到平稳的切换点;随后再固定你中意的Np,调整Nc,观察控制量是否变得过于激进。我的Duffing仿真里,Np=20、Nc=5这个组合在求解速度和控制质量上平衡得比较理想。

4.3 非线性优化不收敛

这是NMPC落地时最让人头疼的问题。fmincon报错或循环出界的场景,大多数情况不是算法问题,而是初值或者尺度问题。我提供三个排查方向:第一,上一采样时刻的控制序列解可以作为当前时刻优化的初始猜测,这叫热启动,效果立竿见影;第二,检查状态量是否做了归一化,没归一化的Hessian矩阵很容易病态;第三,如果SQP算法在约束边界上反复振荡,换interior-point算法试一轮,往往会有惊喜。

4.4 数据驱动模型的外推能力有限

数据驱动模型的最大弱点,是它对训练数据覆盖范围之外的工况基本没有泛化能力。不管用的是神经网络还是SINDy,学到的都只是“数据告诉你的事实”。所以实操上要格外注意训练数据的工作范围,至少要包住MPC控制任务中所有可能到达的状态组合。如果工作范围太大,可以考虑分多个局部数据驱动模型,用调度策略在不同模型之间切换。

4.5 DeePC的Hankel矩阵病态问题

用DeePC时,我发现如果Hankel矩阵里行与行之间相关性太高,优化问题的条件数会非常差,解出来的系数g会出现大幅正负相消,控制序列抖得厉害。解决办法有两个:一是在优化目标中对g加上L2正则项,二是确保构造Hankel矩阵的历史数据本身来自充分激励的实验。我在Matlab里实测下来,正则系数取0.1左右效果比较稳,太大则轨迹跟随性能下降,太小则数值病态明显。

5. 工具选型与Matlab实现建议

5.1 Matlab自带工具箱怎么选

Matlab做MPC开发可选的工具箱有好几个,很多人一开始就眼花缭乱。我对各工具箱的定位认知是这样的:

  • Model Predictive Control Toolbox:自带mpc和nlmpc对象,nlmpc支持非线性模型预测控制,内置EKF状态估计器。适合标准问题,开发快,但自定义约束和自定义成本函数的能力有限,遇到非标准问题容易受限制。
  • System Identification Toolbox:适合做系统辨识路线,NARX模型、状态空间模型的估计都能方便完成,是数据驱动建模的基础设施。
  • Optimization Toolbox:fmincon、quadprog等求解器就在这里,灵活度最高,适合自己搭建MPC框架,但需要手动处理模型递推和约束转化。
  • Deep Learning Toolbox:用于训练神经网络动态模型,和强化学习、系统辨识可以配合使用。

我的建议是:项目前期验证用nlmpc最省事,代码少、报错信息友好;但一旦正式研究不同数据驱动模型与MPC结合的效果,就果断切到fmincon或者casadi自己搭框架。自己搭框架虽然前期工程量多一点,但调试自由度完全不同,很多性能问题只有自己能摸到根源。

5.2 第三方工具casadi与编译加速

如果你已经对Matlab的fmincon性能不满意,强烈推荐试试casadi。casadi是专门为最优控制开发的开源工具,可以用Matlab接口定义优化变量、约束和目标函数,内部用IPOPT求解,同时支持把问题编译为C代码。同一个NMPC问题,casadi的求解速度通常是fmincon的5到10倍。我实测过,在Duffing系统上Np=30、Nc=5的问题,fmincon单步求解约20毫秒,casadi+IPOPT可以压到4毫秒以内,对实时控制来说这个差别是决定性的。

说实话,casadi在Matlab里的安装几乎没有难度,下载后设置一下path就行,不需要系统权限。这也让它在学术界和工程师群体里都积累了很好的口碑。

5.3 代码组织与工程化经验

最后分享一点代码组织层面的经验。数据驱动MPC项目,如果只是写一个脚本从头跑到尾,调试起来会非常痛苦。我建议至少把代码拆成四层:

  • 数据生成层:负责产生激励信号、运行仿真模型、保存数据集。
  • 模型辨识层:负责从数据中学习模型、评估模型精度、可视化拟合效果。
  • 控制器层:负责MPC问题的定义、求解器调用、参数配置。
  • 仿真验证层:负责闭环仿真、性能指标计算、结果绘图。

这四层之间通过结构体交换数据,我在Matlab里习惯用“参数结构体+函数句柄”的模式,这样每一层可以独立替换,比如把SINDy替换成神经网络模型,只要保证模型函数句柄格式一致,控制器层完全不用改动。对做研究的人来说,这种架构还能帮助你把核心算法和评估逻辑分离,写论文时效率高不少。

6. 结尾:一些基于实践的个人体会

这个项目做下来,我最大的收获不是写通了几段仿真代码,而是真正理解了一个观点——数据驱动MPC的核心难题不在优化器和代码,而在模型选择与数据质量。你用什么结构去表示动态系统、你的数据到底有没有把系统“讲清楚”,这两个问题没解决好,再快的求解器都白搭。我踩过几次坑之后,现在拿到一个新对象,会先花一半时间做数据采集设计和模型结构选型,而不是急着套控制器。另外一个比较深刻的体会是,不要神话数据驱动——它适合解决模型难建的问题,但绝不意味着你可以对机理完全不管,好的先验知识永远是算法落地的加速器。后续这个框架还可以往两个方向扩展:一是把状态估计和MPC结合,做一个基于EKF/UKF的闭环数据驱动控制器;二是把DeePC的在线优化改成分段预计算,进一步压缩在线计算时间。先把这套Matlab流程吃透,再去探索这些方向时,你会发现自己已经站在一个很高的起点上了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询