拿到这个“非线性与数据驱动的模型预测控制(MPC)研究”项目的时候,我第一反应是:终于有人把数据驱动、非线性、MPC这三块难啃的骨头放到一起,还用 Matlab 做成了一套可复现的东西。做控制的人都知道,模型预测控制本身在工业界已经很成熟,能处理约束、多变量耦合、大纯滞后,但一旦碰到非线性系统,事情就变了——传统线性 MPC 在强非线性工况点下,模型偏差会让预测完全跑偏;而基于机理模型的非线性 MPC,建模成本又高得让人想放弃。于是数据驱动这条路线自然成了热点:用历史运行数据建立预测模型,或者在线拟合局部线性模型,再交给 MPC 框架做滚动优化。这篇文章就把我在这个项目里跑通的完整链路摊开来讲:数据怎么采、模型怎么建、MPC 怎么搭、踩过哪些坑,给准备做相关课题的同学,以及考虑给现场系统换控制方案的工程师,一份可以直接参考的路线图。
1. 项目拆解:传统MPC为什么搞不定非线性系统
1.1 模型预测控制的“三板斧”
MPC 的核心机制其实就三个阶段:预测、优化、反馈。在当前时刻 t,基于系统状态测量值 x(t),用预测模型推算未来 P 步的输出轨迹;然后在这个有限时域内求解一个带约束的最优控制问题,得到一个控制序列;最后只执行序列的第一步,等下一个采样时刻再重复这个过程,这就是滚动优化。
我经常拿开车来打比方:你不会只盯着脚下三米开车,而是抬头看前方五十米,判断风险和路线,再轻轻修方向盘,而且是每时每刻都重复这个“看前方、调方向”的过程。MPC 干的就是这件事——前方 P 步就是你的视野,方向盘修正量就是控制量。这种机制天然适合约束、耦合、大惯性系统,所以在炼化、电力、汽车领域地位非常稳。
但常规的线性 MPC 有个前提:预测模型是线性的,优化问题是凸二次规划(QP)。凸问题意味着全局最优解有保证,求解速度快,稳定性分析相对成熟。这个前提一旦被非线性破坏,麻烦就来了。
1.2 非线性带来的三个实际麻烦
第一个麻烦是预测精度。线性模型只在某个工作点附近成立,工况一偏移,模型的预测就走样,闭环甚至会发散。我在 benchmark 里试过,目标值切换幅度小的时候,线性 MPC 表现尚可;一旦设定值大步跳变,跟踪曲线直接拉胯,这就是模型失配的典型症状。
第二个麻烦是优化问题性质变了。预测模型一旦是非线性的,目标函数非凸,原本的 QP 变成了非线性规划(NLP),可能陷入局部最优,求解时间也不再可控。这在实际工程里非常要命,因为每个采样周期都要在规定时间内给出控制量,求解器卡住了,控制器就得掉链子。
第三个麻烦是约束可行性。非线性会扭曲可行域的形状,在约束边界附近尤其难处理。原本线性约束下很好满足的边界条件,到了非线性模型里可能直接冲突,导致无解。这三个麻烦叠加起来,就是为什么很多做控制的同行一听到“非线性 MPC”就头皮发麻的原因。
1.3 数据驱动为什么要进来
非线性 MPC 最需要的是一颗好的“预测脑袋”,也就是模型。机理建模要花大量精力去辨识物理参数——传热系数、反应速率常数、阻尼系数等等,工程现场往往根本没有这个预算,或者设备实在太复杂,机理压根建不出来。数据驱动进场就顺理成章了:不依赖机理方程,只看历史输入输出数据,直接从数据里把系统的行为规律提炼出来。
但这里有个非常容易被误解的地方:数据驱动不等于“免建模”。它只是把“人工机理建模”换成了“从数据中构建模型”,数据质量、激励充分性、泛化能力,全都成了新引入的约束。我见过不少同学拿一段现场偶然记录的数据就想建出好模型,结果在线跑的时候一塌糊涂,后面会专门讲数据采集的细节。
1.4 三个关键词如何串成一条线
把项目标题拆开看,本质是一条完整链路:先用充分激励的输入输出数据完成模型构建,再把模型嵌入 MPC 框架做滚动优化,最后闭环仿真验证性能。这条链路上任何一个环节出问题,整体都会翻车。后面我按这条链路顺序讲:核心技术选型、实操流程、参数整定和问题排查。
2. 核心细节解析:数据驱动MPC的几种技术路线
2.1 非线性MPC的求解机制长什么样
非线性 MPC 每一步要解的都是这样一个优化问题:在预测时域 P 内,最小化跟踪误差和控制变化量的加权和,同时满足预测模型、输入约束和状态约束。
目标函数写出来是:J = Σ||y(k+i|k) − r(k+i)||²Q + Σ||Δu(k+i|k)||²R。约束包括预测模型 x(k+i+1|k) = f(x(k+i|k), u(k+i|k)),输入上下限,状态/输出上下限。质心就是:所有东西都在一个目标函数里被权衡,约束是被“硬性要求”的。
求解这个 NLP 的常见算法有 SQP、内点法和信赖域。Matlab 里如果用 MPC Toolbox 的 nlmpc 对象,底层会调用 Optimization Toolbox 的求解器;如果不想依赖工具箱,可以直接用 fmincon 配 'sqp' 算法,或者引入 CasADi 这类外部工具生成更高效的求解器。实战中有一个关键细节:NLP 求解器对状态方程的解析梯度和雅可比矩阵非常敏感。给 fmincon 提供解析梯度,求解速度能提升好几倍;如果只给它数值差分,不仅慢,还容易因为差分扰动导致收敛失败。
2.2 路线A:子空间辨识加线性MPC
子空间辨识是经典的数据驱动建模法,核心思想是从输入输出数据的 Hankel 矩阵里提取出系统的状态空间表示。Matlab 里就是一条命令——n4sid。这条路线本质是“数据驱动建模加线性 MPC”,对工作点附近的小范围非线性很实用,工程落地障碍最小。
但使用 n4sid 有几个硬条件。第一,数据激励要充分,单频正弦激励基本没用,要叠加 PRBS 或 GBN 伪随机信号,覆盖系统的工作频带。第二,数据要做预处理,去趋势、去均值、必要时滤波,不然辨识出来的模型会被噪声成分带偏。第三,系统阶次的选择要看奇异值曲线,取曲线“膝盖拐点”的位置,而不是贪高。阶次太低丢动态特性,阶次太高就会过拟合噪声,验证集上表现极差。
2.3 路线B:在线局部建模加MPC,我最推荐的做法
JITL(Just-In-Time Learning,即时学习)的思路很巧妙:每个采样时刻不固定用一个模型,而是从历史数据库里检索与当前工作点最接近的样本,加权最小二乘在线拟合一个局部线性模型,再基于这个局部模型做 MPC 求解。
这个方案的奥妙在于用“局部线性”逼近“全局非线性”。工作点变了,模型跟着变,控制器内部永远在处理一个相对简单的 QP 问题,却捕获到了全局非线性行为。在 Matlab 里实现起来性价比极高,调试也比硬啃 NLP 稳得多。
JITL 的实操细节有几个:数据库必须覆盖工作空间的多个区域,不然检索不到相似样本,局部拟合会退化。相似性度量可以用欧式距离,也可以用夹角余弦,实际项目里两者结合效果更稳。近邻数 k 值不能太大也不能太小,太大则模型太“平滑”,非线性细节被抹掉;太小则受测量噪声影响严重。我经验上一般取样本总量的 3% 到 10%。另外一个细节后面会单独讲:检索时把上一拍控制量也加入特征向量,局部模型拟合质量会明显改善。
2.4 路线C:严格非线性NMPC与黑箱模型
如果用神经网络、高斯过程这类黑箱非线性模型直接嵌入 MPC,那就是不折不扣的 NLP 问题,每个采样时刻都要解非线性优化,实时性压力非常大。这个路线更适合做研究验证,不太适合直接上工程。如果确实要做,我建议先用 JITL 路线跑通整个流程,再评估要不要升级成严格 NMPC。
我在这个项目里做了一个折中方案:离线用 n4sid 建立基准线性模型,在线用 JITL 做局部修正,MPC 内部保持 QP 求解。最终在 benchmark 上的跟踪效果跟严格 NMPC 非常接近,但求解时间只有后者的十分之一左右。具体数据后面会给出对比表。
2.5 Matlab工具箱选型决策
做这套东西主要涉及三个工具箱:System Identification Toolbox(n4sid、ssest)、Model Predictive Control Toolbox(nlmpc,R2020b 及以上版本)、Optimization Toolbox(fmincon、quadprog)。即使没有工具箱也不影响核心逻辑,MPC 框架可以手写,辨识可以用最小二乘手动实现,但 n4sid 这种成熟算法自己实现会比较费劲。
| 路线 | 模型形式 | 对非线性的处理 | Matlab实现难度 | 适用场景 |
|---|---|---|---|---|
| 子空间辨识+n4sid | 线性状态空间 | 全局线性近似 | 低 | 工作点变化不大 |
| JITL即时学习 | 局部线性模型 | 工作点自适应 | 中 | 工作点频繁切换 |
| 黑箱NLP(NN/GP) | 非线性黑箱模型 | 全局非线性拟合 | 高 | 研究验证为主 |
3. 实操过程:从数据采集到闭环仿真的完整流程
3.1 第一步:激励信号设计与数据采集
演示用的系统我选了一个带非线性修正项的二阶系统,工程上可以理解为弹簧-惯性-阻尼系统加上一个轻微非线性弹簧力。选择这个系统是因为它既有明显的非线性特征,又不至于让 benchmark 跑不动,而且代码简单,大家可以快速复现后换成自己的研究对象。
离散化模型用 x(k+1) = x(k) + Ts·f(x(k), u(k)) 表示,非线性项体现在 f 函数内部。数据采集的第一步是确定采样时间 Ts,我取了 0.1 秒,总时长 100 秒。激励信号很关键,我用了正弦加 PRBS 加小幅随机噪声的叠加,保证频带覆盖。代码长这样:
%% 数据生成:激励信号设计与开环仿真 Ts = 0.1; Ttotal = 100; t = 0:Ts:Ttotal; N = length(t); % 叠加正弦+PRBS的激励信号,确保频带覆盖工作范围 u = 0.4*sin(0.5*t) + 0.2*square(0.3*t) + 0.05*randn(1,N); u = max(min(u, 1), -1); % 幅值限制在[-1,1] % 用离散非线性模型生成输出数据 x = zeros(2, N); y = zeros(2, N); for k = 1:N-1 x(:, k+1) = myStateFcn(x(:, k), u(k), Ts); y(:, k) = x(:, k); end注意这里生成的 y 要做存储上的调整,建模时对应的是 y(:,1:N-1) 和 u(:,1:N-1),因为是预测 y(k+1) 的问题。激励信号幅值的选择需要覆盖你想让控制器工作的工作域,但又不能大到把系统推到约束边界以外太多,这个度要靠试凑。
3.2 第二步:离线辨识数据驱动模型
数据采集完后,我是按“先建模、再验证”的思路,把数据分成辨识集和验证集两段。如果只用同一段数据既建模又评估,模型好坏根本看不出来,这种错误我见过太多次了。用 n4sid 的代码非常简短:
%% 离线辨识:子空间方法 data_id = iddata(y(:, 1:end-1)', u(:, 1:end-1)', Ts); sys_id = n4sid(data_id, 4, 'Form', 'canonical'); A = sys_id.A; B = sys_id.B; C = sys_id.C; D = sys_id.D; % 在验证集上检查拟合效果 y_hat = lsim(sys_id, u', t); plot(t, y', t, y_hat); grid on; legend('真实输出', '辨识模型输出');如果验证集上模型输出和真实输出对不上,先别急着骂求解器,排查顺序是:激励信号是否充分、数据是否去均值、噪声是否太大、阶次是否选对。这一步做好了,后面 MPC 才能站得住脚。
3.3 第三步:搭建MPC控制器,JITL和严格NMPC两种拆解
如果走 JITL 路线,每个采样时刻要做的工作是:计算当前工作点与历史数据库所有样本的距离,抽取最近的 k 个样本,用加权最小二乘拟合局部线性模型,然后在这个局部模型上解 QP。核心伪代码长这样:
%% 每采样时刻:JITL局部建模 dist = vecnorm(DB_x - x_now, 2, 2); % 计算当前工作点与历史库的距离 [~, idx] = mink(dist, k); % 抽取k个近邻样本 % 用加权最小二乘拟合局部线性模型 A_local = fit_local_model(DB_x(idx), DB_u(idx), dist(idx)); B_local = fit_local_b(DB_x(idx), DB_u(idx), dist(idx)); [A_d, B_d] = c2d_local(A_local, B_local, Ts); % 离散化如果走严格 NMPC,用 MPC Toolbox 的 nlmpc 对象配置:
%% 严格NMPC:基于nlmpc对象 nx = 2; nu = 1; ny = 2; nlobj = nlmpc(nx, ny, nu); nlobj.Ts = Ts; nlobj.PredictionHorizon = 15; % 预测时域 nlobj.ControlHorizon = 3; % 控制时域 nlobj.Model.StateFcn = @(x, u) myStateFcn(x, u, Ts); nlobj.Model.OutputFcn = @(x, u) [x(1); x(2)]; nlobj.Weights.OutputVariables = [10 5]; nlobj.Weights.ManipulatedVariablesRate = 0.2; % 允许求解失败时输出次优解 nlobj.Optimization.UseSuboptimalSolution = true;这里有个非常重要的坑:nlmpc 要求状态函数连续可微。如果你在状态方程里写了 if-else、饱和保护开关、查表跳变这类不光滑逻辑,求解器一定会出问题。工程上常见的办法是用平滑近似函数替代硬切换,比如饱和函数用带平滑过渡的函数逼近。
3.4 第四步:闭环仿真与三种方案对比
闭环仿真的主流做法是用 for 循环逐步推进:每一步先更新模型(JITL 路径)或保持模型(固定线性路径),然后求解 MPC 优化问题得到当前控制量,再把控制量送入仿真系统,推进到下一时刻。完整代码骨架如下:
%% 闭环仿真主循环 x_hist = zeros(nx, N_steps+1); u_hist = zeros(nu, N_steps); x_hist(:, 1) = x0; for k = 1:N_steps xk = x_hist(:, k); % JITL更新局部模型 [A_d, B_d] = jitl_update(xk, DB_x, DB_u, k_nearest); % 求解线性MPC问题(QP) [u_opt, ~] = quadprog(H, g, Aineq, bineq, [], [], lb, ub, [], options); % 限幅并记录 u_hist(:, k) = max(min(u_opt, 1), -1); % 推进真实非线性系统 x_hist(:, k+1) = myStateFcn(x_hist(:, k), u_hist(:, k), Ts); end我在同样的非线性系统上对比了三个方案:固定线性 MPC(基于 n4sid 模型)、JITL 自适应 MPC、严格 NMPC。结果如下:
| 方案 | RMSE跟踪误差 | 平均求解时间 | 是否压力可控 |
|---|---|---|---|
| 固定线性MPC | 0.82 | 0.8 ms | 设定值大跳变时发散 |
| JITL自适应MPC | 0.21 | 2.1 ms | 全程稳定 |
| 严格NMPC | 0.17 | 35 ms | 大预测时域时变慢 |
严格 NMPC 跟踪精度确实是最好的,但求解时间高出将近两个数量级。JITL 在精度损失不大的情况下,实时性优势非常明显,这也是我为什么推荐工程落地优先考虑 JITL 路线。
3.5 参数整定的经验和公式依据
预测时域 P 的选取,我通常是看系统开环上升时间,取它的三分之一到二分之一。选太小,控制器“看不远”,约束处理能力弱;选太大,计算量涨得快,而且远期预测本来就不准,反而引入噪声。控制时域 M 一般取 P 的 10% 到 30%。M 等于 P 时,自由度过大,输入序列抖动;M 等于 1 时又过于保守,跟踪响应慢。
权重矩阵 Q 和 R 的整定,我的习惯是先固定 Q 为单位阵,再看输入输出量纲来定 R。如果输出跟得上但输入抖动厉害,加大 R;如果输出响应迟钝,加大 Q。两个矩阵不要一上来同时乱调,否则很难判断是哪个参数引起的响应变化。
4. 常见问题与排查技巧实录
4.1 求解无解或不可行怎么办
最典型的症状是某个采样时刻求解器报 no feasible solution。原因一般是约束设得过紧,或者预测模型和约束条件冲突。解决办法有两个方向:一是对输出和状态约束引入软约束,也就是松弛变量,让约束可以在极端情况下被“软化”;二是在 nlmpc 里设置 UseSuboptimalSolution 为 true,让求解器在找不到最优解时输出一个可行的次优解。手写 fmincon 时,可以在目标函数里加入约束违反量的惩罚项,达到类似效果。
4.2 闭环发散或稳态误差大,先查模型失配
闭环跑起来经常遇到两种情况:设定值一变化系统就发散,或者跟踪始终有静差。发散大概率是预测模型离工作点太远,模型失配严重,排查方法是在验证集上检查开环预测曲线——如果开环预测都不准,闭环当然不可能稳。稳态误差大的话,给 MPC 加输出扰动模型(Output Disturbance Model),让控制器在闭环里自己“学”出偏差补偿,这是工业 MPC 处理静差的标准做法。
4.3 求解时间太长实时性不够
实时性是 NMPC 落地最大的坎。我用过几个有效的手段:缩小预测时域 P;把控制时域 M 压到 1 或 2;给求解器提供解析雅可比矩阵而不是数值差分;每次求解时用上一时刻的可行解做初值,这叫热启动,效果立竿见影。如果这些手段还不行,就退回到 JITL 加 QP 的路线,QP 求解毫秒级完全没压力。
4.4 数据激励不够,模型直接不靠谱
数据驱动 MPC 最让人头大的一个坑,就是离线数据质量不行,导致辨识出来的模型根本不能用。典型特征是验证集误差大,模型对数据“死记硬背”。处理办法是保证采集数据同时具备多频率激励和幅值覆盖,PRBS 和 GBN 信号是标配。建模的时候一定留独立验证集交叉检查。如果现场采集的数据天生可观测性不足,我建议不要硬扛数据驱动了,退回去用 PID 修修补补可能更划算。
4.5 常见问题速查表
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| 求解器报无解 | 约束过紧、约束冲突 | 软约束、UseSuboptimalSolution |
| 设定值跳变后发散 | 模型失配严重 | 换JITL路线、检查验证集误差 |
| 稳态误差大 | 模型通道不准、无积分作用 | 加输出扰动模型 |
| 求解时间超标 | 预测时域太大、无解析梯度 | 缩短P、热启动、解析雅可比 |
| n4sid输出符号不对 | 通道顺序、C矩阵方向问题 | 检查C矩阵、输入输出顺序 |
| 模型阶次过高不收敛 | 过拟合噪声 | 看奇异值拐点,降低阶次 |
| 输入高频抖动 | 控制时域过大、R权重过小 | 减小M、加大R、特征向量加u(k-1) |
我把这套项目跑完后的体会是:数据驱动 MPC 里一半以上的功夫都花在数据和建模上,算法本身反而是相对成熟的部分。第二个体会是,别一上来就追求“严格的非线性 NMPC”,先用 JITL 加 QP 跑通整个链路,你会发现很多看起来是算法的问题其实都是数据和参数的问题。最后再分享一个小技巧:在 JITL 里做近邻检索的时候,不要只拿当前状态当特征向量,把上一拍控制量也加进去,局部模型的拟合质量会明显变好。我最初忽略这一点的时候,模型总是出现高频抖动,加了之后整个闭环瞬间稳了下来。希望这篇东西能成为你项目起步时的参考基准,少走一点弯路。