多智能体编队控制这块,我一向对有“非仿射”三个字的题目格外上心。原因是理想化的线性模型写起来漂亮,但真实对象几乎都带着死区、饱和、输入耦合这类非线性,控制量往往不是以线性方式出现在方程里,这就是“非仿射”最直白的含义。最近我在MATLAB里把积分滑模、数据驱动迭代学习放在同一个框架下,对一类非仿射多智能体系统做了编队形成控制仿真,跑通了一套完整的MATLAB代码。这篇文章把整个思路、建模过程、控制器搭配方式、代码结构和调参踩坑都记下来,给做多智能体协同控制、滑模控制或者迭代学习方向的同学做个参考。
适合看这篇内容的人,我觉得有三类:一是刚接触多智能体一致性或编队控制,想找一个能落地的仿真框架;二是已经做了滑模控制,但被非仿射对象的模型不确定性搞得头疼;三是熟悉迭代学习控制(ILC),但不想依赖精确模型,想走数据驱动路线的。这三类视角其实正好对应标题里的几个关键词。
1. 四个关键词拆开看:这个组合到底在解决什么
标题看起来长,但控制方案设计其实就是在回答四个问题:被控对象长什么样、控制目标是什么、用什么方法压制不确定性和干扰、怎么利用重复运行的经验提升性能。
1.1 “非仿射”三个字是第一重麻烦
先解释“非仿射”的概念。对一个连续系统,如果方程能写成x_dot = f(x) + g(x)*u,我们说系统关于输入是仿射的,控制量u线性地出现在方程右侧,很多反馈线性化、滑模设计都建立在这样形式上。可实际情况里,很多对象只能写成:
x_dot = f(x, u) + d(t)u不是以线性项出现,而是夹在非线性函数里面,比如f(x,u)=x^2+sin(u)+u^3这种,就找不出一个能直接除出去的“控制增益”。此时传统滑模面设计里的等价控制项没法直接算,模型驱动方法会非常被动。
我在仿真里直接往被控对象里塞了个比较狠的例子:控制量同时以平方项和立方项出现,还混了位置相关的系数。这种模型在数学上就是典型的非仿射形式,用来验证方案合不合适,比线性模型有说服力得多。
1.2 积分滑模:不只抗干扰,还带“记忆”
滑模控制是处理系统不确定性和外部干扰的老牌工具。但常规滑模面如果只包含误差和误差导数,系统的稳态误差在持续扰动下还是可能收敛不到零。积分滑模在滑模面里多放一项误差的积分,相当于给控制器加了“记忆”——只要偏差持续存在,积分项就一直积累,迫使系统不断修正,直到误差真正归零。
这个性质在做编队控制时很关键。编队控制的本质是多个智能体之间的相对位置误差同时收敛到预设值,如果每个智能体各自都有一点稳态偏差,整个队形就会变形。积分项的加入把“局部稳态误差”压低了,队形精度就上来了。
1.3 数据驱动迭代学习:从精确模型里“解放”出来
迭代学习控制适合执行重复任务的系统:每次运行完,把这次获得的误差信息存下来,下一次运行开始时控制量在上一次基础上修正。它最大的优势是沿迭代轴“越用越准”,重复次数多了,误差曲线会整体压下去。
传统ILC设计往往要依赖系统的线性模型或相对阶信息。数据驱动迭代学习则绕开了这个限制,用输入输出数据在线建构一个等效模型,比如通过估计“伪偏导”来描述当前输入对输出的影响方向与强度,然后学习律在估计模型的基础上更新。即使被控对象是非仿射的,只要输入输出数据可用,这个框架依然能跑起来,这正是数据驱动路线在这里最实用的价值。
1.4 三方合在一起的分工逻辑
这个方案里三部分各司其职:数据驱动机制处理“非仿射”带来的模型难以精确获知的难题;迭代学习利用重复运行的误差历史提升渐近精度;积分滑模负责压制外部扰动和残余的非线性动态。可以说数据驱动给了学习能力,积分滑模给了鲁棒性,迭代学习给了精度。这个分工在仿真里验证下来是成立的:仅靠滑模,误差会维持在一个小范围内;加上迭代学习之后,误差上下限随时间迭代明显被压缩。
2. 编队问题建模:先把“形成控制”翻译成误差方程
控制设计之前,先要把抽象的“形成控制”变成一组能写进MATLAB的误差方程。这个环节看起来基础,但很多仿真的不收敛问题,根源恰恰是这里没理清楚。
2.1 通信拓扑怎么选
多智能体系统要协同,就得有通信关系。编队控制常见两种设定:一种是所有智能体都知道一条公共参考轨迹,另一种是leader-follower(领导者-跟随者)结构,只有部分智能体获取外部信息,其他人靠局部通信间接跟踪。
我在这套代码里用的是后者。原因是这种拓扑更贴近实际,编队信息不需要广播给所有人,同时也更能体现“分布式”的味道。通信关系用有向图描述,拉普拉斯矩阵(Laplacian matrix)是核心,矩阵的特征值分布直接关系到系统能不能达成一致。
有向图和无向图的差别在仿真里特别明显。无向图的信息交换是双向的,收敛条件比较宽松,好调参;有向图更接近智能体之间单向广播通信的现实,但需要满足“有生成树”这类条件,否则编队可能整体漂移或者根本收敛不到目标位置。
2.2 期望队形与参考坐标
编队不能只定义个形状,还得和“位置”绑在一起。我用的方式是:先给所有智能体设定一条期望运动轨迹,然后为每个智能体定义一个相对偏移量,这个偏移量组合起来就是整体队形。比如一架无人机为leader,在坐标系中朝某一方向飞行,其余三架与leader保持的几何关系分别是前方左右侧飞和后侧跟进,整个系统在运动中始终保持编队形态。
在MATLAB里,这个逻辑就是一个简单明了的矩阵映射:每个智能体的期望位置等于leader轨迹加上该智能体的队形偏移。代码里用向量化写法处理,不需要逐智能体循环去算参考轨迹。
2.3 误差模型与滑模面表达
定义第 i 个智能体的位置误差为e_i = x_i - x_i_ref,其中x_i_ref包含了基线轨迹和队形偏移。如果智能体自身状态能准确刻画,误差的收敛也就是队形形成的收敛。
积分滑模面在误差坐标系里可以取成:
s_i = e_i + lambda * integral(e_i)这里的 lambda 是个对角权重矩阵,决定积分误差对滑模面的贡献比例。滑模面为0时,误差动态变成了带积分特性的收敛过程,可以同时保证位置误差和它的累积量都归零。这一步是整个控制器设计里最简单也最关键的一步——滑模面取法决定了后续所有公式的表现力。
3. 控制律怎么设计:学习项、鲁棒项和数据模型怎么配合
控制律设计是整个方案的核心。我把它拆成三步来理解:先用数据估计伪偏导,构造等效数据模型;再设计沿迭代轴更新的学习项;最后把积分滑模趋近律加进去,压制扰动和估计误差。
3.1 伪偏导估计器:数据模型的核心
对非仿射系统直接求解析模型往往不可行,数据驱动方法会借助一个叫伪偏导(Pseudo Partial Derivative, PPD)的量,把系统在某个工作点的输入输出关系描述成线性化的增量形式。通俗点说:不追求精确的全局模型,只建立一个“当前时刻输入增加这么多,输出大致会响应多少”的增量关系。
伪偏导估计的标准形式我用的是类似投影算法的更新式:
phi_hat = phi_hat + (mu * delta_u / (mu + delta_u^2)) * (delta_y - phi_hat * delta_u)这里delta_u是控制增量,delta_y是输出增量,mu是权重系数,防止分母过小导致数值爆炸。代码里我特意在abs(delta_u) < 1e-6的时候保持上一拍估计不变,避免除零问题。这个细节在仿真中救了命:一旦系统进入滑模状态的等幅振荡区,控制量可能在同一数值附近反复横跳,delta_u 严重偏小,不加保护的话 phi_hat 就直接飞了。
3.2 迭代学习律的结构逻辑
迭代学习的核心思想可以浓缩成一句话:下一轮的控制量,等于上一轮控制量沿误差负梯度方向修正。写成伪代码大致就是:
u_next = u_now + rho * phi_hat * srho 是学习步长,决定这一轮修正的力度;phi_hat 是刚才提到的伪偏导,它起到了“修正方向”的作用——把误差 s 的变化折算成控制量应该补偿的方向。因为 phi_hat 是从数据里估出来的,就算模型是非仿射的,只要估计器在线运行,方向就不太容易错。
这个学习律看起来简单,但实际做过迭代学习的都知道,步长和方向一个都不能错。步长过小收敛慢,过大则可能出现误差振荡加剧的情况,反映在仿真图上就是对数值发散。
3.3 积分滑模趋近律的加入
纯迭代学习有一个弱点:如果每轮都有随机扰动,学习律会把上一轮的扰动补偿和这一轮的扰动混在一起,鲁棒性不够。这时候积分滑模那一项的作用就出来了,最终控制量结构可以写成:
u = u_ilc + eta * sat(s / theta) + integral 相关项这里的eta * sat(s / theta)是基于滑模面的趋近控制项,sat是饱和函数,theta是边界层厚度。饱和函数的主要意义是避免直接用符号函数sign(s)带来的高频抖振——这个抖振在纯数值仿真里会表现为控制曲线毛刺,在实物上则是执行器发热和磨损的最常见来源。
我实际测试过保留sign(s)和不保留的差异:用sign项时,收敛是快一点,但控制输入曲线几乎全是刺,像噪声一样高频切换;换成边界层饱和函数后,收敛速度损失很小,曲线却平缓得多。对仿真验证方案合理性而言,后者明显更合适。
3.4 参数之间怎么搭配才稳
这几个控制参数不是独立起作用的。我调参的顺序是:先定滑模面的 lambda(决定误差动态快慢),再调 eta(决定鲁棒项强度),最后动 rho(决定学习速度)。
有个比较直观的规律:eta 太小,抗扰动能力差;eta 太大,控制量会长期“满负荷”运行,系统进入边界层后反复打磨,损耗核心的控制余量;rho 和 lambda 共同决定学习收敛速度,但 rho 过大时,误差曲线可能会出现像心跳图一样的上下振荡。这三个参数之间彼此牵扯,不存在一个“万能参数”,只能根据每次仿真的具体情况去平衡取舍。
4. MATLAB 仿真实践:代码结构、核心函数和结果分析
仿真代码我尽量按模块化思路组织,每个模块职责单一,方便换模型、换拓扑、换参数做对比试验。整个仿真环境已经跑通,用MATLAB R2021b及以上版本都能直接运行。
4.1 文件组织与主程序框架
这套代码的核心文件结构是:
main_formation_ilc_smc.m:主程序,包含迭代循环、误差记录、画图agent_dynamics.m:智能体非仿射动力学模型controller_ilc_ism.m:迭代学习+积分滑模控制器update_ppd.m:伪偏导估计器formation_ref.m:编队参考轨迹与队形偏移生成
主程序的核心循环逻辑是嵌套结构:外层是迭代次数 k(ILC的迭代轴),内层是每个采样步 t(时间轴)。每一次迭代完成后,把整段时间轴上的误差记录下来,作为下一轮迭代学习的修正依据。这个结构是所有迭代学习控制仿真的基本骨架,理解它之后,换任何被控对象都只是替换动力学模块而已。
主程序大致长这样:
% main_formation_ilc_smc.m 核心循环 K = 30; % 迭代学习次数 T = 100; % 一次运行的采样点数 dt = 0.01; lambda = 2.0; % 滑模面积分权重 eta = 0.8; % 滑模趋近项系数 rho = 0.05; % 学习步长 u_record = zeros(N, T, K); error_norm = zeros(K, 1); for k = 1:K x = x_init; % 每次迭代重置状态 integral_e = zeros(N, 1); for t = 1:T-1 % 参考队形位置 ref = formation_ref(t, t*dt); % 编队误差 e = x - ref; integral_e = integral_e + e * dt; % 积分滑模面 s = e + lambda * integral_e; % 控制量由上次学习控制量与滑模修正构成 u = u_record(:, t, max(k-1,1)) + eta * tanh(s / 0.1); % 数据驱动伪偏导修正 phi_hat = update_ppd(u_prev, x, phi_hat, mu); u = u + rho * phi_hat .* s; % 推进被控对象 x = agent_dynamics(x, u, dt); end error_norm(k) = max(abs(x - formation_ref(T, T*dt))); end4.2 智能体动力学的非仿射实现
我的被控对象没有写成简单的x_dot = A x + B u,而是故意弄成非仿射形式:
function x_next = agent_dynamics(x, u, dt) % 非线性非仿射模型示例: % 控制量u同时以平方项和立方项出现 x_dot = (0.2 + 0.1 * sin(x)) .* (u.^2) + 0.2 * (u.^3) + 0.05 * randn(size(x)); x_next = x + x_dot * dt; end这句u.^2和u.^3同时出现,就是在模仿真实对象里执行器非线性、机械结构耦合这类情况。注意这里控制量的符号会影响平方项和立方项的不同行为,方向控制变得不再像线性系统那样直观。这也是非仿射系统的一个经典难处理点:控制增益的符号(正负)可能随着状态变化,设计控制器时如果直接套线性系统的方法很容易出现方向性失误。
仿真中遇到的方向问题我单独在下一节详细讲,这里先留个脚跟。
4.3 伪偏导估计器的防御式实现
update_ppd.m模块里,我做了两层防御。第一层是防止控制增量为零时的除零,第二层是限制估计值变化幅度,防止单次异常数据把估计值拉飞:
function phi_hat_next = update_ppd(delta_u, delta_y, phi_hat, mu) if abs(delta_u) < 1e-6 phi_hat_next = phi_hat; % 控制增量太小,保持原有估计 return; end phi_hat_next = phi_hat + ... (mu * delta_u / (mu + delta_u^2)) * (delta_y - phi_hat * delta_u); % 限幅保护,防止估计值突变 phi_hat_next = max(min(phi_hat_next, 5), 0.1); end那段限幅保护不是多此一举。迭代学习中后期,系统误差已经很小,信号信噪比下降,伪偏导估计容易莫名跳变。一旦估计值失真,学习律修正方向就会歪掉,后面的误差曲线可能出现“爆开”。限幅等于给了估计器一层保险丝,保证它在小误差区间内依然正常运转。
4.4 结果怎么看:误差收敛曲线与队形轨迹
仿真结果主要看两张图:一张是队形运动轨迹,各智能体的颜色区分明显,能直观看到队列有没有成形;另一张是误差范数随迭代次数k的变化曲线。后者是最重要的验收指标:从第一轮到第三十轮,误差整体应该呈收敛趋势,如果曲线出现平台期,说明参数还有优化空间;如果曲线发散,优先查学习步长rho和滑模面权重lambda。
在我最终确定的参数下,大概第8轮之后编队形状就已经可以辨认,第20轮之后误差范数进入一个稳定的低水平区间。这个收敛速度和纯ILC方案对比不算快,因为积分滑模项的存在会稍微拖慢学习初期的收敛,但换来的是后期更好的抗扰平稳性。
5. 调试经验与避坑心得
这节把我在仿真过程中踩过的坑集中提一下。大多数问题不是数学推导出错,而是仿真实现里细节把握不当导致的。
5.1 初始条件重置是迭代学习的“地基”
迭代学习理论通常有一个隐含假设:每一轮迭代开始时的系统状态是一致的。我一开始忽略了这个问题,每轮迭代直接用上一轮末端状态当作新一轮初值,结果误差曲线第一轮和第二轮之间来回乱跳,完全看不到收敛趋势。
后来把初值固定为同一个x_init,每个迭代循环开始前重新赋一遍初值,收敛曲线马上恢复了正常形态。这个细节看起来简单,但在写代码时最容易漏掉。如果你在自己仿真里发现ILC完全收敛不动,第一件事就去检查每一轮是不是重复了相同初始条件。
5.2 滑模抖振的仿真表现与处理
用符号函数做滑模趋近律,数值仿真里控制量会出现极高频率的切换。从纯理论角度这没问题,但MATLAB数值积分面对这种高频切换时,结果曲线会变得粗糙刺眼,甚至影响积分精度。我在边界层函数tanh(s/0.1)替代硬切换之后,控制量平滑很多,误差收敛曲线几乎不受影响,这是一个非常值得养成的习惯。
边界层厚度 0.1 这个参数也不是随便选的。太厚相当于把滑模降级成纯比例控制,鲁棒性明显下降;太薄又回到抖振的包围。我试过从 0.01 到 1 的几组取值,最终0.1附近在抗扰和平滑上取到了平衡点,你可以把它作为初始参考值然后自行调整。
5.3 非仿射带来的“控制方向”问题
这是一个我觉得值得单独写一节的隐患。仿射系统的控制方向由控制增益矩阵决定,符号通常是固定的;而非仿射系统里,控制量的符号和大小可能改变系统响应的方向,控制律很容易“帮倒忙”。
我的对策是在伪偏导估计器上加了符号自适应的能力:每次更新完 phi_hat 后,如果发现估计值接近 0 或出现符号翻转趋势,就将其约束到一个可接受的范围内,避免学习律向错误方向疯狂修正。这在数据驱动控制里叫“方向不确定性”或者“增益符号不确定性”,做实物实验时尤其要留心,因为实物系统中方向判断错的后果远不像仿真里那么好收拾。
从最基本的原理上讲,如果你的被控对象控制输入是二次项占主导,正向输入和负向输入产生的效果可能有本质区别,系统响应可能在某个区间表现完全相反。这就意味着设计学习律时要考虑:即便 x 误差是正的,实际需要补偿的控制量可能是负的,而不是想当然地沿误差方向给正控制量。
5.4 伪偏导初值怎么设
phi_hat 的初值我建议别太大也别太小。太大容易让学习律第一轮修正就冲出合理范围;太小则前期收敛明显变慢。一个实用的初始值可以取 0.5 左右,配合限幅范围 [0.1, 5],运行效果比较稳定。如果你的被控对象是三维甚至更高维系统,伪偏导会退化为矩阵形式,初值的设定逻辑还要再细致一些,建议先从对角占优的矩阵形式入手。
我在调试中还有个习惯:把u、phi_hat、s三个量都存在 workspace 里,每次跑完用plot直接看控制量有没有异常跳动、伪偏导有没有突变、滑模面是否在零附近震荡。这一步虽然多花几秒钟,却能把问题从“看不见摸不着”变成“一眼就清楚”。
6. 一些实际操作层面的体会
这套方法真正落地之后,我对数据驱动和滑模结合这件事有了点更深的体会。设计任何控制方案时,越是对模型依赖少的方法,越需要依赖仿真细节的反复校核,因为信息不足只能用更多的冗余设计来补。数据驱动迭代学习的校核,核心是伪偏导估计器的稳定性;积分滑模的校核,核心是边界层厚度和控制量的平滑性,两者都不能只停留在理论推导层面。
如果你想把这套代码扩展到更复杂的场景,我建议按以下顺序尝试:先把单积分体模型换成双积分体模型,但此时滑模面里的误差就要同时包含位置误差和速度误差;再把静态编队参考改成时变编队,比如整个编队在飞行中切换队形。这两个方向都属于工程中常见的需求,而且只会复用现有代码框架,不需要推翻重写。
我自己做仿真有个小习惯:每调整一次参数,就把误差曲线截下来和上一组参数时的曲线放在一起对比,而不是只看最终数值是否收敛。这个过程能帮你累积一种感觉,看到曲线形态就能判断该调哪个参数,这可能比再读十篇论文都管用。
对于正在被非仿射多智能体编队控制搞到头大的同学,我的建议很直接:先把模型简化和理论推导放一放,试着在MATLAB里把最简版本跑通,哪怕用的是单积分体、只有两个智能体、也是最简单的直线编队参考。跑通之后再逐步增加系统个数和使用复杂度。一旦基本框架能跑不散架,后面加非线性、加干扰也都能从容应对;反过来,一开始就憋大招,最后可能连误差曲线都拉不出来。