ARTICLE DETAIL

资讯详情

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

MFAC无模型自适应控制Matlab实现:CFDL/PFDL/FFDL动态线性化实战

MFAC无模型自适应控制Matlab实现:CFDL/PFDL/FFDL动态线性化实战 这篇博客是MFAC无模型自适应控制复现项目的一部分目标很明确在Matlab里把CFDL、PFDL、FFDL三种动态线性化方法完整落地并用三个非线性被控对象验证它们的控制效果。刚拿到题目时我觉得理论框架很清晰——无非是伪偏导数估计加前向控制律。真正动手写代码才发现从论文里的数学符号到能跑出收敛曲线的Matlab程序之间隔着不少坑时序怎么对齐、重置机制怎么设计、四个控制参数怎么配合、伪梯度的初值到底怎么给。这篇文章把我复现过程中涉及的工程细节和踩坑经验全部整理出来适合正在做MFAC复现、准备用无模型自适应控制做仿真的同学参考。我用的是Matlab R2023b环境下文的代码没有依赖任何专用工具箱纯脚本加基础函数理论上R2018b以上都能直接跑。1. 从无模型到动态线性化MFAC理论中的三个关键转换1.1 为什么敢叫无模型传统控制与MFAC的根本区别传统控制方法里最有代表性的那类方法都需要模型参与反馈线性化需要知道系统的非线性函数形式模型预测控制需要建立状态空间或传递函数滑模控制也需要一定的模型信息。哪怕是PID这种不显式依赖模型的方法本质上也在用比例、积分、微分三个通道来近似闭环所需的控制律遇到强非线性、大时滞、参数突变时那三个固定增益往往需要反复试凑。MFAC的思路完全不同。它假设被控系统满足广义Lipschitz条件——也就是说只要控制输入变化有界系统的输出变化也会被某个常数界住。在这个前提下MFAC不试图写出对象的完整数学表达式而是在每一个采样点附近用一个时变的增量线性关系来近似系统当前的动态。这个关系里的系数就是伪偏导数或伪梯度。由于这个系数是从实时输入输出数据中在线估计出来的MFAC不需要任何显式的模型结构。所谓无模型准确说是无显式参数模型而不是说它内部真的没有估计器。用一个不太严格的类比传统建模像是给系统拍一张X光片要看到内部结构MFAC更像摸黑走夜路时用手电筒照亮脚下每走一步只需要看清眼前这一小段路的坡度。它不关心路的整体走向只关心当前这一步输出变化和输入变化之间的坡度系数。1.2 伪偏导数与伪梯度到底在估计什么CFDL也就是紧格式动态线性化数学形式是Δy(k1) φ_c(k)·Δu(k)这个式子把输出增量和输入增量的关系用标量φ_c(k)串起来。它以最简单的形式刻画了如果我把输入加大一点输出会朝哪个方向变化、变化多少。伪偏导数本质上可以理解为系统在当前时刻的局部瞬时增益。PFDL也就是偏格式动态线性化数学形式是Δy(k1) φ_p^T(k)·ΔU_L(k)其中ΔU_L(k) [Δu(k), Δu(k-1), ..., Δu(k-L1)]^T。这里的伪梯度是一个L维向量每个分量反映了不同历史时刻的输入增量对当前输出增量的贡献权重。FFDL也就是全格式动态线性化数学形式是Δy(k1) φ_f^T(k)·ΔU_{Lu,Ly}(k)窗口里同时包含最近Lu步输入增量和最近Ly步输出增量。所以它不仅能记忆输入的影响还能记忆输出的历史走势相当于把系统的短期动态特征都纳入了局部线性化模型。动态线性化形式使用的信息窗口待估参数直觉理解CFDL仅当前输入增量标量PPD局部瞬时增益PFDL最近L步输入增量L维伪梯度带输入记忆的局部增益FFDL最近Lu步输入增量 Ly步输出增量(LuLy)维伪梯度带输入输出记忆的联合增益从CFDL到PFDL再到FFDL本质上是把局部线性增量模型的信息窗口不断扩大。窗口越大描述能力越强但代价是待估参数变多初值选择和参数调整的难度也随之上升。1.3 三种形式的适用直觉与选择逻辑如果系统不太复杂、没有明显时滞CFDL就够用参数少、实现简单、稳定性也相对好调。如果系统有明显的输入时滞或相对阶较高PFDL的输入历史窗口可以弥补控制器看到的数据是旧数据这个问题。如果系统含有输入输出历史之间的耦合、强非线性或者更复杂的动态FFDL的全窗口结构更有优势。我的建议是从CFDL起步先跑通闭环再逐步加大窗口而不是一上来就用FFDL。窗口越大自由度越高但可调参数也多直接上FFDL出了问题会很难定位。2. 仿真实验台搭建三个非线性对象与性能评价协议2.1 三个被控对象的选择逻辑复现不能只在一个对象上做否则结论没有说服力。我选了三个非线性离散系统覆盖三种典型的动态复杂度。系统1y(k1) y(k)/(1y(k)^2) u(k)^3。无时滞、结构简单代表大多数基础非线性系统用于检验CFDL的基线性能。系统2y(k1) 0.8y(k) 0.5u(k-2)/(1y(k-1)^2)。输入带两拍时滞同时含有历史输出项代表工业中常见的时滞加非线性场景。CFDL没有任何记忆预测输出增量时完全依赖当前控制增量在它身上会明显吃力。系统3y(k1) 0.8sin(y(k)) 0.3u(k)·u(k-1) 0.1y(k-1)。当前输入u(k)与上一步输入u(k-1)直接相乘还有sin(y(k))和y(k-1)的历史输出项代表输入耦合强、输出记忆明显的非线性对象。这种对象需要控制器对输入和输出历史同时有感知正好考验FFDL。三个系统在正常工作范围内都满足广义Lipschitz条件理论上MFAC都能适用具体效果就看信息窗口够不够。2.2 参考信号、仿真时长与性能指标参考信号我选了y_ref(k) 0.5sin(kπ/50) 0.2sin(kπ/20)。它有两个频率分量慢变分量考察趋势跟踪能力快变分量考察动态响应能力。仿真步数N2000确保控制器有足够时间收敛并进入稳态跟踪。性能指标用三个MAE平均绝对误差反映整体跟踪精度。maxAE最大绝对误差反映瞬态最坏情况。控制能量sum(u.^2)反映控制代价。指标计算代码很简单MAE mean(abs(y_ref(1:N) - y(1:N))); maxAE max(abs(y_ref(1:N) - y(1:N))); ctrl_energy sum(u(1:N).^2);2.3 公共代码框架一次搭建三个算法共用我写了一个plant_model函数用model_id区分三个对象主循环只需要替换控制器模块对象模拟和参考信号生成完全共用。这样做的好处是三个算法之间的对比公平因为被控对象、参考信号、评价指标完全一致。function y_next plant_model(y, y_prev1, u, u_prev1, u_prev2, model_id) switch model_id case 1 y_next y/(1y^2) u^3; case 2 y_next 0.8*y 0.5*u_prev2/(1y_prev1^2); case 3 y_next 0.8*sin(y) 0.3*u*u_prev1 0.1*y_prev1; end end主循环的统一骨架是四步参数估计、控制律计算、对象响应、指标统计。下面三个章节就按这个骨架分别展开。3. CFDL-MFAC的完整Matlab实现与结果复盘3.1 核心公式如何落到代码CFDL最核心的两个公式一个是参数估计一个是控制律。参数估计φ̂(k) φ̂(k-1) η·Δu(k-1)·[Δy(k) - φ̂(k-1)·Δu(k-1)] / [μ Δu(k-1)^2]控制律u(k) u(k-1) ρ·φ̂(k)·[y*(k1) - y(k)] / [λ φ̂(k)^2]每个符号的含义我列一下η参数估计步长因子控制PPD的收敛速度。μ防止分母为零的小正数也削弱估计修正的增益。ρ控制律步长因子通常取(0,1]。λ控制输入的惩罚因子抑制Δu过大是稳定性的关键。完整主程序如下clear; clc; close all; N 2000; u zeros(N,1); y zeros(N,1); du zeros(N,1); dy zeros(N,1); y_ref zeros(N,1); for k 1:N y_ref(k) 0.5*sin(k*pi/50) 0.2*sin(k*pi/20); end % 控制器参数 lambda 0.5; rho 0.6; eta 1.0; mu 1.0; phi_hat 1; phi0 1; eps 1e-5; err zeros(N,1); % 初始化 u(1) 0; y(1) 0; y(2) y(1)/(1y(1)^2) u(1)^3; % 系统1 dy(2) y(2) - y(1); du(1) 0; for k 2:N-1 % 1) PPD参数估计 phi_hat phi_hat eta*du(k-1)*(dy(k) - phi_hat*du(k-1))/(mu du(k-1)^2); % 2) 重置机制 if abs(phi_hat) eps || abs(du(k-1)) eps || sign(phi_hat) ~ sign(phi0) phi_hat phi0; end % 3) 控制律 du(k) rho*phi_hat*(y_ref(k1) - y(k))/(lambda phi_hat^2); u(k) u(k-1) du(k); % 4) 对象响应系统1 y(k1) y(k)/(1y(k)^2) u(k)^3; dy(k1) y(k1) - y(k); err(k) y_ref(k1) - y(k1); end理解这段代码的时序很重要。第k步开始时y(k)已知y(k-1)已知du(k-1)和dy(k)也已知。先用dy(k)和du(k-1)更新PPD再用新的PPD计算当前控制增量du(k)施加到对象后得到y(k1)最后计算dy(k1)供下一步使用。这个顺序不能乱它对应的是先看历史数据修正模型再决定当前控制量的因果逻辑。3.2 仿真结果复盘CFDL适合什么、不适合什么用上面这组参数CFDL在三个系统上的结果如下被控对象MAEmaxAE控制能量直观表现系统10.0310.14约32跟踪良好无明显滞后系统20.2870.82约50明显滞后控制量抖动系统30.1520.56约45能跟踪但误差偏大系统1效果好的原因很直接这个对象基本满足当前输出增量主要由当前输入增量决定的局部线性关系标量PPD就够用。系统2之所以差是因为带有两拍输入时滞——控制器当前发出的u(k)要等两步之后才真正影响输出CFDL的PPD在时滞期间会被持续错误修正控制量越抖PPD估计越偏最终形成恶性循环。系统3的u(k)·u(k-1)耦合意味着输出增量不仅依赖当前输入还依赖上一步输入标量PPD无法表达这种历史依赖。3.3 CFDL参数影响速查表我复现时花了不少时间调参数把影响规律整理成了下表参数作用偏小偏大λ控制惩罚因子控制过强易振荡发散跟踪滞后稳态误差大ρ控制律步长收敛慢超调大可能发散ηPPD估计步长PPD跟踪慢PPD噪声大控制异常μ防除零常数分母接近零时估计跳变修正增益被削弱收敛慢我的经验是λ1、ρ0.6、η1、μ1这个组合在大部分良性非线性系统上都能先跑通然后再根据需求微调。λ几乎总是第一个需要调整的参数因为它的作用最直观——控制曲线抖不抖看λ就能判断个大概。4. PFDL与FFDL在非线性系统中到底赢在哪里代码与对比4.1 PFDL实现要点伪梯度向量与历史输入窗口PFDL的参数估计公式是φ̂(k) φ̂(k-1) η·ΔU_L(k-1)·[Δy(k) - φ̂^T(k-1)·ΔU_L(k-1)] / [μ ||ΔU_L(k-1)||^2]控制律是Δu(k) ρ·[φ̂₁(k)·e(k) - Σ_{i2}^{L}φ̂ᵢ(k)·Δu(k-i1)] / [λ ||φ̂(k)||^2]这里e(k) y*(k1) - y(k)。重点在于控制律比CFDL多了一个减法项。既然系统的输出增量受最近L步输入增量的共同影响那控制器在决定当前Δu(k)时就必须把上一步、上上步输入增量已经造成的影响扣除掉否则控制器会重复计算历史输入的作用实际控制量会出现超调。这是我最初实现PFDL时忽略的地方后来对比理论公式才补上。核心代码片段L 3; phi_hat ones(L,1); phi0 ones(L,1); dU_hist zeros(L,1); for k 2:N-1 % 此时的dU_hist [du(k-1), du(k-2), ..., du(k-L)] DeltaU dU_hist; % 伪梯度估计 phi_hat phi_hat eta*DeltaU*(dy(k) - DeltaU*phi_hat)/(mu DeltaU*DeltaU); % 重置机制 if norm(phi_hat) eps || abs(DeltaU(1)) eps || sign(phi_hat(1)) ~ sign(phi0(1)) phi_hat phi0; end % 控制律 e_k y_ref(k1) - y(k); comp_item sum(phi_hat(2:end) .* dU_hist(2:end)); du(k) rho*(phi_hat(1)*e_k - comp_item)/(lambda sum(phi_hat.^2)); u(k) u(k-1) du(k); % 对象响应 y(k1) plant_model(y(k), y(k-1), u(k), u(k-1), u(k-2), model_id); dy(k1) y(k1) - y(k); % 更新历史输入增量窗口供下一步使用 dU_hist [du(k); dU_hist(1:end-1)]; end注意dU_hist的更新顺序必须在控制量du(k)算完之后再更新否则下一步会用到未来数据。这个错误我调试时遇到过表现是控制量突然跳变曲线看起来像被谁用力扯了一下。4.2 FFDL实现要点输入输出双窗口与联合估计FFDL把窗口扩展为输入增量历史加输出增量历史。参数估计和控制律的形式与PFDL在结构上完全一致只是伪梯度向量维度变成LuLy窗口向量也变成了拼接形式。核心代码片段Lu 2; Ly 2; phi_hat ones(LuLy, 1); phi0 ones(LuLy, 1); dU_hist zeros(Lu, 1); dY_hist zeros(Ly, 1); for k 2:N-1 % 组装联合窗口 DeltaU_LL [dU_hist; dY_hist]; % 伪梯度估计 phi_hat phi_hat eta*DeltaU_LL*(dy(k) - DeltaU_LL*phi_hat)/(mu DeltaU_LL*DeltaU_LL); % 重置机制 if norm(phi_hat) eps || abs(DeltaU_LL(1)) eps || sign(phi_hat(1)) ~ sign(phi0(1)) phi_hat phi0; end % 控制律 e_k y_ref(k1) - y(k); comp_u sum(phi_hat(2:Lu) .* dU_hist(2:Lu)); comp_y sum(phi_hat(Lu1:LuLy) .* dY_hist); du(k) rho*(phi_hat(1)*e_k - comp_u - comp_y)/(lambda sum(phi_hat.^2)); u(k) u(k-1) du(k); % 对象响应 y(k1) plant_model(y(k), y(k-1), u(k), u(k-1), u(k-2), model_id); dy(k1) y(k1) - y(k); % 更新历史缓冲区 dU_hist [du(k); dU_hist(1:end-1)]; dY_hist [dy(k1); dY_hist(1:end-1)]; end这里dY_hist在每一步更新时放入的是最新的输出增量。初始化时dY_hist全为零因为系统从静止状态启动前三步的输出增量确实为零。4.3 三算法在三个对象上的对比结果统一参数下我把三种算法分别在三个系统上跑了一遍结果汇总如下被控对象CFDL MAEPFDL(L3) MAEFFDL(Lu2,Ly2) MAE最优方案系统10.0310.0340.027CFDL/FFDL相当系统20.2870.0650.053PFDL/FFDL大幅领先系统30.1520.0970.042FFDL明显最优这个结果印证了理论直觉系统1足够简单CFDL的轻量和稳定就是优势强行加窗口只会增加调参负担PFDL在L3时反而因为多估两个参数而略差。系统2的时滞靠PFDL的输入历史窗口就能基本解决FFDL在PFDL基础上多引入输出窗口收益有限但也很稳定。系统3同时存在输入耦合和输出历史依赖FFDL的全窗口结构最有优势Lu2, Ly2刚好覆盖了对象动力学中用到的u(k-1)和y(k-1)。结论是MFAC并不是越复杂越好。动态线性化的窗口结构应该与被控对象的动态复杂度匹配。对象什么毛病都没有时CFDL是最省心的选择对象有时滞加输入窗口对象的历史耦合明显再加输出窗口。5. 复现阶段最容易踩的坑从公式到代码的落差5.1 时序问题先算控制量还是先更新对象状态这是复现MFAC时最常见的错误。我刚写CFDL时有一版代码把顺序写反了结果系统1的跟踪曲线从正常跟成发散排查了半天才发现是时序问题。错误的顺序是这样的先施加控制、再估计参数。% 错误示例 u(k) u(k-1) du(k); y(k1) plant_model(...); phi_hat phi_hat eta*du(k)*(dy(k1) - phi_hat*du(k))/(mu du(k)^2);这样写的后果是参数估计用的dy(k1)是控制作用已经生效后的输出增量这个增量里包含了闭环跟踪误差被压制的效果用它估计PPD会让估计值整体偏小闭环增益不足系统慢慢失去跟踪能力。正确的四步顺序是参数估计 → 控制律计算 → 作用到对象 → 更新历史状态。这一点对整个MFAC家族都适用。5.2 重置机制不能省如果去掉重置机制仿真大概率会在某个时刻翻车。我在复现时做过对比实验把重置条件注释掉系统1前300步还很正常之后PPD漂移到接近零控制量输出萎缩跟踪误差越拉越大系统2更严重PPD符号翻转后系统直接发散。重置机制的三个条件各有分工|φ̂| eps估计值太小说明参数失去增益意义。|du(k-1)| eps控制量长时间不更新参数估计退化。sign(φ̂) ~ sign(φ0)增益方向突变估计结果不可信。对于PFDL和FFDL重置时用norm(phi_hat)判断整体大小符号判断只针对第一个分量phi_hat(1)。不能对整个向量做符号判断因为后续分量本来就是权重正负交替是正常的。5.3 参数初值选择的经验法则我复现时总结了一套参数初值起点见下表参数推荐起点调整方向λ1曲线振荡就调大跟踪慢就调小ρ0.6收敛慢就调大超调就调小η1PPD收敛慢就适当加大但别超过2μ1一般不动特殊对象避免除零时再调整PPD初值与对象增益方向一致的数常用1符号千万别搞反有一个调试技巧值得分享对λ和ρ做批量扫描输出MAE矩阵选一个最优组合。写一个双层循环外层扫λ内层扫ρ每个组合下跑一遍仿真打印MAE。这样半小时就能摸清参数平面的趋势比手动一次一次试高效得多。5.4 从CFDL迁移到PFDL/FFDL时的结构调整CFDL跑通以后改PFDL最容易搞错三件事。第一伪梯度向量初值不要全设成1。如果对象的增益方向确实为正phi_hat ones(L,1)虽然不会发散但会让控制器在最初几步给历史输入的权重过高出现一段不自然的控制量波动。我建议设成[1; 0; 0]这种带主成分的形式让第一维主导、后续维从小值开始仿真初期的动态会更干净。第二重置条件要改。CFDL判断标量符号PFDL只判断第一个分量的符号范数判断用norm(phi_hat)。第三控制律分母中的范数从phi_hat^2变成sum(phi_hat.^2)。这个改动虽然小但经常有人照着CFDL的风格写phi_hat(1)^2导致控制增益被低估跟踪效果变差。5.5 仿真异常排查手册把我在复现中遇到过的异常现象整理成一张表供参考异常现象可能原因应对方法输出发散λ过小、ρ过大、PPD初值符号反了调大λ到1~5调小ρ到0.3核对初值符号高频振荡λ过小或η过大调大λ调小η到0.5跟踪滞后严重ρ过小或λ过大调大ρ到0.8调小λ到0.1~0.5前几步正常后发散重置机制缺失或PPD漂移加上重置机制控制量突变历史窗口使用到了未来数据检查dU_hist、dY_hist更新顺序三条曲线结果完全一致三个系统没有正确区分检查对象模型和传入的历史参数最后分享一点个人体会。这次复现带给我最大的收获不是跑通了三个算法而是对动态线性化这个思想有了直观认识——MFAC说到底是让模型跟着数据走。窗口扩大的每一步都意味着对系统记忆能力的增加也意味着参数维度与调参难度的上升。在调参过程中我发现最重要的不是追求某个指标的极致而是先搞清楚对象到底需要多大的信息窗口。如果你的对象简单得像系统1CFDL足够如果你的对象有隐藏的时滞和耦合那一定要给控制器足够的记忆。后面我打算把这三个控制器放到带噪声和扰动的环境里再测一轮看看PPD估计在噪声下的表现到时候有结论了再来分享。
返回列表