http://blog.csdn.net/runatworld/article/details/50774215
BP神經(jīng)網(wǎng)絡(luò)
2016-03-01 17:27 271人閱讀 評(píng)論(0) 收藏 舉報(bào)
分類: 今天來(lái)講BP神經(jīng)網(wǎng)絡(luò),神經(jīng)網(wǎng)絡(luò)在機(jī)器學(xué)習(xí)中應(yīng)用比較廣泛,比如函數(shù)逼近,模式識(shí)別,分類,數(shù)據(jù)壓縮,數(shù)據(jù)
挖掘等領(lǐng)域。接下來(lái)介紹BP神經(jīng)網(wǎng)絡(luò)的原理及實(shí)現(xiàn)。
Contents
1. BP神經(jīng)網(wǎng)絡(luò)的認(rèn)識(shí)
2. 隱含層的選取
3. 正向傳遞子過(guò)程
4. 反向傳遞子過(guò)程
5. BP神經(jīng)網(wǎng)絡(luò)的注意點(diǎn)
6. BP神經(jīng)網(wǎng)絡(luò)的C++實(shí)現(xiàn)
1. BP神經(jīng)網(wǎng)絡(luò)的認(rèn)識(shí)
BP(Back PRopagation)神經(jīng)網(wǎng)絡(luò)分為兩個(gè)過(guò)程
(1)工作信號(hào)正向傳遞子過(guò)程
(2)誤差信號(hào)反向傳遞子過(guò)程
在BP神經(jīng)網(wǎng)絡(luò)中,單個(gè)樣本有
個(gè)輸入,有
個(gè)輸出,在輸入層和輸出層之間通常還有若干個(gè)隱含層。實(shí)際
上,1989年Robert Hecht-Nielsen證明了對(duì)于任何閉區(qū)間內(nèi)的一個(gè)連續(xù)函數(shù)都可以用一個(gè)隱含層的BP網(wǎng)
絡(luò)來(lái)逼近,這就是萬(wàn)能逼近定理。所以一個(gè)三層的BP網(wǎng)絡(luò)就可以完成任意的
維到
維的映射。即這三層分
別是輸入層(I),隱含層(H),輸出層(O)。如下圖示

2. 隱含層的選取
在BP神經(jīng)網(wǎng)絡(luò)中,輸入層和輸出層的節(jié)點(diǎn)個(gè)數(shù)都是確定的,而隱含層節(jié)點(diǎn)個(gè)數(shù)不確定,那么應(yīng)該設(shè)置為多少
才合適呢?實(shí)際上,隱含層節(jié)點(diǎn)個(gè)數(shù)的多少對(duì)神經(jīng)網(wǎng)絡(luò)的性能是有影響的,有一個(gè)經(jīng)驗(yàn)公式可以確定隱含層
節(jié)點(diǎn)數(shù)目,如下

其中
為隱含層節(jié)點(diǎn)數(shù)目,
為輸入層節(jié)點(diǎn)數(shù)目,
為輸出層節(jié)點(diǎn)數(shù)目,
為
之間的調(diào)節(jié)常數(shù)。
3. 正向傳遞子過(guò)程
現(xiàn)在設(shè)節(jié)點(diǎn)
和節(jié)點(diǎn)
之間的權(quán)值為
,節(jié)點(diǎn)
的閥值為
,每個(gè)節(jié)點(diǎn)的輸出值為
,而每個(gè)節(jié)點(diǎn)的輸出
值是根據(jù)上層所有節(jié)點(diǎn)的輸出值、當(dāng)前節(jié)點(diǎn)與上一層所有節(jié)點(diǎn)的權(quán)值和當(dāng)前節(jié)點(diǎn)的閥值還有激活函數(shù)來(lái)實(shí)現(xiàn)
的。具體計(jì)算方法如下

其中
為激活函數(shù),一般選取S型函數(shù)或者線性函數(shù)。
正向傳遞的過(guò)程比較簡(jiǎn)單,按照上述公式計(jì)算即可。在BP神經(jīng)網(wǎng)絡(luò)中,輸入層節(jié)點(diǎn)沒(méi)有閥值。
4. 反向傳遞子過(guò)程
在BP神經(jīng)網(wǎng)絡(luò)中,誤差信號(hào)反向傳遞子過(guò)程比較復(fù)雜,它是基于Widrow-Hoff學(xué)習(xí)規(guī)則的。假設(shè)輸出層
的所有結(jié)果為
,誤差函數(shù)如下

而BP神經(jīng)網(wǎng)絡(luò)的主要目的是反復(fù)修正權(quán)值和閥值,使得誤差函數(shù)值達(dá)到最小。Widrow-Hoff學(xué)習(xí)規(guī)則
是通過(guò)沿著相對(duì)誤差平方和的最速下降方向,連續(xù)調(diào)整網(wǎng)絡(luò)的權(quán)值和閥值,根據(jù)梯度下降法,權(quán)值矢量
的修正正比于當(dāng)前位置上E(w,b)的梯度,對(duì)于第
個(gè)輸出節(jié)點(diǎn)有

假設(shè)選擇激活函數(shù)為

對(duì)激活函數(shù)求導(dǎo),得到

那么接下來(lái)針對(duì)
有

其中有

同樣對(duì)于
有

這就是著名的
學(xué)習(xí)規(guī)則,通過(guò)改變神經(jīng)元之間的連接權(quán)值來(lái)減少系統(tǒng)實(shí)際輸出和期望輸出的誤差,這個(gè)規(guī)
則又叫做Widrow-Hoff學(xué)習(xí)規(guī)則或者糾錯(cuò)學(xué)習(xí)規(guī)則。
上面是對(duì)隱含層和輸出層之間的權(quán)值和輸出層的閥值計(jì)算調(diào)整量,而針對(duì)輸入層和隱含層和隱含層的閥值調(diào)
整量的計(jì)算更為復(fù)雜。假設(shè)
是輸入層第k個(gè)節(jié)點(diǎn)和隱含層第i個(gè)節(jié)點(diǎn)之間的權(quán)值,那么有

其中有

這樣對(duì)
學(xué)習(xí)規(guī)則理解更為深刻了吧。
有了上述公式,根據(jù)梯度下降法,那么對(duì)于隱含層和輸出層之間的權(quán)值和閥值調(diào)整如下

而對(duì)于輸入層和隱含層之間的權(quán)值和閥值調(diào)整同樣有

至此BP神經(jīng)網(wǎng)絡(luò)的原理基本講完。
5. BP神經(jīng)網(wǎng)絡(luò)的注意點(diǎn)
BP神經(jīng)網(wǎng)絡(luò)一般用于分類或者逼近問(wèn)題。如果用于分類,則激活函數(shù)一般選用Sigmoid函數(shù)或者硬極限函
數(shù),如果用于函數(shù)逼近,則輸出層節(jié)點(diǎn)用線性函數(shù),即
。
BP神經(jīng)網(wǎng)絡(luò)在訓(xùn)練數(shù)據(jù)時(shí)可以采用增量學(xué)習(xí)或者批量學(xué)習(xí)。
增量學(xué)習(xí)要求輸入模式要有足夠的隨機(jī)性,對(duì)輸入模式的噪聲比較敏感,即對(duì)于劇烈變化的輸入模式,訓(xùn)
練效果比較差,適合在線處理。批量學(xué)習(xí)不存在輸入模式次序問(wèn)題,穩(wěn)定性好,但是只適合離線處理。
標(biāo)準(zhǔn)BP神經(jīng)網(wǎng)絡(luò)的缺陷:
(1)容易形成局部極小值而得不到全局最優(yōu)值。
BP神經(jīng)網(wǎng)絡(luò)中極小值比較多,所以很容易陷入局部極小值,這就要求對(duì)初始權(quán)值和閥值有要求,要使
得初始權(quán)值和閥值隨機(jī)性足夠好,可以多次隨機(jī)來(lái)實(shí)現(xiàn)。
(2)訓(xùn)練次數(shù)多使得學(xué)習(xí)效率低,收斂速度慢。
(3)隱含層的選取缺乏理論的指導(dǎo)。
(4)訓(xùn)練時(shí)學(xué)習(xí)新樣本有遺忘舊樣本的趨勢(shì)。
BP算法的改進(jìn):
(1)增加動(dòng)量項(xiàng)
引入動(dòng)量項(xiàng)是為了加速算法收斂,即如下公式

動(dòng)量因子
一般選取
。
(2)自適應(yīng)調(diào)節(jié)學(xué)習(xí)率
(3)引入陡度因子
通常BP神經(jīng)網(wǎng)絡(luò)在訓(xùn)練之前會(huì)對(duì)數(shù)據(jù)歸一化處理,即將數(shù)據(jù)映射到更小的區(qū)間內(nèi),比如[0,1]或[-1,1]。
6. BP神經(jīng)網(wǎng)絡(luò)的C++實(shí)現(xiàn)
BP神經(jīng)網(wǎng)絡(luò)的C++文件如下

BP.h:
[cpp] view plain copy 
#ifndef _BP_H_ #define _BP_H_ #include <vector> #define LAYER 3 //三層神經(jīng)網(wǎng)絡(luò) #define NUM 10 //每層的最多節(jié)點(diǎn)數(shù) #define A 30.0 #define B 10.0 //A和B是S型函數(shù)的參數(shù) #define ITERS 1000 //最大訓(xùn)練次數(shù) #define ETA_W 0.0035 //權(quán)值調(diào)整率 #define ETA_B 0.001 //閥值調(diào)整率 #define ERROR 0.002 //單個(gè)樣本允許的誤差 #define ACCU 0.005 //每次迭代允許的誤差 #define Type double #define Vector std::vector struct Data { Vector<Type> x; //輸入數(shù)據(jù) Vector<Type> y; //輸出數(shù)據(jù) }; class BP{ public: void GetData(const Vector<Data>); void Train(); Vector<Type> ForeCast(const Vector<Type>); private: void InitNetWork(); //初始化網(wǎng)絡(luò) void GetNums(); //獲取輸入、輸出和隱含層節(jié)點(diǎn)數(shù) void ForwardTransfer(); //正向傳播子過(guò)程 void ReverseTransfer(int); //逆向傳播子過(guò)程 void CalcDelta(int); //計(jì)算w和b的調(diào)整量 void UpdateNetWork(); //更新權(quán)值和閥值 Type GetError(int); //計(jì)算單個(gè)樣本的誤差 Type GetAccu(); //計(jì)算所有樣本的精度 Type Sigmoid(const Type); //計(jì)算Sigmoid的值 private: int in_num; //輸入層節(jié)點(diǎn)數(shù) int ou_num; //輸出層節(jié)點(diǎn)數(shù) int hd_num; //隱含層節(jié)點(diǎn)數(shù) Vector<Data> data; //輸入輸出數(shù)據(jù) Type w[LAYER][NUM][NUM]; //BP網(wǎng)絡(luò)的權(quán)值 Type b[LAYER][NUM]; //BP網(wǎng)絡(luò)節(jié)點(diǎn)的閥值 Type x[LAYER][NUM]; //每個(gè)神經(jīng)元的值經(jīng)S型函數(shù)轉(zhuǎn)化后的輸出值,輸入層就為原值 Type d[LAYER][NUM]; //記錄delta學(xué)習(xí)規(guī)則中delta的值 }; #endif //_BP_H_ BP.cpp:
[cpp] view%20plain copy ![在CODE上查看代碼片]()
#include <string.h> #include <stdio.h> #include <math.h> #include <assert.h> #include "BP.h" //獲取訓(xùn)練所有樣本數(shù)據(jù) void BP::GetData(const Vector<Data> _data) { data = _data; } //開始進(jìn)行訓(xùn)練 void BP::Train() { printf("Begin to train BP NetWork!/n"); GetNums(); InitNetWork(); int num = data.size(); for(int iter = 0; iter <= ITERS; iter++) { for(int cnt = 0; cnt < num; cnt++) { //第一層輸入節(jié)點(diǎn)賦值 for(int i = 0; i < in_num; i++) x[0][i] = data.at(cnt).x[i]; while(1) { ForwardTransfer(); if(GetError(cnt) < ERROR) //如果誤差比較小,則針對(duì)單個(gè)樣本跳出循環(huán) break; ReverseTransfer(cnt); } } printf("This is the %d th trainning NetWork !/n", iter); Type accu = GetAccu(); printf("All Samples Accuracy is %lf/n", accu); if(accu < ACCU) break; } printf("The BP NetWork train End!/n"); } //根據(jù)訓(xùn)練好的網(wǎng)絡(luò)來(lái)預(yù)測(cè)輸出值 Vector<Type> BP::ForeCast(const Vector<Type> data) { int n = data.size(); assert(n == in_num); for(int i = 0; i < in_num; i++) x[0][i] = data[i]; ForwardTransfer(); Vector<Type> v; for(int i = 0; i < ou_num; i++) v.push_back(x[2][i]); return v; } //獲取網(wǎng)絡(luò)節(jié)點(diǎn)數(shù) void BP::GetNums() { in_num = data[0].x.size(); //獲取輸入層節(jié)點(diǎn)數(shù) ou_num = data[0].y.size(); //獲取輸出層節(jié)點(diǎn)數(shù) hd_num = (int)sqrt((in_num + ou_num) * 1.0) + 5; //獲取隱含層節(jié)點(diǎn)數(shù) if(hd_num > NUM) hd_num = NUM; //隱含層數(shù)目不能超過(guò)最大設(shè)置 } //初始化網(wǎng)絡(luò) void BP::InitNetWork() { memset(w, 0, sizeof(w)); //初始化權(quán)值和閥值為0,也可以初始化隨機(jī)值 memset(b, 0, sizeof(b)); } //工作信號(hào)正向傳遞子過(guò)程 void BP::ForwardTransfer() { //計(jì)算隱含層各個(gè)節(jié)點(diǎn)的輸出值 for(int j = 0; j < hd_num; j++) { Type t = 0; for(int i = 0; i < in_num; i++) t += w[1][i][j] * x[0][i]; t += b[1][j]; x[1][j] = Sigmoid(t); } //計(jì)算輸出層各節(jié)點(diǎn)的輸出值 for(int j = 0; j < ou_num; j++) { Type t = 0; for(int i = 0; i < hd_num; i++) t += w[2][i][j] * x[1][i]; t += b[2][j]; x[2][j] = Sigmoid(t); } } //計(jì)算單個(gè)樣本的誤差 Type BP::GetError(int cnt) { Type ans = 0; for(int i = 0; i < ou_num; i++) ans += 0.5 * (x[2][i] - data.at(cnt).y[i]) * (x[2][i] - data.at(cnt).y[i]); return ans; } //誤差信號(hào)反向傳遞子過(guò)程 void BP::ReverseTransfer(int cnt) { CalcDelta(cnt); UpdateNetWork(); } //計(jì)算所有樣本的精度 Type BP::GetAccu() { Type ans = 0; int num = data.size(); for(int i = 0; i < num; i++) { int m = data.at(i).x.size(); for(int j = 0; j < m; j++) x[0][j] = data.at(i).x[j]; ForwardTransfer(); int n = data.at(i).y.size(); for(int j = 0; j < n; j++) ans += 0.5 * (x[2][j] - data.at(i).y[j]) * (x[2][j] - data.at(i).y[j]); } return ans / num; } //計(jì)算調(diào)整量 void BP::CalcDelta(int cnt) { //計(jì)算輸出層的delta值 for(int i = 0; i < ou_num; i++) d[2][i] = (x[2][i] - data.at(cnt).y[i]) * x[2][i] * (A - x[2][i]) / (A * B); //計(jì)算隱含層的delta值 for(int i = 0; i < hd_num; i++) { Type t = 0; for(int j = 0; j < ou_num; j++) t += w[2][i][j] * d[2][j]; d[1][i] = t * x[1][i] * (A - x[1][i]) / (A * B); } } //根據(jù)計(jì)算出的調(diào)整量對(duì)BP網(wǎng)絡(luò)進(jìn)行調(diào)整 void BP::UpdateNetWork() { //隱含層和輸出層之間權(quán)值和閥值調(diào)整 for(int i = 0; i < hd_num; i++) { for(int j = 0; j < ou_num; j++) w[2][i][j] -= ETA_W * d[2][j] * x[1][i]; } for(int i = 0; i < ou_num; i++) b[2][i] -= ETA_B * d[2][i]; //輸入層和隱含層之間權(quán)值和閥值調(diào)整 for(int i = 0; i < in_num; i++) { for(int j = 0; j < hd_num; j++) w[1][i][j] -= ETA_W * d[1][j] * x[0][i]; } for(int i = 0; i < hd_num; i++) b[1][i] -= ETA_B * d[1][i]; } //計(jì)算Sigmoid函數(shù)的值 Type BP::Sigmoid(const Type x) { return A / (1 + exp(-x / B)); } Test.cpp:
[cpp] view%20plain copy ![在CODE上查看代碼片]()
#include <iostream> #include <string.h> #include <stdio.h> #include "BP.h" using namespace std; double sample[41][4]= { {0,0,0,0}, {5,1,4,19.020}, {5,3,3,14.150}, {5,5,2,14.360}, {5,3,3,14.150}, {5,3,2,15.390}, {5,3,2,15.390}, {5,5,1,19.680}, {5,1,2,21.060}, {5,3,3,14.150}, {5,5,4,12.680}, {5,5,2,14.360}, {5,1,3,19.610}, {5,3,4,13.650}, {5,5,5,12.430}, {5,1,4,19.020}, {5,1,4,19.020}, {5,3,5,13.390}, {5,5,4,12.680}, {5,1,3,19.610}, {5,3,2,15.390}, {1,3,1,11.110}, {1,5,2,6.521}, {1,1,3,10.190}, {1,3,4,6.043}, {1,5,5,5.242}, {1,5,3,5.724}, {1,1,4,9.766}, {1,3,5,5.870}, {1,5,4,5.406}, {1,1,3,10.190}, {1,1,5,9.545}, {1,3,4,6.043}, {1,5,3,5.724}, {1,1,2,11.250}, {1,3,1,11.110}, {1,3,3,6.380}, {1,5,2,6.521}, {1,1,1,16.000}, {1,3,2,7.219}, {1,5,3,5.724} }; int main() { Vector<Data> data; for(int i = 0; i < 41; i++) { Data t; for(int j = 0; j < 3; j++) t.x.push_back(sample[i][j]); t.y.push_back(sample[i][3]); data.push_back(t); } BP *bp = new BP(); bp->GetData(data); bp->Train(); while(1) { Vector<Type> in; for(int i = 0; i < 3; i++) { Type v; scanf("%lf", &v); in.push_back(v); } Vector<Type> ou; ou = bp->ForeCast(in); printf("%lf/n", ou[0]); } return 0; }
Makefile:
[cpp] view%20plain copy ![在CODE上查看代碼片]()
Test : BP.h BP.cpp Test.cpp g++ BP.cpp Test.cpp -o Test clean: rm Test