ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

无模型自适应控制仿真复现:伪偏导数估计与MFAC调参实战全解析

无模型自适应控制仿真复现:伪偏导数估计与MFAC调参实战全解析

做控制仿真的同学应该都有过这种经历:读论文时觉得无模型自适应控制(MFAC)很清晰,无非是动态线性化、伪偏导数估计、加权一步预测控制三步走。可真到写代码的时候,伪偏导数的初值该给多少、重置条件要不要写、控制器里的 λ 调成多少、MIMO 矩阵运算为什么会报“singular matrix”——每个问题都能卡住一下午。

这篇博文是对我整理的一套 MFAC 仿真资料包的完整复盘。包里有六套可运行的仿真程序,覆盖 SISO/MIMO、CFDL/PFDL 两类动态线性化、伪偏导数估计的完整实现,还配了一批按阅读顺序整理好的参考资料。我尽量把“书上没明说但跑仿真必须知道”的细节也写出来,适合正在复现 MFAC、准备往论文方向扩展、或者只想快速跑通一个无模型控制器看看效果的人。

这篇文章不会先从“什么是 MFAC”开始背书,而是直接回答三个更实际的问题:这六套仿真为什么这样拆?每套程序的伪偏导数和控制律到底怎么写?跑不通、跑不平、跑出 NaN 时应该查哪里?

1. 为什么值得把六套MFAC仿真放在一起:从CFDL到PFDL再到MIMO的演进路径

我最初接触 MFAC 时,只看单个 SISO 的 CFDL 例子。当时觉得逻辑很顺:非线性系统在相邻采样点上可以写成输入增量与输出增量之间的等价线性关系,然后估计那个比例系数——伪偏导数。等我把程序跑通、换到 PFDL、再换到 MIMO 之后才发现,单案例演示和体系化理解之间差着十万八千里。这也是我把六套仿真打包在一起的核心原因:它们不是六个重复的模板工程,而是沿着“从简单到复杂、从单回路到耦合系统”的台阶往上走。

1.1 MFAC解决什么问题,以及“无模型”到底免掉的是什么

传统自适应控制先要建立被控对象的结构方程,再在线辨识参数。这套路的问题是:很多工业过程根本没有可靠机理模型,或者模型阶次不确定,辨识参数和控制器设计搅在一起,容易顾此失彼。

MFAC 的思路完全换了个方向。它不自以为知道对象长什么样子,而是利用相邻两个采样时刻的输入输出数据,在每一个当前时刻构造一个“局部的动态线性化数据模型”。这里的“无模型”不是没有模型,而是不依赖任何先验的物理机理模型。你可以把它类比成一位经验丰富的调试老师傅:不需要你画出设备内部原理图,他只看你操作了什么、系统响应了什么,就能在当前位置附近估计出一个等效增益,然后根据这个等效增益决定下一步怎么调整。

这套思想落到代码里,就是标题中反复出现的两个词:

  • CFDL(紧格式动态线性化 Compact Form Dynamic Linearization):用当前时刻输入增量与输出增量之间一个标量关系来描述局部动态,适合大多数 SISO 系统。
  • PFDL(偏格式动态线性化 Partial Form Dynamic Linearization):把最近 L 个时刻的输入增量拼成一个向量,用向量点乘来描述局部动态,对付大惯性、时滞、非最小相位系统更稳。

有了动态线性化模型,MFAC 剩下的任务就是两件事:估计伪偏导数,然后基于估计结果设计控制器。下面所有仿真案例,本质上都在反复练习这套“估计—控制—更新”的闭环。

1.2 六套仿真怎么划分更科学

我见过有人把六个仿真做成六个不同的被控对象,但控制器代码完全一样,只是改一改传递函数系数。这种做法不能说没用,但对理解 MFAC 帮助不大。我整理时直接按算法难度和系统复杂度划分,每一套都解决一个不同的“坎”:

案例编号被控对象特征动态线性化方式控制目标主要学习点
Case 1SISO 无时滞非线性对象CFDL跟踪阶跃与正弦信号理解 PPD 估计与基本控制律
Case 2SISO 参数时变 + 外部扰动CFDL抗干扰与时变跟踪学习重置机制和参数再整定
Case 3SISO 大惯性 + 纯滞后对象PFDL稳准快地跟踪斜坡信号体会 PFDL 在滞后系统中的优势
Case 42×2 MIMO 弱耦合对象CFDL-MIMO双通道同时跟踪设定值掌握伪偏导数矩阵估计
Case 52×2 MIMO 强耦合对象CFDL-MIMO减弱通道间耦合影响分析耦合通道的非对角元素
Case 62×2 MIMO 强耦合 + 模型参数切换PFDL-MIMO多工况切换下保持稳定综合调试与矩阵运算鲁棒性

我可以直说,Case 3 和 Case 5 是我自己踩坑最多的两个案例。之前只做 SISO CFDL 时,我一度觉得 PFDL 只是多几个参数、没必要用;等到了纯滞后加时间常数很大的系统才发现,CFDL 的控制器增益会被“等效增益被大惯性拉得很小”这个问题带偏,而 PFDL 把最近 L 个时刻的输入变化都纳入预测,补偿效果明显改善。

如果你模仿这套案例顺序,也不要跳步。至少先把 SISO CFDL 跑透,再去动 MIMO 矩阵,否则你会在矩阵求逆和符号检查上疲于奔命,分不清问题是出在控制器还是出在对象。

2. 伪偏导数估计与动态线性化:仿真代码里最容易出错的地方

很多初学代码的人会把伪偏导数估计看成“最小二乘辨识”的变体,然后就照着最小二乘公式写。这个直觉有参考价值,但 MFAC 的 PPD 估计走的是一步前向梯度校正,而且必须配一个不可或缺的重置机制,否则仿真跑着跑着就发散。

2.1 CFDL与PFDL的数学形式,以及PPD估计器的设计逻辑

假设一个 SISO 非线性离散系统,在相邻两个时刻,一定存在一个有界标量 φ(k),使得输出增量可以写成:

Δy(k+1) = y(k+1) - y(k) = φ(k) · Δu(k)

这里的 φ(k) 就是 CFDL 中的伪偏导数。它不是系统真实梯度,而是一个在“当前工作点附近”把非线性映射等价成线性关系的时变参数。因为系统是时变的,φ(k) 也被允许随时间变化。这个形式不需要对系统阶次作任何假定,所以它对一大类非线性系统都成立。

为了估计 φ(k),MFAC 的经典做法是极小化如下指标:

J = [Δy(k) - φ_hat(k) · Δu(k-1)]² + μ · [φ_hat(k) - φ_hat(k-1)]²

第一项让估计误差变小,第二项把 φ_hat 的变化速度限制住,防止它跳得太厉害。μ 就是这个“变化惩罚”的权重。对上述准则求极值后得到递推估计式:

φ_hat(k) = φ_hat(k-1) + [η · Δu(k-1) / (μ + Δu(k-1)²)] · [Δy(k) - φ_hat(k-1) · Δu(k-1)]

这里 η 是步长因子。分母中保留 Δu(k-1)² 项,是为了解决“当输入增量很小时,估计更新幅度过大”的问题。你可以理解为:如果上一次操作基本没动,那么这次观察到的输出变化很可能来自噪声或扰动,不应该让 φ_hat 产生剧变。

PFDL 的推导是 CFDL 的自然延伸。它对长度为 L 的输入增量窗口建模:

Δy(k+1) = φ1(k)Δu(k) + φ2(k)Δu(k-1) + … + φ_L(k)Δu(k-L+1)

写成向量形式就是伪梯度向量 φ_{n,L}(k) 与输入增量向量 ΔU_L(k) 的内积。其估计式形式类似,只是分母中的标量平方变成向量模平方:

φ_hat(k) = φ_hat(k-1) + [η · ΔU_L(k-1) / (μ + ||ΔU_L(k-1)||²)] · [Δy(k) - φ_hat(k-1)^T · ΔU_L(k-1)]

在代码里,我建议把 CFDL 和 PFDL 的估计封装成两个独立函数,而不是在同一函数里用 if 判断。因为两者的数据缓冲结构不同:CFDL 只需要上一拍输入增量,PFDL 需要维护一个长度 L 的队列。封装分开,后面做 MIMO 扩展时也不容易乱。

2.2 仿真中PPD初始化、重置条件和参数μ/η/λ的调节顺序

PPD 估计器有三个点最容易被代码新手忽略。

第一个是初值 φ_hat(1)。这个初值不能设成 0。原因很简单:控制律中要用 φ_hat 计算控制增益,如果初值为 0,第一拍根本算不出有效控制量。我习惯根据被控对象正常工作点处的“粗略增益”来设初值。比如对象输入是 0 到 1,输出是 0 到 10,初值我会给 5 到 15 之间的一个数。如果你完全没有任何先验,就给一个正的常数,比如 1,也比 0 好。

第二个是重置机制。递推估计算法本身不能保证 φ_hat 永远合理。当出现以下情况之一时,需要把 φ_hat 重置为初值:

  • |Δu(k-1)| 小于某个阈值 ε,此时估计信息太少;
  • |φ_hat(k)| 小于某个阈值 ε,说明估计值失去意义;
  • φ_hat(k) 的符号发生突变,比如从正变成负,且与上一拍符号不一致。

这个重置机制非常像经验丰富的操作员发现仪表读数可疑后,重新归零校准。没有它,PPD 估计很容易在一两次异常扰动后跑飞,后续的控制量也会跟着剧烈振荡。

第三个是参数调节顺序。建议的优先级是:先调控制器权重 λ,再调重置阈值 ε,然后调估计步长 η 和惩罚系数 μ,最后调控制律步长 ρ。

  • λ 在主对角线上相当于控制输入的“软化系数”,λ 越大,控制量变化越平缓,但跟踪可能变慢。
  • η 是估计器步长,太大会让 PPD 估计抖动,太小会让系统没法跟上参数时变。
  • μ 是估计器变化惩罚,太大会让 PPD 僵化,太小会导致估计器过于敏感。
  • ρ 是控制器步长,通常取 0 到 1 之间,用来折中跟踪速度和控制能量。

我自己的经验是,对于一台“看起来正常”的被控对象,起始参数可以设 η=1,μ=1,λ=2,ρ=0.6,然后再根据响应曲线微调。如果你一上来就遇到发散,第一反应应该去调 λ,而不是死磕 ρ。λ 稍微调大一点往往就能让系统安静下来。

3. 六个仿真实例逐个拆解:对象、参数与输出对比

这一节我把六套仿真程序的被控对象形式、控制器配置和典型结果都列出来。你完全可以用相同配置去复现,数值上小有差异是正常的,整体趋势应该一致。

3.1 SISO CFDL/PFDL案例的实现与分析

Case 1 我故意选了一个带有非线性平方项的对象:

y(k+1) = 0.8·y(k)/(1+y(k)²) + 0.6·u(k) + 0.2·sin(y(k)·u(k))

采样周期取 0.01 秒,仿真 2000 步。CFDL 控制器参数我取 η=1,μ=0.8,λ=2.5,ρ=0.6,φ 初值取 0.8。跟踪信号先给阶跃 1.0,在第 800 步切换为幅值 0.5、频率 0.01 的正弦信号。结果上,阶跃跟踪基本没有超调,调整时间约 60 步;切换到正弦后相位滞后很小,最大绝对跟踪误差在 0.05 以内。

Case 2 在对象里加入了时变增益和扰动,比如在 600 步时让原对象增益从 0.6 变成 1.1,并在第 400 到 450 步叠加一个幅值 0.3 的阶跃扰动。此时保持 Case 1 的参数不换,你会发现 PPD 估计值会在增益突变后短暂波动,系统约 80 步后重新收敛。这正好说明 MFAC 的自适应能力不是来自模型在线辨识,而是来自 PPD 的在线校正。

Case 3 换成了一个带纯滞后的大惯性环节,我把它写成差分方程形式:

y(k+1) = 0.92·y(k) + 0.08·u(k-10)

这是一个典型的一阶惯性加滞后系统,滞后 10 拍。如果继续用 CFDL,控制器常常会显得“迟钝”,因为当前输出变化反映的是 10 拍之前的控制作用。改用 PFDL 之后,我取 L=8,把最近 8 拍的输入增量都用于估计,控制效果明显改善。这里我想特别提醒:L 不是越大越好。L 大了确实能覆盖更长滞后,但需要估计的参数变多,PPD 向量更新的计算量增大,且对噪声更敏感。L 取滞后拍数附近是比较合理的经验值。

3.2 MIMO CFDL/PFDL案例,以及如何看耦合补偿效果

Case 4 和 Case 5 都是 2×2 系统,我使用了一组带非线性耦合的差分方程,它的结构在资料里作为“通用两入两出对象函数”提供,核心形式是:

y1(k+1) = 0.9·y1(k)/(1+y1(k)²) + 0.5·u1(k) + c·u2(k)·sin(y1(k)) y2(k+1) = 0.9·y2(k)/(1+y2(k)²) + 0.5·u2(k) + c·u1(k)·cos(y2(k))

其中 c 代表耦合强度。Case 4 里取 c=0.1,Case 5 里取 c=0.4。这样两者的差别就只在于耦合强度,便于对比 MIMO MFAC 的表现。

MIMO 用 CFDL 时,伪偏导数从标量变成矩阵 Φ(k),ΔY(k+1)=Φ(k)ΔU(k)。Φ(k) 的对角元素反映各通道自身增益,非对角元素反映耦合通道的影响。控制律变成:

U(k) = U(k-1) + ρ·Φ(k)^T·[λ·I + Φ(k)·Φ(k)^T]^{-1}·[Y*(k+1) - Y(k)]

这里面最容易让人不安的就是矩阵求逆。不过只要 λ 取得合适,λI + ΦΦ^T 是对称正定矩阵,求逆是安全的。我在 Case 4 里取 λ=1.5,ρ=0.5,φ 对角初值取 0.6,非对角初值取 0。两个通道的设定值分别是 1.0 和 2.0。刚开始时第二通道会出现一个小的耦合波动,幅度约为设定值的 10% 左右,控制作用很快把它压回去了。

Case 5 把耦合系数提高到 0.4 后,如果继续用 Case 4 的固定参数,系统的耦合波动会变大,但 MFAC 矩阵中的非对角元素并不自动“解耦”,它只是把耦合的影响当成对象动态的一部分,通过 PPD 矩阵去实时补偿。这句话要画重点:MFAC 没有显式解耦器,它的“解耦”是隐式的、数据驱动的。Case 6 则是在 Case 5 基础上,每隔 500 步切换一次对象参数,并换成 PFDL-MIMO 形式,主要为了观察多工况下矩阵估计是否能跟上变化。

4. MIMO MFAC仿真的耦合问题:伪偏导数矩阵里该放什么

很多同学第一次接触 MIMO MFAC 时,脑子里想的都是“能不能先分别控制每一个回路,把耦合通道忽略掉”。如果耦合很弱,这么做并无不可;但耦合强时,被忽略的通道间影响会在两个回路上形成正反馈振荡。所以 MIMO 仿真真正要解决的问题不是理论上的显式解耦,而是“如何让控制器及时发现耦合并把影响吃掉”。

4.1 MIMO控制律的矩阵运算与稳定性/可行性条件

MIMO CFDL 中,伪偏导数矩阵 Φ(k) 的估计是一个矩阵更新,而不是对每个元素独立地套用 SISO 公式。教科书里常见的方式是把矩阵“向量化”,然后用等效的递推公式逐行更新。实际编程时更推荐直接对每一行做标量式更新。因为第 i 个输出通道对应的伪偏导数行向量 φ_row_i(k) 满足:

Δy_i(k+1) = φ_row_i(k)^T · ΔU(k)

所以我可以把 SISO 的估计式直接推广到每一行:

φ_hat_row_i(k) = φ_hat_row_i(k-1) + [η·ΔU(k-1) / (μ + ||ΔU(k-1)||²)] · [Δy_i(k) - φ_hat_row_i(k-1)^T·ΔU(k-1)]

这样写,代码结构最接近 SISO 版本,也最容易检查。

控制律中的矩阵求逆 (λI + ΦΦ^T)^(-1),本质上是在输入变化量上做一个非线性的加权投影。ΦΦ^T 半正定,λI 确保求和后正定,所以求逆这个动作本身是稳定的。但如果你把 λ 设得太小,比如 λ=1e-6,矩阵条件数会非常大,仿真中可能出现“矩阵接近奇异”的警告。这时不要只是调 λ,先停下来看看 PPD 矩阵的范数是不是已经发散到一个非常夸张的量级。如果 PPD 矩阵发散,多半是估计步长 η 太大或者 μ 太小,先修正估计器再回来看矩阵。

4.2 耦合强不强,怎么在仿真中量化,以及什么时候该从MIMO退回多回路

仿真实战里,耦合强度不只是一个理论参数。我会在实际调试时做一个小实验:把其中一个控制通道的输入在前 200 步保持恒定,另外一个通道输入加一个阶跃,然后观察另一个输出的变化幅度。变化越大,说明耦合越强。用这个简单方法,你可以很快判断当前工作点附近的耦合强度,而不是只看整组方程里的系数 c。

耦合强度判定之后,决定用 MIMO-MFAC 还是多回路独立 MFAC,我会按下面这个经验规则:

耦合情况通道间影响推荐方案
弱耦合小于本通道增益的 10%多回路独立 MFAC,参数更简单
中等耦合10% ~ 30%MIMO MFAC,但可容忍初始耦合波动
强耦合大于 30%必须用 MIMO MFAC,且注意 PPD 矩阵非对角项的估计质量

需要提醒的是,MIMO MFAC 也不是万能的。当系统存在输入输出通道数不一致,或者工作点附近增益符号频繁翻转时,矩阵形式的 PPD 估计会变得很吃力。此时与其强行用一个 MIMO MFAC,不如在工程上考虑串级、前馈这些更成熟的架构。

5. 程序框架与调试经验:跑通只是第一步

这六套仿真程序的力量不在于“能跑”,而在于“改起来方便”。如果每一套都是把全部代码堆在一个几百行的脚本里,后续替换对象、切换算法、加扰动都会很难受。所以我按工程化的方式组织了目录和接口。

5.1 工程目录、接口与仿真流程

目录结构大致如下:

mfac_sim/ ├── README.md # 说明每套案例的用途和文件入口 ├── config/ │ └── default_params.m # 统一参数结构体 ├── plants/ │ ├── plant_siso_nonlinear.m │ ├── plant_siso_lag.m │ └── plant_mimo_coupled.m ├── controllers/ │ ├── mfac_cfdl.m │ ├── mfac_pfdl.m │ ├── mfac_mimo_cfdl.m │ └── mfac_mimo_pfdl.m ├── estimators/ │ ├── ppd_estimate_cfdl.m │ ├── ppd_estimate_pfdl.m │ └── ppd_matrix_estimate.m └── scripts/ ├── run_case1.m ├── run_case2.m └── ...

每个主脚本的执行流程都是同一个结构:

  1. 读取参数结构体,设置采样步数、设定值、扰动区间;
  2. 初始化状态变量、PPD 初值、历史输入输出缓冲;
  3. 进入仿真循环,每个采样时刻先由对象函数算出当前输出;
  4. 基于当前输出与设定值,调用 PPD 估计函数更新伪偏导数;
  5. 调用控制器函数计算控制增量,并执行输入饱和检查;
  6. 记录数据,绘制响应曲线。

这套流程最大的好处是:换对象、换算法时,你只需要改初始化和函数调用,不用动主循环。比如从 CFDL 切到 PFDL,核心改动就是估计器多维护一个长度为 L 的历史向量,以及控制律从标量增益变成向量内积。这正好呼应了前文说的“理解台阶”思路。

5.2 调试中常见的五个坑和排错链路

我整理资料时,把调试过程中最常踩的坑连同排错链路一起写进了 README。这里挑五个最容易复现的:

  • 症状一:第一拍控制量为 NaN。排查链路:先检查 PPD 初值是否为零,再检查 λI+ΦΦ^T 求逆结果,最后检查对象输出是否有除零项。
  • 症状二:输出发散,曲线呈“锯齿状”快速振荡。优先怀疑控制律 ρ 太大或 λ 太小;先调 λ,再看 η。
  • 症状三:输出收敛但存在稳态静差。先看 PPD 估计是否有重置,再检查控制器是否因为 λ 过大导致积分效果太弱。MFAC 本身是增量式控制器,有类似积分的功能;如果 λ 过大,静差就会拖长。
  • 症状四:PPD 估计值在短时间内从正跳到负。这是符号突变,通常是重置机制没有生效,或者对象增益符号在当前工作点附近确实发生了翻转。前者修代码,后者建议改成多工作点分段处理。
  • 症状五:MIMO 仿真时出现 “Matrix is singular to working precision”。别急着加 λ,先打印 ΦΦ^T 的秩;如果秩小于输入维数,说明某个通道的 PPD 估计向量退化,回到估计式检查 μ 和 η。

每个坑,我都建议在代码里加一个“诊断开关”,可以在仿真中途把 PPD、控制量、输出误差画在同一个图里。不要只画输出曲线,否则你看到发散时,根本不知道是估计器先坏还是控制器先坏。

6. 配套参考资料的使用顺序与下一步扩展思路

这套资料包里不仅有六个仿真工程,还有一批按阅读顺序整理的文档。我不建议你把资料 PDF 全部塞进网盘就完事,然后从头到尾翻一遍。文本阅读顺序和代码调试顺序是两回事。

6.1 我整理资料时的阅读顺序建议

第一梯队是“入门综述”:先看 MFAC 的整体框架、与传统自适应控制的区别、动态线性化为什么成立。这一部分不需要看懂每一个证明,只需要建立“估计 PPD—计算控制律—重置校正”的闭环直觉。

第二梯队是“算法推导”:重点看 CFDL 和 PFDL 的详细推导、PPD 估计准则的构造、稳定性分析的基本假设。读这一梯队时,建议对照本包里的estimators目录,每读一个公式,就在代码里找到对应的一行。这样可以把抽象符号落到具体变量上,记忆效果远好于单纯看公式。

第三梯队是“MIMO 与扩展”:这部分内容是给已经跑通 SISO 仿真的人看的,重点在 PPD 矩阵的结构、控制律矩阵求逆的几何意义、输入约束与时滞系统的扩展方法。读之前,最好先跑一遍 Case 4 和 Case 5,这样你再看矩阵公式就不会觉得它是一堆冰冷的符号。

第四梯队才是“代码逐行注释”和“常见问题 FAQ”。很多初学者一上来就抱着代码逐行啃,啃到第 50 行就忘了系统全貌。我建议先读文档建立系统观,再回到代码里看细节,效率会高得多。

6.2 从仿真到实际应用还能做哪些扩展

做完这六套仿真之后,你可以沿着几个方向继续延伸。

第一个方向是输入约束和输出约束。实际系统中控制量不可能无限大,把 MFAC 控制律的输出加一个饱和限幅是很容易的事,但限幅之后 PPD 估计是否还能保持稳定,需要做额外测试。我自己会用“约束激活计数器”去观察控制量在限幅边界上停留的时间,如果长时间抱死,就要考虑抗饱和处理。

第二个方向是时滞系统的进一步补偿。PFDL 用滑动窗口缓解了滞后影响,但如果滞后很大而且时变,简单的固定长度窗口就不够聪明。可以尝试把系统识别出的纯滞后步数与 PFDL 的窗口长度联动,滞后变大时自动增大 L。

第三个方向是控制器结构的混合。比如把 MFAC 作为主回路,外面套一个 PID 或前馈回路,用于抑制可预知的扰动。这种混合结构在工业现场很常见,因为现场工程师习惯保留一个“可解释”的基础控制回路,MFAC 作为校正回路只需要在线修正增量,不会让人觉得黑箱不可控。

最后一点个人体会:做 MFAC 仿真,最大的收获不是“把代码跑通了”,而是形成了“数据—模型—控制”三者循环修正的直觉。即使是同一条曲线,今天跑是平滑的,明天换一组初值可能就出现奇怪振荡,这不代表算法有问题,而是说明你对系统动态的理解还不够。仿真阶段多设几个“病态工况”,比永远在理想曲线上满意更有价值。按这套程序包 drill 下来,再回去读任何与无模型自适应控制相关的论文,你应该能很快判断出它宣称的改进点到底落在哪一环:是 PPD 估计更鲁棒,还是控制律更平滑,还是 MIMO 耦合处理更聪明。有了这个判断力,后续做创新也就不再是无从下手。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进