单神经元自适应 PID
1)一句话
经典 PID:
u(t)=Kpe(t)+Ki∫e(t)dt+Kddedtu(t)=K_p e(t)+K_i\int e(t)dt+K_d\frac{de}{dt}u(t)=Kpe(t)+Ki∫e(t)dt+Kddtde
单神经元 PID:
把 [e, ∫e, e˙][e,\ \int e,\ \dot e][e, ∫e, e˙] 当作“输入”,把 [wp,wi,wd][w_p,w_i,w_d][wp,wi,wd] 当作“可调权重”,用监督学习式的增量更新去逼近“让误差更快变小”,同时用工程约束避免权重飞掉。
它不是什么神秘智能,本质是一个带约束的在线梯度/δ规则在 PID 基函数上的投影。
2)离散形式(最常用:增量式写法)
离散误差:
e(k)=r(k)−y(k)e(k)=r(k)-y(k)e(k)=r(k)−y(k)
∑e(k)=∑e(k−1)+e(k)⋅Ts\sum e(k)=\sum e(k-1)+e(k)\cdot T_s∑e(k)=∑e(k−1)+e(k)⋅Ts
e˙(k)=e(k)−e(k−1)Ts\dot e(k)=\frac{e(k)-e(k-1)}{T_s}e˙(k)=Tse(k)−e(k−1)
构造神经元输入向量(三个“突触”):
x1(k)=e(k),x2(k)=∑e(k),x3(k)=e˙(k)x_1(k)=e(k),\quad x_2(k)=\sum e(k),\quad x_3(k)=\dot e(k)x1(k)=e(k),x2(k)=∑e(k),x3(k)=e˙(k)
神经元输出(即 PID 的输出增量):
Δu(k)=K⋅(w1(k)x1(k)+w2(k)x2(k)+w3(k)x3(k))\Delta u(k)=K\cdot\big(w_1(k)x_1(k)+w_2(k)x_2(k)+w_3(k)x_3(k)\big)Δu(k)=K⋅(w1(k)x1(k)+w2(k)x2(k)+w3(k)x3(k))
u(k)=u(k−1)+Δu(k)u(k)=u(k-1)+\Delta u(k)u(k)=u(k−1)+Δu(k)
其中:
- (K>0) 是统一的学习/增益缩放因子(常取 0.01~1,取决于你对象有多“猛”)
- (wiw_iwi) 就是你要自适应的东西(对应“Kp/Ki/Kd”的趋势)
3)权重更新怎么来
用最常用的性能指标:
J=12e2(k)J=\frac12 e^2(k)J=21e2(k)
假设我们沿着“让 J 下降”的方向去改权重(梯度下降),会得到:
wi(k+1)=wi(k)−ηi∂J∂wiw_i(k+1)=w_i(k)-\eta_i\frac{\partial J}{\partial w_i}wi(k+1)=wi(k)−ηi∂wi∂J
而
∂J∂wi=e(k)⋅∂e(k)∂u(k)⋅∂u(k)∂wi\frac{\partial J}{\partial w_i}=e(k)\cdot\frac{\partial e(k)}{\partial u(k)}\cdot\frac{\partial u(k)}{\partial w_i}∂wi∂J=e(k)⋅∂u(k)∂e(k)⋅∂wi∂u(k)
工程上最关键的一步来了:
(∂e∂u=−1\frac{\partial e}{\partial u}=-1∂u∂e=−1),但 (∂u∂wi=Kxi\frac{\partial u}{\partial w_i}=K x_i∂wi∂u=Kxi),而且被控对象的 Jacobian (\partial y/\partial u)通常是未知的。
于是工程写法:用一个“符号/估计”代替它:
s^(k)≈−sgn (∂y∂u)\hat s(k)\approx -\text{sgn}\!\left(\frac{\partial y}{\partial u}\right)s^(k)≈−sgn(∂u∂y)
或者直接把性能灵敏度近似为:
∂J∂wi≈e(k) (−1) K xi(k)\frac{\partial J}{\partial w_i}\approx e(k)\,(-1)\,K\,x_i(k)∂wi∂J≈e(k)(−1)Kxi(k)
再吸收常数进学习率,得到最实用的单神经元 PID 更新律:
wi(k+1)=wi(k)+ηi K e(k) xi(k)\boxed{w_i(k+1)=w_i(k)+\eta_i\;K\;e(k)\;x_i(k)}wi(k+1)=wi(k)+ηiKe(k)xi(k)
这就是你能在很多教材里看到的“单神经元 PID 规则”的来源——它不是瞎凑,是梯度下降 + Jacobian 符号近似。
4)把更新律写得更稳
4.1 加限幅(防发散)
wimin≤wi≤wimaxw_i^{\min}\le w_i\le w_i^{\max}wimin≤wi≤wimax
4.2 加动量/阻尼
wi(k+1)=wi(k)+ηiKe(k)xi(k)+α(wi(k)−wi(k−1))w_i(k+1)=w_i(k)+\eta_i K e(k)x_i(k)+\alpha\big(w_i(k)-w_i(k-1)\big)wi(k+1)=wi(k)+ηiKe(k)xi(k)+α(wi(k)−wi(k−1))
- (α∈(0,0.2)\alpha\in(0,0.2)α∈(0,0.2)) 通常就够(阻尼振荡、抑制权重抖)
4.3 用误差变化方向做粗略 Jacobian 估计
J^yu(k)=sign(y(k)−y(k−1))⋅sign(u(k−1)−u(k−2))\hat J_{yu}(k)=\text{sign}\big(y(k)-y(k-1)\big)\cdot\text{sign}\big(u(k-1)-u(k-2)\big)J^yu(k)=sign(y(k)−y(k−1))⋅sign(u(k−1)−u(k−2))
若 (J^yu(k)>0\hat J_{yu}(k)>0J^yu(k)>0) 表示同方向,(≈1\approx 1≈1);反向时你通常冻结/衰减更新。
更保守的写法:
if (dy_sign == du_sign) g = 1.0f; else g = 0.0f; /* or decay */
w[i] += eta[i] * K * e * x[i] * g;
5)C 代码
#include <stdint.h>
#include <string.h>
#include <float.h>
#define N_NEURON 3
typedef struct {
float Ts; /* 采样周期(s) */
float K; /* 统一增益缩放(0.01~1) */
float eta[N_NEURON]; /* 学习率 [p,i,d] */
float w[N_NEURON]; /* 权重 */
float w_prev[N_NEURON]; /* 上次权重(动量) */
float alpha; /* 动量阻尼[0,0.2] */
float w_min, w_max;
float u; /* 当前输出 */
float u_min, u_max;
float e; /* e(k) */
float e_prev; /* e(k-1) */
float sum_e; /* ∫e */
float y_prev; /* y(k-1) */
uint8_t reset;
} SN_PID_t;
void SN_PID_Reset(SN_PID_t *sp, float u0)
{
memset(sp->w, 0, sizeof(sp->w));
memset(sp->w_prev, 0, sizeof(sp->w_prev));
sp->e = sp->e_prev = 0.0f;
sp->sum_e = 0.0f;
sp->y_prev = 0.0f;
sp->u = u0;
sp->reset = 0;
}
/* r:设定值, y:反馈值, u0_hold:若reset用u0 */
float SN_PID_Step(SN_PID_t *sp, float r, float y, float u0_hold)
{
if (sp->reset) {
SN_PID_Reset(sp, u0_hold);
}
sp->e = r - y;
/* 积分(简单矩形) */
sp->sum_e += sp->e * sp->Ts;
/* 微分(可用不完全微分,这里给最简) */
float de = (sp->e - sp->e_prev) / sp->Ts;
/* 神经元输入 */
float x[3] = { sp->e, sp->sum_e, de };
/* 简单 Jacobian 符号估计(可选但推荐) */
float dy = y - sp->y_prev;
float du = sp->u - u0_hold; /* 注意:这里只用于 sign,不是严格 du(k-1) */
float j_sign = 1.0f;
if (sp->Ts > 1e-9f && fabsf(dy) > 1e-9f)
j_sign = (dy*(r-y) >= 0.0f) ? 1.0f : 0.3f; /* 你可按需冻结 */
/* —— 权重更新 —— */
for (int i = 0; i < 3; i++) {
float dw = sp->eta[i] * sp->K * sp->e * x[i] * j_sign;
/* 动量/阻尼 */
dw += sp->alpha * (sp->w[i] - sp->w_prev[i]);
sp->w_prev[i] = sp->w[i];
sp->w[i] += dw;
/* 限幅 */
if (sp->w[i] > sp->w_max) sp->w[i] = sp->w_max;
if (sp->w[i] < sp->w_min) sp->w[i] = sp->w_min;
}
/* 输出增量 -> 绝对输出 */
float du_out = sp->K *
(sp->w[0]*x[0] + sp->w[1]*x[1] + sp->w[2]*x[2]);
sp->u += du_out;
/* 饱和 + 抗windup(积分钳位思想:若饱和则别让∫e继续推) */
if (sp->u > sp->u_max) { sp->u = sp->u_max; sp->sum_e -= sp->e * sp->Ts; }
if (sp->u < sp->u_min) { sp->u = sp->u_min; sp->sum_e -= sp->e * sp->Ts; }
sp->e_prev = sp->e;
sp->y_prev = y;
return sp->u;
}
初始化示例(你要按对象量纲调)
SN_PID_t spid = {
.Ts = 0.0005f, /* 500us */
.K = 0.05f, /* 先小,后慢慢加大 */
.eta = {0.12f, 0.002f, 0.08f}, /* p i d 学习率:i通常最小 */
.alpha = 0.05f,
.w_min = -5.0f, .w_max = 5.0f,
.u_min = 0.0f, .u_max = 3.3f, /* DA/占空比范围 */
};
SN_PID_Reset(&spid, 0.0f);
参考代码 基于单神经的一种自适应PID控制算法 www.youwenfan.com/contentcsv/72630.html
6)怎么解释它“自适应”
你可以运行时打印:
w[0]≈随 “比例效应”w[1]≈随 “积分积累”w[2]≈随 “微分阻尼”
当工况变化(负载突变/输入阶跃/参数漂移),权重会自行重新分布,表现为:
- 稳态误差大 → w[1] 会被推上去
- 过冲严重 → w[2] 变大抑制振荡
- 响应太慢 → w[0] 拉大
更多推荐


所有评论(0)