尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

基于RBF神经网络与S函数的自适应PID控制器Simulink实现

基于RBF神经网络与S函数的自适应PID控制器Simulink实现 1. 从PID到智能控制为什么我们需要RBF神经网络在工业控制、机器人、自动驾驶这些领域PID控制器就像空气和水一样无处不在。它的结构简单三个参数比例、积分、微分调节直观对于大量线性、时不变的系统调好了就能稳定运行。但干过几年工程的人都知道现实世界里的系统往往没那么“听话”。非线性、时变、大滞后、强耦合这些词儿一提起来工程师的眉头就得皱起来。传统的PID参数是固定的面对一个今天和明天特性都可能不一样的被控对象比如电池的温度管理、无人车的轨迹跟踪或者一个复杂的化学反应过程固定参数的PID就显得力不从心了。你调一组参数工况一变性能就下降甚至失稳。这时候大家就会想能不能让PID“聪明”一点这就是自适应PID或者智能PID的由来。其中RBF径向基函数神经网络与PID的结合是一条被理论和实践都验证过的有效路径。RBF神经网络结构相对简单训练速度快并且具有万能逼近特性理论上可以以任意精度逼近任何非线性函数。它的核心思想是用神经网络这个“黑盒”去在线辨识被控对象的动态特性比如Jacobian信息也就是系统输出对控制量的灵敏度然后根据这个实时辨识出来的“系统脾气”动态地调整PID的三个参数。所以这个项目的核心价值就出来了我们不满足于用一个固定的S函数模块去实现一个固定的算法而是要构建一个能够自我学习、自我调整的智能控制器框架。在Simulink里S函数是我们实现自定义算法、与Simulink求解器深度交互的终极武器。通过S函数来封装RBF神经网络在线辨识和PID参数自整定的逻辑我们就能在Simulink这个强大的仿真环境中构建、测试并验证一个完整的智能控制系统。这对于算法研发、控制策略前期验证来说效率是颠覆性的。你不用先吭哧吭哧用C语言写一堆代码再搭建测试环境直接在Simulink里建模、仿真、调参所见即所得。2. S函数打通Simulink自定义算法的任督二脉在开始设计控制器之前我们必须先搞定工具——S函数。很多初学者觉得S函数很神秘其实把它理解成Simulink和你自己写的C/C或MATLAB代码之间的一个“标准接口”或者“协议”就对了。Simulink在仿真时每个时间步长需要知道你这个自定义模块要做什么初始化、计算输出、更新离散状态、计算导数连续状态、结束仿真等等。S函数就是规定好了这些关键回调函数callback的名字和调用时机你只需要按照这个规矩把算法的具体实现填进去。2.1 S函数的工作原理与核心回调函数Simulink仿真引擎就像一个严格的导演S函数模块就是一个演员。导演引擎在每个固定的时间点采样时刻会向演员发出明确的指令演员必须按照事先约定好的剧本回调函数来表演。这个剧本的核心章节包括mdlInitializeSizes: 这是“角色设定”阶段。在这里你需要告诉导演我这个模块有几个输入端口比如误差e和误差变化率ec、几个输出端口比如控制量u、有几个连续状态用于微分方程、几个离散状态用于差分方程、采样时间是多少是继承的还是固定的。这一步至关重要如果定义错了仿真一开始就会报错。mdlInitializeSampleTimes: 设定采样时间。对于我们的RBF-PID控制器通常采用离散工作方式即设定一个固定的采样周期Ts。这里要明确指定采样时间[Ts, 0]其中Ts是采样周期0表示偏移量。mdlOutputs: 这是“说台词”阶段也是每个采样时刻必执行的核心函数。Simulink在这里需要你这个模块计算出当前的输出值。对于控制器来说就是根据输入误差等经过RBF网络计算和PID运算输出最终的控制量。这里实现的必须是当前时刻的输出计算不能包含状态更新。mdlUpdate: 这是“更新内心戏”阶段。对于离散系统在每个采样时刻mdlOutputs计算完输出后mdlUpdate会被调用来更新模块的内部离散状态。什么是内部状态对于我们这个系统RBF神经网络的权值、PID的参数Kp Ki Kd就是需要随时间更新的“状态”。所以一个非常关键且容易混淆的点是PID参数的自适应调整、神经网络权值的迭代学习这些“更新”操作应该放在mdlUpdate函数中而不是mdlOutputs中。这样可以保证在当前时刻t输出使用的是t-1时刻的状态参数而t时刻计算出的新状态将用于t1时刻的输出计算符合离散系统的时序逻辑。mdlTerminate: 仿真结束时的清理工作比如关闭文件、释放内存对于C MEX S函数尤其重要。对于我们要实现的RBF神经网络PID其在线学习机制可以概括为在每个采样周期mdlOutputs函数利用上一周期更新的网络权值和PID参数根据当前输入计算控制量紧接着mdlUpdate函数根据当前系统的输出或误差对RBF网络权值进行修正并据此计算出新的PID参数为下一个周期做好准备。2.2 编写S函数的两种主要方式Level-2 M-file vs. C MEXSimulink提供了多种编写S函数的方式最常用的是两种Level-2 M-file S函数用MATLAB语言编写文件扩展名为.m。它的优点是开发调试非常方便你可以直接使用MATLAB强大的矩阵运算和工具箱函数设置断点、单步调试就跟调试普通MATLAB脚本一样。对于算法原型验证、学术研究来说这是首选。本文的示例也将主要基于这种方式因为它最直观最能让我们聚焦于算法逻辑本身。C MEX S函数用C或C语言编写通过mex命令编译成Simulink可调用的二进制模块.mexw64等。它的优点是执行速度极快适合用于大规模仿真或最终产品级代码生成。如果你做的仿真模型很复杂或者最终需要从Simulink生成嵌入式C代码那么C MEX S函数是必经之路。但缺点也很明显开发调试复杂需要C语言基础并且内存管理需要自己负责容易出错。注意在MATLAB较新的版本中如R2020b以后官方更推荐使用“Simulink Function”模块、“MATLAB Function”模块或“System Object”来实现自定义功能它们比传统的S函数更易用、集成度更高。但对于学习底层原理、实现复杂迭代学习算法如神经网络在线训练或者需要与遗留代码集成S函数仍然是不可替代的深度工具。理解S函数意味着你真正理解了Simulink如何与自定义算法交互。3. RBF神经网络PID控制器的原理拆解现在让我们把焦点放回核心算法。RBF神经网络PID控制器不是一个黑箱它的结构清晰每一步都有明确的数学意义。我们把它拆开来看。3.1 RBF神经网络一个高效的非线性函数逼近器RBF网络通常有三层输入层、隐藏层径向基层和输出层。输入层对于PID参数整定输入通常选择系统的误差e(k)和误差变化率ec(k)即x [e(k) ec(k)]^T。有些设计也会加入更早时刻的信息。隐藏层由一系列径向基函数构成常用高斯函数hj exp(-||x - cj||^2 / (2 * bj^2))其中cj是第j个神经元的中心向量bj是它的宽度或标准差。||x - cj||是输入向量x到中心cj的欧氏距离。这个函数的特点是当输入x越接近中心c时输出越接近1距离越远输出越接近0。所以每个隐藏层神经元都负责感知输入空间的一个局部区域。输出层通常是线性加权和。对于我们的系统RBF网络的输出可以设计为被控对象输出对控制输入的灵敏度信息即Jacobian这是一个标量ym(k) sum(wj * hj)其中wj是连接隐藏层第j个神经元到输出层的权值。网络的训练目标是让网络输出ym(k)逼近系统的真实灵敏度。通过梯度下降法权值的调整律为Δwj η * (y_real(k) - ym(k)) * hjwj(k1) wj(k) Δwj α * (wj(k) - wj(k-1))后面一项是动量项用于平滑学习过程α是动量因子 其中η是学习率y_real(k)需要通过系统输入输出数据间接估计得到。3.2 PID参数的自整定律将辨识结果转化为动作增量式数字PID的控制律为Δu(k) Kp * [e(k)-e(k-1)] Ki * e(k) Kd * [e(k)-2e(k-1)e(k-2)]u(k) u(k-1) Δu(k)关键来了KpKiKd如何根据RBF网络的输出ym即辨识出的灵敏度来调整这里需要一个性能指标。通常我们定义一个二次型性能指标E(k) 0.5 * e(k)^2我们希望E(k)最小。根据梯度下降法PID参数应沿着E(k)对参数负梯度方向调整。以Kp为例ΔKp -η_p * ∂E/∂Kp -η_p * e(k) * (∂y/∂Δu) * (∂Δu/∂Kp)其中∂y/∂Δu就是系统输出对控制量增量的灵敏度这正是RBF网络在线辨识的目标ym(k)而∂Δu/∂Kp ec(k)误差变化率。因此我们得到PID参数的自适应调整律Kp(k1) Kp(k) η_p * e(k) * ym(k) * ec(k)Ki(k1) Ki(k) η_i * e(k) * ym(k) * e(k)Kd(k1) Kd(k) η_d * e(k) * ym(k) * [e(k)-2e(k-1)e(k-2)]其中η_pη_iη_d分别是三个参数的学习率需要手动设定。ym(k)由RBF网络实时提供。这个公式的物理意义非常清晰当误差e(k)大时调整幅度大调整的方向正负由误差e(k)、灵敏度ym(k)和误差微分/积分项共同决定。而ym(k)反映了系统的当前动态特性使得参数调整是“有的放矢”的。4. 在Simulink中构建RBF-PID控制器S函数模块理论清晰后我们进入实战环节。我们将用Level-2 M-file S函数来实现这个控制器。4.1 模块接口与初始化设计首先我们需要规划S函数的“对外接口”和“内部状态”。输入端口至少需要两个。Port 1系统的参考输入rinPort 2系统的实际输出yout。在模块内部我们可以计算误差e(k)rin-yout和误差变化率ec(k)e(k)-e(k-1)。输出端口一个。输出控制量u(k)。参数可以通过S函数的对话框输入一系列初始化参数例如PID的初始参数Kp0 Ki0 Kd0RBF网络的结构参数隐藏层节点数node_num、中心c、宽度b、初始权值w学习率η η_p η_i η_d动量因子α采样时间Ts等。离散状态我们需要定义一组离散状态向量DStates来存储所有需要跨周期记忆的变量。这至少应包括上一时刻和上上时刻的误差e(k-1)e(k-2)用于计算ec(k)和PID的微分项。上一时刻的控制量u(k-1)用于计算增量式PID。RBF网络的当前权值向量w。当前的PID参数Kp Ki Kd。上一时刻的权值w_old用于动量项计算。上一时刻的PID参数Kp_old等同样用于动量项。在mdlInitializeSizes方法中我们就需要把这些信息通过sizes结构体告知Simulink。function setup(block) % 注册输入输出端口数量 block.NumInputPorts 2; % rin, yout block.NumOutputPorts 1; % u block.SetPreCompInpPortInfoToDynamic; block.SetPreCompOutPortInfoToDynamic; % 设置输入端口属性 block.InputPort(1).DatatypeID 0; % double block.InputPort(1).Complexity Real; block.InputPort(1).Dimensions 1; block.InputPort(1).DirectFeedthrough true; % 输出直接依赖于该输入 block.InputPort(2).DatatypeID 0; block.InputPort(2).Complexity Real; block.InputPort(2).Dimensions 1; block.InputPort(2).DirectFeedthrough true; % 设置输出端口属性 block.OutputPort(1).DatatypeID 0; block.OutputPort(1).Complexity Real; block.OutputPort(1).Dimensions 1; % 通过对话框参数获取采样时间 Ts block.DialogPrm(1).Data; % 假设第一个对话框参数是Ts block.SampleTimes [Ts 0]; % 注册离散状态的数量 num_discrete_states 2 2 node_num 3 node_num 3; % 解释e(k-1), e(k-2) - 2 % u(k-1) - 1? 这里需要仔细规划。实际上e(k-1), e(k-2), u(k-1), w(1:node_num), Kp,Ki,Kd, w_old(1:node_num), Kp_old, Ki_old, Kd_old % 更清晰的做法是定义一个总长度并在初始化时按索引赋值。 block.NumDiscStates num_discrete_states; block.RegBlockMethod(PostPropagationSetup, DoPostPropSetup); block.RegBlockMethod(InitializeConditions, InitConditions); block.RegBlockMethod(Outputs, Outputs); block.RegBlockMethod(Update, Update); end上面代码是一个框架示意node_num需要从参数获取。实际编写时我们需要一个更严谨的状态向量定义和索引方案。4.2 输出计算mdlOutputs的实现逻辑在Outputs方法中我们的任务是利用上一时刻更新好的状态计算当前时刻的控制输出。读取输入和状态从block.InputPort(1).Data和block.InputPort(2).Data获取rin(k)和yout(k)。从离散状态向量block.Dwork(1).Data中读取e(k-1)e(k-2)u(k-1)wKp Ki Kd。计算当前误差e(k) rin(k) - yout(k)。计算误差变化率ec(k) e(k) - e(k-1)。RBF网络前向传播用于控制量计算构造输入向量x [e(k); ec(k)]。计算隐藏层输出h对于每个隐藏层节点j计算hj exp(-norm(x - cj)^2 / (2*bj^2))。计算网络输出ym(k) sum(wj * hj)。注意这里的ym(k)是网络基于t-1时刻权值对当前输入x的计算结果它将被用于当前控制量的计算但权值本身还未更新。计算PID控制量计算PID增量delta_u Kp*ec(k) Ki*e(k) Kd*(e(k)-2*e(k-1)e(k-2))。计算当前控制量u(k) u(k-1) delta_u。这里通常需要加入输出限幅防止控制量饱和u(k) max(min(u(k) umax) umin)。设置输出将计算出的u(k)赋值给block.OutputPort(1).Data。关键点在Outputs函数中我们只进行前向计算和输出不修改任何离散状态权值、PID参数。状态的更新留给Update函数。4.3 状态更新与网络学习mdlUpdate的实现逻辑Update方法在Outputs之后被调用用于为下一个采样周期准备状态。可选灵敏度估计为了训练网络我们需要一个“教师信号”即真实的灵敏度∂y/∂u。在无法直接获取的情况下常用近似方法approx_J (yout(k) - yout(k-1)) / (u(k-1) - u(k-2))。这需要用到上一时刻的输出和控制量因此这些历史值也需要存储在状态中。这是一个简化处理更精确的估计需要更复杂的辨识方法。RBF网络权值更新计算权值调整量delta_w eta * (approx_J - ym(k)) * h。其中eta是网络权值学习率h是当前隐藏层输出向量。加入动量项w_new w_old delta_w alpha * (w_old - w_old_prev)。更新权值状态将w_new存回block.Dwork中并更新w_old_prev和w_old。PID参数更新利用刚刚计算出的ym(k)注意这里用的是Outputs中计算的那个ym(k)它基于旧权值但用于参数更新是合理的因为参数更新本身也是一个迭代过程。根据3.2节的调整律计算参数增量delta_Kp eta_p * e(k) * ym(k) * ec(k)delta_Ki eta_i * e(k) * ym(k) * e(k)delta_Kd eta_d * e(k) * ym(k) * (e(k)-2*e(k-1)e(k-2))同样加入动量项并更新Kp Ki Kd的状态。参数限幅为了防止参数漂移到不合理区域通常需要对更新后的PID参数进行限幅Kp max(min(Kp Kp_max) Kp_min)Ki和Kd同理。更新历史状态将当前的e(k)存入e(k-1)原来的e(k-1)存入e(k-2)将当前的u(k)存入u(k-1)更新其他用于动量项的历史状态。至此一个完整的“计算输出-更新状态”周期结束。Simulink会在下一个采样时刻重复这个过程。5. 仿真搭建、调试与核心参数整定心得有了S函数模块我们还需要在Simulink中搭建一个完整的闭环仿真系统来验证它。5.1 构建测试仿真模型典型的测试模型包括以下几个部分信号源例如阶跃信号、正弦信号或更复杂的参考轨迹作为rin。RBF-PID控制器S函数模块将我们编写好的.m文件封装成模块。在Simulink Library Browser中搜索“S-Function”模块拖入模型在其参数对话框中设置“S-function name”为我们的函数名如rbf_pid_controller并在“S-function parameters”中按顺序填入我们setup函数中定义的对话框参数TsKp0node_num等。被控对象模型为了验证控制器的自适应能力我们应该选择一个非线性或时变对象。例如一个简单的非线性系统yout(k1) 0.5*yout(k) u(k) / (1 yout(k)^2)。这个对象在yout较大时增益会变小。一个时变系统其参数随时间缓慢变化。或者更复杂的如倒立摆、电机等模型。示波器与数据导出连接rinyouteu以及KpKiKd到Scope或者To Workspace模块便于观察波形和分析。尤其重要的是要把PID参数和RBF权值也输出到工作空间这样你才能看到它们是如何自适应变化的这是调试的关键。5.2 调试过程中的常见问题与解决策略第一次仿真几乎肯定会失败或者性能很差。别慌这是常态。问题一仿真发散输出NaN或无穷大。可能原因1控制量或状态未初始化。在InitializeConditions方法中必须为所有离散状态e(k-1)u(k-1)wKp等赋予合理的初始值。PID参数可以设为对话框传入的初始值权值w可以初始化为0或很小的随机数历史误差和控制量初始为0。可能原因2学习率过大。这是最可能的原因。无论是网络权值学习率eta还是PID参数学习率eta_p等如果设置过大会导致更新步伐太大权值和参数剧烈震荡进而使控制量发散。策略从一个非常小的值开始尝试例如1e-4甚至1e-5。观察参数变化曲线应该是平滑、缓慢地调整而不是剧烈跳变。可能原因3被控对象本身开环不稳定而初始PID参数无法镇定它。在自适应算法起效前控制器需要靠初始参数工作一段时间。如果初始参数太差系统可能一开始就发散了等不到自适应过程。策略先手动整定一组能勉强稳住系统的PID参数作为初始值。或者在仿真开始的前几秒暂时“冻结”自适应过程即不更新权值和参数让固定PID先工作然后再开启自适应。问题二系统稳定但稳态误差大或者跟踪性能不理想。可能原因1积分项学习率eta_i过小或初始Ki过小。积分项负责消除稳态误差。如果它的自适应能力弱就无法将误差收敛到零。可以适当增大eta_i或者给Ki一个更大的初始值。可能原因2RBF网络辨识能力不足。隐藏层节点数node_num太少或者中心c和宽度b设置不合理导致网络无法有效拟合系统的非线性。策略中心c可以均匀分布在输入[e ec]的预期范围内。宽度b需要覆盖通常设置为相邻中心距离的1~2倍。可以尝试增加节点数。可能原因3灵敏度估计approx_J不准确。我们的近似方法(y(k)-y(k-1))/(u(k-1)-u(k-2))在信号平滑时还行但噪声大或采样快时误差很大。不准确的教师信号会导致网络学偏。策略可以引入一阶低通滤波对yout和u进行平滑处理后再计算差分。或者考虑使用更鲁棒的辨识方法但这会大大增加S函数的复杂度。问题三参数Kp Ki Kd收敛到一个不合理的值或者始终在震荡。可能原因缺少参数限幅。自适应过程可能会将参数调整到极大或极小甚至负值这在实际物理系统中是不可能的例如负的积分时间。必须加入参数限幅。根据你对被控对象的先验知识设定[Kp_min Kp_max]等范围。例如比例增益通常为正积分时间应为正数即Ki为正微分时间通常也为正。另一个原因动量因子alpha设置不当。alpha太大接近1会导致参数更新惯性过大容易超调震荡alpha太小则平滑效果差。一般设置在0.5~0.9之间调整。5.3 参数整定经验先“静”后“动”先“粗”后“细”整定这样一个有多个学习率的复杂控制器需要耐心和策略。第一步冻结学习整定初始PID。将etaeta_p等所有学习率设为0让控制器退化为一个普通PID。手动调节Kp0 Ki0 Kd0使得系统对阶跃响应的基本性能上升时间、超调、稳态误差达到一个可接受的水平。这为自适应提供了一个好的起点。第二步只开启RBF网络学习固定PID参数。设置eta为一个较小值如1e-4eta_p等设为0。运行仿真观察RBF网络的输出ym是否能够跟随你估算的系统灵敏度变化趋势你可以用前述近似公式计算一个粗糙的灵敏度作为参考。调整eta、node_num、c和b使ym的输出相对平滑且能反映系统动态。第三步开启PID参数学习但用很小的学习率。在第二步稳定的基础上逐步引入eta_peta_ieta_d从非常小的值开始如1e-6。观察PID参数的变化曲线它们应该是缓慢、平滑地调整。如果出现跳变或发散立即减小学习率。第四步引入动量微调性能。在参数能平稳学习的基础上加入动量因子alpha如0.7观察是否能加快收敛或使曲线更平滑。然后可以尝试略微增大学习率或者改变参考信号如从阶跃变为正弦测试控制器的自适应跟踪能力。第五步鲁棒性测试。改变被控对象的参数模拟对象时变或者在输出中加入小幅噪声观察控制器能否保持稳定参数能否重新调整到新的合适值。这个过程可能反复多次。务必充分利用Simulink的实时示波和Workspace数据绘图功能将关键变量误差、控制量、三个PID参数、几个关键的RBF权值绘制出来对比分析。
返回列表