首页 / 资讯中心 / 文章详情

BP神经网络PID自整定:告别手调Kp、Ki、Kd的工程指南

BP神经网络PID自整定:告别手调Kp、Ki、Kd的工程指南 ★ FEATURED ARTICLE
简介面向自动控制学习者和工程师的 PID 参数自整定资源用反向传播神经网络优化经典 PID 控制器的比例、积分、微分参数解决传统人工整定耗时且难以适应复杂工况的问题适用于实验室调试与实际项目验证。压缩包含两个文件MATLAB 脚本实现网络构建、训练与参数输出Simulink 模型搭建仿真环境验证优化后参数的系统响应包体仅 17KB轻量便于快速演练。目前已有 871 人学习适合具备一定控制理论并想用工具实践智能整定的读者。通过该资源可走通从准备误差数据、搭建输入隐藏输出层网络、反向传播更新权重到获取最优 Kp、Ki、Kd 参数并在 Simulink 中对比控制效果的完整流程还能学习数据准备、网络训练、系统应用与反馈调整等环节便于迁移到其他对象的自整定场景。1. BP神经网络优化PID参数不调程序也能把Kp、Ki、Kd定下来做电机调速、温控、或者倒立摆的时候最烦的不是写PID算法本身而是那三个参数Kp、Ki、Kd怎么调。手调费时间经验不足的人调半天可能还把系统调震荡了查表或者Ziegler-Nichols整定在某些非线性、时变工况下又经常失灵。BP神经网络做的这件事看起来玄学本质上是把「调参」这件事从人工经验变成了参数寻优——用一个三层的BP网络在线学习被控对象输入输出之间的映射关系然后按梯度下降方向实时修正PID控制器的三个系数。这个方案真正能落地的场景是系统模型不确定、工况会漂移、或者负载变化频繁的闭环控制回路。这篇笔记按我实际做过的方案来讲从网络结构怎么定、训练数据哪来、Simulink和MATLAB脚本怎么配合到PID的增量式输出怎么和神经网络接口对上最后把我在整定过程里踩过的几个坑一并列出来。适合手里已经能跑通普通PID环、但想把控制品质再往上提一截的工程师也适合做毕设要快速出结果的学生。2. 网络结构定多大六输入四输出的三层BP才是积木级配置BP神经网络的层数和节点数直接决定它能不能在嵌入式环境里跑得动以及训练收敛稳不稳。针对PID参数自整定这个用途网络一般取三层输入层负责感知系统状态输出层负责修正PID系数。先说最经典的配置这也是我在电机转速环上用过很多遍的结构输入六个节点分别是被控量的当前值和目标值或给定值、误差值、上一步误差、上上步误差、以及当前的系统输出输出三个节点对应Kp、Ki、Kd的修正值。隐含层节点数取6到10之间具体要看对象复杂度我常用的是8个。为什么不用更深或者更宽的配置因为PID参数整定问题本身是一个小规模映射拟合三层网络在理论上已经能逼近任意连续函数节点再多在线训练时矩阵维数变大、梯度更新变慢反而让单片机的定时中断扛不住。而少于三个输入又不够——如果你只把误差塞进去网络学到的映射没有「趋势」信息Kp和Kd的区别根本分不出来。下面是Simulink里搭S-Function时用到的一个结构生成片段用来初始化权重矩阵和网络参数% 初始化BP网络结构 —— 用于simulink中的s-function或matlab function块 % 结构输入节点6, 隐含节点8, 输出节点3 u_num 6; % 输入节点数: 当前值、期望值、误差、误差历史 h_num 8; % 隐含层节点数按经验取8对象复杂可加到10 y_num 3; % 输出节点数: Kp修正、Ki修正、Kd修正 w1 rands(h_num, u_num); % 输入层到隐含层权值 [-1,1]随机初始化 w2 rands(y_num, h_num1); % 隐含层到输出层权值1是阈值节点 x zeros(u_num, 1); % 输入向量 h zeros(h_num1, 1); % 隐含层输出向量含阈值 y zeros(y_num, 1); % 网络输出向量 learn_rate 0.05; % 在线学习率别取太大后面讲为什么 momentum 0.1; % 动量项抑制震荡这里随机初始化用rands权重范围在[-1,1]之间。注意隐含层多了一个阈值的输入维度w2的列数是h_num1而不是h_num实际写程序时这个维度最容易错。另外在线自整定场景里学习率不是越大越好PID参数在系统开跑之后是边跑边调的学习率大了Kp可能会被一步推出很远表现出来就是电机转速忽然抽风。2.1 输入信号怎么取误差链和历史误差才是BP能学到东西的关键网络输入的六个节点很多人第一个版本只放误差e(k)结果训练半天Kp、Ki、Kd输出全是常数——网络根本没有区分比例、积分、微分作用的依据。我一般取六个输入为r(k)期望值、y(k)当前输出、e(k)r(k)-y(k)、e(k-1)、e(k-2)以及u(k-1)上一拍的控制量。注意最后这个u(k)反馈进去非常重要它让网络能感知到上一拍PID已经输出了多少力度否则网络无法学习系统的迟滞特性。% 构造BP网络输入向量在每个采样周期内执行 e rk - yout; % rk为期望yout为当前系统输出 x(1) rk; % 期望值 x(2) yout; % 当前输出 x(3) e; % 当前误差 x(4) e_1; % 上一拍误差 x(5) e_2; % 上上拍误差 x(6) u_1; % 上一拍PID输出误差的三步历史数据让网络能够学习误差变化率从而区分出微分项和积分项的贡献。u_1的反馈则是把控制器自身的历史输出也纳入考量对于带惯性或延迟的被控对象来说这个输入能显著减少超调。采样周期的选择上六路输入必须在同一个时刻采集不能用上一次中断里的旧值混进来。如果发现网络输出乱跳先检查是不是跨采样周期混用了数据。2.2 网络输出怎么变成PID增量直出绝对值还是输出修正量BP网络输出层的激活函数用的是非负的Sigmoid或Sigmoid变形这样输出值域落在[0,1]区间再乘以系数映射到实际的Kp、Ki、Kd范围内。关键问题是网络每次输出的是PID参数绝对值还是增量修正值。两种做法都有但我更推荐修正值方案——网络每次输出一个小的增量ΔKp、ΔKi、ΔKd叠加到当前PID参数上这样即使网络某次输出抖动也不会让PID参数突变。% 网络输出到PID参数的映射输出修正量方式 Kp Kp_0 gain_kp * out(1); % gain_kp是Kp缩放系数如20 Ki Ki_0 gain_ki * out(2); % gain_ki如5 Kd Kd_0 gain_kd * out(3); % gain_kd如1 % 使用位置式PID用于输出控制量u u Kp*e Ki*sum_e Kd*(e - e_1); % 使用增量式PID配合PWM输出场景 du Kp*(e - e_1) Ki*e Kd*(e - 2*e_1 e_2); u u_1 du;增量式PID计算出来的du是控制量的增量叠加到上一拍的控制量上。这个格式特别适合输出给PWM寄存器——占空比的增减量天然就是小数值变化不需要额外限幅处理。位置式的sum_e会有积分累积问题一旦系统长时间处于饱和状态积分项会造成大幅超调用神经网络做自整定时位置式会更容易把参数带偏。3. 离线训练与在线自整定先学会通用工况再实时微调BP网络优化PID参数有两条路线。离线学习是把系统在不同工况下的输入输出数据采下来标定好对应的最优PID参数训练网络学会这个映射关系在线自整定则是让网络在系统运行过程中实时更新权值直接面对当前工况做参数寻优。我最终落地的是「离线预训练 在线微调」两步走先用历史数据把网络训练到一个合理的初始状态再让它在运行中继续学习。3.1 离线训练数据怎么采集阶跃响应测试是最省事的起点没有历史数据的情况下先给系统做几组不同幅度的阶跃输入记录下不同给定值下系统响应曲线。用Ziegler-Nichols方法在每组数据上做一次开环整定得到该工况下的经验最优PID参数把「系统输入输出历史」和「对应PID参数」组成训练样本对。训练时输入节点仍然取六路输出节点的监督信号就是这批整定好的Kp、Ki、Kd。采集数据时要注意覆盖足够宽的幅值范围如果只采了低转速段的响应数据网络线性区间外推能力很差高转速段几乎一定偏。% 离线训练脚本核心逻辑 load train_data.mat % 假设包含input_seq(N*6), pid_target(N*3) N size(input_seq, 1); for step 1 : 200 % 200个迭代轮次 for i 1 : N % 前向计算 net_in input_seq(i, :); h_in w1 * net_in; h_out 1 ./ (1 exp(-h_in)); h_out [1; h_out]; % 补阈值节点 y_out w2 * h_out; y_out 1 ./ (1 exp(-y_out)); % 输出层Sigmoid到(0,1) % 反向传播误差 err pid_target(i, :) - y_out; delta2 err .* (y_out .* (1 - y_out)); delta1 (w2 * delta2) .* (h_out .* (1 - h_out)); delta1 delta1(2:end); % 去掉阈值节点的误差项 % 梯度更新 w2 w2 learn_rate * delta2 * h_out momentum * (w2 - w2_old); w1 w1 learn_rate * delta1 * net_in momentum * (w1 - w1_old); w1_old w1; w2_old w2; end end这个脚本的核心逻辑是标准的BP反向传播梯度项里最后加的momentum动量项是为了让更新方向更平滑。200轮迭代是我常用的起始次数实际要看误差曲线收敛情况如果最后一轮loss还在明显下降就继续加跑如果前面50轮就不再下降则及时停。Sigmoid的导数项y_out .* (1 - y_out)在这个场景下是安全的因为前面已经约束了网络输出在(0,1)区间内。3.2 在线自整定的计算时耗在定时中断里能不能跑得完在线自整定容易让人担心算力问题每个采样周期都要做一次BP前向计算加一次权值更新普通MCU跑不跑得动我实测的数据是6-8-3这个结构一次前向加反向的浮点运算量大约是几千次乘法累加在STM32F4系列主频168MHz上大约耗时100到200微秒放在1kHz的电流环或者100Hz的速度环里完全可行。采样周期10ms的控制回路用在线自整定毫无压力但如果采样频率到10kHz级别就只能做前向计算而把权值更新降频到每几十拍做一次。在线更新权值的学习率要比离线训练小一个量级取0.01到0.02比较稳。离线训练时0.05能快速收敛但到在线场景里系统本身还在运行权值突变会直接反映到PID参数变化上表现为控制量抖动。如果发现在线运行时Kp、Ki、Kd输出呈现锯齿状跳动第一个要查的就是学习率。4. Simulink里把BP-PID闭环搭起来从S-Function到封装成模块频繁在MATLAB脚本和Simulink模型之间来回拖数据又慢又容易乱。我的做法是在控制回路里直接用S-Function做BP网络的核心计算保留系统的非线性模型在Simulink里把控制器部分替换成BP-PID整体封装这样一个模型就能完整仿真。下面给出可用的完整S-Function骨架里面已经适配了主流的Simulink模板接口。% bp_pid_sfun.m —— Simulink S-Function: BP-PID控制器 function [sys, x0, str, ts] bp_pid_sfun(t, x, u, flag, ... Kp0, Ki0, Kd0, learn_rate, node_num) switch flag case 0 [sys, x0, str, ts] mdlInitializeSizes(t, x, u, ... Kp0, Ki0, Kd0, learn_rate, node_num); case 3 sys mdlOutputs(t, x, u, Kp0, Ki0, Kd0, learn_rate, node_num); case {1,2,4,9} sys []; otherwise error([Unhandled flag , num2str(flag)]); end function [sys, x0, str, ts] mdlInitializeSizes(t, x, u, Kp0, Ki0, Kd0, learn_rate, node_num) sizes simsizes; sizes.NumContStates 0; sizes.NumDiscStates 0; sizes.NumOutputs 1; % 输出控制量u sizes.NumInputs 2; % 输入 [期望; 当前输出] sizes.DirFeedthrough 1; % 控制器直接使用输入必须设为1 sizes.NumSampleTimes 1; sys simsizes(sizes); x0 []; str []; ts [0.001 0]; % 采样周期单位秒按实际控制周期调整 end function sys mdlOutputs(t, x, u, Kp0, Ki0, Kd0, learn_rate, node_num) persistent w1 w2 e_1 e_2 u_1 sum_e Kp Ki Kd if isempty(w1) [w1, w2] init_bp_net(node_num); % 初始化权值 e_1 0; e_2 0; u_1 0; sum_e 0; Kp Kp0; Ki Ki0; Kd Kd0; end rk u(1); yout u(2); e rk - yout; sum_e sum_e e; % 积分项累加注意防饱和 % 构造BP网络输入(六个节点) x_in [rk; yout; e; e_1; e_2; u_1]; % 前向计算得到输出修正量 out bp_forward(x_in, w1, w2); % 叠加修正到PID系数(映射比例按对象量程调整) Kp Kp0 20*out(1); Ki Ki0 5*out(2); Kd Kd0 1*out(3); % 位置式PID u_out Kp*e Ki*sum_e Kd*(e - e_1); % 更新记忆变量 e_2 e_1; e_1 e; u_1 u_out; sys u_out; endS-Function在Simulink里的工作方式是按ts定义的采样周期反复调用mdlOutputs。u是输入向量这里取期望值和当前输出两个输入其余的历史数据全存在persistent变量里。注意DirFeedthrough设为了1意思是输出直接依赖输入——只要你在输出表达式里用了u就必须设1否则仿真会报代数环错误。仿真模型里用阶跃信号当给定被控对象传函先放一个简单的二阶系统比如10/(s^22s5)用来验证BP-PID在标准二阶对象上的表现。跑完之后换成你实际项目里的对象传函再做细节微调。4.1 用MATLAB脚本实现前向计算把网络计算独立成函数便于移植和调试在线自整定场景里调试S-Function不太方便把前向计算独立成一个单独函数会好处理得多——既可以在Simulink里调用也可以单独用输入向量测试看输出是否合理。bp_forward函数的外部接口保持简单传入六维输入向量和两个权值矩阵返回三维输出。这个函数后续做C代码移植时结构也可以保持不变只是把矩阵乘法手写成嵌套循环罢了。function out_vec bp_forward(x_in, w1, w2) % BP网络前向传播 % x_in: 6x1输入向量 % w1: h_num x 6 输入层权值 % w2: 3 x (h_num1) 输出层权值 h_num size(w1, 1); h_in w1 * x_in; % 隐含层加权输入 h_out 1 ./ (1 exp(-h_in)); % 隐含层Sigmoid h_out [1; h_out]; % 加阈值节点 y_in w2 * h_out; % 输出层加权输入 out_vec 1 ./ (1 exp(-y_out));% 输出层Sigmoid到(0,1) end这个函数里唯一需要注意的细节是Sigmoid用exp实现在MATLAB里没问题但一旦移植到嵌入式平台要改成查表法或快速近似函数因为exp在MCU上的计算开销大约是几十微秒级别虽然不致命但在高频率中断里会挤占时间。我一般会在C代码里用-3到3区间上的查表加线性插值替代精确exp计算精度损失在控制回路里可以忽略。4.2 Simulink里的示波器验证技巧先看误差曲线再看参数输出曲线模型搭好后第一件事不是看Kp曲线标不标准而是看系统输出对阶跃给定的跟踪情况。超调量小于10%、调节时间比手调参数时短、稳态无静差这三个指标同时满足才说明网络学到了东西。然后打开Kp、Ki、Kd的Scope输出曲线看它们是否在系统稳定后趋于定值——如果参数还在持续波动说明学习率偏大或者网络输入里存在高频振荡分量。增益曲线正常的情况下Kp会先变大把系统拉向目标稳态时下降到某个稳定值Ki会在误差持续存在时缓慢上升误差归零后停止增长Kd在启动瞬间会有一个较大峰值随后迅速回落到接近零。如果在Scope里看到Kd长时间悬在一个较大的值上多半是误差的噪声被微分项放大了这种情况要去查采样数据的滤波。5. BP-PID自整定避坑我在参数调崩与数据翻车现场踩过的六个坑坑1网络输出先收敛到边界导致Kp被限定死现象BP网络输出层Sigmoid的值一开始就在0.95以上或0.05以下修正量一直贴在上限或下限PID参数完全没有调节空间。原因初始化权值太大Sigmoid饱和区的梯度接近零反向传播的修正量几乎为零网络卡死在饱和状态。解决把w1和w2的随机初始化幅值改小用0.1到0.5的均匀分布而不是全区间[-1,1]。另外检查六路输入的量级是否都落在合理区间内如果误差e的量纲很大比如转速误差达到几百转输入进Sigmoid之前需要先做归一化否则网络第一层就被打进了饱和区。坑2Simulink仿真步长和S-Function采样周期不一致造成参数输出跳变现象系统输出看起来正常但Kp曲线在相邻两个仿真节拍之间出现脉冲式跳变严重影响后续对网络学习效果的判断。原因S-Function里ts设置的是0.001秒但Simulink用变步长求解器ode45在某些快变区间会强行把步长缩短到微秒量级反复调用mdlOutputs导致e_1和e_2的更新节奏错乱。解决把求解器设置为固定步长步长取和S-Function采样周期一致或更小。我的固定步长用0.0001秒配合0.001秒的控制器采样周期这样每个控制周期内S-Function恰好被调用一次历史误差链保持完整。坑3在线训练时PID参数在稳态附近持续震荡现象转速已经稳定在目标值附近但Kp、Ki、Kd半小时后还在小幅波动系统的调节阀或PWM占空比随之抖动。原因学习率对在线场景来说还是偏大。系统已经进入稳态输入向量x的微小扰动比如传感器噪声经过BP网络前向计算后仍然会生成一个非零的修正量每一拍都叠加到PID系数上。解决学习率从0.05降到0.01。另外可以在输入向量上做惯性滤波比如e(k)采进来之后先做一阶滤波再进网络滤波系数取0.3到0.5之间能明显压低高频噪声对网络输出的干扰。坑4离线训练loss降到很低但在线跑起来超调巨大现象离线训练时把训练集拟合得很漂亮loss曲线贴到零附近但把训练好的权值装到Simulink里跑闭环系统直接大幅超调甚至发散。原因训练数据的分布和测试工况不一致。离线训练数据只覆盖了低速段或小幅阶跃的情况在线闭环里给了一个大幅阶跃目标值网络碰到的是外推区域它输出的Kp值在训练集里从未出现过自然不合理。解决离线训练数据里必须包含大幅阶跃、小幅阶跃、正反向多组数据覆盖系统的完整工作范围。我后来给训练集里混入了几组斜坡输入和随机幅值阶跃在线效果明显改变。坑5增量式PID的u_1初值不匹配导致第一个控制周期跳变现象仿真刚启动的第一拍控制量u输出一个大得反常的值然后下一拍恢复正常。原因persistent变量u_1初始化为0但位置式PID第一个周期的输出可能就是一个较大的正值增量式输出在启动瞬间等于u_1 du如果u_1按0而实际系统控制量初值不为0第一拍就会跳变。解决u_1的初始化值应取系统的实际零位控制量。如果是电机PWM可以设一个死区占空比的初值如果是阀门那就取开度初值。或者在Simulink里让S-Function延时一个采样周期再投入使用。坑6权值矩阵维度不匹配运行时直接报矩阵乘法错误现象仿真一运行就报Matrix dimensions must agree错误定位在bp_forward函数。原因w2的列数定义的是h_num1但代码里写矩阵乘法时用的h_out向量没有补阈值节点维度差了一列。解决矩阵维度问题建议用size命令打印验证而不是肉眼检查每次修改网络结构后都把维度检查写在初始化函数里跑初始化脚本就直接断言。我给函数入口加了一段调试输出assert(size(w1, 2) length(x_in), w1列数必须等于输入维数); assert(size(w2, 2) size(h_out, 1), w2列数必须等于隐含层输出维数);6. 从仿真到半实物验证BP-PID是否值得投入的四个检查点做BP-PID项目到一定程度真正的考验不是网络结构和训练算法而是仿真里跑出来的效果能不能在实物上复现。我建议按以下顺序做验证每一步不过关就不要往系统里硬塞。第一步把训练好的权值矩阵w1和w2导出为文本文件或C头文件注意这里的关键是确认权值精度——MATLAB默认double类型导出后在8位单片机上要转成float建议先检查一下权值的数值范围再做float转换和无损评估。# 导出权值矩阵为文本文件便于核对或转C数组 save(bp_weights.txt, w1, w2, -ascii)第二步是拿一组固定的离线测试输入做前向计算的输出对照分别用MATLAB端计算和C端计算跑一遍两者的输出差异应在1e-6以内。这一步的作用是排除C代码里矩阵索引错位或Sigmoid近似带来的数值偏差。第三步是关于采样周期的验证。我的判断标准是在最差工况下同时启用BP网络的完整学习不是只用前向、不做权值更新CPU占用率在控制中断之内需保持在70%以下。超过这个水位就要考虑把网络搬离中断上下文放到主循环里做周期性更新并做好临界变量保护。第四步针对半实物的参数修正策略我一般会给Kp、Ki、Kd输出层各设一个限幅范围。网络输出的修正量在在线运行中必须被钳位在这个范围内这样即使传感器异常或外部干扰把输入向量推向异常区域系统也不至于因为参数突变而翻车。整个BP-PID方案值不值得做我的判断标准很简单如果你的系统工况单一、负载不变化手调的PID已经完全够用那就不要去上神经网络纯粹增加系统复杂度但如果你被时变对象的调参折磨过比如负载变化、温度漂移导致模型特性改变那么BP-PID的在线自整定能力确实能省下大量人工调参的运维成本。我最后一次做这个方案是在一个伺服位置环上离线训练加在线微调两星期后续半年现场没有因为参数而让人去碰过控制器这就是这个方向最实际的回报。希望这些经验和踩坑记录能帮你少走几段弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站