模型预测控制(MPC)在工业过程控制里已经不算新概念,但一旦对象带上非线性,事情立刻变得棘手。前阵子我在做一个模拟项目X的控制器设计,对象是典型的强非线性系统,传统线性MPC在某一个工作点调好之后,稍微挪动工况,模型预测轨迹就开始失真,执行机构甚至出现反复震荡。后来我把方案升级为非线性MPC,并用数据驱动的方式补充模型信息,整套控制器才稳定下来。这篇文章就把这个研究和Matlab代码实现思路完整地拆给你看,适合正在做MPC相关课题、或者想用数据驱动替代繁琐机理建模的读者。
先说结论:非线性与数据驱动的MPC不是两个独立方向的简单叠加,而是解决"模型不准确"与"被控对象复杂"这一对矛盾的系统性思路。文中我会按"为什么做、原理是什么、模型怎么来、代码怎么写、参数怎么调、坑在哪里"这条主线展开,所有代码逻辑都来自我实际调试过的方案,你可以直接照着搭。
1. 我为什么把研究方向定在“非线性+数据驱动”MPC
1.1 一次线性MPC的真实翻车现场
我最早接触MPC是在一个温控实验装置上,当时用的还是线性状态空间模型。常规工况下,预测时域取20步,控制效果很理想,温度曲线几乎没有超调。后来我想测试控制器的鲁棒性,把设定值从一个低负荷区间直接跨到高负荷区间,问题立刻暴露:系统出现明显超调,然后在设定值附近来回小幅振荡。
我做了事后分析,根因并不在求解器或者权重设置,而在模型本身。该对象在高负荷区间呈现出增益收缩效应,同样的控制量增量带来的输出变化比低负荷区小得多,线性模型无法刻画这种变化,预测输出和实际输出的误差随预测时域的延伸被不断放大。MPC的核心竞争力就是"基于模型预测未来",模型一旦失准,后面所有优化动作都建立在错误的地基上。
这个案例让我意识到,对非线性特性显著的对象,线性MPC的适用边界非常狭窄。更麻烦的是,工程中很多对象根本拿不出精确机理模型,就算能写出微分方程,参数辨识的成本也高得吓人。于是我把研究路线调整为"非线性MPC框架 + 数据驱动建模",用数据弥补机理的缺失。
1.2 数据驱动到底在驱动什么
不少初学者听到"数据驱动MPC",第一反应是"不要模型,全靠数据"。这种理解偏差很大。数据驱动在MPC语境下的真正作用是:用数据构建或者修正预测模型,让MPC的预测模块更贴近真实对象。
数据驱动建模可以替代机理模型,属于黑箱路径,比如用神经网络、高斯过程回归、支持向量回归拟合系统的输入输出关系。也可以走灰箱路径,保留部分物理结构,用数据辨识未知参数。两条路我都试过,在样本量中等、激励信号设计合理的情况下,数据驱动模型 + 非线性MPC的闭环表现通常优于"粗略机理模型 + 线性MPC"。
从研究价值看,这个方向还解决了传统MPC推广中的一个痛点:如何快速把新对象的控制器搭起来。只要有一套可靠的数据采集流程,加上相对通用的MPC实现框架,换对象时不需要重新推导模型,响应速度会快很多。这也是我推荐课题或者工程预研往这个方向投入的原因。
2. MPC控制器的核心逻辑:预测模型、滚动优化与反馈校正
2.1 MPC每一拍在做什么
MPC的控制逻辑可以压缩成三步:在当前时刻,基于预测模型,从当前状态出发,预测未来一段固定时域内的系统输出;接着求解一个带约束的优化问题,找到一组最优控制序列,使得预测输出尽量接近参考轨迹,同时满足控制量和控制增量的约束;最关键的一步是,优化结果只取第一个控制量施加到对象上,下一时刻重新测量状态,再次求解。
这就是滚动优化(Receding Horizon)的含义。它在Matlab代码里的实现节奏非常清晰:测量/估计状态,调用优化求解器,输出第一个控制量,等待下一个采样周期。非线性MPC和线性MPC在步骤上完全相同,差别在于预测模型变成了非线性函数,优化问题从二次规划变成了非线性优化。
用数学形式写,非线性MPC每拍求解的问题大致如下:给定当前状态,找到控制序列,使从当前时刻到预测时域末端的累计代价函数最小,同时满足状态、输入、输出约束。代价函数一般包含跟踪误差和控制增量惩罚两部分,权重矩阵用来表达对不同目标的侧重。
2.2 非线性让优化问题从简单变复杂
线性MPC之所以高效,是因为线性模型约束下的优化问题具有凸性,可以快速找到全局最优解。引入非线性之后,目标函数和约束都可能变成非凸的,求解器只能保证找到局部最优解。这意味着初值选择、求解器配置、约束松紧程度都会直接影响控制品质。
这是很多人在Matlab里跑非线性MPC的第一道坎:同样的参数,换个初值,仿真结果差很多,甚至直接求解失败。我在调试中摸索出来的经验是:尽量给求解器一个合理的控制量初值(比如上一时刻的最优解),不要每次都从零开始。同时,约束不要设得刚好卡在边界上,留一点裕量,否则数值求解容易在边界处振荡。
2.3 闭环中的反馈校正角色
MPC能够抵抗模型误差和外部扰动,靠的不是预测模型有多精确,而是反馈校正。每拍重新测量状态、重新求解,相当于不断用真实数据"校准"预测。即使模型有一定的误差,只要误差不是太离谱,反馈机制都能把系统拉回到参考轨迹附近。
不过也要清醒地认识到,反馈校正的作用有上限。当模型误差大到预测方向本身就是错的,控制器会把控制量推向错误的方向,这时只能靠数据驱动建模来提升模型质量。我倾向把"反馈校正"理解为兜底机制,"模型准确度"才是性能天花板,两者缺一不可。
3. 数据驱动建模:从采集数据到可用预测模型
3.1 激励信号设计与数据预处理
数据驱动建模的第一步不是建模,而是采数据。很多人在这一步就失分了:采到的数据大部分时间都停留在某个工作点附近,输入输出变化范围很窄,这种数据训练出来的模型出了训练区间基本不能用。
我的做法是采用幅值随机变化的伪随机序列或者斜坡组合信号作为激励,让系统遍历更多的工况区间。采样频率不能太低,否则会丢失动态信息;也不能太高,否则相邻样本强相关,训练出的模型容易偏向高频噪声。先做几次预实验,观察系统在阶跃输入下的响应速度,再据此确定采样周期,一般取上升时间的十分之一到二十分之一左右。
数据预处理同样关键。采集到的原始信号通常带测量噪声和直流偏置,我一般按这个顺序处理:去除明显野值、零均值化(如果模型需要)、归一化到相近量级。归一化这一步特别容易被忽略,但对神经网络这类模型影响非常大,输入输出量级相差十倍,训练过程会很难收敛。
3.2 黑箱模型与灰箱模型的取舍
拿到干净的数据之后,接着要选模型结构。纯黑箱方案里,神经网络非线性自回归模型(NARX)是常用选择,它直接用过去的输入输出预测未来的输出,结构灵活,拟合能力强。高斯过程回归的优势在于能同时给出预测均值和方差,方差信息可以做随机MPC的输入,但计算量大,样本超过几千个点后会明显变慢。
灰箱方案更适合物理规律部分已知的对象。比如动力学方程结构已知,但某个摩擦系数未知,那就把模型写成带未知参数的形式,用数据估计这些参数。这种模型外推能力通常比纯黑箱好,需要的样本也少。我在项目中优先尝试灰箱,实在无法确定结构时才退回黑箱。
在Matlab里,神经网络训练可以借助自带的深度学习工具箱,也可以自己写一个简单的训练脚本。对于NARX模型,我用过一个比较朴素的实现:构造输入矩阵,用多层前馈网络拟合,配合早停策略防止过拟合,验证集误差不再下降时就停止训练。
3.3 模型验证:不只是看拟合精度
训练出的模型在训练集上误差可能很小,但这不代表它适合做MPC预测。MPC关心的是多步预测能力,而很多模型只优化了一步预测误差,多步预测时会累积误差。
我验证模型的方式有两种。一是多步自由仿真:给模型一个初始状态和一段输入序列,让它自由滚动预测若干步,和真实系统的输出对比,观察误差是否发散。二是闭环验证:把模型嵌进MPC仿真中,看闭环控制品质能不能接受。只有两种验证都通过,我才会把这个模型交给MPC控制器使用。
有一种常见的坑是:模型在开环自由仿真时表现很好,闭环一到极限工况就开始失效。原因在于MPC的优化会主动把系统推到约束边界,训练数据里约束边界附近往往样本不足。解决方法是闭环初调后再补充采集边界工况数据,重新训练模型,形成一个"数据采集-建模-闭环测试-补数据"的迭代循环。
4. Matlab代码实现的核心路径:从预测模型到闭环仿真
4.1 用nlmpc对象搭起控制器骨架
Matlab为非线性MPC提供了专门的控制器对象,基本思路是先创建控制器对象,然后定义状态函数和输出函数,最后配置权重和约束。这是我搭骨架的典型步骤:
% 创建非线性MPC控制器:6个状态,2个输入,4个输出 nx = 6; nu = 2; ny = 4; nlobj = nlmpc(nx, nu, ny); % 指定预测模型 nlobj.Model.StateFcn = @myStateFcn; nlobj.Model.OutputFcn = @myOutputFcn; % 如果状态不可测,需要状态估计器 nlobj.StateFcn = 'myStateEstimator';状态函数是模型的核心,输入是当前状态、上一时刻控制量等,输出是状态导数。对离散时间系统,也可以直接定义离散状态转移函数。实现时需要注意函数的输入顺序和参数个数,Matlab的处理方式和普通函数调用不太一样,我第一次写的时候就是因为参数顺序反了,仿真结果完全混乱。
输出函数定义系统输出如何从状态和控制量得到。如果输出只是部分状态,在这里做选择和变换。整段代码跑通之后,可以用一个内置的合法性检查命令验证模型维度和函数调用是否一致,这一步能提前暴露很多低级错误。
4.2 求解器配置与实时性平衡
非线性MPC每拍要解一个非线性规划问题,求解器配置直接决定控制周期能不能跑得动。默认配置通常偏保守,或者在某些问题上收敛很慢。
% 求解器设置 nlobj.Optimization.Solver = 'fmincon'; nlobj.Optimization.UseSuboptimalSolution = true; nlobj.Optimization.ExitFlags = ...我常用的经验是:允许求解器在达到最大迭代次数时返回次优解,而不是强制要求收敛到最优。实时控制场景下,一个可行的次优解比一个来不及计算出来的最优解有价值得多。收敛阈值也可以适当放宽,只要闭环性能满足要求,不必追求数值上的高精度。
预测时域的长短对求解速度影响很大。时域越长,决策变量越多,求解越慢。我从短的预测时域开始调试,先把闭环控制跑稳定,再逐步加长时域看性能提升,直到性能提升不明显为止。这样既控制计算量,也避免一开始就陷入求解失败的泥潭。
4.3 闭环仿真循环的搭建方式
有两种常见方式搭建闭环仿真。一是用Simulink图形化搭建,控制器模块和对象模块用信号线连接,适合展示和继承已有Simulink模型。二是纯脚本实现,用循环模拟实时控制过程,适合批量实验和调试。
我倾向于先用脚本方式做闭环测试。核心循环结构很清晰:
for k = 1:simSteps % 当前状态测量(或从状态估计器获取) xk = x_history(:, k); % 调用nlmpcmove求解最优控制量 [u, info] = nlmpcmove(nlobj, xk, u_prev, ref, []); % 将控制量施加到非线性对象上(数值积分一步) x_next = simPlant(xk, u); % 保存数据,更新状态 u_history(:, k) = u; x_history(:, k+1) = x_next; u_prev = u; end这段代码的逻辑就是MPC每一拍的真实工作流程。实际调试中,我还会在循环里加时间戳统计,观察单步求解时间是否小于采样周期。如果超时,说明实时性不达标,需要回头调整预测时域、求解器精度或者优化算法。
4.4 状态估计:不可测状态怎么处理
实际系统中很多状态无法直接测量,这时候MPC需要一个状态估计器配合。最简单的方式是用扩展卡尔曼滤波或者移动窗口估计。Matlab中可以自定义状态估计函数,控制器会在每一拍自动调用。
一个容易忽略的问题是:状态估计器的模型误差会和控制器的模型误差叠加。如果预测模型已经是数据驱动训练出来的,估计器用的模型最好和它一致,否则两者各找一个"最合理"的解释,反而把状态估计带偏。我在项目里就把状态函数和估计器的模型做成同一个文件,改模型时只改一处,避免维护两套模型。
5. 参数标定与性能调试:时域、权重与约束
5.1 预测时域和控制时域的确定思路
预测时域到底取多少,没有固定公式,但有一个非常实用的经验参考:应该能覆盖系统动态响应的主要过渡过程。对欠阻尼系统,预测时域至少要包含第一个振荡周期的绝大部分,否则控制器看不到超调的代价,优化结果会允许过度激进的输入。
控制时域则决定了优化的自由度数量。控制时域越短,计算越快,但性能会受限;越长越灵活,但求解变慢,而且后期的控制量变化对当前性能影响很小,性价比低。实际项目中我通常取控制时域为预测时域的五分之一到三分之一,然后根据闭环效果微调。
在Matlab里,预测时域和控制时域都可以通过控制器对象的属性直接修改。我调试时做了个简单的参数扫描:固定其他参数,逐个时域组合跑闭环仿真,记录上升时间、超调量、控制量总变化等指标,据此选择合适的值。这种方式比凭感觉调靠谱得多。
5.2 权重矩阵的调整节奏
权重矩阵体现的是"误差小"和"控制动作平缓"之间的权衡。权重调大的维度,控制器会优先满足该维度的指标。这里面有个新手误区:只关注误差权重,忽视控制增量的权重,导致控制量剧烈抖动,执行机构磨损严重。
我的调整节奏是:先给小控制增量权重,让系统能跟上参考轨迹,观察基本控制品质;再逐步增大控制增量权重,直到控制量的抖动明显减小,同时性能损失在可接受范围内。如果系统出现振荡,优先检查是不是控制增量权重太小,而不是盲目加误差权重。
约束权重也要重视。状态或输出约束在优化中可能找不到可行解,这时可以在约束上设置软约束和惩罚权重。软约束允许短暂越界,但越界会在代价函数中产生惩罚项。实际工程中几乎都用软约束,因为硬约束在扰动和模型误差下非常容易导致求解失败。
5.3 闭环性能的验证指标
调参完成后,不要只给一条好看的曲线就当结束。我习惯做几组对比实验:一是在标称条件下施加设定值阶跃,看跟踪性能;二是在工作点附近加持续扰动,看抗扰能力;三是把模型参数偏移一定幅度,看控制器还能不能稳住,这能检验模型失配的容忍度。
另外,记录每拍的求解时间分布也很重要。如果大部分求解时间都接近采样周期上限,只能说"勉强能跑",任何外界环境变化都可能让某一步超时。给求解时间留至少20%-30%的余量,才算比较稳妥。
6. 实测中遇到的那些坑与解决方案
6.1 求解失败:初值、约束与收敛设置的联动
非线性MPC调试初期,我最常遇到的问题就是求解失败。排查顺序一般是这样:先看约束是不是过紧,尤其是把多个约束都推到边界的情况;再看初值是不是离可行域太远;最后看求解器收敛阈值和最大迭代次数是否合理。
有次现象很典型:控制器在稳态附近一切正常,一旦设定值大范围阶跃,求解立刻失败。原因是最优解附近有约束起作用,求解器从上一时刻的解出发,迭代方向被约束阻塞,无法在有限代数内找到新的可行点。解决办法是给控制量加软约束尾标,同时把控制量初值设为上一时刻的最优值,问题就消失了。
6.2 数据驱动模型的过拟合与漂移
数据驱动模型在训练集上表现很好,但闭环一跑就变形,这种情况多半是过拟合或者训练数据覆盖不足。过拟合的典型表现是:模型对训练数据中的噪声都精确拟合,泛化能力差。我的对策是增加验证集、早停、正则化,以及最重要的,扩充数据覆盖范围。
模型漂移则是长期运行中对象特性发生变化,离线训练的数据驱动模型逐渐偏离当前工况。这时可以让系统在线采集新数据,定期重训或者在线更新模型。数据驱动MPC的完整落地形态一定是带模型在线更新机制的,离线训练只是一个起点。
6.3 计算负担:实时性上不去的三种对策
如果单步求解时间太长,我一般按三种思路依次尝试。第一,缩短预测时域,这是最直接的手段,代价是性能可能下降;第二,把预测模型简化,比如用更轻量的代理模型替换复杂的神经网络;第三,对优化问题做热启动,把上一拍的最优解作为当前拍的初始猜测。
还有一个思路是采用显式MPC的思想:离线把最优控制律在状态空间网格上求好,在线查表。不过对高维非线性系统,网格会急剧膨胀,实际中慎重使用。我在项目里综合用了前三种方案,最终把求解时间压缩到采样周期的四成左右,实时性就算比较健康了。
6.4 仿真到实物迁移的额外提醒
从纯Matlab仿真迁移到实物平台,通常会增加通信延迟和执行机构响应时间。如果仿真时就把这些因素考虑进去,比如在控制循环里加入一拍延时模拟,迁移会顺利很多。不然仿真里调试好的参数,到实物上会因为延迟而产生新的振荡。
还有一点:实物平台上的初始状态很难和仿真完全一致,状态估计器的初始值误差可能让前几步控制动作很激进。建议在启动时给控制量变化率加约束,或者在前几个采样周期采用保守控制策略,等状态估计收敛后再切入正常MPC模式。
这些经验回过头看,其实都是"数据驱动+非线性MPC"落地时最常见的共性问题。尤其是模型验证和实时性这两个环节,几乎每个项目都会遇到。我的体会是,不要指望第一次就能调出完美参数,数据驱动本身就是一个"采集-建模-闭环验证-补数据重训"的迭代过程。只要把这个循环跑顺,无论对象怎么换,整个控制器的搭建速度都会快很多。