ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MFAC无模型自适应控制三种动态线性化方法Matlab复现与对比

MFAC无模型自适应控制三种动态线性化方法Matlab复现与对比 做控制方向的朋友应该多少听说过MFAC。我这阵子刚好在复现一篇关于无模型自适应控制Model Free Adaptive Control的论文把CFDL、PFDL、FFDL三种动态线性化方案放在同一套非线性系统里跑了一遍用的是Matlab。这篇文章就把整个复现过程、原理理解、参数设置和踩过的坑原原本本讲一遍适合正在入门MFAC、或者想快速跑通仿真验证算法的研究生和工程师。MFAC这东西最大的价值在于不需要建立被控对象的精确数学模型直接靠输入输出数据在线估计一个叫“伪偏导数”的量然后把非线性系统在当前时刻改造成线性数据模型。CFDL、PFDL、FFDL就是三种改造方式区别主要在“用到多少历史的输入输出信息”。我最初接触MFAC时也困惑明明是非线性系统凭什么能说“动态线性化”后来跑通仿真才明白它不是把系统全局线性化而是在每一个工作点上用一个含伪偏导数的线性化模型去局部逼近系统动态再配合在线估计和控制律设计让闭环系统在扰动和不确定性下依然能跟踪期望轨迹。这篇文章不讲虚的直接给你可复现的Matlab思路附带参数选择的经验。1. 先搞清楚MFAC到底解决什么问题1.1 无模型控制的核心逻辑传统自适应控制需要一个显式的数学模型但实际很多被控对象要么机理复杂要么参数时变建模成本极高。MFAC的思路很直接我不建模我只用被控系统的输入输出数据在线估计一个等价的动态线性关系再基于这个关系设计控制器。你可以把伪偏导数理解成一个“时变的、数据驱动的局部增益”它把系统过去时刻的输入输出增量映射到当前输出的增量上。具体到离散非线性系统一般写成[ y(k1) f(y(k), y(k-1), \dots, u(k), u(k-1), \dots) ]其中 ( f ) 未知。MFAC的核心条件是对该系统施加一个“广义Lipschitz”假设当输入增量 ( \Delta u(k) ) 有界时输出增量 ( \Delta y(k1) ) 也有界。在这个假设下可以证明存在一个时变参数伪偏导数 ( \phi(k) )使得[ \Delta y(k1) \phi(k) \Delta u(k) ]这就是所谓“动态线性化”。它把未知非线性关系在当前时刻转换为一个简单的线性增量关系。( \phi(k) ) 不需要知道 ( f ) 的表达式只需要通过输入输出数据在线估计。这个逻辑的好处是控制器设计变得极其简单用估计的 ( \hat{\phi}(k) ) 设计控制律直接让输出 ( y(k1) ) 逼近期望值 ( y^*(k1) )。整个闭环里没有模型参数只有估计器和控制器两个部分。我在复现时最大的感受是MFAC很“轻”代码量不大但每个参数都会影响收敛速度和稳定性。1.2 CFDL、PFDL、FFDL三兄弟的区别CFDL、PFDL、FFDL都来自同一个框架区别在于数据模型包含的输入输出信息量CFDL紧格式动态线性化只使用当前时刻的输入增量 ( \Delta u(k) )伪偏导数是一个标量。最简单适合输入输出关系相对平滑、动态不太复杂的系统。PFDL偏格式动态线性化使用过去 ( L_u ) 个输入增量 ( \Delta u(k), \Delta u(k-1), \dots, \Delta u(k-L_u1) )伪偏导数变成一个向量 ( \phi(k) )。适合输入历史信息对输出影响明显的系统。FFDL全格式动态线性化同时使用过去 ( L_y ) 个输出增量和 ( L_u ) 个输入增量伪偏导数由两部分组成( \phi_{y}(k) ) 和 ( \phi_{u}(k) )。信息最全但需要估计的参数也最多计算量和调参难度都上升。简单类比CFDL像是你用“当前的一步油门变化”预测车速变化PFDL则考虑“最近几步油门变化”的共同影响FFDL还把“之前几点车速变化”也纳入考量。信息越多对复杂动态的刻画能力越强但参数越多估计越容易过拟合或发散。实际复现时建议从CFDL跑通再逐步升级到PFDL、FFDL不要一上来就挑战最复杂的。2. 动态线性化方法的数学本质与实现要点2.1 CFDL只有一个伪偏导数CFDL的算法流程很经典。先是伪偏导数估计[ \hat{\phi}(k) \hat{\phi}(k-1) \frac{\eta \Delta u(k-1)}{\mu \Delta u(k-1)^2} \left( \Delta y(k) - \hat{\phi}(k-1) \Delta u(k-1) \right) ]其中 ( \eta ) 是估计步长( \mu ) 是惩罚因子防止分母为零。然后用一个重置机制避免伪偏导数退化[ \text{如果 } |\hat{\phi}(k)| \le \varepsilon \text{ 或 } |\Delta u(k-1)| \le \varepsilon\text{则 } \hat{\phi}(k) \hat{\phi}(k-1) ]控制律采用如下形式[ u(k) u(k-1) \frac{\rho \hat{\phi}(k) \left( y^*(k1) - y(k) \right)}{\lambda |\hat{\phi}(k)|^2} ]其中 ( \rho ) 是控制步长因子( \lambda ) 是控制量惩罚因子。这里 ( \lambda ) 的作用很关键当 ( \hat{\phi}(k) ) 很大或接近零时( \lambda ) 可以限制控制增量避免控制量剧烈变化。实现时我建议把“重置条件”写得严格一点否则仿真前期特别容易因为伪偏导数估计过小导致控制量爆炸。一个常见经验是( \mu ) 在 ( 0.01 \sim 1 ) 之间( \eta ) 在 ( 0.5 \sim 2 ) 之间( \rho ) 在 ( 0.1 \sim 1 ) 之间( \lambda ) 在 ( 0.5 \sim 10 ) 之间。具体数值要结合系统响应调整没有通吃所有系统的固定值。2.2 PFDL让输入信息窗口更长PFDL把单个输入增量扩展成一个窗口向量[ \Delta U_L(k) [\Delta u(k), \Delta u(k-1), \dots, \Delta u(k-L1)]^T ]动态线性化模型变成[ \Delta y(k1) \phi(k)^T \Delta U_L(k) ]对应的伪偏导数估计[ \hat{\phi}(k) \hat{\phi}(k-1) \frac{\eta \Delta U_L(k-1) \left( \Delta y(k) - \Delta U_L(k-1)^T \hat{\phi}(k-1) \right)}{\mu |\Delta U_L(k-1)|^2} ]控制律则基于这个向量模型推导最终控制量增量由多个历史输入增量加权得到。窗口长度 ( L ) 是PFDL最重要的超参数。我测试下来( L2 ) 或 ( L3 ) 效果通常不错再大不仅计算量上升而且容易出现参数耦合导致估计震荡。PFDL适合的系统特征是当前输出变化不仅跟最近一次控制增量有关还跟更早的控制输入有关典型就是带输入滞后或者高阶动态的系统。如果你发现CFDL控制效果不理想误差曲线呈现“跟得上但很勉强”的状态可以试试PFDL让控制器“多看几步历史输入”。2.3 FFDL输入输出历史一起用FFDL进一步加入输出增量窗口[ \Delta Y_{L_y}(k) [\Delta y(k), \Delta y(k-1), \dots, \Delta y(k-L_y1)]^T ][ \Delta U_{L_u}(k) [\Delta u(k), \Delta u(k-1), \dots, \Delta u(k-L_u1)]^T ]动态线性化模型为[ \Delta y(k1) \phi_{y}(k)^T \Delta Y_{L_y}(k) \phi_{u}(k)^T \Delta U_{L_u}(k) ]这意味着控制器不仅考虑输入历史还考虑输出历史的趋势。对于输出自回归特性明显、存在强非线性的系统FFDL的建模能力更强。但注意需要估计的参数总数变成 ( L_y L_u ) 个估计器的负担明显增加。我做过一个例子系统模型是 ( y(k1) \frac{2.5 y(k) y(k-1)}{1 y(k)^2 y(k-1)^2} u(k) )用CFDL已经能跟踪但换成FFDL后由于参数估计初值不合适前几步就出现了高频抖动。所以FFDL不是“越复杂越好”它更适合CFDL、PFDL都失效的难缠场景。3. Matlab代码实现的整体架构与关键函数设计3.1 代码框架主脚本、控制器函数、被控对象函数复现MFAC时我强烈建议分模块写不要全部堆在脚本里。我的工程目录大概是这样的main_compare.m主脚本设置系统类型、控制器类型、仿真步数调用各函数并绘图。plant_model.m被控对象函数内部用switch选择不同的非线性系统。mfac_controller.m控制器函数内部用switch选择CFDL、PFDL还是FFDL。parameter_reset.m伪偏导数重置逻辑单独提出来方便调试。主脚本的结构可以这样% main_compare.m clear; clc; close all; steps 500; sys_type 1; % 1: 光滑非线性 2: 高阶动态 3: 非最小相位 ctrl_type CFDL; % CFDL / PFDL / FFDL % 初始化状态 u zeros(steps, 1); y zeros(steps, 1); yd zeros(steps, 1); % 期望轨迹 for k 1:steps yd(k) 0.5 * sin(k * 0.01) 0.3 * cos(k * 0.02); end % 控制器参数 param.eta 1; param.mu 0.1; param.rho 0.5; param.lambda 1; param.phi0 1; % 伪偏导数初值 param.Lu 2; % PFDL/FFDL输入窗口 param.Ly 2; % FFDL输出窗口 % 核心循环 for k 1:steps-1 t k; % 当前时刻 % 计算控制量需要用到历史 y, yd 和 u u(k1) mfac_controller(t, y, yd, u, param, ctrl_type); % 被控对象输出 y(k1) plant_model(t, y, u, sys_type); end % 绘图 plot(yd, r--); hold on; plot(y, b-); legend(期望,实际);这里t是当前时刻索引历史值可以直接从y、u数组中取。实际写的时候要注意数组索引不能越界尤其是PFDL、FFDL需要用到前几步数据时我在代码里用max(k - Lu, 1)做下限保护。3.2 三个被控系统的选择与建模标题里提到“三个非线性系统动态线性研究”所以我在复现时准备了三个典型对象分别考验三种动态线性化方法的不同侧重点。系统1光滑非线性的典型[ y(k1) \frac{y(k)}{1 y(k)^2} u(k)^3 ]这个系统输出平滑输入带有三次非线性比较适合验证CFDL的基线性能。系统2高阶动态型[ y(k1) \frac{y(k) y(k-1) y(k-2) u(k-1) (y(k-2) - 1)}{1 y(k)^2 y(k-1)^2 y(k-2)^2} u(k) ]这个系统带有输入滞后项 ( u(k-1) )且分母非线性较复杂。CFDL在这个系统上往往需要把控制步长调得很小心PFDL会明显改善跟踪效果。系统3非最小相位型[ y(k1) \frac{2.5 y(k) y(k-1)}{1 y(k)^2 y(k-1)^2} u(k) 0.5 u(k-1) ]这里加入了未来时刻输入反效应( u(k-1) ) 项相位特性不理想FFDL由于同时考虑输出历史和输入历史通常能获得比CFDL更平滑的控制效果。plant_model.m的写法大概是这样function y_next plant_model(k, y, u, sys_type) switch sys_type case 1 yk y(k); uk u(k1); y_next yk / (1 yk^2) uk^3; case 2 yk y(k); ym1 y(max(k-1,1)); ym2 y(max(k-2,1)); um1 u(max(k,1)); uk u(k1); y_next yk * ym1 * ym2 * um1 * (ym2 - 1) / (1 yk^2 ym1^2 ym2^2) uk; case 3 yk y(k); ym1 y(max(k-1,1)); uk u(k1); um1 u(max(k,1)); y_next 2.5 * yk * ym1 / (1 yk^2 ym1^2) uk 0.5 * um1; end end注意系统2、3里我用u(max(k,1))访问历史控制量避免前几步没数据时索引错误。仿真前几个时刻可以给y(1)、u(1)初始化为0MFAC控制器通常能自己拉回来。3.3 控制器函数的设计与参数传递mfac_controller.m是核心。CFDL部分我的实现思路是维护一个状态量phi_hat由于它在每个时刻都要更新我直接把它放在param结构体里函数每次调用时更新并返回。代码片段function u_next mfac_controller(k, y, yd, u, param, ctrl_type) uk u(k); yk y(k); dy yk - y(max(k-1,1)); du_prev uk - u(max(k-1,1)); switch ctrl_type case CFDL phi param.phi_hat; % 伪偏导数估计 if abs(du_prev) 1e-5 phi phi param.eta * du_prev * (dy - phi * du_prev) / (param.mu du_prev^2); end % 重置 if abs(phi) 1e-5 || abs(du_prev) 1e-5 phi param.phi0; end param.phi_hat phi; % 控制律 u_next uk param.rho * phi * (yd(k1) - yk) / (param.lambda phi^2); % 其他case略 end end这里最关键的是“控制律用当前的phi还是更新后的phi”。不同文献写法不同我测试后更推荐先用更新后的phi计算控制量因为它在当前时刻更好地反映了系统局部动态。如果你的仿真发散可以试试改用旧值有时候反而更稳这跟采样周期和对象特性有关。PFDL和FFDL的代码结构类似只是phi从标量变成向量控制律变成矩阵运算。FFDL要额外估计输出窗口的伪偏导数代码更啰嗦但核心公式就是前面那套估计加重置。建议先用结构体把phi_y和phi_u分开存否则容易把自己绕晕。4. 三种方法在非线性系统上的仿真对比与结论4.1 仿真结果怎么看跑完仿真不要急着截图先看三条曲线输出跟踪曲线、误差曲线、控制输入曲线。输出跟踪曲线能看出是否跟上期望轨迹但“跟上”也有区别是相位滞后很小但全程震荡还是平稳但滞后明显。误差曲线能反映稳态精度和收敛速度。控制输入曲线最容易被忽略但它最能暴露问题如果控制量高频大幅振荡说明算法参数不合理即便输出看起来不错实际工程中执行器也受不了。我在系统1上对比三种方法时CFDL经过调参能达到稳态误差在 ( 10^{-3} ) 量级控制输入较平滑。PFDL因为窗口信息多前期暂态误差更小但稳态时会有轻微的高频波动。FFDL在这类平滑系统上反而“用力过猛”需要把 ( \lambda ) 调大才能稳定。这说明方法选择确实依赖系统特性不是越复杂越好。4.2 计算量、参数数量与前向适用性对比我梳理了一张表方便大家一眼看明白三种方法的差异方法动态线性化窗口伪偏导数数量计算量典型适用场景调参难度CFDL仅当前输入增量1低光滑非线性、动态较简单的系统低PFDL输入窗口 ( L_u )( L_u )中输入滞后、高阶动态明显中FFDL输入窗口 输出窗口( L_u L_y )高强非线性、输出自回归特性显著的复杂系统高从复现角度看CFDL适合做算法验证和理解原理PFDL适合处理带滞后的对象FFDL则适合CFDL和PFDL都压不住的情况。要注意伪偏导数数量增加后每个参数的估计都需要充分的激励信号如果期望轨迹太“平坦”估计器可能学不动控制效果会劣化。4.3 稳定性和边界条件不能只看仿真MFAC虽然不需要模型但闭环稳定性是有条件的系统需要满足广义Lipschitz条件且伪偏导数的变化不能太剧烈。实际仿真中如果系统非线性项太过“野”比如输入项是 ( e^u ) 或 ( u^5 )MFAC也会失控。我在复现时把系统1的输入非线性改成 ( u^5 ) 测试发现CFDL明显跟踪变差主要因为系统局部增益变化太快单个标量伪偏导数已经不足以刻画这种剧烈非线性。另外控制律中的 ( \lambda ) 不是越大越好。( \lambda ) 太小时控制增量容易过大太大时控制动作迟钝。一个相对稳妥的调参顺序是先固定 ( \eta ) 和 ( \mu ) 让伪偏导数估计收敛再调 ( \rho ) 让跟踪跟上最后调 ( \lambda ) 抑制控制震荡。不要上来就同时改四个参数否则出了问题根本不知道是哪个参数造成的。5. 复现路上的坑与排查心得5.1 伪偏导数估计发散输出直接飞到无穷大这是新手最容易遇到的问题。多半是 ( \mu ) 太小、( \eta ) 太大或者伪偏导数初值选得和实际动态差太远。我的建议是( \mu ) 不要低于 ( 10^{-3} )( \eta ) 控制在 ( 0.5 \sim 2 ) 之间初值 ( \phi(0) ) 设成 ( 0.5 \sim 2 ) 之间的正数。如果输出还是爆炸就检查重置条件是否生效。我在代码里加了param.phi_hat的上下限限制比如 ( [0.1, 10] ) 之间超出就强制重置稳定性立刻变好。5.2 控制量高频振荡误差曲线像锯齿这个现象通常是 ( \rho ) 偏大或 ( \lambda ) 偏小。控制律里 ( \lambda ) 的作用就是惩罚控制能量建议先把 ( \lambda ) 调到 5 以上再看看。如果振荡仍然存在可能不是参数问题而是伪偏导数估计对噪声过于敏感可以在估计公式里对 ( \Delta y(k) ) 做一阶滤波。实际中我处理过一种情况系统2里的输出突变很多直接滤波后估计才稳定下来。5.3 PFDL/FFDL窗口长度过大导致“过拟合”窗口长度不是越大越好。我试过把 ( L_u ) 设成 5结果控制效果反而不如 ( L_u2 )。原因是需要在线估计的伪偏导数变多但系统能提供的信息量有限多余参数反而被噪声带偏。建议窗口长度从2开始试如果效果不理想再逐步增加到3、4不要一上来就挑战8、9个参数。5.4 期望轨迹的选择影响巨大如果期望轨迹是常数伪偏导数估计得到的激励不足控制器后续遇到变化信号时会“反应迟钝”。我在复现时先让期望信号是正弦或方波让系统充分激励等伪偏导数收敛后再切到实际目标轨迹。这一点在对比CFDL、PFDL、FFDL时尤其重要否则三种方法的差异会被激励不足掩盖。5.5 代码调试时记得检查数组索引Matlab数组索引从1开始而离散系统公式里经常出现 ( k-1 )、( k-2 )。仿真前几步容易出现越界最简单的办法是初始化足够长的历史序列或者在访问历史值时用max(k - n, 1)。另外u(k1)表示当前计算出的控制量要等到下一时刻才作用这个时序关系务必在循环里理清楚。我个人的体会是复现MFAC最大的收获不是记住CFDL、PFDL、FFDL的公式而是理解了“数据模型 在线估计 控制器设计”这一套完全不同于传统建模控制的思路。这套方法很适合快速验证非线性系统的控制可行性尤其当被控对象机理不明、但能拿到可靠输入输出数据时MFAC是一个很实用的备选方案。至于三种动态线性化方法建议你先从CFDL入手跑通了再把窗口信息逐步加上去这样既能直观感受每种方法的变化也不会被一堆伪偏导数参数砸晕。希望这篇记录能让你少走一些弯路。
返回列表