BP神经网络

来源:互联网 发布:局域网电影服务器软件 编辑:程序博客网 时间:2024/04/29 11:26

神经网络基本结构:

人工神经网络由神经元模型构成,这种由许多神经元组成的信息处理网络具有并行分布结构。每个神经元具有单一输出,并且能够与其它神经元连接;存在许多(多重)输出连接方法,每种连接方法对应一个连接权系数。可把 ANN 看成是以处理单元 PE(processing element) 为节点,用加权有向弧(链)相互连接而成的有向图。令来自其它处理单元(神经元)i的信息为Xi,它们与本处理单元的互相作用强度为 Wi,i=0,1,…,n-1,处理单元的内部阈值为 θ。那么本神经元的输入为:

而处理单元的输出为:

式中,xi为第 i 个元素的输入,wi 为第 i 个元素与本处理单元的互联权重。f 称为激发函数(activation function)或作用函数。它决定节点(神经元)的输出。该输出为 1 或 0 取决于其输入之和大于或小于内部阈值 θ。

下图所示神经元单元由多个输入Xi,i=1,2,...,n和一个输出y组成。中间状态由输入信号的权和表示,而输出为:

训练网络

神经网络结构被设计完成,有了输入、输出参数后,我们就要对网络进行训练。神经网络的训练有包括感知器训练、delta 规则训练和反向传播算法等训练,其中感知器训练是基础。

感知器和 delta 训练规则

理解神经网络的第一步是从对抽象生物神经开始,本文用到的人工神经网络系统是以被称为感知器的单元为基础,如图所示。感知器以一个实数值向量作为输入,计算这些输入的线性组合,如果结果大于某个阈值,就输出 1,否则输出 -1,如果 x 从 1 到 n,则感知器计算公式如下:

其中每个 wi 是一个实数常量,或叫做权值,用来决定输入 xi 对感知器输出的贡献率。特别地,-w0是阈值。

尽管当训练样例线性可分时,感知器法则可以成功地找到一个权向量,但如果样例不是线性可分时它将不能收敛,因此人们设计了另一个训练法则来克服这个不足,这个训练规则叫做 delta 规则。感知器训练规则是基于这样一种思路--权系数的调整是由目标和输出的差分方程表达式决定。而 delta 规则是基于梯度降落这样一种思路。这个复杂的数学概念可以举个简单的例子来表示。从给定的几点来看,向南的那条路径比向东那条更陡些。向东就像从悬崖上掉下来,但是向南就是沿着一个略微倾斜的斜坡下来,向西象登一座陡峭的山,而北边则到了平地,只要慢慢的闲逛就可以了。所以您要寻找的是到达平地的所有路径中将陡峭的总和减少到最小的路径。在权系数的调整中,神经网络将会找到一种将误差减少到最小的权系数的分配方式。这部分我们不做详细介绍,如有需要大家可参考相关的人工智能书籍。

反向传播算法

人工神经网络学习为学习实数值和向量值函数提供了一种实际的方法,对于连续的和离散的属性都可以使用。并且对训练数据中的噪声具有很好的健壮性。反向传播算法是最常见的网络学习算法。这是我们所知用来训练神经网络很普遍的方法,反向传播算法是一种具有很强学习能力的系统,结构比较简单,且易于编程。

鲁梅尔哈特(Rumelhart)和麦克莱兰(Meclelland)于 1985 年发展了 BP 网络学习算法,实现了明斯基的多层网络设想。BP网络不仅含有输入节点和输出节点,而且含有一层或多层隐(层)节点。输入信号先向前传递到隐藏节点,经过作用后,再把隐藏节点的输出信息传递到输出节点,最后给出输出结果。节点的激发函数一般选用 S 型函数。

反向传播(back-propagation,BP)算法是一种计算单个权值变化引起网络性能变化值的较为简单的方法。由于BP算法过程包含从输出节点开始,反向地向第一隐含层(即最接近输入层的隐含层)传播由总误差引起的权值修正,所以称为"反向传播"。反向传播特性与所求解问题的性质和所作细节选择有极为密切的关系。

对于由一系列确定的单元互连形成的多层网络,反向传播算法可用来学习这个多层网络的权值。它采用梯度下降方法试图最小化网络输出值和目标值之间的误差平方,因为我们要考虑多个输出单元的网络,而不是像以前只考虑单个单元,所以我们要重新计算误差E,以便对所有网络输出的误差求和:

Outpus 是网络输出单元的集合,tkd 和 okd 是与训练样例 d 和第 k 个输出单元的相关输出值.

反向传播算法的一个迷人特性是:它能够在网络内部的隐藏层发现有用的中间表示:

1.训练样例仅包含网络输入和输出,权值调节的过程可以自由地设置权值,来定义任何隐藏单元表示,这些隐藏单元表示在使误差E达到最小时最有效。

2.引导反向传播算法定义新的隐藏层特征,这些特征在输入中没有明确表示出来,但能捕捉输入实例中与学习目标函数最相关的特征

反向传播训练神经元的算法如下:

//将三位二进制数转为一位十进制数#include <iostream>#include <cmath>using namespace std;#define  innode 3  //输入结点数#define  hidenode 10//隐含结点数#define  outnode 1 //输出结点数#define  trainsample 8//BP训练样本数class BpNet{public:    void train(double p[trainsample][innode ],double t[trainsample][outnode]);//Bp训练    double p[trainsample][innode];     //输入的样本    double t[trainsample][outnode];    //样本要输出的    double *recognize(double *p);//Bp识别    void writetrain(); //写训练完的权值    void readtrain(); //读训练好的权值,这使的不用每次去训练了,只要把训练最好的权值存下来就OK    BpNet();    virtual ~BpNet();public:    void init();    double w[innode][hidenode];//隐含结点权值    double w1[hidenode][outnode];//输出结点权值    double b1[hidenode];//隐含结点阀值    double b2[outnode];//输出结点阀值    double rate_w; //权值学习率(输入层-隐含层)    double rate_w1;//权值学习率 (隐含层-输出层)    double rate_b1;//隐含层阀值学习率    double rate_b2;//输出层阀值学习率    double e;//误差计算    double error;//允许的最大误差    double result[outnode];// Bp输出};BpNet::BpNet(){    error=1.0;    e=0.0;    rate_w=0.9;  //权值学习率(输入层--隐含层)    rate_w1=0.9; //权值学习率 (隐含层--输出层)    rate_b1=0.9; //隐含层阀值学习率    rate_b2=0.9; //输出层阀值学习率}BpNet::~BpNet(){}void winit(double w[],int n) //权值初始化{  for(int i=0;i<n;i++)    w[i]=(2.0*(double)rand()/RAND_MAX)-1;}void BpNet::init(){    winit((double*)w,innode*hidenode);    winit((double*)w1,hidenode*outnode);    winit(b1,hidenode);    winit(b2,outnode);}void BpNet::train(double p[trainsample][innode],double t[trainsample][outnode]){    double pp[hidenode];//隐含结点的校正误差    double qq[outnode];//希望输出值与实际输出值的偏差    double yd[outnode];//希望输出值    double x[innode]; //输入向量    double x1[hidenode];//隐含结点状态值    double x2[outnode];//输出结点状态值    double o1[hidenode];//隐含层激活值    double o2[hidenode];//输出层激活值    for(int isamp=0;isamp<trainsample;isamp++)//循环训练一次样品    {        for(int i=0;i<innode;i++)            x[i]=p[isamp][i]; //输入的样本        for(int i=0;i<outnode;i++)            yd[i]=t[isamp][i]; //期望输出的样本        //构造每个样品的输入和输出标准        for(int j=0;j<hidenode;j++)        {            o1[j]=0.0;            for(int i=0;i<innode;i++)                o1[j]=o1[j]+w[i][j]*x[i];//隐含层各单元输入激活值            x1[j]=1.0/(1+exp(-o1[j]-b1[j]));//隐含层各单元的输出            //    if(o1[j]+b1[j]>0) x1[j]=1;            //else x1[j]=0;        }        for(int k=0;k<outnode;k++)        {            o2[k]=0.0;            for(int j=0;j<hidenode;j++)                o2[k]=o2[k]+w1[j][k]*x1[j]; //输出层各单元输入激活值            x2[k]=1.0/(1.0+exp(-o2[k]-b2[k])); //输出层各单元输出            //    if(o2[k]+b2[k]>0) x2[k]=1;            //    else x2[k]=0;        }        for(int k=0;k<outnode;k++)        {            qq[k]=(yd[k]-x2[k])*x2[k]*(1-x2[k]); //希望输出与实际输出的偏差            for(int j=0;j<hidenode;j++)                w1[j][k]+=rate_w1*qq[k]*x1[j];  //下一次的隐含层和输出层之间的新连接权        }        for(int j=0;j<hidenode;j++)        {            pp[j]=0.0;            for(int k=0;k<outnode;k++)                pp[j]=pp[j]+qq[k]*w1[j][k];            pp[j]=pp[j]*x1[j]*(1-x1[j]); //隐含层的校正误差            for(int i=0;i<innode;i++)                w[i][j]+=rate_w*pp[j]*x[i]; //下一次的输入层和隐含层之间的新连接权        }        for(int k=0;k<outnode;k++)        {            e+=fabs(yd[k]-x2[k])*fabs(yd[k]-x2[k]); //计算均方差        }        error=e/2.0;        for(int k=0;k<outnode;k++)            b2[k]=b2[k]+rate_b2*qq[k]; //下一次的隐含层和输出层之间的新阈值        for(int j=0;j<hidenode;j++)            b1[j]=b1[j]+rate_b1*pp[j]; //下一次的输入层和隐含层之间的新阈值    }}double *BpNet::recognize(double *p){    double x[innode]; //输入向量    double x1[hidenode]; //隐含结点状态值    double x2[outnode]; //输出结点状态值    double o1[hidenode]; //隐含层激活值    double o2[hidenode]; //输出层激活值    for(int i=0;i<innode;i++)        x[i]=p[i];    for(int j=0;j<hidenode;j++)    {        o1[j]=0.0;        for(int i=0;i<innode;i++)            o1[j]=o1[j]+w[i][j]*x[i]; //隐含层各单元激活值        x1[j]=1.0/(1.0+exp(-o1[j]-b1[j])); //隐含层各单元输出        //if(o1[j]+b1[j]>0) x1[j]=1;        //    else x1[j]=0;    }    for(int k=0;k<outnode;k++)    {        o2[k]=0.0;        for(int j=0;j<hidenode;j++)            o2[k]=o2[k]+w1[j][k]*x1[j];//输出层各单元激活值        x2[k]=1.0/(1.0+exp(-o2[k]-b2[k]));//输出层各单元输出        //if(o2[k]+b2[k]>0) x2[k]=1;        //else x2[k]=0;    }    for(int k=0;k<outnode;k++)    {        result[k]=x2[k];    }    return result;}void BpNet::writetrain(){    FILE *stream0;    FILE *stream1;    FILE *stream2;    FILE *stream3;    int i,j;    //隐含结点权值写入    if(( stream0 = fopen("w.txt", "w+" ))==NULL)    {        cout<<"创建文件失败!";        exit(1);    }    for(i=0;i<innode;i++)    {        for(j=0;j<hidenode;j++)        {            fprintf(stream0, "%f\n", w[i][j]);        }    }    fclose(stream0);    //输出结点权值写入    if(( stream1 = fopen("w1.txt", "w+" ))==NULL)    {        cout<<"创建文件失败!";        exit(1);    }    for(i=0;i<hidenode;i++)    {        for(j=0;j<outnode;j++)        {            fprintf(stream1, "%f\n",w1[i][j]);        }    }    fclose(stream1);    //隐含结点阀值写入    if(( stream2 = fopen("b1.txt", "w+" ))==NULL)    {        cout<<"创建文件失败!";        exit(1);    }    for(i=0;i<hidenode;i++)        fprintf(stream2, "%f\n",b1[i]);    fclose(stream2);    //输出结点阀值写入    if(( stream3 = fopen("b2.txt", "w+" ))==NULL)    {        cout<<"创建文件失败!";        exit(1);    }    for(i=0;i<outnode;i++)        fprintf(stream3, "%f\n",b2[i]);    fclose(stream3);}void BpNet::readtrain(){    FILE *stream0;    FILE *stream1;    FILE *stream2;    FILE *stream3;    int i,j;    //隐含结点权值读出    if(( stream0 = fopen("w.txt", "r" ))==NULL)    {        cout<<"打开文件失败!";        exit(1);    }    float  wx[innode][hidenode];    for(i=0;i<innode;i++)    {        for(j=0;j<hidenode;j++)        {            fscanf(stream0, "%f", &wx[i][j]);            w[i][j]=wx[i][j];        }    }    fclose(stream0);    //输出结点权值读出    if(( stream1 = fopen("w1.txt", "r" ))==NULL)    {        cout<<"打开文件失败!";        exit(1);    }    float  wx1[hidenode][outnode];    for(i=0;i<hidenode;i++)    {        for(j=0;j<outnode;j++)        {            fscanf(stream1, "%f", &wx1[i][j]);            w1[i][j]=wx1[i][j];        }    }    fclose(stream1);    //隐含结点阀值读出    if(( stream2 = fopen("b1.txt", "r" ))==NULL)    {        cout<<"打开文件失败!";        exit(1);    }    float xb1[hidenode];    for(i=0;i<hidenode;i++)    {        fscanf(stream2, "%f",&xb1[i]);        b1[i]=xb1[i];    }    fclose(stream2);    //输出结点阀值读出    if(( stream3 = fopen("b2.txt", "r" ))==NULL)    {        cout<<"打开文件失败!";        exit(1);    }    float xb2[outnode];    for(i=0;i<outnode;i++)    {        fscanf(stream3, "%f",&xb2[i]);        b2[i]=xb2[i];    }    fclose(stream3);}//输入样本double X[trainsample][innode]= {    {0,0,0},{0,0,1},{0,1,0},{0,1,1},{1,0,0},{1,0,1},{1,1,0},{1,1,1}    };//期望输出样本double Y[trainsample][outnode]={    {0},{0.1429},{0.2857},{0.4286},{0.5714},{0.7143},{0.8571},{1.0000}    };int main(){    BpNet bp;    bp.init();    int times=0;    while(bp.error>0.0001)    {        bp.e=0.0;        times++;        bp.train(X,Y);        cout<<"Times="<<times<<" error="<<bp.error<<endl;    }    cout<<"trainning complete..."<<endl;    double m[innode]={1,1,1};    double *r=bp.recognize(m);    for(int i=0;i<outnode;++i)       cout<<bp.result[i]<<" ";    double cha[trainsample][outnode];    double mi=100;    double index;    for(int i=0;i<trainsample;i++)    {        for(int j=0;j<outnode;j++)        {            //找差值最小的那个样本            cha[i][j]=(double)(fabs(Y[i][j]-bp.result[j]));            if(cha[i][j]<mi)            {                mi=cha[i][j];                index=i;            }        }    }    for(int i=0;i<innode;++i)       cout<<m[i];    cout<<" is "<<index<<endl;    cout<<endl;    return 0;}


0 0
原创粉丝点击