1. 程式人生 > 其它 >PID控制算法系列(6)-單神經元自適應PID控制器

PID控制算法系列(6)-單神經元自適應PID控制器

0 前言

寫了前面幾篇文章,其實面對大部分的場景來說應該是夠用了,但是在PID中還有一個自適應版本的PID讓我覺得十分神奇,所以打算把這個演算法也填一填坑。

如今神經網路研究的火爆也讓許多的控制領域學者開始將神經網路與自動控制相結合起來,其中的原因主要是神經網路的幾大特性:

  1. 能夠以任意的精度逼近任意連續非線性函式;
  2. 對複雜不確定的問題具有自適應和自學習的能力;
  3. 並行的資訊處理機制可以解決控制系統中大規模的實時計算問題,並且並行機制中的冗餘性可利用使得控制系統擁有很強的容錯能力;
  4. 具有很強的資訊綜合能力,能同時處理定量和定性的資訊,能很好地協調多種輸入資訊的關係,適用於多資訊融合和多媒體技術;

0.1 什麼是單神經元自適應PID控制器?

單神經元自適應PID控制器就是使用單神經元控制的,可以實現自行調整系統PID引數的PID控制器。

其實自適應的PID還有其他的控制方式,本篇文章就只是介紹以單神經元控制的自適應PID控制器。(下文簡稱自適應PID控制器)

0.2 為什麼需要自適應PID?它與傳統PID控制器的區別是什麼?

自適應PID與普通的PID演算法最大的區別就是它能夠根據系統的性質的改變從而自行調整PID的引數,而普通的PID演算法則無法做到這一點。

在工業控制上,有很多的PID系統的執行時間極長,與我們平時做的平衡小車等玩意不同,工業設施在長時間的運轉過程中其自身的物理性質是很容易發生改變的,比如更換了不同的零件、設施本身發生了腐蝕等等。一旦物理性質發生了改變,那麼原來出廠時設定的PID引數就不是當前系統的最佳設定了。而如果設施採用的自適應的PID演算法,它就能在執行的過程中不斷修正自己的PID引數設定,從而保證系統一直都工作在最佳的狀態。

綜合看來,自適應PID的強大之處在於其的自適應能力,後面會詳細介紹它是如何實現的。首先我們先從神經元開始講起。

本人非計算機/電控專業,如文章有疏漏歡迎指出~

1.0 神經元與學習規則

  1. 腦袋中的神經元
    神經元的大致結構就像下面這樣:

    複習一下高初中的生物知識,神經元由細胞體與突起組成,而突起分為樹突與軸突,樹突就是上圖中左側的像網路一樣的突起,它是一個接收器,與其他神經元細胞的軸突相連線,接受其他神經元細胞發出的訊號;軸突就是上圖中右側的長條狀的突起,是一個發射器,負責將訊號傳送給其他的神經元細胞。

    無數的神經元組成了一個龐大的神經網路,叫做神經中樞,而人能夠產生各種行為,就是根據神經中樞傳出的指令而做出的反應。

  2. 晶片中的神經元
    文章中所說的並不是生物大腦中的神經元,而是科學家們通過了解生物神經元的結構之後使用計算機模擬出來的一種計算機結構。為什麼科學家會模擬大腦?原因是希望能將大腦“思考”的能力賦予給機器。

    最簡單的神經元模型叫做“感知器”,長下面這個樣子:

    前面\(x_{1}\)\(x_{2}\)\(x_{3}\)模擬的就是生物神經元的樹突,而\(output\)表示的就是神經元的軸突。無數個這樣的神經元前後連線,就形成了晶片中的“大腦”。

    但是常用的神經元模型的樹突上還會加上“權重”,輸入與權重相乘後相加,如果最後的值大於神經元的閾值,神經元就會產生一個\(output\)訊號,這就是模擬了生物大腦中只有當一個神經元受到足夠大的刺激才能產生電訊號的過程。

    有:

\[y_{i}=\sum_{j=1}^{N}w_{ij}X_{i}-\theta _{i}——————————(12) \]

其中神經元的閾值項\(\theta_{i}\)表示,只有輸入的值的線性和大於這個值,神經元才會有輸出。

  1. 學習規則
    學習規則可以使得模擬出來的神經元通過某種方式得到“學習”的能力。它主要的功能是實現對神經元之間的連線強度的修正,即修改“權重”。不同的學習規則可以形成不同的神經網路,這些神經網路的具體性質也有不同。較為常用的學習規則有Hebb、Perceptron、Delta、BP等。

    學習分為無監督學習和監督學習,簡單區分就是是否引入了期望值。通用的學習規則如下:

\[\Delta w_{ij}(t)=\eta *r*x(t)——————————(1) \]

表示式表示的意思是權重在t時刻的差值\(\Delta W_{j(t)}\)與t時刻的輸入\(x(t)\)和學習訊號\(r\)的乘積成正比,這裡的比例係數是\(\eta\),也稱為學習常數,決定了學習的速率。

所以可以得到下一時刻的加權:

\[w_{ij}(t+1)=w_{ij}(t)+\eta * r * x(t)——————(2) \]

1.1 單神經元採用的學習規則

這裡採用的神經元學習規則為有監督Hebb學習規則,可選用的規則並不侷限於此。有監督的Hebb學習規則是Hebb學習規則與\(\Delta\)學習規則的結合,所以下面介紹一下學習規則的相關資訊。

1.1.1 無監督的Hebb學習規則

Hebb學習規則預設所說的就是無監督的Hebb規則,後面人們提出了有監督的Hebb規則以適應不同模型的需要。Hebb學習規則其實與“條件反射”的機理一致。

相信大家都還記得生物書上巴普洛夫做過一個著名的實驗以研究條件反射:

  1. 給狗聽鈴聲,狗沒有分泌唾液;
  2. 給狗食物,狗分泌唾液;
  3. 讓鈴聲先於食物數秒後出現,狗分泌唾液,重複多次;
  4. 給狗聽鈴聲,狗分泌唾液。

Hebb理論認為同一時間被啟用的兩個神經元(網路)之間的聯絡會被強化,從而讓細胞記憶這兩個事物之間存在著聯絡。相反如果這兩個事物的聯絡越來越弱,那麼這兩個神經元(網路)之間的聯絡也就越來越被弱化。

Hebb學習規則就是通過調整神經元聯結的權重來起到了“學習”的作用,也就是:權值的差值與輸入輸出的乘積呈正比例關係

運用上邊的公式\((2)\),可以看出如果一個樹突結構(一個輸入)經常有輸入產生,那麼在這條路線上的權值就會隨著時間不斷地累積,正好對應了上面所說的“同一時間被啟用的兩個神經元(網路)之間的聯絡會被強化”。但是最開始的Hebb學習並沒有“弱化”的過程。

無監督的Hebb學習規則的學習表示式如下:

\[\Delta w_{ij}(t)=\eta X_{i}(t)Y_{i}(t)—————————(3) \]

其中\(\Delta w_{j}(t)\)\(\eta\)\(X_{i}(t)\)\(Y_{j}(t)\)分別表示權值的差值、學習速率、上一個神經元對此神經元的輸入(即理解為當前神經元的輸入)、當前神經元對下一個神經元的輸入(即理解為當前神經元的輸出)。

1.1.2 有監督的\(\Delta\)學習規則

首先作為一個有監督的學習規則,所以學習的過程中就必須引入期望值了。這裡設期望值為\(y\),而當前神經元輸出的值為\(\hat{y}\)。現在我們就能得到二者的差值了,不過在這裡一般使用類似方差的方式來描述:

\[E=\frac{1}{2}(y-\hat{y})^2————————————(4) \]

其中\(E\)就是所求的誤差(是不是感覺跟前面PID控制器中的差值都是類似的東西?)

不管差值的形式如何,為了讓學習的效果最大化,肯定是要讓這個差值越小越好。它是一個\(y\)關於\(E\)的二次函式,但是我們需要討論的是誤差值\(E\)與權值的差值\(\Delta w_{ij}\)的關係。它們之間肯定也是呈一個凹函式的關係,那麼我們只要找到它的最低點就好了,不是嗎?

但是事情沒有這麼簡單。神經元的學習差值函式並不是一個值都是已知的函式,我們只知道過去的以及當前的\(E\),對還未通過神經元的輸入無法知道其輸出值,這就無法直接計算了。所以我們採用的是梯度下降法:神經元通過一次次的迭代,每一次都根據當前輸出的值做一次修正,直到找到目標誤差函式的最小值。這就類似於我們如果想要找到一個山谷的最低處,我們只要一直往比當前低的地方走,直到沒有更低的地方就好了。當然這其中可能會有比當前最低處更低的地方沒有被發現,誤以為當前的深度就是最低點的情況。只是對於本文討論的單個神經元模型來說,並沒有這種複雜的情況。

為了求得\(E\)上的最低點,我們對\((4)\)\(E\)關於權值的偏導,有:

\[\frac{\partial E}{\partial w_{ij}}=\frac{\partial E}{\partial y}\frac{\partial y}{\partial g}\frac{\partial g}{\partial w_{ij}}——————————(5) \]

其中\(g\)表示的是啟用函式,這個函式模擬的就是神經元的輸出值,恆為正數。但簡單來看,也可把中間這一步驟略去,因為非負值對權值的修正方向沒有影響。所以\((5)\)式化簡得到:

\[\frac{\partial E}{\partial w_{ij}}=\frac{\partial E}{\partial y}\frac{\partial y}{\partial w_{ij}}=\eta(y-\hat{y})X_{i}——————(6) \]

現在我們就得到了修正的規則,\(\Delta\)規則:權值的差值與當前輸出值與期望之差和輸入的乘積呈正比關係

其學習規則的表示式為:

\[\Delta w_{ij}(t)=\eta (y-\hat{y})X_{i}(t)—————————(7) \]

隨著\(\Delta\)學習規則的不斷呼叫,神經元會不斷修正自己每一個樹突上的權值,從而使得輸出值向給出的期望值靠近。(聽上去是不是有PID那味兒了?還沒完呢!)這個修正的過程對於單神經元來說,權值的差值是一個負值。

等等,負值!前面的Hebb學習規則不就是沒有“弱化”的過程嗎?

1.1.3 有監督的Hebb學習規則

為了解決hebb學習規則沒有“弱化”連線神經元的過程,所以我們用有監督的\(\Delta\)學習規則與其相乘,便得到了有監督的Hebb學習規則,這個學習規則可以在給定期望值的情況下將同一時間被啟用的兩個神經元(網路)之間的聯絡強化,從而讓神經元記憶這兩個事物之間存在著聯絡;相反如果這兩個事物的聯絡越來越弱,那麼這兩個神經元之間的聯絡也就越來越被弱化。

其學習規則的表示式為:

\[\Delta w_{ij}(t)=\eta (y-Y_{i}(t))X_{i}(t)Y_{i}(t)——————(8) \]

2.0 PID的歸來

學了那麼久的電腦科學,你是否又開始想念我們的控制理論了呢?下面我們就將先前的PID控制論移植到神經元中去。

在這裡我們還是選擇將演算法移植到增量式的PID演算法中。(位置式PID:嗚嗚嗚)

還是將增量式的PID表示式搬出來:

\[\Delta U(t)=K_{P}(err(t)-err(t-1))+K_{I}err(t)+K_{D}(err(t)-2err(t-1)+err(t-2))————(9) \]

在這裡可以將PID本身看作一個單神經元:三個樹突分別是以上式子中的三項差值,輸出就是軸突,其中三個PID引數就是三個樹突的權值。為了對應單神經元的學習速率,我們再引入一個比例係數\(K\)好了。

所以上面這個增量式的PID表示式就被我們改寫成了下面這個樣子:

\[\Delta U(t)=K(\sum_{i=1}^{3} {w}’ _{i}(t)X_{i}(t))——————(10) \]

其中\({W}'\)PID的引數,也就是神經元樹突的權值,計算的方式為:

\[{w}' _{i}(t)=\frac{w_{i}(t)}{\sum_{i=1}^{3}|w_{i}(t)|}—————————(11) \]

我們將PID的輸出直接寫出來,並將PID引數的調整用前面的學習規則來書寫,有:

\[u(t)=u(t-1)+K\sum_{i=1}^{3}{w}'_{ij}(t)X_{i}(t) \]\[w_{1}(t)=w_{1}(t-1)+\eta _{p}z(t)u(t)x_{1}(t) \]\[w_{2}(t)=w_{2}(t-1)+\eta _{I}z(t)u(t)x_{2}(t) \]\[w_{3}(t)=w_{3}(t-1)+\eta _{D}z(t)u(t)x_{3}(t) \]

式中:

\[x_{1}(t)=e(t)-e(t-1) \]\[x_{2}(t)=e(t) \]\[x_{3}(t)=\Delta ^2e(t)=e(t)-2e(t-1)+e(t-2) \]

可以看到在計算PID的三個引數的時候,其引數的權值增量使用了前面推匯出來的有監督的Hebb學習規則來調整:學習速率直接照搬,誤差值也就是PID中的誤差值\(err(t)\);系統的輸入便是上面的三個式子;系統的輸出便是\(u(t)=u(t-1)+\Delta u(t)\).

其中(t-1)代表的就是上一時刻的量;我們在PID中需要的三個PID引數\(K_{p}\)\(K_{I}\)\(K_{D}\)分別對應著上面的\(w_{1}(t)\)\(w_{2}(t)\)\(w_{3}(t)\)\(\eta _{p}\)\(\eta _{I}\)\(\eta _{D}\)分別是比例、積分、微分項的學習速率,採用不同的學習速率的原因是以便對不同的權重進行分別調整。

這裡還有最後一個引數的值需要確定:那就是我們引入的\(K\).K值的選擇十分重要,可以看到K值越大則PID調整的也就越快速,但是如果過快的話會使得系統執行不穩定(類似單P控制)。所以一般來說當被控制的物件時延增大的時候K值必須減小以保證系統的穩定,但是K值如果設定得過小,又會使得系統得快速穩定性變差。

至此單神經元自適應PID控制器就已經被完全推匯出來了。

2.1 改進的單神經元自適應PID控制

在大量的實際運用中,人們發現PID的線上學習修正主要與\(e(t)\)\(\Delta e(t)\)有關,所以人們索性將PID三個引數的學習公式都改成了下面的形式:

\[w_{1}(t)=w_{1}(t-1)+\eta _{p}z(t)u(t)(e(t)+\Delta e(t)) \]\[w_{2}(t)=w_{2}(t-1)+\eta _{I}z(t)u(t)(e(t)+\Delta e(t)) \]\[w_{3}(t)=w_{3}(t-1)+\eta _{D}z(t)u(t)(e(t)+\Delta e(t)) \]

其中\(\Delta e(t)=e(t)-e(t-1)\)。改進之後權重的線上修正就不再完全是根據神經網路的學習原理而是參雜了實際經驗的考慮了。

3.0 單神經元自適應PID控制器的程式碼實現

有了前面的演算法基礎,我們就可以開始將他們轉換成程式碼實現了。這裡沒有使用改進的演算法,而還是使用原本的純神經元自適應PID演算法。

首先還是定義一個結構體,在其中準備演算法所需要的變數:

/*定義結構體和公用體*/
typedef struct
{
  float setpoint;               /*設定值*/
  float kcoef;                  /*神經元輸出比例*/
  float kp;                     /*比例學習速度*/
  float ki;                     /*積分學習速度*/
  float kd;                     /*微分學習速度*/
  float lasterror;              /*前一拍偏差*/
  float preerror;               /*前兩拍偏差*/
  float deadband;               /*死區*/
  float result;                 /*輸出值*/
  float output;                 /*百分比輸出值*/
  float maximum;                /*輸出值的上限*/
  float minimum;                /*輸出值的下限*/
  float wp;                     /*比例加權係數*/
  float wi;                     /*積分加權係數*/
  float wd;                     /*微分加權係數*/
}NEURALPID;

這一步是先先給PID引數賦一個基礎值,目的是讓PID的自適應功能先運轉起來,後面這些值都會改變的。

/* 單神經元PID初始化操作,需在對vPID物件的值進行修改前完成                     */
/* NEURALPID vPID,單神經元PID物件變數,實現資料交換與儲存                    */
/* float vMax,float vMin,過程變數的最大最小值(量程範圍)                    */
void NeuralPIDInitialization(NEURALPID *vPID,float vMax,float vMin)
{
  vPID->setpoint=vMin;                  /*設定值*/
  
  vPID->kcoef=0.12; /*神經元輸出比例*/
  vPID->kp=0.4;                         /*比例學習速度*/
  vPID->ki=0.35;                        /*積分學習速度*/
  vPID->kd=0.4;                         /*微分學習速度*/
  
  vPID->lasterror=0.0;                  /*前一拍偏差*/
  vPID->preerror=0.0;                   /*前兩拍偏差*/
  vPID->result=vMin;                    /*PID控制器結果*/
  vPID->output=0.0;                     /*輸出值,百分比*/
 
  vPID->maximum=vMax;                   /*輸出值上限*/
  vPID->minimum=vMin;                   /*輸出值下限*/  
  vPID->deadband=(vMax-vMin)*0.0005;    /*死區*/
 
  vPID->wp=0.10; /*比例加權係數*/
  vPID->wi=0.10; /*積分加權係數*/
  vPID->wd=0.10; /*微分加權係數*/
}

下面是增量式單神經元自適應PID控制器的演算法部分:

/* 神經網路引數自整定PID控制器,以增量型方式實現                              */
/* NEURALPID vPID,神經網路PID物件變數,實現資料交換與儲存                    */
/* float pv,過程測量值,物件響應的測量資料,用於控制反饋                     */
void NeuralPID(NEURALPID *vPID,float pv)
{
  float x[3];
  float w[3];
  float sabs
  float error;
  float result;
  float deltaResult;
 
  error=vPID->setpoint-pv;
  result=vPID->result;
  if(fabs(error)>vPID->deadband)
  {
    x[0]=error;
    x[1]=error-vPID->lasterror;
    x[2]=error-vPID->lasterror*2+vPID->preerror;
  
    sabs=fabs(vPID->wi)+fabs(vPID->wp)+fabs(vPID->wd);
    w[0]=vPID->wi/sabs;
    w[1]=vPID->wp/sabs;
    w[2]=vPID->wd/sabs;
    
    deltaResult=(w[0]*x[0]+w[1]*x[1]+w[2]*x[2])*vPID->kcoef;
  }
  else
  {
    deltaResult=0;
  }
 
  result=result+deltaResult;
  if(result>vPID->maximum)
  {
    result=vPID->maximum;
  }
  if(result<vPID->minimum)
  {
    result=vPID->minimum;
  }
  vPID->result=result;
  vPID->output=(vPID->result-vPID->minimum)*100/(vPID->maximum-vPID->minimum);
 
  //單神經元學習
  NeureLearningRules(vPID,error,result,x);
  
  vPID->preerror=vPID->lasterror;
  vPID->lasterror=error;
}

這裡是有監督的Hebb學習規則的演算法部分:

/*單神經元學習規則函式*/
static void NeureLearningRules(NEURALPID *vPID,float zk,float uk,float *xi)
{
  vPID->wi=vPID->wi+vPID->ki*zk*uk*xi[0];
  vPID->wp=vPID->wp+vPID->kp*zk*uk*xi[1];
  vPID->wd=vPID->wd+vPID->kd*zk*uk*xi[2];
}

在推導公式之前我曾經試過硬啃程式碼,結果發現啃了個寂寞……但是在一頓推導猛如虎之後,發現程式碼實現其實是一件很簡單的事情。(我記得這事好像教我資料結構的老師說過……老師我學廢了)

4.0後記

在這一段時間的PID演算法學習過程中,逐漸發現了演算法以及模擬驗證的重要性。自己先前主力學習的是硬體設計等,以為會設計幾塊電路,能驅動微控制器就學到頭了,結果發現外面的世界是多麼地廣大。

上面的程式碼是一位老哥在對照著Matlab的模擬程式碼移植到C中的。談到MatLab,我也不禁感嘆這個巨無霸工業軟體能力的恐怖如斯。美帝對華部分的封鎖中也包含著對matlab的禁用,這對許多科學研究來說都是一場不小的災難。希望我們能夠臥薪嚐膽,早日擺脫帝國主義的技術封鎖,實現中華的技術無限突破。

參考文獻

[1]佚名. 單神經元自適應PID控制器設計. 遼寧科技大學本科生畢業設計論文
[2]郝志紅. 單神經元自適應PID控制演算法. 《冶金自動化》2012S1
[3]單神經元PID控制器的實現:https://blog.csdn.net/foxclever/article/details/84678393
[4]人工神經元和Delta規則:https://zhuanlan.zhihu.com/p/23478187
[5]深度學習 --- 神經網路的學習原理(學習規則):https://blog.csdn.net/weixin_42398658/article/details/83816633
[6]《先進PID控制MATLAB模擬》第四版 劉金琨編著