bp神經網絡模型推導與c語言實現(轉載)
轉載出處:http://www.cnblogs.com/jzhlin/archive/2012/07/28/bp.html
BP 神經網絡中的 BP 為 Back Propagation 的簡寫,最早它是由Rumelhart、McCelland等科學家於 1986 年提出來的,Rumelhart 並在Nature 上發表了一篇非常著名的文章 《Learning representations by back-propagating errors》 。隨著時代的遷移,BP神經網絡理論不斷的得到改進、更新,現在無疑已成為了應用最為廣泛的神經網絡模型之一。讓我們一起來探索下 BP神經網絡最初的 基本模型和概念!
從神經網絡的生物模型說起
我們知道人大腦信息的傳遞、對外界刺激產生反應都由神經元控制的,人腦就是由上百億個的這樣神經元構成。這些神經元之間並不孤立而且聯系很密切,每個神經元平均與幾千個神經元相連接,因此構成了人腦的神經網絡。刺激在神經網絡中的傳播是遵循一定的規則的,一個神經元並非每次接到其他神經傳遞過來的刺激都產生反應。它首先會將與其相鄰的神經元傳來的刺激進行積累,到一定的時候產生自己的刺激將其傳遞給一些與它相鄰的神經元。這樣工作的百億個的神經元構成了人腦對外界進行的反應。而人腦對外界刺激的學習的機制就是通過調節這些神經元之間聯系以及其強度。當然,實際上以上說的是對人腦真正神經工作的一種簡化的生物模型,利用這種簡化的生物模型可以將它推廣至機器學習中來,並把它描述成人工神經網絡。BP神經網絡就是其中的一種,來看看具體對神經元的分析。
圖1 神經網絡中神經元示意圖
神經元的積累的刺激是由其他神經元傳遞過來的刺激量和對應的權重之和,用 Xj 表示這種積累,Yi 表示某個神經元傳遞過來的刺激量,Wi 表示鏈接某個神經元刺激的權重,得到公式:
Xj = (y1 * W1)+(y2 * W2)+...+(yi * Wi)+...+ (yn * Wn)
而當 Xj 完成積累後,完成積累的神經元本身對周圍的一些神經元傳播刺激,將其表示為 yj 得到如下所示:
yj = f(Xj)
神經元根據積累後 Xj 的結果進行處理後,對外傳遞刺激 yj
BP神經網絡的構成
分析完單個的神經元後,再來看看它們組成網絡後的情形,用圖形來說明是最直觀的方法,如圖2所示:
圖2 BP神經網絡示意圖
第一區域的來說,它們相當於外界的刺激,是刺激的來源並且將刺激傳遞給神經元,因此把第一區域命名為輸入層。第二區域,表示神經元相互之間傳遞刺激相當於人腦裏面,因此把第二區命名為隱藏層。第三區域,表示神經元經過多層次相互傳遞後對外界的反應,因此把第三區域命名為輸出層。
簡單的描述就是,輸入層將刺激傳遞給隱藏層,隱藏層通過神經元之間聯系的強度(權重)和傳遞規則(激活函數)將刺激傳到輸出層,輸出層整理隱藏層處理的後的刺激產生最終結果。若有正確的結果,那麽將正確的結果和產生的結果進行比較,得到誤差,再逆推對神經網中的鏈接權重進行反饋修正,從而來完成學習的過程。這就是BP神經網的反饋機制,也正是BP(Back Propagation)名字的來源:運用向後反饋的學習機制,來修正神經網中的權重,最終達到輸出正確結果的目的!
BP神經網絡的數學推導
從數學上對BP神經網絡模型進行分析,本文第一部分神經網的生物模型中可以得到關於BP神經網絡的第一個公式(1):
對於神經元本身的輸出的激活函數,一般來說選取 Sigmoid 函數,那麽可以得到第二個公式(2):
通過以上兩個公式,可以分析出來BP神經網絡中輸出結果的計算過程。每個神經元收到刺激 yi 然後加權積累(權重 Wji )完成後產生 xj ,再通過激活函數產生刺激 yj ,向下一層與它相連的神經元傳遞,依次類推最終輸出結果。
我們再來分析如何利用向後反饋機制來修正神經元權重 Wji,這一部分數學推導需要運用到多元微分的數學內容。要修正 Wji 就需要得到誤差量。具體來看,首先用 dj 來表示真實的正確結果,並且設誤差為 E ,那麽( yj - dj )對應的就是 E 對於 yj 的微分增量,即 yj 減去( yj - dj )後就能得到正確值,得到公式(3):
然後,明確目標,需要知道的是對於權重 Wji 的誤差量是多少也就是 的值。而由公式(1)中知道 Wji 與 xj 相關, 那麽可以推導出公式(4):
需要求得 Wji 的誤差量,轉換為需要求 的值了。它的推導如下:
其中 的值,可以通過公式(2)求導得出來:
所以最終得到的的誤差量的值為:
以上公式需要註意下標:最後一個是 yi ,前面的都是 yj 。推到這裏可以算是完成了運用神經網絡的輸出值 yj 和正確值 dj 對最後一層隱藏層 Wji的修正,那麽對其他隱藏層呢? 接著往下看。
上面的推導過程由公式(3)開始,如果我們知道 (註意是yi ,公式(3)中是 yj ),就可以 同理 推導求出其對應其他隱藏層需要修正的權重值誤差量了。推導如下:
這樣所有的誤差量的都可以 同理 推導完成!
最後一步修正 Wji ,就是加上下面變量了,設置一個 l (0 到 1 之間)學習率。
至此,BP神經網絡反饋部分的數學推導算完成了。
一些數據的定義
首先,我們介紹些下文中描述的程序裏面的一些重要數據的定義。
#define Data 820 #define In 2 #define Out 1 #define Neuron 45 #define TrainC 5500
Data 用來表示已經知道的數據樣本的數量,也就是訓練樣本的數量。In 表示對於每個樣本有多少個輸入變量; Out 表示對於每個樣本有多少個輸出變量。Neuron 表示神經元的數量,TrainC 來表示訓練的次數。再來我們看對神經網絡描述的數據定義,來看下面這張圖裏面的數據類型都是 double 型。
圖1
d_in[Data][In] 存儲 Data 個樣本,每個樣本的 In 個輸入。d_out[Data][Out] 存儲 Data 個樣本,每個樣本的 Out 個輸出。我們用鄰接表法來表示 圖1 中的網絡,w[Neuron][In] 表示某個輸入對某個神經元的權重,v[Out][Neuron] 來表示某個神經元對某個輸出的權重;與之對應的保存它們兩個修正量的數組 dw[Neuron][In] 和 dv[Out][Neuron]。數組 o[Neuron] 記錄的是神經元通過激活函數對外的輸出,OutputData[Out] 存儲BP神經網絡的輸出。
程序的執行過程
在這裏,先不考慮具體函數的執行細節,從大體上來介紹程序的執行過程。用偽代碼來表示,具體的內容後面一步步介紹,如下:
主函數main{ 讀取樣本數據 readData(); 初始化BP神經網絡 initBPNework(){ 包括數據的歸一,神經元的初始化 w[Neuron][In]、v[Out][Neuron]等; } BP神經網絡訓練 trainNetwork(){ do{ for(i 小於 樣本容量 Data){ 計算按照第 i 個樣本輸入,產生的BP神經網絡的輸出 computO(i); 累記誤差精度; 反饋調節BP神經網絡中的神經元,完成第 i 個樣本的學習 backUpdate(i); } }while(達到訓練次數 或者 符合誤差精度); } 存儲訓練好的神經元信息 writeNeuron(); 用一些數據來測試,訓練出來的BP神經網絡的結果; return 0; }
以上是處理的流程,對於讀取數據、保存數據之類的處理本文將略去這方面內容,突出主幹部分。
初始化BP神經網絡
初始化主要是涉及兩個方面的功能,一方面是對讀取的訓練樣本數據進行歸一化處理,歸一化處理就是指的就是將數據轉換成0~1之間。在BP神經網絡理論裏面,並沒有對這個進行要求,不過實際實踐過程中,歸一化處理是不可或缺的。因為理論模型沒考慮到,BP神經網絡收斂的速率問題,一般來說神經元的輸出對於0~1之間的數據非常敏感,歸一化能夠顯著提高訓練效率。可以用以下公式來對其進行歸一化,其中 加個常數A 是為了防止出現 0 的情況(0不能為分母)。
y=(x-MinValue+A)/(MaxValue-MinValue+A)
另一方面,就是對神經元的權重進行初始化了,數據歸一到了(0~1)之間,那麽權重初始化為(-1~1)之間的數據,另外對修正量賦值為0。實現參考代碼如下:
void initBPNework(){ int i,j; /* 找到數據最小、最大值 */ for(i=0; i<In; i++){ Minin[i]=Maxin[i]=d_in[0][i]; for(j=0; j<Data; j++) { Maxin[i]=Maxin[i]>d_in[j][i]?Maxin[i]:d_in[j][i]; Minin[i]=Minin[i]<d_in[j][i]?Minin[i]:d_in[j][i]; } } for(i=0; i<Out; i++){ Minout[i]=Maxout[i]=d_out[0][i]; for(j=0; j<Data; j++) { Maxout[i]=Maxout[i]>d_out[j][i]?Maxout[i]:d_out[j][i]; Minout[i]=Minout[i]<d_out[j][i]?Minout[i]:d_out[j][i]; } } /* 歸一化處理 */ for (i = 0; i < In; i++) for(j = 0; j < Data; j++) d_in[j][i]=(d_in[j][i]-Minin[i]+1)/(Maxin[i]-Minin[i]+1); for (i = 0; i < Out; i++) for(j = 0; j < Data; j++) d_out[j][i]=(d_out[j][i]-Minout[i]+1)/(Maxout[i]-Minout[i]+1); /* 初始化神經元 */ for (i = 0; i < Neuron; ++i) for (j = 0; j < In; ++j){ w[i][j]=(rand()*2.0/RAND_MAX-1)/2; dw[i][j]=0; } for (i = 0; i < Neuron; ++i) for (j = 0; j < Out; ++j){ v[j][i]=(rand()*2.0/RAND_MAX-1)/2; dv[j][i]=0; } }
BP神經網絡訓練
這部分應當說是整個BP神經網絡形成的引擎,驅動著樣本訓練過程的執行。由BP神經網絡的基本模型知道,反饋學習機制包括兩大部分,一是BP神經網絡產生預測的結果,二是通過預測的結果和樣本的準確結果進行比對,然後對神經元進行誤差量的修正。因此,我們用兩個函數來表示這樣的兩個過程,訓練過程中還對平均誤差 e 進行監控,如果達到了設定的精度即可完成訓練。由於不一定能夠到達預期設定的精度要求,我們添加一個訓練次數的參數,如果次數達到也退出訓練。實現參考代碼如下:
void trainNetwork(){ int i,c=0; do{ e=0; for (i = 0; i < Data; ++i){ computO(i); e+=fabs((OutputData[0]-d_out[i][0])/d_out[i][0]); backUpdate(i); } //printf("%d %lf\n",c,e/Data); c++; }while(c<TrainC && e/Data>0.01); }
其中的函數,computO(i) (O是output縮寫)計算BP神經網絡預測第 i 個樣本的輸出也就是第一個過程。backUpdate(i) 是根據預測的第 i 個樣本輸出對神經網絡的權重進行更新,e用來監控誤差。
BP神經網絡輸出
函數 computO(i) 負責的是通過BP神經網絡的機制對樣本 i 的輸入,預測其輸出。回想BP神經網絡的基本模型(詳情見 基本模型)對應的公式(1)還有 激活函數對應的公式(2):
在前篇設計的BP神經網絡中,輸入層與隱藏層權重對應的數據結構是w[Neuron][In],隱藏層與輸出層權重對應的數據結構是v[Out][Neuron],並且數組 o[Neuron] 記錄的是神經元通過激活函數對外的輸出,BP神經網絡預測的樣本結果保存在OutputData[Out]中。由此,就可以得到以下實現的參考代碼:
void computO(int var){ int i,j; double sum,y; /* 神經元輸出 */ for (i = 0; i < Neuron; ++i){ sum=0; for (j = 0; j < In; ++j) sum+=w[i][j]*d_in[var][j]; o[i]=1/(1+exp(-1*sum)); } /* 隱藏層到輸出層輸出 */ for (i = 0; i < Out; ++i){ sum=0; for (j = 0; j < Neuron; ++j) sum+=v[i][j]*o[j]; OutputData[i]=sum; } }
BP神經網絡的反饋學習
函數 backUpdate(i) 負責的是將預測輸出的結果與樣本真實的結果進行比對,然後對神經網絡中涉及到的權重進行修正,也這是BP神經網絡實現的關鍵所在。如何求到對於 w[Neuron][In] 和 v[Out][Neuron] 進行修正的誤差量便是關鍵所在!誤差修正量的求法在基本模型一文中數學分析部分有解答,具體問題具體分析,落實到我們設計的這個BP神經網絡上來說,需要得到的是對w[Neuron][In] 和 v[Out][Neuron] 兩個數據進行修正誤差,誤差量用數據結構 dw[Neuron][In] 和 dv[Out][Neuron] 來進行存儲。那麽來分析下這兩個修正誤差量是什麽樣的?推導的思路與基本模型中推導誤差量的一致,這裏僅列出對具體對於我們設計的BP神經網絡中的數學推導過程:
如果你不想知道推導過程,那麽只需要看上面中的兩個 所以(有三個點的地方) 的內容,就可以知道所需要的誤差量是什麽樣的了;如果想要想弄明白的話,或許需要自己在稿子上畫畫看推導推導。到這裏完成了數學推導,實現的代碼就很容易寫了。在具體實現對誤差修改中,我們再加上學習率,並且對先前學習到的修正誤差量進行繼承,直白的說就是都乘上一個0到1之間的數,具體的見如下實現參考代碼:
#define A 0.2 #define B 0.4 #define a 0.2 #define b 0.3
void backUpdate(int var) { int i,j; double t; for (i = 0; i < Neuron; ++i) { t=0; for (j = 0; j < Out; ++j){ t+=(OutputData[j]-d_out[var][j])*v[j][i]; dv[j][i]=A*dv[j][i]+B*(OutputData[j]-d_out[var][j])*o[i]; v[j][i]-=dv[j][i]; } for (j = 0; j < In; ++j){ dw[i][j]=a*dw[i][j]+b*t*o[i]*(1-o[i])*d_in[var][j]; w[i][j]-=dw[i][j]; } } }
好了,至此BP神經網絡的C語言實現就全部完成了。最後,我們可以測試下BP神經網絡的運行。我這裏是這樣給出數據的,兩個輸入a、b(10以內的數),一個輸出 c,c=a+b。換句話說就是教BP神經網絡加法運算。在 45個神經元,820個訓練樣例,樣本平均誤差小於0.01時完成訓練(學習率等見參考代碼)的條件下,最後預測 (6,8),(2.1,7),(4.3,8)實際輸出結果如下:
最後附上參考實現代碼,以及實驗訓練時的數據、和神經元信息。(本示例 僅為BP神經網絡實現的 簡單DEMO,若實際使用還需多加考慮!!!)
bp神經網絡模型推導與c語言實現(轉載)