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)+Kie(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_se(k)=e(k1)+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(k1)

构造神经元输入向量(三个“突触”):

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(k1)+Δ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)ηiwiJ

∂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}wiJ=e(k)u(k)e(k)wiu(k)

工程上最关键的一步来了:
(∂e∂u=−1\frac{\partial e}{\partial u}=-1ue=1),但 (∂u∂wi=Kxi\frac{\partial u}{\partial w_i}=K x_iwiu=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(uy)

或者直接把性能灵敏度近似为:

∂J∂wi≈e(k) (−1) K xi(k)\frac{\partial J}{\partial w_i}\approx e(k)\,(-1)\,K\,x_i(k)wiJe(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≤wimax⁡w_i^{\min}\le w_i\le w_i^{\max}wiminwiwimax

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(k1))

  • (α∈(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(k1))sign(u(k1)u(k2))

若 (J^yu(k)>0\hat J_{yu}(k)>0J^yu(k)>0) 表示同方向,(≈1\approx 11);反向时你通常冻结/衰减更新。

更保守的写法:

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] 拉大

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐