ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

BP神经网络PID参数自整定:从原理推导到代码实现

BP神经网络PID参数自整定:从原理推导到代码实现 简介资源提供基于BP神经网络整定PID控制器参数的MATLAB实现面向自动化、智能控制方向的学习者与工程师用于解决传统PID参数依赖经验整定、难以适应非线性对象的问题。代码采用4-5-3网络结构通过梯度下降法动态修正权值输出Kp、Ki、Kd三个控制参数兼具算法演示与工程参考价值。压缩包共1个文件为MATLAB脚本.m体积仅1KB内容紧凑、便于快速阅读和二次修改。已有230人学习浏览适合正在研究神经网络控制、参数自整定或梯度下降应用的读者下载参考。通过这份资源可以直观理解BP网络如何结合PID进行在线优化掌握从误差采集、梯度计算到权值更新的完整实现思路为后续扩展其他智能控制算法打下基础。1. BP 神经网络与 PID 参数自整定为什么要绑在一起PID 参数整定这件事绝大多数工程师都经历过“先按经验给一组数再凑合着试”的过程。对象一变Kp、Ki、Kd 就要重新调遇到大延迟、强耦合或者负载频繁波动的对象人工整定很容易在超调与响应速度之间反复横跳。BP 神经网络恰好擅长做非线性映射把“系统误差、误差变化、误差累积”映射成“Kp、Ki、Kd”再用反向传播和梯度下降在线修正网络权值等于把整定经验塞进了一个可训练的控制器里。这个思路从刘金琨《智能控制》里的经典仿真开始一直被沿用到现在。本文就围绕这个标题讲清楚 BPNNPID 的结构、梯度下降权值修正的来龙去脉以及一份能跑能改的核心代码。2. 先定结构BP 神经网络的输入输出与增量式 PID 的绑定很多初学者拿到 BPNNPID 第一反应是问“神经网络该有几层”。实际上网络本身不复杂复杂的是怎么把网络的输出和 PID 控制律严丝合缝地接起来。这一章先把结构定下来后续所有代码都是在这个骨架上长出来的。2.1 增量式 PID 比位置式更适合 BP 网络输出在决定网络输出之前先想清楚 PID 部分用哪种形式。位置式 PID 输出的是控制量绝对值u(k) Kp * e(k) Ki * sum(e) Kd * (e(k) - e(k-1))位置式的麻烦在于误差累积项 sum(e) 容易积分饱和而且 u(k) 直接作为控制信号一旦网络输出抖动执行机构会跟着剧烈摆动。增量式 PID 只输出控制量增量Δu(k) Kp * (e(k) - e(k-1)) Ki * e(k) Kd * (e(k) - 2*e(k-1) e(k-2))实际控制量 u(k) u(k-1) Δu(k)。BP 网络输出 Kp、Ki、Kd 三个参数控制律用增量式这样即使网络参数短时间内波动对执行机构的冲击也是缓慢累积的不会直接砸出一个大步进。这也是 BPNNPID 系列仿真代码里最常见的组合。2.2 三层网络结构与激活函数选型网络结构取输入层 3 节点、隐含层 5 节点、输出层 3 节点已经够用。输入向量选择三种误差信号输入节点含义备注x1e(k)当前误差x2e(k) - e(k-1)误差变化量x3e(k) - 2*e(k-1) e(k-2)误差二阶差分供 Kd 项使用隐含层激活函数用 tanh 的变形f(x) (1 - exp(-x)) / (1 exp(-x))这个函数输出范围 (-1, 1)对称性比 logistic 好误差反传时不容易出现梯度全为正或全为负的情况。输出层激活函数不能用 tanh因为 PID 参数不能为负常见的处理是用 sigmoid 变形g(x) exp(x) / (exp(x) exp(-x))输出范围 (0, 1)再乘以一个比例系数映射到 Kp、Ki、Kd 的实际取值范围。比如 Kp out1 * 10Ki out2 * 5Kd out3 * 1具体系数按被控对象的量纲决定后面第 5 章会详细讲。2.3 归一化与初值设置输入信号不归一化网络很难训练。e(k) 的数值如果动辄几十上百隐含层的净输入 net w * x 也会很大tanh 直接进入饱和区梯度消失。常见做法是把误差缩放到 [-1, 1] 区间用最大期望误差做分母即可。例如系统目标值是 1.0稳态误差预期不超过 1.0那就直接让 x e / max_abs_e。权值初值一般取 [-0.5, 0.5] 或 [-1, 1] 均匀分布的随机数。注意阈值偏置的处理很多实现把阈值当成一个额外的输入节点权重来更新隐含层到输出层的权值矩阵维度写成 (输出节点数, 隐含层节点数 1)最后一位对应阈值输入固定为 1这样代码更简洁。3. 梯度下降与权值修正反向传播的数学落地BP 神经网络的核心词是反向传播而反向传播本质上就是在算梯度然后用梯度下降更新权值。这一章把从性能指标到权值修正公式的每一步拆开读者自己推导一遍后再看第 4 章代码就不会懵。3.1 性能指标定义与链式求导控制目标不是让网络输出逼近某个标签而是让系统误差最小化所以性能指标定义为E 0.5 * e(k)^2其中 e(k) r(k) - y(k)r 是目标值y 是被控对象输出。这里没有标签数据属于在线自监督场景反向传播的目标变成了对 E 求梯度而不是常见的对 MSE 损失求梯度。输出层第 j 个节点的误差信号 delta 定义为delta_j ∂E / ∂net_j (∂E / ∂y) * (∂y / ∂u) * (∂u / ∂O_j) * f(net_j)逐项拆开分析。∂E / ∂y -e(k)。∂y / ∂u 是被控对象的雅可比信息实际工程中对象模型未知经典代码里的处理是用符号函数近似sign(∂y / ∂u) sign((y(k) - y(k-1)) / (u(k) - u(k-1) eps))这个近似是 BPNNPID 能在模型未知情况下运行的关键后面第 5 章会讨论它的失效边界。∂u / ∂O 这一项取决于 PID 输出形式和网络输出的含义如果 u 是增量式O1 对应 Kp那么∂u / ∂Kp e(k) - e(k-1)∂u / ∂Ki e(k)∂u / ∂Kd e(k) - 2*e(k-1) e(k-2)这三项分别就是增量式 PID 各项的误差组合代码里直接算出来存进一个向量就行。3.2 输出层与隐含层的权值修正公式输出层的梯度已经拿到权值修正遵循最朴素的梯度下降加动量项Δwo_ij(k) xite * delta_j * h_i afa * (wo_ij(k-1) - wo_ij(k-2))wo_ij(k) wo_ij(k-1) Δwo_ij(k)其中 xite 是学习率afa 是动量因子。动量项的作用是让权值更新方向保持连贯避免在狭长的误差曲面上来回震荡。注意这里用的是上一轮权值增量而不是常见的 momentum 累积变量写法两者效果相近但要注意保存的是哪一份历史值。输出层激活函数的导数也要算对。g(x) exp(x) / (exp(x) exp(-x))它的导数是g(x) 2 * g(x) * (1 - g(x))如果代码里写成 g(x) * (1 - g(x))相当于学习率被缩了一半训练会偏慢。隐含层的误差传播是反向传播的关键第 j 个输出节点的 delta 通过连接权回传给隐含层节点 idelta_h_i (1 - h_i^2) * Σ_j (delta_j * wo_ij)这里的 (1 - h_i^2) 是 tanh 的导数。隐含层权值更新和输出层完全同构Δwi_hi(k) xite * delta_h_i * x_i afa * (wi_hi(k-1) - wi_hi(k-2))3.3 反向传播能解决梯度下降局部最小值的问题吗网上关于“反向传播能否解决梯度下降局部最小值”的讨论很多答案是不能。反向传播只是用链式法则高效计算梯度它解决的是梯度计算困难问题不是最优化困难问题。BP 网络权值空间是非凸的局部极小值确实存在。BPNNPID 这个场景里缓解局部极值的常用手段有三条一是在线学习时每个时刻的误差面都在变化目标函数本身是动态的不容易困死在某个固定极小值点二是加动量项三是先用随机初值跑若干次选误差指标最好的一次作为最终参数。这三条在工程里都比追求一个数学上的全局最优解更实际。4. 实现 BPNNPID 核心代码从零写出可运行的网络理论推导结束后直接进入实现。以下代码是 BPNNPID 仿真的最小可运行版本控制对象用一个带延迟的一阶惯性系统代替真实硬件方便读者观察完整的学习过程。4.1 前向计算得出 Kp、Ki、Kd以下是核心代码的 MATLAB 版本代码结构遵循经典的 BPNNPID 实现思路读者可以直接复制进脚本运行也可以对照着改成 Python 版本。% BPNNPID 核心仿真三层 BP 神经网络在线整定增量式 PID % 网络结构输入层 3 节点隐含层 5 节点输出层 3 节点 clear; clc; ts 0.001; % 采样周期 1ms xite 0.25; % 学习率 afa 0.05; % 动量因子 IN 3; H 5; OUT 3; % 各层节点数 % 权值初始化输入层到隐含层 5x3隐含层到输出层 4x3最后一列是阈值 wi rand(H, IN) .* 2 - 1; wo rand(OUT, H 1) .* 2 - 1; wi_1 wi; wi_2 wi; % 保存历史权值用于动量项 wo_1 wo; wo_2 wo; % 状态变量初始化 y zeros(1, 10000); u zeros(1, 10000); e zeros(1, 10000); % 误差 ei zeros(1, 10000); % 误差累积备用 u_1 0; y_1 0; e_1 0; e_2 0; for k 1:10000 r 1.0; % 目标值阶跃 % 被控对象一阶惯性 纯延迟离散化模型 if k 50 % 模拟对象延迟 2 个采样周期 y(k) 0.92 * y_1 0.08 * u(max(k - 2, 1)); else y(k) 0; end e(k) r - y(k); % 构造网络输入当前误差、误差一阶差分、误差二阶差分 xi [e(k); e(k) - e_1; e(k) - 2 * e_1 e_2]; % 前向计算隐含层 net1 wi * xi; h (1 - exp(-net1)) ./ (1 exp(-net1)); % tanh 激活 h(5, 1) 1.0; % 阈值输入并入隐含层输出 % 前向计算输出层 net2 wo * h; yo exp(net2) ./ (exp(net2) exp(-net2)); % sigmoid 变形输出 (0,1) Kp yo(1) * 10; Ki yo(2) * 5; Kd yo(3) * 1; % 增量式 PIDu(k) u(k-1) Kp*de Ki*e Kd*dde du Kp * (e(k) - e_1) Ki * e(k) Kd * (e(k) - 2 * e_1 e_2); u(k) u_1 du; % 反向传播dyu 用符号函数近似雅可比 dyu sign((y(k) - y_1) / (u(k) - u_1 1e-8)); % 输出层 delta性能指标 E 0.5 * e(k)^2 delta_o e(k) * dyu * du_eq(e(k), e_1, e_2, yo, net2); % 注意上面的 du_eq 需要展开见下方替代写法 % 实际常用写法按 PID 各项偏导数分别计算 du_dKp e(k) - e_1; du_dKi e(k); du_dKd e(k) - 2 * e_1 e_2; du_dO [du_dKp; du_dKi; du_dKd]; % delta_o 先不乘激活导数 delta_o e(k) .* dyu .* du_dO .* (2 .* yo .* (1 - yo)); % 输出层权值修正带动量项 wo wo_1 xite * delta_o * h afa * (wo_1 - wo_2); % 隐含层 delta 反向传播 delta_h (wo(:, 1:H) * delta_o) .* (1 - h(1:H).^2); wi wi_1 xite * delta_h * xi afa * (wi_1 - wi_2); % 状态更新 wi_2 wi_1; wi_1 wi; wo_2 wo_1; wo_1 wo; u_1 u(k); y_1 y(k); e_2 e_1; e_1 e(k); end代码里du_eq那一行是占位示意实际运行时用下面紧跟着的三行du_dKp、du_dKi、du_dKd替代。逻辑说明前向计算分两步隐含层先用 tanh 压缩输入特征再把阈值以固定值 1.0 拼进隐含层输出向量 h输出层用 sigmoid 变形把净输入压到 (0,1) 区间乘系数后得到 Kp、Ki、Kd。反向传播时dyu是对被控对象雅可比的符号近似du_dO是增量式 PID 的误差组合项在 MATLAB 中.*是按元素乘法等效于把三个偏导数分别乘到对应输出节点上。权值修正统一采用 “当前梯度 动量项” 的形式wi_1与wi_2保存的是上一轮和上上轮的权值动量项取两者之差。4.2 反向传播的两个易错点第一个易错点是输出层激活函数的导数系数。前面推导过 g(x) 2g(x)(1-g(x))有人图省事写成 g(x)*(1-g(x))学习率实际被减半收敛变慢但系统不一定崩所以这个问题容易被忽略。第二个易错点是 h 向量已经包含了阈值输入 1.0回传隐含层误差时要把这一位去掉否则wo(:, 1:H)与h(1:H)的维度对不上。这两个点几乎能解释 90% 的 BPNNPID 代码跑飞现象。4.3 Python 版本的单步更新等价实现MATLAB 适合仿真但很多读者更习惯 Python。下面给一个 numpy 版本的单步更新函数逻辑与 MATLAB 代码等价方便做集成测试import numpy as np class BPNNPID: def __init__(self, xite0.25, afa0.05, h_nodes5): self.xite, self.afa xite, afa self.wi np.random.uniform(-1, 1, (h_nodes, 3)) self.wo np.random.uniform(-1, 1, (3, h_nodes 1)) self.wi_1, self.wi_2 self.wi.copy(), self.wi.copy() self.wo_1, self.wo_2 self.wo.copy(), self.wo.copy() self.e_1 self.e_2 0.0 def forward(self, x): net1 self.wi x h np.tanh(net1) h np.append(h, 1.0) # 阈值固定输入 net2 self.wo h yo 1.0 / (1.0 np.exp(-2.0 * net2)) # 等价于 sigmoid 变形 return h, yo def update(self, x, e, dyu, du_dO): h, yo self.forward(x) delta_o e * dyu * du_dO * (2.0 * yo * (1.0 - yo)) self.wo self.wo_1 self.xite * np.outer(delta_o, h) \ self.afa * (self.wo_1 - self.wo_2) delta_h (self.wo[:, :h.shape[0] - 1].T delta_o) * (1.0 - h[:-1] ** 2) self.wi self.wi_1 self.xite * np.outer(delta_h, x) \ self.afa * (self.wi_1 - self.wi_2) self.wi_2, self.wi_1 self.wi_1, self.wi.copy() self.wo_2, self.wo_1 self.wo_1, self.wo.copy()参数说明du_dO是增量式 PID 三个偏导数组成的数组形状为 (3,)含义与 MATLAB 版一致dyu是符号近似的雅可比e是当前时刻误差。Python 版用np.outer构造外积矩阵等价于 MATLAB 里的delta * h。注意forward里用1.0 / (1.0 np.exp(-2 * net2))代替exp(net) / (exp(net) exp(-net))数学上完全等价但数值稳定性更好在 net2 绝对值较大时不会出现 exp 溢出。5. BPNNPID 参数怎么设学习率、动量因子与采样周期的联动网络结构定了代码能跑了剩下的问题全在参数设置上。BPNNPID 比标准 BP 网络多了一层复杂性因为它的参数不仅影响网络收敛还直接影响控制系统的稳定性。5.1 学习率与动量因子的经验区间xite 在经典实现里通常是 0.2 到 0.5afa 取 0.03 到 0.1。这个区间与输入归一化、隐含层节点数和 PID 输出系数是配套的。如果 Kp 的映射系数从 10 改成 100误差信号会被放大等效梯度也变大xite 需要相应缩小到 0.02 量级否则第一个时刻的权值更新就会把网络打出正常工作区。现象常见诱因调整方向误差曲线高频震荡学习率过大或 Kp 映射系数过大xite 减半或调小 Kp 系数收敛极慢几百步误差才缓慢下降学习率过小xite 翻倍观察是否震荡权值更新方向来回跳动量因子过小afa 上调到 0.1 左右训练后期误差平台无法突破动量因子过大afa 下调尝试重新初始化5.2 归一化、采样周期与系统延迟的影响采样周期 ts 是 BPNNPID 最容易忽略的参数。被控对象是连续系统时ts 决定了离散化模型的准确性。经典仿真里 ts 通常取对象惯性时间常数的 1/10 到 1/20例如一个时间常数为 0.1s 的电机ts 取 0.01s 或更小。ts 变小后相邻采样点的误差差值也会变小二阶差分项 e(k) - 2*e(k-1) e(k-2) 接近零Kd 项的学习信号变弱这时候输入归一化要采用“越限压缩”而不是“精确线性缩放”保证小误差信号仍有足够的激励幅度。5.3 被控对象存在大延迟时的雅可比近似失效dyu 用符号函数近似当系统的相位延迟超过一定范围时符号会翻转。例如纯延迟 5 个采样周期当前时刻控制量对输出的影响要 5 步后才显现sign((y(k)-y_1)/(u(k)-u_1)) 算出来的可能是反方向。这种场景下的靠谱做法有两种一是把延迟纳入对象建模误差计算时用延迟后的预测值二是放弃符号近似改用被控对象的辨识模型求解析雅可比。后者实现复杂但收敛质量和稳定性都上一个台阶。标题里的 BPNNPID 属于经典实现默认对象延迟不超过 1 个采样周期读者应用到真实硬件时务必检查这个前提。5.4 输出系数与初始控制量的配合Kp、Ki、Kd 的映射系数不是越大越好。网络输出 yo 在 (0,1) 区间初值随机时 yo 约等于 0.5乘系数后 Kp 约等于 5。如果被控对象在 Kp5 时就剧烈震荡初始控制量就不稳定网络永远学不到有效梯度。常见的处理是先离线整定一组合适的 PID 初值然后反推映射系数让 yo0.5 附近的输出对应这组初值。例如离线整定 Kp2、Ki0.5、Kd0.1那映射系数就设为 4、1、0.2网络在初始点就具备基本控制能力后续梯度下降只是在局部微调。6. 收敛性验证如何分辨网络“学会了”还是“记住了”BPNNPID 这类在线学习系统最怕的不是不收敛而是误把误差曲线变好看当成学习成功。最后这一章给出三个可操作的验证方法。最直接的验证是对比实验。把随机种子固定跑两次一次用零初值wi 和 wo 全 0一次用随机初值观察误差曲线。零初值下隐含层输出 h 全为 0输出层净输入也全为 0yo 全部等于 0.5此时系统等价于一组固定的中等增益 PID。如果零初值曲线本身就很平滑说明被控对象对 PID 参数不敏感BP 网络只是在做细微调整这时候即使随机初值的误差曲线最终也很好看也不能说明网络学到了有效的映射关系。第二个方法是扰动测试。训练到稳态后在第 500 步加入一个幅值 0.5、持续 50 个采样周期的负载扰动观察网络是否能在扰动结束后自动把 PID 参数调整回抗扰动状态。真正学进去的网络扰动期间的权值变化会体现“先快速增大 Kp 压误差、再慢慢恢复”的轨迹如果权值基本不动只是靠 PID 的固有鲁棒性扛过去那说明网络整定并没有在学习。记录 Kp 的实时轨迹能直观看出网络有没有在干活。最后是泛化验证。训练时目标值是 1.0 阶跃训练结束后把目标值改成 2.0 再跑一遍同时保持网络权值不重置。如果网络具备泛化能力新的误差条件下网络会自动调整 PID 参数超调量和调节时间应当与新目标值下的离线整定结果接近。如果误差曲线发散或者剧烈震荡问题几乎都出在第 5 章提到的两处一是输入归一化没有跟随目标值变化误差直接超界迫使 tanh 饱和二是输出层映射系数上限限制了 Kp 的可达范围新的误差条件下需要更大的控制增益但网络输出已经顶到上限。此时把归一化分母改成动态跟踪目标值再增加输出系数的上限泛化能力立刻会有可感知的提升。本文还有配套的精品资源点击获取
返回列表