Koopman-EDMD与LQR:四旋翼无人机数据驱动控制实践
2026/9/11 17:17:59 网站建设 项目流程

简介:非线性系统的分析与控制是工程领域长期面临的挑战,传统小扰动线性化方法只在平衡点附近有效,大机动工况下模型失准问题突出。Koopman算子理论通过将非线性动力学提升到高维线性空间,为这一难题提供了新思路。EDMD(扩展动态模态分解)作为数据驱动辨识工具,可直接从输入输出数据中构建Koopman矩阵,避免复杂的机理建模。以四旋翼无人机为对象,利用EDMD结合字典函数建立高维线性模型,进而设计LQR控制器,并引入积分项消除稳态误差,实现大范围姿态跟踪与轨迹控制。该方法融合数据驱动与线性控制理论,显著提升了强非线性系统的控制性能,为无人机工程实践提供了可复现的解决路径。本文完整阐述了从数据生成、字典设计、模型辨识到闭环控制的实现细节,并总结了常见问题与调试经验。 做四旋翼无人机控制这几年,我越来越觉得“模型与控制”之间那道坎特别有意思。传统做法里,非线性太强了就用反馈线性化、反步法,要么把模型算得很精细,要么控制律设计得极其复杂,一旦参数漂移或者工况变了,整套东西就跟着变脆。后来接触了 Koopman 算子理论,再用 EDMD(扩展动态模态分解)在 Matlab 里把四旋翼的模型用数据驱动方式“提升”成高维线性系统,整个设计思路一下就清爽了——非线性问题丢进高维空间变成线性问题,然后用现成的 LQR 等线性工具处理。这篇文章想把从模型搭建、数据生成、EDMD 辨识到 LQR 闭环控制的完整链路、实现细节和踩坑点都写清楚,适合理工科研究生、无人机方向工程师,以及被非线性控制头疼折磨的同学参考。

我不会只贴一堆现成结论,而是把每一步“为什么这么设计”也一并讲透。你在复现过程中遇到问题,也能在这篇文章里找到对应的排查思路。内容全部基于我在 Matlab 2022b 环境下的真实试验,版本老一点或新一点都没关系,核心逻辑不受影响。

1. 为什么四旋翼控制要引入 Koopman-EDMD

1.1 四旋翼模型的非线性痛点与线性化局限

四旋翼无人机看着飞行姿态灵活,其实动力学上是个不折不扣的强非线性系统。它的非线性主要来自几个方面:姿态角与角速度之间的三角函数关系、旋转矩阵带来的惯性耦合、电机转速与推力之间的平方关系,还有飞行时气动阻力、陀螺效应这些难精确建模的量。这些非线性项叠加在一起,直接导致很多线性控制方法没法大范围使用。

传统工程上最常用的做法是“小扰动线性化”:在悬停平衡点附近做一阶泰勒展开,得到线性状态空间模型,然后套用 LQR、PID 或者 H∞ 这类成熟线性控制器。这个方法在悬停点附近很有效,可一旦飞行器做大机动动作,比如大角度俯仰、急加减速、偏航快速转向,模型误差就会迅速放大,控制性能跟着跳水。我自己做过一组对比,悬停附近线性化模型的迎角响应在 5 度以内还行,超过 20 度后开环预测轨迹基本偏离实际值一大截。

另一种思路是反步法、滑模控制这类直接面向非线性模型的设计。它们对模型精度要求高,控制律推导复杂,参数整定也不直观。工程现场最怕的是“模型和实际对不上”:桨叶磨损、载荷变化、电池电压下降引起的推力衰减,都会让基于精确模型的控制器失效。数据驱动控制的优势就在这——它不依赖手推的物理模型,而是直接从输入输出数据中学习系统的动态行为。

1.2 Koopman 算子的核心思想:换个视角看非线性

Koopman 算子理论最早要追溯到 1931 年,但直到近几年才在控制领域火起来。它的核心思想用一句话概括:任何非线性动力学系统,都可以被嵌入到一个无限维线性算子框架中。

怎么理解这句话?假设系统状态是 x,非线性离散动力学方程是 x_{k+1}=F(x_k)。传统线性化是在状态空间中找一个局部的线性近似,而 Koopman 的思路是,不再直接观察状态 x,而是观察 x 的某个标量函数 g(x),比如 x 的平方、x 的三次方、sin(x)、exp(x) 等等。系统在状态空间里的非线性演化,在观测函数空间里就可以被一个线性算子 K 作用在 g 上来刻画,也就是 (Kg)(x)=g∘F(x)。对任意一个观测函数 g,K 把 g 映到另一个观测函数 g',这个映射本身是线性的。

可以打个比方:把非线性系统想象成一条在平面上弯弯曲曲的山路,线性控制工具相当于在路面上做一条直线的近似,只能覆盖一小段。而 Koopman 算子相当于把整条山路抬升到高维空间,用一座高架桥从上方笔直穿过去,桥上是直线的、线性的。问题是,从桥面往下看,路怎么走都是清晰的,非线性被藏到了“抬升”这一步里。

这里的代价是,观测函数空间是无限维的。也就是说,要用线性工具处理非线性系统,必须处理一个无穷维线性系统。幸运的是,我们并不需要全部的无穷维空间,只要选取一组足够丰富的观测函数(也就是字典),把系统状态映射到一个高维但有限维的空间,就能得到 Koopman 算子的一个有限维近似。

1.3 EDMD:从数据到 Koopman 矩阵的桥梁

理论上 Koopman 算子是无限维的,实际工程中怎么拿到它的有限维表示?EDMD(Extended Dynamic Mode Decomposition,扩展动态模态分解)是目前最主流的方法,本质上是一个基于数据的最小二乘拟合问题。

具体来说,先选定一组基函数字典 ψ(x)=[ψ_1(x), ψ_2(x), ..., ψ_N(x)]^T。然后采集系统的状态轨迹数据:一对一对相邻时刻的状态快照 (x_k, x_{k+1})。接着构造两个矩阵:

  • G = (1/M) Σ ψ(x_k) ψ(x_k)^T
  • A = (1/M) Σ ψ(x_k) ψ(x_{k+1})^T

于是 Koopman 矩阵的有限维近似就是 K = G^{-1} A。这个矩阵把一个时刻的提升状态 z_k=ψ(x_k) 映射到下一时刻 z_{k+1}≈K z_k。算出来之后,非线性系统的预测问题就变成了高维线性系统的预测问题,LQR、MPC 这些工具直接可以用上。

这个流程里最核心的变量是字典函数的选择和数据的质量。EDMD 的名字里带了个“扩展”,就是因为相比早期的 DMD(动态模态分解),它不再局限于线性观测,而是通过字典把系统提升到非线性观测空间,从而使线性近似在大工作域内成立。我实测下来,只要字典选得合适,Koopman 模型在较大机动范围内的预测精度远优于小扰动线性化模型。

2. 四旋翼动力学模型与仿真环境搭建

2.1 刚体动力学方程与控制分配

做数据驱动建模,第一步得有足够可靠的被控系统数据。四旋翼的动力学方程分为运动学和动力学两部分。运动学描述位置、姿态与速度、角速度的关系,动力学描述受力与加速度、力矩与角加速度的关系。

常用简化模型选取状态变量为 x = [p_x, p_y, p_z, φ, θ, ψ, v_x, v_y, v_z, ω_x, ω_y, ω_z]^T

其中 (p_x, p_y, p_z) 为位置,(φ, θ, ψ) 为滚转、俯仰、偏航角,(v_x, v_y, v_z) 为机体速度,ω 为机体角速度。控制输入通常取 u = [T, τ_φ, τ_θ, τ_ψ]^T

即总推力与三个轴的力矩。

动力学方程可以写成:

  • 线加速度:m v_dot = m g e_z + R(φ, θ, ψ) [0,0,T]^T - 阻力项
  • 角加速度:I ω_dot = -ω × (I ω) + τ

其中 I 是惯量矩阵,R 是旋转矩阵。这个方程包含了三角函数、叉乘和非线性惯性耦合,是典型的非线性系统。控制分配部分,四个电机转速 Ω_i 的平方与推力和力矩之间有固定映射矩阵,将 T 和 τ 分配到各电机转速,常用等比例分配矩阵。

在 Matlab 里实现时,我建议把动力学方程封装成一个函数,输入当前状态与控制量,输出状态导数,再用 ode45 积分得到轨迹。注意这里的欧拉角在接近 ±90° 时会出现万向锁问题,但四旋翼正常飞行姿态不会到那个范围,作为数据生成和中等幅度机动验证是够用的。

2.2 物理参数与仿真设定

建模时我采用的是一组典型的小型四旋翼参数,不算特别精确,但足以验证算法链路。质量 m=0.8 kg,重力加速度 g=9.81 m/s²,机臂长度 l=0.15 m,惯量矩阵取对角形式 I_x=0.005 kg·m²,I_y=0.005 kg·m²,I_z=0.009 kg·m²,推力系数 c_T=1.4e-5,扭矩系数 c_M=2.6e-7。这些参数单位必须保持统一,我一开始就吃过单位不一致的亏,算出来的 K 矩阵完全不对。

仿真设置方面,我把采样周期设为 0.02 s(50 Hz),与常见飞控传感器输出频率一致。仿真时长设定为 30 s,生成的数据用于后续 EDMD 辨识。推进器模型考虑了一阶惯性环节,模拟真实电机响应延迟,实测下来这个延迟对辨识结果的影响很大,不能省。

数据生成时还需要给系统加一点测量噪声。实际传感器不可能输出完美数据,加噪声还能顺带考察 Koopman 模型对噪声的鲁棒性。我用的是高斯白噪声,位置和姿态通道的标准差设为相应量程的 1%,速度通道设 2%,这个比例大概能模拟 GPS+IMU 融合后的水平。

3. 基于 EDMD 的 Koopman 模型辨识完整流程

3.1 激励信号设计:数据质量决定模型上限

做数据驱动建模,最容易被低估的就是数据怎么采。EDMD 题目看起来线性回归很简单,但模型能学到什么,完全取决于你喂给它什么。系统从来没有展示过的运动范围,模型必然预测不准。

激励信号要做到“充分激励”。对四旋翼这类多输入多输出系统,每个输入通道都要有足够丰富的频带覆盖。我最常用的方案是 chirp 扫频信号叠加多正弦,外加少量随机白噪声。chirp 信号从 0.1 Hz 扫到 5 Hz,覆盖了四旋翼主要的刚体模态;白噪声保证高频段激励;逐通道轮流扫,同时保持其他通道小幅抖动,这样可以避免输入耦合导致的辨识偏差。

幅值选择也要讲究。四旋翼的姿态角机动范围我设在 ±25°,速度机动范围 ±1 m/s。幅值太小的话,系统主要在小范围内运动,辨识出的模型还是偏局部;幅值太大则容易触碰执行器饱和,把非线性效应推到极限。我踩过坑:一开始为了“探索充分”把姿态激励加到 ±45°,结果电机转速进入饱和区,数据里混入了严重的非线性失真,K 矩阵估计出来根本没法用。后来把幅值控制在 ±25° 以内,效果就好多了。

采集数据的长度也很关键。一条 30 秒的数据量可以用来做初步辨识,但要让模型在更大工作域内可靠,我习惯把多个不同幅值、不同频率组合的试验数据拼接起来用,数据集总时长拉到 60 到 90 秒。EDMD 对数据量的需求没有深度神经网络那么苛刻,但数据“覆盖范围广”比“数量多”重要得多。

3.2 字典(提升函数)的选择与参数设置

EDMD 的核心机密全在字典函数 ψ(x) 里。字典选得好不好,直接决定 Koopman 矩阵近似误差的下限。

我试过三类字典:

  • 多项式基:比如 x_1、x_1²、x_1x_2、x_1³ 等。实现简单,在光滑非线性系统上效果不错,但阶数高了容易造成矩阵病态。
  • 三角函数基:sin、cos、tanh 这类,适合描述含周期或饱和特性的动力学。
  • 径向基函数(RBF):比如高斯核 exp(-||x-c||²/σ²),拟合能力强,但中心点和带宽需要额外设置,字典维度容易爆炸。

实际项目里我比较推荐“多项式+三角函数”的混合字典,兼顾光滑非线性拟合和周期项刻画。对四旋翼,状态量 12 个,如果每个状态取到二阶项,再补充少量交叉项和三角函数项,字典维度大概在 40 到 60 之间。我最终用的是 52 维。维度不是越高越好,太高了 G 矩阵容易近奇异,最小二乘解对噪声极其敏感,模型泛化能力反而下降。

另外提醒一句,字典里必须包含原始状态本身,也就是至少要有一个恒等映射把状态 x 原样放进提升状态里。否则你后续做状态反馈控制时,没法从提升状态中还原出真实状态。这个细节容易漏,但一漏就是连环 bug。

正则化也是必选项。EDMD 的 K = G^{-1} A 如果直接用反斜杠求解,在 G 条件数较大时会得到剧烈震荡的解。我通常加一个岭正则化项,把求解改为 K = (G + λI)^{-1} A,其中 λ 是个小量,一般在 1e-6 到 1e-4 之间。需要根据条件数实际调整。这个操作对模型稳定性的提升非常明显。

3.3 Koopman 矩阵求解与模型验证

EDMD 的算法流程可以用下面这段 Matlab 代码概括:

% Psi: M x N 字典矩阵 % X: M x n 状态快照 % Yp: M x n 下一时刻状态快照 Phi = dict(X); % 提升状态 z_k Psi = dict(Yp); % 提升状态 z_{k+1} G = Phi' * Phi / M; A = Phi' * Psi / M; K = (G + lambda * eye(N)) \ A;

其中 dict 函数根据你选定的字典计算提升状态。我把字典函数单独写成一个脚本,方便切换不同字典组合。注意矩阵规模不大时直接用密集矩阵没问题,但如果字典维度超过 100 且数据量很大,建议用稀疏矩阵或分块计算,避免内存爆炸。

模型辨识出来之后,关键一步是验证。单步预测误差小不代表多步预测稳定,很多 Koopman 模型会出现“短期精确、长期发散”的问题。我习惯做两个层面的验证:

一是开环多步预测。从某一初始状态出发,用 K 矩阵连续递推 2 到 5 秒,对比预测轨迹与实际仿真轨迹。四旋翼是高度不稳定的系统,开环预测误差累积很快,所以我更关注前 2 秒内的吻合度以及整体趋势是否一致。

二是计算方差解释率(VAF,Variance Accounted For),公式为 VAF = (1 - ||y_pred - y_true||² / ||y_true - mean(y_true)||²) × 100%。80% 以上算合格,90% 以上算良好。我调好的模型在姿态通道 VAF 能到 92% 左右,位置通道稍低,在 85% 左右。这个指标也可以用来对比不同字典的优劣,比肉眼看图要客观。

4. 基于 Koopman 模型的线性控制器设计

4.1 在提升空间设计 LQR

Koopman 模型的最大卖点,是辨识完成后系统变成了线性的: z_{k+1} = K z_k + B u_k

但这个 B 矩阵怎么来?EDMD 的扩展形式需要在辨识时同时考虑控制输入的影响。常见做法是把输入也纳入数据快照:定义增广状态 x_aug = [x^T, u]^T,然后对增广系统做 EDMD。但这样会把输入也“提升”到非线性空间,导致控制器设计时 u 的计算变复杂。

更常用、也更好用的方案是分两段处理:先用一组零输入或常值输入的数据辨识系统的零输入动态 K,再通过额外的小幅脉冲实验估计输入矩阵 B。这样做的好处是控制通道的线性假设更干净,LQR 设计也更直观。代价是 B 矩阵是局部的线性近似,但在中等机动范围内足够用。

在提升空间设计 LQR 时,代价函数沿用线性二次型形式: J = Σ (z^T Q z + u^T R u)

状态反馈控制率为 u = -K_lqr z。这里 z 是 52 维的提升状态,所以 Q 矩阵是 52×52 的。如何设 Q 是个难点,不能像原始状态那样直接设定。我的做法是,先把 Q 映射回原始状态空间,提升空间中的 Q 矩阵取为 Φ^T Q_x Φ,相当于把对原始状态的惩罚投影到提升空间。这样设计出来的控制权重物理意义清楚,调试也直观。R 则直接对四个控制通道设权重,初始值取单位矩阵然后微调。

4.2 引入积分项实现轨迹跟踪

单纯的状态反馈 LQR 只能做调节,没法实现对参考轨迹的跟踪。要跟踪轨迹,最直接的办法是在提升空间中引入误差积分项。

定义参考提升状态 z_ref,误差 e = z - z_ref。在增广状态中加入误差积分 z_i = ∫e dt,扩展后的状态维度变成 53 或更高(如果多个通道分开积分会更高)。扩展后的系统矩阵写成:

A_aug = [K, zeros(N, Ni); -C, zeros(Ni, Ni)]; B_aug = [B; zeros(Ni, m)];

其中 C 是输出选择矩阵,选择需要积分控制的状态跟踪误差。Ni 是积分状态数量。然后对增广系统重新设计 LQR。积分增益的作用是消除模型误差和外部扰动带来的稳态误差,这一点我实测下来非常关键:不加积分项,姿态跟踪会有 2 到 3 度的常值偏差;加了之后偏差降到 0.2 度以内。

控制率变成 u = -K1 z - K2 ∫e dt。注意这里的 z 是实时从状态 x 通过字典函数计算出来的,所以控制器内部还需要一个字典计算模块,实时把当前量测状态映射到提升空间。这部分代码要写得高效,因为每个控制周期都要执行一次字典计算。

4.3 控制器效果对比与调参要点

我用同一套四旋翼模型和同一组工况,跑三个控制器的对比:传统的悬停点线性化 LQR、基于 Koopman-EDMD 模型的 LQR、以及带积分项的 Koopman-LQR 轨迹跟踪。测试工况包括大幅俯仰机动、圆形轨迹跟踪和抗风扰动。

结果是,Koopman-LQR 在机动幅度达到 ±25° 时,姿态跟踪误差只有传统线性化 LQR 的三分之一左右。圆形轨迹跟踪时,位置误差 R=1m 的圆上,Koopman-LQR 的最大跟偏约 0.12 m,传统线性化 LQR 约 0.35 m。差距的原因就是 Koopman 模型在整个机动包络内提供了更准确的预测,LQR 反馈增益不再是为单一工作点“量身定做”的。

调参上,我建议按“先大机动性能、再稳态精度”的顺序来。先把 Q_x 设大、R 设小,让系统跟得紧,看是否出现高频抖动;如果抖了,就加大 R 中对应通道的权重。四旋翼的偏航通道响应比横滚、俯仰慢,相应 R 的权重要设得大一点,否则控制器会过度激进。积分项的增益不要一开始就调大,积分饱和会导致大机动后长时间振荡。我在实际调试中给积分项加了一个限幅,效果立竿见影。

5. Matlab 实现要点与代码框架

5.1 工程文件结构

Matlab 工程虽然不需要像大型软件那样做精细模块划分,但合理的文件组织能让你在修改参数或换数据集时省下大量时间。我的项目文件结构如下:

  • params.m:物理参数与控制器参数统一配置脚本
  • drone_dynamics.m:四旋翼非线性动力学方程函数
  • data_generation.m:生成激励信号并采集训练数据
  • dict_func.m:字典(提升函数)计算函数
  • edmd_training.m:EDMD 辨识主脚本,输出 K、B 矩阵
  • model_validation.m:多步预测与 VAF 指标计算
  • lqr_design.m:增广状态 LQR 设计
  • simulation_closed_loop.m:闭环仿真主程序

这样拆分的核心价值是隔离。比如你想换一组字典,只需要改 dict_func.m,其他脚本不用动。想换一组激励,改动 data_generation.m 即可。我在最初把所有逻辑写在一个脚本里,结果调参时每次都得从头跑,遇到 bug 排查也极度痛苦。拆分之后,每个环节可以独立调试,效率提升非常多。

5.2 训练与仿真关键代码

EDMD 训练核心代码前面已经展示了几个关键行,这里再补充闭环仿真的核心结构:

% 主循环 for k = 1:N_steps % 当前状态 -> 提升状态 z = dict_func(x_current); % 误差积分 err_int = err_int + (z_ref - z) * dt; % 控制律 u = -K_lqr * [z; err_int] + u_ff; % 系统动力学一步积分 x_next = rk4_step(@drone_dynamics, x_current, u, dt); end

这里用了 RK4 积分器做系统推进,比直接调 ode45 在循环里跑要快得多。控制周期是 0.02 s,和仿真积分步长一致,保证控制器与系统同步。

仿真中还有一个关键点是控制输入限幅。四旋翼每个电机转速有上下限,对应总推力有限制。控制器输出的期望推力如果超过限幅,必须做饱和处理。不处理的话,LQR 在大误差状态下会输出离谱的控制指令,仿真结果直接发散。我在控制输出后加了一个 saturate 操作,实测非常关键。

由于 EDMD 训练涉及大量矩阵乘法,数据量上来后 for 循环速度会拖后腿。我在数据生成和字典计算阶段都做了向量化处理,一次算完整个数据集的字典矩阵,比逐条循环快了一个数量级。如果你的数据规模更大,还可以考虑用 tall array 或并行池,但小微场景下没必要。

6. 常见问题与排查技巧实录

6.1 高频问题速查表

现象原因排查与解决方法
开环预测模型快速发散K 矩阵特征值幅值大于 1 且振型落在不稳定区域,字典覆盖不足检查字典是否包含充分非线性项;增加数据覆盖范围;加正则化;必要时对 K 做稳定化修正
单步预测精度高,多步预测精确度很差数据激励不足,系统某些模态未被激发增加扫频段与幅值;补充多组不同工况数据
Koopman 矩阵条件数巨大字典维度过高或基函数相关性太强删减高次项;加入岭正则化;检查字典是否有线性相关列
闭环仿真控制指令剧烈振荡Q/R 权重不合适,或 LQR 参考状态计算有误;积分项过大加大 R 对应通道权重;先关积分项调纯状态反馈;检查 z_ref 是否计算正确
跟踪稳态误差始终无法消除积分项未加或积分饱和加入误差积分状态,并设置积分限幅
字典里漏了原始状态映射控制器输出乱跳,无法从提升状态还原真实状态在 dict_func.m 中强制保留恒等映射项
训练数据和验证数据差异过大导致建模异常激励信号不一致或数据分段拼接时系统状态不连续拼接数据时保证每段初始状态连续,或重新生成统一的合成轨迹

这七条是我实际复现过程中碰到最频繁的问题。其中“单步精度高、多步发散”这个坑最隐蔽,因为初期验证很容易只看单步预测误差,模型看起来很好,一上闭环就崩。我建议从一开始就坚持做多步预测验证,哪怕先只看 1 秒内的趋势。

6.2 几个值得长期保留的经验

第一,数据比算法更重要。EDMD 的数学原理不复杂,复杂在怎么让数据把系统的真实动态完整交出来。我花了大量时间在信号设计和仿真参数调整上,最终它对模型质量的提升比换更复杂的字典都明显。

第二,字典的维度不是拍脑袋定的。我摸索出一个实用套路:先用纯二次多项式字典跑通全流程,确认闭环没问题,再逐步加三角函数、高次项,每次加完都重新验证模型稳定性和控制性能。这样能清楚定位每一项对模型的贡献,比一次性上一个大字典然后出问题无从下手要高效得多。

第三,Koopman-EDMD 适合中等维度的非线性系统,四旋翼正好是这个量级。状态量 12 个,字典 50 维左右,计算负担完全可控。如果状态量上百个,EDMD 的性能会明显下降,这时候可能需要深层字典或神经网络 Koopman 这类更进阶的解法。

第四,Matlab 里求 K 矩阵我用的是反斜杠\而不是显式的求逆。Matlab 的反斜杠会根据矩阵结构自动选择求解策略,数值稳定性和速度都更好。写代码时养成这个习惯,矩阵大了以后差距很明显。

按我个人的经验,Koopman-EDMD 这条路最妙的地方在于它给了控制工程师一种“带着数据工具箱去做控制”的思维模式:不再纠结于非线性项怎么消掉,而是让数据自己去“发现”一个线性化的坐标系统。这篇文章里所有代码和参数,都是在真实工程逻辑下反复调出来的,你顺着流程走一遍,应该能体会到从数据到模型、再到控制器的完整闭环。后续如果你想继续扩展,还可以试试闭环辨识、用 MPC 替换 LQR,或者把 Koopman 模型用于故障检测,方向很多,但核心链路就是这套,打牢了比什么都强。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询