单神经元自适应 PID

1)一句话

经典 PID:

\(u(t)=K_p e(t)+K_i\int e(t)dt+K_d\frac{de}{dt}\)

单神经元 PID:
\([e,\ \int e,\ \dot e]\) 当作“输入”,把 \([w_p,w_i,w_d]\) 当作“可调权重”,用监督学习式的增量更新去逼近“让误差更快变小”,同时用工程约束避免权重飞掉。

不是什么神秘智能,本质是一个带约束的在线梯度/δ规则在 PID 基函数上的投影。


2)离散形式(最常用:增量式写法)

离散误差:

\(e(k)=r(k)-y(k)\)

\(\sum e(k)=\sum e(k-1)+e(k)\cdot T_s\)

\(\dot e(k)=\frac{e(k)-e(k-1)}{T_s}\)

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

\(x_1(k)=e(k),\quad x_2(k)=\sum e(k),\quad x_3(k)=\dot e(k)\)

神经元输出(即 PID 的输出增量):

\(\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)=u(k-1)+\Delta u(k)\)

其中:

  • (K>0) 是统一的学习/增益缩放因子(常取 0.01~1,取决于你对象有多“猛”)
  • (\(w_i\)) 就是你要自适应的东西(对应“Kp/Ki/Kd”的趋势)

3)权重更新怎么来

用最常用的性能指标:

\(J=\frac12 e^2(k)\)

假设我们沿着“让 J 下降”的方向去改权重(梯度下降),会得到:

\(w_i(k+1)=w_i(k)-\eta_i\frac{\partial J}{\partial w_i}\)

\(\frac{\partial J}{\partial w_i}=e(k)\cdot\frac{\partial e(k)}{\partial u(k)}\cdot\frac{\partial u(k)}{\partial w_i}\)

工程上最关键的一步来了:
(\(\frac{\partial e}{\partial u}=-1\)),但 (\(\frac{\partial u}{\partial w_i}=K x_i\)),而且被控对象的 Jacobian (\partial y/\partial u)通常是未知的

于是工程写法:用一个“符号/估计”代替它:

\(\hat s(k)\approx -\text{sgn}\!\left(\frac{\partial y}{\partial u}\right)\)

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

\(\frac{\partial J}{\partial w_i}\approx e(k)\,(-1)\,K\,x_i(k)\)

再吸收常数进学习率,得到最实用的单神经元 PID 更新律

\(\boxed{w_i(k+1)=w_i(k)+\eta_i\;K\;e(k)\;x_i(k)}\)

这就是你能在很多教材里看到的“单神经元 PID 规则”的来源——它不是瞎凑,是梯度下降 + Jacobian 符号近似。


4)把更新律写得更稳

4.1 加限幅(防发散)

\(w_i^{\min}\le w_i\le w_i^{\max}\)

4.2 加动量/阻尼

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

  • (\(\alpha\in(0,0.2)\)) 通常就够(阻尼振荡、抑制权重抖)

4.3 用误差变化方向做粗略 Jacobian 估计

\(\hat J_{yu}(k)=\text{sign}\big(y(k)-y(k-1)\big)\cdot\text{sign}\big(u(k-1)-u(k-2)\big)\)

若 (\(\hat J_{yu}(k)>0\)) 表示同方向,(\(\approx 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/contentcnv/72630.html

6)怎么解释它“自适应”

你可以运行时打印:

  • w[0]≈随 “比例效应”
  • w[1]≈随 “积分积累”
  • w[2]≈随 “微分阻尼”

当工况变化(负载突变/输入阶跃/参数漂移),权重会自行重新分布,表现为:

  • 稳态误差大 → w[1] 会被推上去
  • 过冲严重 → w[2] 变大抑制振荡
  • 响应太慢 → w[0] 拉大

posted @ 2026-06-17 16:58  hczyydqq  阅读(7)  评论(0)    收藏  举报