
做控制的人都知道传统自适应控制最头疼的一件事就是建模。实际对象要么非线性强、要么参数慢变、要么机理根本说不清花在辨识上的时间往往比控制器设计本身还多。MFAC无模型自适应控制就是冲着这个问题去的不需要被控对象精确数学模型直接用系统的输入输出数据在线估计一个“等价线性化参数”然后把非线性系统当成一个时变线性系统来控。标题里的CFDL、PFDL、FFDL正是三种核心的动态线性化方法分别代表紧凑格式、偏格式和全格式。这篇文章我按复现思路把三个方法从理论到Matlab实现完整走了一遍跑通后的跟踪效果和参数敏感性都做了对比适合正在学数据驱动控制、想快速上手MFAC的同行参考。1. 复现项目概览与MFAC核心思路1.1 为什么选MFACMFAC是侯忠生教授提出的一套数据驱动控制框架核心思想是基于系统的输入输出数据在每个动态工作点上建立一个等价的时变线性模型再对这个线性模型设计无模型自适应控制器。和传统自适应控制最大的区别是它不经过“先辨识模型、再设计控制器”两步而是把“动态线性化”和“参数估计”合并到控制过程中一边估计参数一边更新控制量所以对未建模动态和参数变化有天然的适应性。我复现这个项目时选MFAC还有一个现实原因很多实际系统拿不到机理模型能拿到的只有传感器数据和执行机构指令。MFAC只需要在线采集u(k)和y(k)就能生成下一拍的控制量这对现场调试非常友好。你不需要系统辨识工具箱不需要花几周做阶跃实验只要把算法代码嵌进去调几个参数就能跑起来。当然它也不是万能药对伪偏导数的估计有要求如果系统阶数高、纯时滞大需要配合FFDL或者额外补偿策略。1.2 三种动态线性化方法的关系CFDL、PFDL、FFDL都是对非线性系统做“动态线性化”区别在于线性化时用了多少历史信息。CFDL紧凑格式只用当前控制增量Δu(k)来线性化输出增量Δy(k1)等价参数是一个标量叫伪偏导数PPDPseudo Partial Derivative。PFDL偏格式把控制增量在过去滑动窗口内的L个值都拿出来Δy(k1)被表示成这些历史控制增量的线性组合等价参数是一个向量叫伪梯度PG。FFDL全格式不仅用控制输入的历史增量还同时用输出序列的历史增量输入输出两侧的信息都纳入线性化模型表达能力最强。三者的关系可以看成“信息维度从低到高”。CFDL最简单参数少、计算量小但对复杂动态的刻画能力有限PFDL在输入侧加了多个历史点能更好处理输入通道特性FFDL进一步把输出历史也引进来等于给系统建模了一个“内部动态记忆”对高阶、振荡、时滞明显的对象效果往往更好。代价是FFDL需要估计的伪梯度向量更长在线计算量和参数整定难度都上去了。2. 理论推导与算法细节2.1 CFDL方法原理考虑单输入单输出离散非线性系统如果系统满足输出关于控制输入是广义Lipschitz的那么一定存在一个伪偏导数φ_c(k)使得系统输出增量可以用如下紧凑格式动态线性化表示Δy(k1) φ_c(k) Δu(k)其中Δy(k1)y(k1)-y(k)Δu(k)u(k)-u(k-1)。这里φ_c(k)不是一个固定常数它随工作点变化而变化但MFAC只需要在线估计它的当前值不需要知道它随什么规律变化。基于这个线性化模型CFDL-MFAC的控制律一般写成u(k) u(k-1) [ρ φ_c(k) (y*(k1) - y(k))] / [λ φ_c(k)^2]式子里y*(k1)是期望输出ρ是步长因子λ是惩罚因子主要防止分母为零以及限制控制量变化幅度。这个控制律从形式上很像线性系统的前馈加比例控制但φ_c(k)是实时估计的所以能自适应非线性特性。伪偏导数的估计算法同样关键我复现时用的是带重量的投影算法φ_hat(k) φ_hat(k-1) [η Δu(k-1) (Δy(k) - φ_hat(k-1) Δu(k-1))] / [μ Δu(k-1)^2]这里的η是估计步长μ是防止除零的正数。为了防止伪偏导数估计值漂移还要加一个重置机制当|φ_hat(k)|小于某个阈值ε或者|Δu(k-1)|很小导致分母接近零时把φ_hat重置为初值。整个CFDL算法每拍只需要三步读y(k)估计φ_hat算u(k)。算下来几乎是标量运算适合嵌入PLC或者单片机。我第一次在Matlab里跑通这个算法时感觉它像是一个“自动调参的PID”但比PID聪明的地方在于它知道自己该以多大增益补偿误差。2.2 PFDL方法原理CFDL只用了当前控制增量这在很多情况下不够用。比如被控对象的输入通道存在滞后或者执行机构有惯性当前控制增量要经过好几拍才能影响输出这时候CFDL的伪偏导数其实把多步动态全部压缩成一个标量估计起来容易失真。PFDL对此做了改进它引入了滑动时间窗口长度L把过去L拍的控制增量都纳入线性化模型Δy(k1) φ_p^T(k) ΔU(k)其中ΔU(k)[Δu(k), Δu(k-1), ..., Δu(k-L1)]^Tφ_p(k)[φ_1(k), φ_2(k), ..., φ_L(k)]^T是一个L维伪梯度向量。这样每个历史控制增量对当前输出增量的贡献都被单独估计模型对输入动态的刻画能力强了不少。PFDL的控制律推导思路和CFDL类似最终形式是把CFDL的标量φ_c(k)替换成伪梯度向量和输入向量的内积然后求解优化问题得到u(k) u(k-1) [ρ φ_1(k) e(k1)] / [λ ||φ_p(k)||^2]注意这里只有φ_1(k)直接对应当前控制增量Δu(k)其他φ_i(k)对应的历史增量已经被包含在u(k-1)及其之前的控制量里了所以控制律更新时只需要第一维。伪梯度向量的估计也是向量形式的递推需要同时更新L个值。复现PFDL时我踩过一个坑L如果取得太小比如L1PFDL退化成CFDL没意义如果L取得太大比如L10伪梯度向量各分量之间容易耦合估计值振荡控制效果反而不如L3或L4。实际使用中L一般取3到5具体要根据被控对象的输入动态时间常数来试。2.3 FFDL方法原理FFDL是三种方法里信息利用最完整的一种。它不再只盯着控制输入而是把输出序列的历史增量也当成线性化模型的回归变量。具体形式是Δy(k1) φ_f^T(k) [ ΔY(k); ΔU(k) ]其中ΔY(k)[Δy(k), Δy(k-1), ..., Δy(k-L_y1)]^TΔU(k)[Δu(k), Δu(k-1), ..., Δu(k-L_u1)]^T对应的伪梯度向量φ_f(k)维度是L_yL_u。也就是说FFDL把系统输出自身的动力学也纳入了模型这相当于给无模型控制器加了一个“轻量级的输出预测器”。由于模型里同时有输出增量和输入增量FFDL控制器能利用输出历史中的趋势信息对系统的振荡模态和非最小相位特性有更好的建模能力。控制律仍然可以从一个带惩罚项的优化问题里解析解出来形式上类似PFDL只是分母上的范数变成了整个φ_f(k)的范数更新项只保留Δu(k)对应的系数。复现FFDL时要特别注意维度匹配伪梯度向量长度是L_yL_u而回归向量也是L_yL_u维两者做内积时必须一一对应。我习惯把输出历史放在前面、输入历史放在后面同时把L_y和L_u都先设为2然后逐步调整。很多人第一次实现FFDL报错“维度不一致”多半就是这里写错了。3. Matlab代码实现与工程要点3.1 仿真系统设置为了对比三种方法我选了三个非线性程度、动态特性都不同的离散系统作为被控对象。系统A简单非线性y(k1)0.5y(k) u(k)/(1u(k)^2) 0.1sin(k)。输出有振荡扰动输入非线性饱和特性。系统B输入时滞y(k1)y(k)/(1y(k)^2) u(k)^2 0.3*u(k-1)。控制量对输出的影响不止当前拍考验PFDL和FFDL对输入历史的利用能力。系统C输出强烈非线性y(k1)0.7y(k)/(1y(k)^2)u(k)0.2u(k-1)。这个系统本身输出受到自身非线性约束适合看FFDL引入输出历史后的优势。参考信号我用了两种一个是幅值在0.5和1.5之间切换的方波考验跟踪动态响应另一个是频率适中的正弦信号考验稳态跟踪精度。仿真步数设成300拍足够看清楚三种方法的差异。所有代码都在Matlab R2021b上跑的不需要额外工具箱只要基础环境就能执行。我把公共部分封装成一个主循环函数三种方法的区别只体现在“线性化模型”和“参数更新”这两段代码上方便平行对比。3.2 核心代码实现下面这段是CFDL-MFAC的主循环核心只保留控制律和参数估计部分系统模型放在函数plant里实现方便替换成前面说的三个系统% CFDL-MFAC 主循环核心 % y_ref: 参考序列, y: 输出序列, u: 控制序列 % phi_hat: 伪偏导数估计值, phi_hat0: 初始值 y zeros(1, N); u zeros(1, N); phi_hat phi_hat0; % 例如 0.8 for k 1:N-1 % 1. 由被控对象产生当前输出实际工程中来自传感器 y(k1) plant(u(k), y(k)) 0.01 * randn; % 2. 计算输出增量和跟踪误差 dy y(k1) - y(k); e y_ref(k1) - y(k1); % 3. 伪偏导数在线估计 if k 1 du_prev u(k) - u(k-1); phi_hat(k) phi_hat(k-1) ... eta * du_prev / (mu du_prev^2) * (dy - phi_hat(k-1) * du_prev); % 重置机制防止估计值漂移 if abs(phi_hat(k)) eps_thresh || abs(du_prev) eps_thresh phi_hat(k) phi_hat0; end end % 4. 控制律 du rho * phi_hat(k) / (lambda phi_hat(k)^2) * e; u(k1) u(k) du; endPFDL的改动主要出现在伪梯度向量估计和控制律的分子。伪梯度向量phi_p是L维的估计时要用窗口内的历史du向量% PFDL 关键改动滑动窗口向量化 du_win zeros(1, L); for i 1:L if k i du_win(i) u(k-i1) - u(k-i); end end % 伪梯度更新 phi_p phi_p eta * du_win / (mu du_win*du_win) * (dy - phi_p*du_win); % 控制律分子中的当前项 du rho * phi_p(1) / (lambda phi_p*phi_p) * e;FFDL则需要把输出历史增量也拼进回归向量。我实现时用一个函数buildRegressor把dy_win和du_win拼接起来然后同一个更新框架直接复用。只要保持回归向量和伪梯度向量的维度一致代码结构和PFDL几乎相同这也是模块化复现最方便的地方。3.3 参数整定与避坑MFAC参数虽然不多但每个都对性能有直接影响。我复现时采用的初始参数范围如下参数含义典型范围我的调试取值ρ控制律步长因子0.11.00.50.8λ控制量惩罚因子0.1100.52η估计步长因子0.12.00.61.0μ估计惩罚因子0.0110.050.5φ0伪偏导数初始值0.520.81.2L滑动窗口长度PFDL/FFDL253调试顺序我建议先固定η、μ把控制律的ρ和λ调稳定再去动估计部分的参数。很多人一上来就乱调结果系统发散时根本分不清是谁的问题。还有一个经验λ不要取得太小虽然λ小能提高响应速度但会让控制量变化过猛对存在测量噪声的系统很容易引发振荡。实际我带噪声仿真时λ1比λ0.2稳得多。另外一个特别容易踩坑的地方是仿真初始几拍。因为MFAC要用Δu和Δy在k1时历史值不存在必须人为给定前几拍的初始值。常见做法是把u(1)、y(1)设置为等于参考信号初始值和0同时在k1时不更新伪偏导数只执行控制律。我在代码里也专门加了if k1的判定。4. 仿真结果对比与分析4.1 跟踪性能对比我先把三种方法用在系统A上参考信号是方波。CFDL收敛后上升时间大约10拍超调量在5%左右PFDLL3上升时间缩短到7拍超调量只有3%FFDLL_y2, L_u2上升时间和PFDL接近但过渡过程几乎没有振荡稳定精度也略好。这个结果说明系统A虽然有非线性但主要动态还集中在输入通道所以引入输入历史就能明显改善。系统B带输入时滞时CFDL出现了大约3拍的延迟后振荡伪偏导数估计值在0.5和-0.2之间来回跳控制效果勉强稳定但不好。PFDL因为把u(k-1)的历史增量纳入了模型对输入时滞有相当的补偿能力跟踪误差明显减小。FFDL则更进一步由于输出历史也参与了建模应对时滞导致的状态延迟更从容方波切换时几乎没有过冲。系统C输出非线性强CFDL依然能用但正弦跟踪时相位延迟比较明显。FFDL的优势在这个系统上最突出因为输出历史项捕捉到了y/(1y^2)这种非线性项带来的输出惯性控制系统表现出更好的预测性跟踪正弦信号的均方根误差比CFDL降低了约40%。PFDL介于两者之间。系统方法RMSE正弦跟踪最大超调方波ACFDL0.0835.2%APFDL0.0523.1%AFFDL0.0492.8%BCFDL0.1169.8%BPFDL0.0714.4%BFFDL0.0643.6%CCFDL0.1327.5%CPFDL0.0964.9%CFFDL0.0793.2%4.2 动态线性化方法的选择建议仿真做下来我对三种方法的定位有了更具体的判断。CFDL最适合初步验证和简单对象。如果被控对象没有明显时滞输出响应不诡异用CFDL就够毕竟它只需要一个标量参数肉眼都能盯着收敛过程。对实时性要求极高的嵌入式环境CFDL的标量浮点运算优势是实打实的。PFDL值得在输入通道有惯性、时滞或者执行机构动态不可忽略时使用。滑动窗口L的引入让控制律能“记住”前几拍的控制动作等于给控制器增加了一个短时记忆对抑制震荡和补偿时滞很有帮助。调试成本比CFDL略高但还在可控范围内。FFDL则是对付难缠对象的最好武器尤其是系统自身动力学复杂、输出存在强非线性反馈的情况。输出历史增量让控制器对系统有“形态感”不会只盯着输入输出差值一个维度。如果项目预算允许牺牲一点计算量FFDL的鲁棒性通常是最好的。我的个人习惯是先用CFDL快速搭建闭环验证可行性然后根据跟踪曲线的不足把CFDL直接替换成PFDL或FFDL。因为三种方法的控制器结构非常相似同一套仿真框架下改一个模块就能升级这也是MFAC工程化特别友好的地方。5. 常见问题与调试心得5.1 伪偏导数初始值的影响伪偏导数初始值φ0对启动阶段影响极大。我一开始在系统C上把φ0设成0.1结果前30拍输出根本跟不上参考信号误差一直在0.5以上后来检查发现是φ0太小导致控制增益不足。反过来如果φ0设成5控制律分母λφ^2里φ占主导增益又被压住了响应照样慢。建议φ0取被控对象在稳态工作点附近的增益估计值。如果没有先验知识从0.8到1.5之间试多数情况都能跑稳定。对伪偏导数重置后的数值我习惯直接设回φ0而不是设成0因为设0会让控制器瞬间失去增益。5.2 控制器参数的影响ρ和λ的作用有点像PID里的比例和微分。ρ过大控制量跳变剧烈伪偏导数估计容易过激系统出现高频抖动ρ过小跟踪速度又太慢。λ的作用是限制控制量变化幅度λ越大约束越强响应越平缓。我实测系统B时λ从0.1调到1振荡幅度能下降60%虽然跟踪慢了2拍但换来了稳定。η和μ控制的是参数估计的带宽。η越大伪偏导数往真实值方向修正的步子越大但容易震荡μ保护分母本质上是给估计过程加阻尼。一个实用的规律如果输出曲线在参考信号附近来回小幅震荡先把λ调大如果输出出现大幅周期波动再检查η是否过大。5.3 其他问题实现过程中还有几个小问题值得注意。一是噪声。MFAC直接用差分数据对测量噪声很敏感仿真中我加了0.01的白噪声伪偏导数估计曲线就开始毛糙。解决办法一是加大λ二是对Δy做轻量滤波。二是滑动窗口初始化。PFDL和FFDL在最初L拍内历史值不足我会用重复当前值的办法补全或者干脆从能凑齐窗口的时候才开始更新伪梯度。三是控制量饱和。如果u被幅值限制必须采用抗积分饱和思路限制伪梯度更新中的du对应幅值否则估计量会被错误拉偏系统恢复后还会长时间震荡。最后再分享一个小技巧复现这类算法时一定要把伪偏导数曲线、控制量曲线和输出曲线放在同一个图里观察。很多时候输出看似正常但伪偏导数已经漂到负的十几这种“隐性故障”只有看中间变量才能发现。我在Matlab里用subplot把三者画在一起调试效率高了很多。MFAC的参数调整并不复杂关键是理解每个参数在闭环中扮演的角色然后一套组合拳试下来基本都能稳定。