BP神經網絡

BP神經網絡

起源:線性神經網絡與單層感知器

古老的線性神經網絡,使用的是單層Rosenblatt感知器。該感知器模型已經再也不使用,可是你能夠看到它的改良版:Logistic迴歸。html

 

能夠看到這個網絡,輸入->加權->映射->計算分類偏差->迭代修改W、b,其實和數學上的迴歸擬合別無二致。ios

 

Logistic迴歸對該模型進行了改良:算法

線性神經網絡(迴歸)使用的LMS(最小均方)的數學原理其實可由最大似然估計+假設偏差機率模型獲得。(詳見Andrew Ng視頻)網絡

在二類分類(偏差非0即1)狀況下,適用於連續型數據的最小均方顯然不是很好的cost函數,會引發梯度過大。dom

仿照線性迴歸假設偏差服從正態分佈創建機率模型,Logistic迴歸假設偏差服從二項分佈創建機率模型。ide

Logistic函數的(0~1連續特性)在這裏充當着,由輸入評估機率的角色,而不是像下面的BP網絡同樣,起的是高維空間非線性識別做用。函數

該手法一樣在RBM限制玻爾茲曼機中使用。post

 

實際上,這兩種模型的起源都是最小二乘法的線性迴歸。不一樣的是,早期的解決線性迴歸使用的矩陣解方程組,求得參數。測試

而基於梯度降低使目標函數收斂的數學方法,在計算神經科學領域,就變成神經網絡了。優化

 

 

Part I :BP網絡的結構與工做方式

 

BP網絡中使用隱層(HideLayer)設定,目的是經過全鏈接的網絡+非線性Sigmoid函數,瘋狂地經過特徵空間映射來區分非線性數據。

注意,BP網絡的非線性處理能力主要來自於隱層結構,而不僅僅是非線性激活函數。

因此BP網絡的這種結構稱爲多層感知器(MLP,Multi-layer Perceptron)

從數學上是很難解釋原理的。(與之相對的是SVM支持向量機,最大化間隔數學原理讓你竟無言以對)。

隱層層數和每層的神經元個數決定着網絡複雜度,已有數據代表(單、雙隱層), 每層神經元個數與樣本個數相近,網絡效率最高。

輸出層很特殊,若是須要多類分類的話,須要本身設計輸出方式。經常使用的是經過0/1二進制編碼來設計。

如00、0一、十、11表示不一樣的類,這樣的方式須要log2(K)+1的神經元,儘量減輕整個網絡的壓力,畢竟神經網絡用的是全鏈接。

BP網絡的工做方式分爲兩部分:

①FP(Front Propagation)前向傳播:輸入->線性加權傳至隱層->在隱層Sigmoid並輸出->線性加權傳至輸入層

                                 ->輸入層使用Sigmoid(或線性函數)處理輸入,進行分類,計算偏差並累加到總偏差(目標函數)

②BP(Back Propagation) 反向傳播:從輸入層開始,由當前處理單條數據的偏差,經過梯度法更新Wij、Bij。

                                 因爲網絡的特殊性,此時Wmi、Bmi的更新依賴於Wij、Bij,只能反向更新。

BP網絡訓練數據有兩種方法:

①單樣本串行<相似隨機梯度算法>:按順序/隨機輸入每一個樣本,每次迭代只對一個樣本執行FP、BP。直至單個樣本偏差收斂,退出迭代。

②批樣本並行<相似批梯度算法>:按順序輸入每一個樣本,每次迭代對每一個按順序執行FP、BP,累計總偏差。

                                               執行完畢以後,算一次迭代,繼續從第一個樣本開始,進行第二次迭代。直至總偏差收斂,退出迭代。

因爲串行每次迭代只用了一個樣本,於是總迭代次數應該是並行的M倍,不然偏差很大。

批樣本並行計算能夠直觀看到總偏差,通常用來調參數,確認收斂狀況。

而單樣本串行計算,則更多的是在調完參數後,觀察是否提高正確率。

 

 

Part II:BP過程的公式推導

定義ui,vi,uj,vj,分別是隱層、輸入層的I/O。

Logistic-Sigmoid函數的導數:S(x)=S(x)(1S(x))

輸出層偏差:ej=djvjJ,其中d是真值,v是預測值,因爲輸出層自行設計,因此分類->真值之間須要加工處理。

輸出層總偏差(LMS目標函數):e=12Jj=1e2j

輸出層第j個神經元的輸出:vjJ=S(ujJ)

輸出層第j個神經元的輸入:ujJ=Ii=1(WijviI+bij)

Wij的梯度:eWij=eejejvjJvjJujJuiIWij==ejS(ujJ)viI

求導使用的是鏈式法則(最好複習一遍高數),它的鏈式:e>ej>vjJ>ujJ>Wij

其中eej=ej,ejvjJ=1,vjJujJ=S(ujJ),uiIWij=viI

定義局部梯度:δjJ=eejejvjJvjJujJ=ejS(ujJ) (δjJ將在Wmi更新中使用)。

因而:Wnewij=Woldij+αδjJviI      bnewij=boldij+αδjJ

按照一樣的方式,有Wnewmi=Woldmi+αδiIMm

δiI=euiI的推導比較有趣,其鏈式:(e>ej>vjJ>ujJ>viI)>uiI

注意因爲是全鏈接,因此單I神經元,與後一層所有J神經元有關,借用δj的導數式,括號部分=Jj=1Wijδj

結合上面的推導,有:δiI=Jj=1WijδjviIuiI=Jj=1WijδjS(uiI)

這樣,便可經過先計算δI 、δJ這兩個局部梯度的向量,而後拼成W的完整梯度,進而使用梯度法。

 

Part III:參數調整與動量BP優化

   BP網絡很是吃參數,調整很複雜。於是推薦取100個樣本,先進行小規模調參。

   因爲Sigmoid函數的有效定義域大概是[-3,3],於是,首先對數據進行縮放,控制在[-1,1]內最佳。

   梯度法的參數調整一直是個麻煩。步長α須要根據輸入數據的特徵大小調整,若是特徵數值過大或太小,都會致使Sigmoid函數爆掉而致使沒法迭代收斂。

  在梯度方面,可使用動量BP方法。動量BP法引入動量因子λ (0<λ<1),一般取值0.1~0.8

原更新量變成:ΔWij=αδjJviI=>(1λ)(αδjJviI)+λΔWoldij

動量因子的使用,適當的考慮了前次更新:

①若先後兩次梯度方向相同,因爲梯度值隨着目標函數的減少而減少,於是上一次的較大的梯度值會加大本次梯度混合值。

②若先後兩次梯度方向相反,代表可能在兩個位置有極值,上一次的梯度值能夠抵消本次的部分梯度值,減少更新量。

動量因子的選取要看狀況,一般先取0,而後,逐步增長。過大的動量因子,會致使抵消過大,沒法收斂。

關於W參數初始值:已有論文表示,應當初始化隨機這個範圍的值[46√LayerInput+LayerOut√,46√LayerInput+LayerOut√]

這個和α同樣,得根據數據狀況,自行調節,過大或太小都會致使初始迭代失敗。

注意:BP網絡的W、b初始化很是重要,若是像Logistic迴歸那樣直接設0,  最終迭代出來的網絡就和屎同樣。(好比下面的異或問題)

 

Part IV:測試與代碼

#include "cstdio"
#include "fstream"
#include "iostream"
#include "sstream"
#include "vector"
#include "math.h"
#include "stdlib.h"
using namespace std;
#define Dim dataSet[0].feature.size()
#define M dataSet.size()
#define alpha 0.6
#define delta 0.0000001
#define gamma 0.8
struct Data
{
    vector<double> feature;
    int y;
    Data(vector<double> feature,int y):feature(feature),y(y) {}
};
int HideLayerNum,OutputLayerNum,now_data=0;
vector<Data> dataSet,testSet;
vector<double> u_i,v_i,u_j,v_j;
vector<double> delta_j,delta_i,pdelta_j,pdelta_i;
vector< vector<double> > W_m_i,W_i_j,B_m_i,B_i_j,pW_i_j,pW_m_i;
double random(int f_in,int f_out)
{
    double ret1=rand()%((int)(sqrt(6)/sqrt(f_in+f_out)*100));
    double ret2=rand()%((int)(sqrt(6)/sqrt(f_in+f_out)*100));
    ret1/=100,ret2/=100;
    return ret1-ret2;
}
void read()
{
    ifstream fin("in.txt");
    string line;
    double fea;int cls;
    while(getline(fin,line))
    {
        stringstream sin(line);
        vector<double> feature;
        while(sin>>fea) feature.push_back(fea);
        cls=feature.back();feature.pop_back();
        dataSet.push_back(Data(feature,cls));
    }
    HideLayerNum=M-1; //隱層神經元個數=樣本數-1
    OutputLayerNum=1; //二類分類,一個輸出便可
   for(int i=0;i<Dim;i++) //初始化權W與偏置B
    {
        W_m_i.push_back(vector<double>(HideLayerNum,random(Dim,HideLayerNum)));
        pW_m_i.push_back(vector<double>(HideLayerNum,random(Dim,HideLayerNum)));
        B_m_i.push_back(vector<double>(HideLayerNum,random(Dim,HideLayerNum)));
    }
    for(int i=0;i<HideLayerNum;i++)
    {
       W_i_j.push_back(vector<double>(OutputLayerNum,random(HideLayerNum,OutputLayerNum)));
       pW_i_j.push_back(vector<double>(OutputLayerNum,random(HideLayerNum,OutputLayerNum)));
       B_i_j.push_back(vector<double>(OutputLayerNum,random(HideLayerNum,OutputLayerNum)));
    }
}
double sigmoid(double x) {return exp(x)/(1+exp(x));}
void buildInputLayer() //build inputLayer->hideLayer
{
    u_i.clear(); //re-calc
    v_i.clear();
    for(int i=0;i<HideLayerNum;i++)
    {
        double ret=0.0;
        for(int m=0;m<Dim;m++) ret+=(W_m_i[m][i]*dataSet[now_data].feature[m]+B_m_i[m][i]);
        u_i.push_back(ret);
        v_i.push_back(sigmoid(ret));
    }
}
double buildHideLayer() //build hideLayer->OutputLayer
{
    double error=0.0;
    u_j.clear();
    v_j.clear();
    for(int j=0;j<OutputLayerNum;j++)
    {
        double ret=0.0;
        for(int i=0;i<HideLayerNum;i++) ret+=(W_i_j[i][j]*v_i[i]+B_i_j[i][j]);
        u_j.push_back(ret);
        v_j.push_back(sigmoid(ret));
        error+=(dataSet[now_data].y-sigmoid(ret))*(dataSet[now_data].y-sigmoid(ret));
    }
    return error;
}
double FP()
{
    buildInputLayer();
    return buildHideLayer();
}
void BP()
{
    delta_i.clear();
    delta_j.clear();
    for(int j=0;j<OutputLayerNum;j++)  //calc delta_j=error*sigmoid'(u_j)=error*v_j(1-v_j)
    {
        double error=0.0;
        error+=(dataSet[now_data].y-v_j[j]); //all error;
        delta_j.push_back(v_j[j]*(1-v_j[j])*error);
    }
    for(int i=0;i<HideLayerNum;i++) //calc delta_i=Σ(delta_j*W_i_j)*sigmoid'(u_i)=Σ(delta_j*W_i_j)*v_i(1-v_i)
    {
        double ret=0.0;
        for(int j=0;j<OutputLayerNum;j++) ret+=W_i_j[i][j]*delta_j[j];
        delta_i.push_back(v_i[i]*(1-v_i[i])*ret);
    }
    for(int i=0; i<HideLayerNum; i++) //update W_i_j
        for(int j=0; j<OutputLayerNum; j++)
            {
                    double Delta=alpha*delta_j[j]*v_i[i];
                    W_i_j[i][j]+=(Delta*(1-gamma)+gamma*pW_i_j[i][j]);
                    pW_i_j[i][j]=(Delta*(1-gamma)+gamma*pW_i_j[i][j]);
                    B_i_j[i][j]+=alpha*delta_j[j];
            }
    for(int m=0; m<Dim; m++) //update W_m_i
        for(int i=0; i<HideLayerNum; i++)
            {
                double Delta=alpha*delta_i[i]*dataSet[now_data].feature[m];
                W_m_i[m][i]+=(Delta*(1-gamma)+gamma*pW_m_i[m][i]);
                pW_m_i[m][i]=(Delta*(1-gamma)+gamma*pW_m_i[m][i]);
                B_m_i[m][i]+=alpha*delta_i[i];
            }
}
void classify()
{
    /*
    ifstream fin("testdata.txt");
    dataSet.clear();
    double fea;int cls;
    string line;
    while(getline(fin,line))
    {
        stringstream sin(line);
        vector<double> feature;
        while(sin>>fea) feature.push_back(fea);
        cls=feature.back();feature.pop_back();
        dataSet.push_back(Data(feature,cls));
    }*/
    now_data=0;
    for(int s=0;s<dataSet.size();s++)
    {
        buildInputLayer();
        double ret=0.0;
        for(int j=0;j<OutputLayerNum;j++)
          for(int i=0;i<HideLayerNum;i++)
            ret+=(W_i_j[i][j]*v_i[i]+B_i_j[i][j]);
        printf("test%d: origin:%d test:%lf\n",now_data++,dataSet[s].y,sigmoid(ret));
    }
}
double iterProcess()
{
    double err=0.0;
    //random
    /*
    now_data=rand()%M;
    err=FP();
    BP();*/
    //batch
    now_data=0;
    for(int i=0;i<M;i++)
    {
        err+=FP();
        BP();
        now_data++;
    }
    return err;
}
int main()
{
    double oldLw,newLw;
    int iter=0;
    read();
    oldLw=iterProcess();
    newLw=iterProcess();
    while(fabs(oldLw-newLw)>delta)
    {
        oldLw=newLw;
        newLw=iterProcess();
        iter++;
        if(iter%1000==0) printf("iter:%d %lf\n",iter,newLw);
    }
    cout<<iter<<endl<<endl;
    classify();
}

複製代碼
#include "cstdio"
#include "fstream"
#include "iostream"
#include "sstream"
#include "vector"
#include "math.h"
#include "stdlib.h"
using namespace std;
#define Dim dataSet[0].feature.size()
#define M dataSet.size()
#define alpha 0.6
#define delta 0.0000001
#define gamma 0.8
struct Data
{
    vector<double> feature;
    int y;
    Data(vector<double> feature,int y):feature(feature),y(y) {}
};
int HideLayerNum,OutputLayerNum,now_data=0;
vector<Data> dataSet,testSet;
vector<double> u_i,v_i,u_j,v_j;
vector<double> delta_j,delta_i,pdelta_j,pdelta_i;
vector< vector<double> > W_m_i,W_i_j,B_m_i,B_i_j,pW_i_j,pW_m_i;
double random(int f_in,int f_out)
{
    double ret1=rand()%((int)(sqrt(6)/sqrt(f_in+f_out)*100));
    double ret2=rand()%((int)(sqrt(6)/sqrt(f_in+f_out)*100));
    ret1/=100,ret2/=100;
    return ret1-ret2;
}
void read()
{
    ifstream fin("in.txt");
    string line;
    double fea;int cls;
    while(getline(fin,line))
    {
        stringstream sin(line);
        vector<double> feature;
        while(sin>>fea) feature.push_back(fea);
        cls=feature.back();feature.pop_back();
        dataSet.push_back(Data(feature,cls));
    }
    HideLayerNum=M-1; //隱層神經元個數=樣本數-1
    OutputLayerNum=1; //二類分類,一個輸出便可
   for(int i=0;i<Dim;i++) //初始化權W與偏置B
    {
        W_m_i.push_back(vector<double>(HideLayerNum,random(Dim,HideLayerNum)));
        pW_m_i.push_back(vector<double>(HideLayerNum,random(Dim,HideLayerNum)));
        B_m_i.push_back(vector<double>(HideLayerNum,random(Dim,HideLayerNum)));
    }
    for(int i=0;i<HideLayerNum;i++)
    {
       W_i_j.push_back(vector<double>(OutputLayerNum,random(HideLayerNum,OutputLayerNum)));
       pW_i_j.push_back(vector<double>(OutputLayerNum,random(HideLayerNum,OutputLayerNum)));
       B_i_j.push_back(vector<double>(OutputLayerNum,random(HideLayerNum,OutputLayerNum)));
    }
}
double sigmoid(double x) {return exp(x)/(1+exp(x));}
void buildInputLayer() //build inputLayer->hideLayer
{
    u_i.clear(); //re-calc
    v_i.clear();
    for(int i=0;i<HideLayerNum;i++)
    {
        double ret=0.0;
        for(int m=0;m<Dim;m++) ret+=(W_m_i[m][i]*dataSet[now_data].feature[m]+B_m_i[m][i]);
        u_i.push_back(ret);
        v_i.push_back(sigmoid(ret));
    }
}
double buildHideLayer() //build hideLayer->OutputLayer
{
    double error=0.0;
    u_j.clear();
    v_j.clear();
    for(int j=0;j<OutputLayerNum;j++)
    {
        double ret=0.0;
        for(int i=0;i<HideLayerNum;i++) ret+=(W_i_j[i][j]*v_i[i]+B_i_j[i][j]);
        u_j.push_back(ret);
        v_j.push_back(sigmoid(ret));
        error+=(dataSet[now_data].y-sigmoid(ret))*(dataSet[now_data].y-sigmoid(ret));
    }
    return error;
}
double FP()
{
    buildInputLayer();
    return buildHideLayer();
}
void BP()
{
    delta_i.clear();
    delta_j.clear();
    for(int j=0;j<OutputLayerNum;j++)  //calc delta_j=error*sigmoid'(u_j)=error*v_j(1-v_j)
    {
        double error=0.0;
        error+=(dataSet[now_data].y-v_j[j]); //all error;
        delta_j.push_back(v_j[j]*(1-v_j[j])*error);
    }
    for(int i=0;i<HideLayerNum;i++) //calc delta_i=Σ(delta_j*W_i_j)*sigmoid'(u_i)=Σ(delta_j*W_i_j)*v_i(1-v_i)
    {
        double ret=0.0;
        for(int j=0;j<OutputLayerNum;j++) ret+=W_i_j[i][j]*delta_j[j];
        delta_i.push_back(v_i[i]*(1-v_i[i])*ret);
    }
    for(int i=0; i<HideLayerNum; i++) //update W_i_j
        for(int j=0; j<OutputLayerNum; j++)
            {
                    double Delta=alpha*delta_j[j]*v_i[i];
                    W_i_j[i][j]+=(Delta*(1-gamma)+gamma*pW_i_j[i][j]);
                    pW_i_j[i][j]=(Delta*(1-gamma)+gamma*pW_i_j[i][j]);
                    B_i_j[i][j]+=alpha*delta_j[j];
            }
    for(int m=0; m<Dim; m++) //update W_m_i
        for(int i=0; i<HideLayerNum; i++)
            {
                double Delta=alpha*delta_i[i]*dataSet[now_data].feature[m];
                W_m_i[m][i]+=(Delta*(1-gamma)+gamma*pW_m_i[m][i]);
                pW_m_i[m][i]=(Delta*(1-gamma)+gamma*pW_m_i[m][i]);
                B_m_i[m][i]+=alpha*delta_i[i];
            }
}
void classify()
{
    /*
    ifstream fin("testdata.txt");
    dataSet.clear();
    double fea;int cls;
    string line;
    while(getline(fin,line))
    {
        stringstream sin(line);
        vector<double> feature;
        while(sin>>fea) feature.push_back(fea);
        cls=feature.back();feature.pop_back();
        dataSet.push_back(Data(feature,cls));
    }*/
    now_data=0;
    for(int s=0;s<dataSet.size();s++)
    {
        buildInputLayer();
        double ret=0.0;
        for(int j=0;j<OutputLayerNum;j++)
          for(int i=0;i<HideLayerNum;i++)
            ret+=(W_i_j[i][j]*v_i[i]+B_i_j[i][j]);
        printf("test%d: origin:%d test:%lf\n",now_data++,dataSet[s].y,sigmoid(ret));
    }
}
double iterProcess()
{
    double err=0.0;
    //random
    /*
    now_data=rand()%M;
    err=FP();
    BP();*/
    //batch
    now_data=0;
    for(int i=0;i<M;i++)
    {
        err+=FP();
        BP();
        now_data++;
    }
    return err;
}
int main()
{
    double oldLw,newLw;
    int iter=0;
    read();
    oldLw=iterProcess();
    newLw=iterProcess();
    while(fabs(oldLw-newLw)>delta)
    {
        oldLw=newLw;
        newLw=iterProcess();
        iter++;
        if(iter%1000==0) printf("iter:%d %lf\n",iter,newLw);
    }
    cout<<iter<<endl<<endl;
    classify();
}
複製代碼

本身測試,推薦使用著名的異或問題數據集,該數據只有四個點[0,0]、[1,0]、[0,1]、[1,1] ,分類[0,1,1,0]

線性分類器準確沒法分類,緣由參見:http://www.guokr.com/blog/793310/

使用α=0.6λ=0.8(動量因子)

若是你的BP網絡代碼沒錯的話,並行9000次,  單次迭代全部的樣本SSE(偏差平方和)大概是這樣。

 很好地解決了,這個線性不可分問題。

相關文章
相關標籤/搜索