多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

BP神经网络自整定PID控制仿真:从原理到Simulink实现

BP神经网络自整定PID控制仿真:从原理到Simulink实现 简介一份关于BP神经网络自整定PID控制仿真的PDF技术文档面向自动化、控制工程相关专业学生与工程师帮助解决传统固定参数PID难以适应时变对象的问题。内容涵盖控制器结构、4-5-3型BP网络设计、增量式数字PID算法、性能指标函数、梯度下降法调整权系数的完整推导并配有MATLAB仿真流程和时变非线性对象数学模型。文档以公式与文字推导为主可指导读者从原理层面理解神经网络自整定机制并参照流程完成仿真实验。该压缩包仅含1个PDF文件大小526KB内容紧凑、聚焦算法核心。目前已有144人学习下载适合需要系统掌握BP神经网络PID自整定方法的研究生、工程师及竞赛备赛者可作为课程实验或课题研究的参考资料。1. “基于BP神经网络的自整定PID控制仿真”到底在解决什么问题如果你调过PID一定经历过这样的场景Kp调大了超调调小了响应慢Ki好不容易把稳态误差压下去系统又开始震荡Kd加了点阻尼噪声却又被放大。一个双容水箱、一台直流电机或者一个二阶倒立摆就能让人在反复试凑里耗掉一整个下午。我做控制系统仿真这几年最深的体会是常规PID不是不能用而是参数跟着工况变——负载一换、给定值一跳原来那组参数瞬间就不好用了。“基于BP神经网络的自整定PID控制仿真”这个方向就是为了把“人的试凑”换成“网络的在线学习”用一个三层BP神经网络去实时调整PID的三个系数让控制器跟着被控对象的状态走。它不是什么新理论但工程上非常实用尤其适合做嵌入式自整定控制器、Simulink仿真验证和毕业设计里的智能控制模块。这篇笔记我会用能直接复现的路径把这个仿真从原理讲到落坑全程围绕在MATLAB/Simulink里实现这套自整定控制的完整流程。适合正在做智能控制相关的学生、想给现有PID加自适应能力的工程师以及被“调参玄学”折磨的入门者。2. 从PID到BP自整定控制器为什么要长出一颗“网络脑袋”2.1 常规PID的固定参数困境与增量式输出的好处先把底层的控制律说清楚。常规PID的离散形式一般写成位置式u(k) Kp * e(k) Ki * Σ e(j) Kd * [e(k) - e(k-1)]位置式有一个实际痛点它直接输出绝对控制量积分项是历史误差的累加一旦传感器掉线或者给定值突变容易产生很大的控制量跳变执行机构受不了。工程里更常用的是增量式PID它只输出控制量的增量Δu(k)Δu(k) Kp * [e(k) - e(k-1)] Ki * e(k) Kd * [e(k) - 2e(k-1) e(k-2)]这个形式的好处在于第一计算不需要累加历史误差不存在积分饱和第二输出的是增量手动/自动无扰切换方便第三如果后续要给控制量加限幅很容易处理。BP自整定PID的仿真里我一般建议用增量式因为神经网络的输出层直接映射到Kp、Ki、Kd而这三个系数在增量式里只影响当前一步的增量训练时梯度回传更直接。如果你用的是位置式网络的输出还要经过积分环节误差累积会让网络训练发散得更快。2.2 BP神经网络如何“学会”调PID网络结构到参数映射用BP网络做自整定核心思想是把PID参数看成被控对象状态和性能指标的函数。常见做法是采用一个三层前馈网络输入层选3到4个变量系统给定值r(k)、实际输出y(k)、误差e(k)或者再加上误差变化率ec(k)。我通常在仿真里取4个输入——r(k)、y(k)、e(k)、ec(k)其中ec(k)e(k)-e(k-1)。隐含层节点数取5到7个输出层3个节点分别对应Kp、Ki、Kd。网络前向计算的公式如下隐含层输入 net_i Σ w_ij * x_j激活函数取双曲正切tanh输出层 net_l Σ w_li * h_i激活函数取sigmoid因为Kp、Ki、Kd必须是正数。然后做线性变换把网络输出映射到实际PID参数范围比如Kp Kp_min (Kp_max - Kp_min) * out_1这样网络输出的0到1就对应一个可调的PID参数区间。训练过程不是离线的而是每个控制周期在线做一次BP更新。性能指标取误差平方的积分但实际计算时为了简化常用误差平方的瞬时值加一个惯性项目标是让 J 0.5 * e(k1)^2 最小。在推导梯度时需要知道被控对象的雅可比矩阵 ∂y / ∂u也就是控制量变化一点点系统输出变化多少。这个值在纯仿真里可以通过被控对象的离散模型解析算出但在实际工程里通常用符号函数sign(∂y/∂u)近似或者再嵌套一个辨识网络去估计。仿真阶段不必太纠结直接取系统模型的一阶差分近似即可。2.3 学习率、动量项与归一化网络训练的三个“先决条件”BP自整定PID最容易翻车的地方不在网络结构而在训练参数。我自己的血泪经验是学习率η必须分开设置——输出层和隐含层用不同的值一般输出层取0.05到0.1隐含层取0.01到0.05。如果共用一个大学习率隐含层权值更新的幅度会被输出层梯度放大权值很容易在几百步内发散到NaN。动量项α也建议加上取0.05左右它能把权值更新方向平滑化减少震荡。更重要的一点是输入归一化r(k)、y(k)、e(k)的量纲可能差一个数量级比如给定值是100误差只有0.1如果不归一化网络的误差梯度会被e(k)主导Kp的学习过程就是无效的。我一般对每个输入先做z-score标准化或者mapminmax到[-1,1]然后在Simulink里用单位延迟配合增益模块实现。注意归一化的均值和方差要用仿真初期的在线统计值或者在启动阶段用一组已知工况的数据预统计。不要用“全局固定值”因为系统工况变化时输入范围会漂移固定归一化反而会失效。3. 在Simulink里搭一个BP自整定PID仿真从被控对象到S-Function3.1 被控对象选型与离散化为什么用二阶惯性加延迟仿真的第一步是选被控对象。我推荐用二阶惯性环节加纯延迟传递函数形如G(s) K / (T1s 1)(T2s 1) * exp(-τ*s)这类对象既不像一阶惯性那样“太乖”也不像高阶系统那样“难收敛”非常适合验证BP自整定PID的效果。参数随便设一组比如K2T10.5T20.2τ0.1秒。如果你用的是MATLAB/Simulink最省事的是在Simulink里用Transfer Fcn模块配合Transport Delay模块搭连续模型然后让S函数在每个采样周期跑一步BP更新。但要注意Simulink的变步长求解器会让S函数的执行间隔不固定而BP算法的递推式是基于固定采样周期的所以必须在S-Function里强制用“离散采样时间”比如Ts0.01秒。在S-Function的mdlInitializeSizes里设置function [sys,x0,str,ts] bp_pid_sfun(t,x,u,flag) % BP神经网络自整定PID控制器 S-Function % u(1): 给定值 r, u(2): 系统输出 y % 输出: Kp, Ki, Kd 三个参数给PID控制器 switch flag case 0 sizes simsizes; sizes.NumContStates 0; sizes.NumDiscStates 0; % 不设离散状态权值用全局变量保存 sizes.NumOutputs 3; % 输出 Kp, Ki, Kd sizes.NumInputs 2; sizes.DirFeedthrough 1; % 输入直接传给输出必须设为1 sizes.SampleTime 0.01; % 固定采样周期避免变步长干扰 sys simsizes(sizes); x0 []; str []; ts [0.01 0]; case 3 % 计算当前时刻的PID参数 global bp_w1 bp_w2 bp_x bp_y bp_yd; r u(1); y u(2); err r - y; % 此处调用bp_online_update函数见下文更新权值 [kp, ki, kd] bp_online_update(r, y, err); sys [kp ki kd]; otherwise sys []; end逻辑说明这个S-Function只做两件事——在flag0时声明采样周期和端口尺寸在flag3时计算一次输出。真正的前向计算和权值更新放在单独函数里方便调试。DirFeedthrough1是必须的因为本时刻输出直接依赖本时刻输入如果设成0Simulink会报代数环错误或者输出延迟一拍。ts[0.01 0]表示定步长离散采样第二项为偏移量0这能避免变步长ODE求解器把采样时刻打乱。整个仿真文件只需要让PID控制器用这三个参数算增量输出PID计算本身可以用离散PID模块也可以自己写一个函数。3.2 BP网络的在线更新实现前向计算与反向传播的完整代码下面是BP自整定核心的在线更新函数。这里我采用“先预测下一步误差再用梯度下降”的结构学习率按层分开配置function [kp, ki, kd] bp_online_update(r, y, err) % BP网络在线自整定PID参数 % 输入: r-给定值, y-实际输出, err-当前误差 % 输出: kp, ki, kd 当前时刻的控制参数 % 使用全局变量保存权值和历史输入模拟持续在线学习 global bp_w1 bp_w2 bp_x bp_y bp_yd bp_dpool; % 初始化参数 if isempty(bp_w1) bp_w1 randn(5, 4) * 0.1; % 隐含层权值5个神经元4个输入 bp_w2 randn(3, 5) * 0.1; % 输出层权值3个输出kp,ki,kd bp_x [r; y; err; 0]; % 4维输入向量 bp_y [0; 0; 0]; % 输出层未经变换前的值 bp_yd [0; 0; 0]; % 期望输出占位 bp_dpool [0; 0; 0]; % 历史误差差分组 end % 输入向量整理 x [r; y; err; (err - bp_dpool(1))]; bp_x x; % 前向计算 net1 bp_w1 * x; h tanh(net1); % 隐含层激活函数 net2 bp_w2 * h; out 1.0 ./ (1.0 exp(-net2)); % 输出层Sigmoid输出范围(0,1) % 映射到PID参数范围 kp 0.0 10.0 * out(1); % Kp范围[0,10] ki 0.0 2.0 * out(2); % Ki范围[0,2] kd 0.0 1.0 * out(3); % Kd范围[0,1] % ---- 反向传播更新权值 ---- % 定义性能指标对输出的梯度这里用符号近似 dy_dy 1.0; delta_out [err; err; err] .* (out .* (1 - out)); % 简化梯度 % 隐含层梯度 delta_h (bp_w2 * delta_out) .* (1 - h .^ 2); % 权值更新 eta1 0.05; % 输出层学习率 eta2 0.02; % 隐含层学习率 alpha 0.05; % 动量项 bp_w2 bp_w2 eta1 * delta_out * h alpha * (bp_w2 - bp_w2); bp_w1 bp_w1 eta2 * delta_h * x alpha * (bp_w1 - bp_w1); % 保存历史误差 bp_dpool [err; bp_dpool(1:2)]; end我在这里故意把反向传播的梯度计算简化了只保留“误差驱动”的核心。严格推导时输出层梯度应该包含∂J/∂u、∂u/∂Kp、∂Kp/∂out这些链式项但在仿真里尤其是用符号函数近似雅可比时直接用err乘以sigmoid导数已经能工作。为什么因为BP自整定不需要精确的梯度它只需要梯度方向大致正确——误差为正时增大对应增益误差为负时减小这个趋势对就够了。如果你做改进型可以把dy_dy改为被控对象的离散差分估计例如dy_dy (y - bp_yprev) / (u_prev - u_prevprev)不过这会引入噪声建议加一个低通滤波。3.3 Simulink模型搭建与联合调试信号连接和参数配置清单S函数写好之后Simulink模型的结构很直接。整体分三路信号给定值阶跃源Step模块比如从0阶跃到1、被控对象二阶惯性加延迟、BP-PID控制器S-Function加离散PID。控制器的输出是Kp、Ki、Kd它们不能直接作为PID模块的“参数”因为Simulink的PID模块在仿真过程中不允许参数被外部信号实时修改。常见做法是放弃PID模块自己写一个离散增量式PID的MATLAB Function让Kp、Ki、Kd作为它的输入信号。离散增量式PID的代码可以内嵌在MATLAB Function里function [u_increment, u_out] discrete_incremental_pid(r, y, kp, ki, kd, Ts) % 离散增量式PID输出控制量增量和累计控制量 persistent e_prev e_prev2 u_prev; if isempty(e_prev) e_prev 0; e_prev2 0; u_prev 0; end e r - y; du kp * (e - e_prev) ki * e kd * (e - 2*e_prev e_prev2); u_out u_prev du; u_increment du; % 更新历史值 e_prev2 e_prev; e_prev e; u_prev u_out; end注意这里PID模块的采样时间必须和S函数一致我设为0.01秒。被控对象的连续模型部分用变步长求解器没矛盾但S函数和PID函数都是定步长离散模块Simulink会自动把它们放在离散采样点执行。再用一个Simout模块保存y和u_out到工作区。运行15秒左右观察y能否快速跟踪阶跃且没有大幅震荡。如果想看网络是否在“学”可以把bp_w1和bp_w2在仿真结束前一并保存比较初值和终值的差异。注意如果你直接在Simulink里用时钟信号作为BP网络的输入并期望网络输出连续变化的Kp那么必须保证网络更新周期和PID计算周期同步。否则会出现“PID执行了10次网络才更新1次”的不匹配造成控制量抖动。4. 避坑指南BP自整定PID仿真的五个经典翻车现场4.1 仿真发散控制量直接飞到负数现象仿真运行到0.3秒左右u_out突然变成负几千Scope里的曲线冲出去就不回来。原因BP网络的输出没有做范围限制或者权值初始化过大导致Sigmoid输入的绝对值超过20激活函数进入饱和区。一旦饱和梯度几乎为零权值长时间不更新或者更新方向错乱。另一个常见原因是Kp初始值过大比如网络随机输出映射到Kp9被控对象被激振误差瞬间拉大进而又推动Kp增大形成正反馈。解决初始化权值范围从randn*0.1改成randn*0.01并给PID参数映射加上更窄的初始区间比如把sigmoid输出的初始偏移设为0.5再乘系数。我习惯在S函数第一次调用时强制给一组保守的PID初值Kp0.5Ki0.1Kd0.05让网络在启动阶段不参与调节等系统稳定运行100步后再切入自适应。4.2 误差已经为零网络还在“学习”现象阶跃响应进入稳态后误差e接近0按理说Kp应该收敛但实际上Kp还在缓慢变动导致输出出现小幅正弦漂移。原因反向传播公式里有误差项e但误差趋近0不代表梯度为0。因为误差变化率ec(k)e(k)-e(k-1)在噪声下并不为0而且sigmoid导数在输出接近0.5时最大网络会继续调整。更本质的问题是性能指标用的是瞬时误差平方没有加入稳态约束。解决在性能指标里加入控制量变化惩罚项0.5 * (u(k) - u(k-1))^2让网络同时抑制PID输出的剧烈变化。或者简单一点在误差绝对值小于某个阈值比如0.001时冻结学习直接跳过权值更新步骤。我在仿真里会加一个if abs(err) 0.001, return; end这条逻辑简单有效。4.3 被控对象延迟过大时BP网络学到的“规律”全是错的现象把被控对象的延迟从0.1秒改到0.8秒之后网络输出的Kp不停震荡甚至不收敛。原因BP自整定是基于当前误差调整当前参数但纯延迟系统的当前输出反映的是0.8秒前的控制作用。也就是说网络在用一个“过期的输出”和“当前的误差”做梯度计算因果关系错位。学习率再大也只是加速震荡。解决对误差做“Smith预估”式修正或者更简单地把输入从r-y改为r-yp其中yp是被控对象去掉纯延迟部分的模型输出。如果你不想引入预估器就降低学习率到原来的三分之一并把采样周期加大。注意加大采样周期会导致控制粒度变粗但往往能换来稳定。4.4 S-Function输出出现NaN权值矩阵里布满NaN现象仿真中途停止提示“Not a number detected”查工作区发现bp_w1全是NaN。原因梯度计算过程中某个sigmoid激活函数为零导致其导数项为零而下一步更新时用到了1/(1-h^2)当h接近±1时这个值趋向无穷再乘以误差就可能溢出。另一个原因是tanh函数在MATLAB里对很大的输入返回±1而权值更新时减去旧权值可能产生inf - inf NaN。解决在激活函数上加一个小量比如sigmoid输出改为1.0/(1.0exp(-net2)1e-8)tanh导数计算采用(1-h^2)时除非h恰好为1一般不会出问题但为了安全把1-h^2限制在1-1e-8以上。同时给权值更新加上限幅比如每次更新不超过0.5。这条“梯度截断”是神经网络控制仿真里最实用的保命技巧。4.5 Simulink报错“S-Function must have continuous or discrete sample time but not both”现象模型搭好后点击运行直接报S-Function采样时间错误。原因S-Function的mdlInitializeSizes里设置了ts[0.01 0]但同时又有一个连续被控对象的输出连接到S函数的输入Simulink认为S函数在连续和离散之间产生了混叠。严格说这不是S函数的错而是S函数没有处理输入信号的变化率——也就是没有声明直接馈通导致代数环冲突。解决检查S-Function代码里DirFeedthrough是否设为1。如果设为0Simulink会假设输出不依赖当前输入然后它尝试在连续时间步里插值从而报采样时间错误。另一种情况是S函数的输入端口直接来自连续模块的输出而这在变步长求解器里会导致执行顺序不确定。我通常把S函数的输入加上一个Memory模块单位延迟隔开这样输入在离散时刻被采样避免连续信号直通。5. 仿真结果怎么验证以及让自整定PID更实用的三个进阶操作仿真跑通之后别急着截图写报告。第一步先做“压力测试”把给定值改成一串方波比如每3秒跳变一次观察每次跳变后的超调量和调节时间是否一致。如果后面的响应明显比前面好说明网络确实在在线学习如果每次都一样说明权值几乎没有更新得回头查学习率是不是太小。第二步是“参数鲁棒性测试”把被控对象的增益K从2改成1.5或者把时间常数T1从0.5改成0.8继续跑仿真看BP是否能在几十步内重新收敛出一组可用的PID参数。这是自整定PID相比固定PID最有说服力的证据。进阶方向上我推荐三个容易落地又能在论文里讲清楚的做法。第一个是给BP加动量项这个前面已经提到但更细的做法是让动量系数随时间衰减从0.1线性降到0这样可以兼顾前期的快速学习与后期的稳定。第二个是采用RBF神经网络替代BP做参数辨识但输出层仍然映射到PID参数相当于用一个RBF网络预估被控对象的雅可比另一个BP网络调PID参数形成双网络结构这在处理非线性对象时优势明显。第三个是把离线训练和在线调整结合起来先用一系列典型工况下的数据离线训练BP网络得到一个合理的初始权值然后再在Simulink里小学习率在线微调。离线预训练可以让仿真一开始就很稳也能避免随机初始化导致的不可复现问题。最后说一个我自己的习惯每次仿真跑完不要只看曲线把bp_w1和bp_w2在仿真终点的值打印出来看看权值分布是否均匀。如果某些权值巨大、某些接近零说明网络存在退化可以适当增加隐含层神经元数量或者改用Leaky ReLU激活函数。如果权值变化幅度很小说明学习率低了可以按两倍步长往上试直到出现震荡再退回一半。这个“试探-回退”的调参过程虽然是老办法但在神经网络控制里依然比盲调高效很多。希望帮到你。本文还有配套的精品资源点击获取
返回列表