反向传播神经网络(BP网络)介绍及Java实现

在之前的文章中我简单展望过人工智能。并提出过神经网络是一个核心技术,今天我就要介绍神经网络的经典算法反向传播神经网络(Backpropagation Neural Network)

BP网络由DE Rumelhart, GE Hinton等于1986年提出(值得注意的是GE Hinton也是深度学习的奠基人),它是最经典的一个神经网络算法。神经网络是一组连接的输入/输出神经元,每个连接都有相对应的权重。如图,是一个典型的多层前馈(multilayer feed-forward)神经网络结构。它由一个输入层、一个输出层和多个隐藏层组成。在给出输入后,经过层层网络,最后得到一个输出。而神经网络算法本质就是找为各连接找出合适的权值
这里写图片描述

网络计算过程

在讨论BP算法前,还是先明确一下,在上面的网络中得到权值后,具体的计算过程到底是怎么回事。首先明确,上图中的输入层其实是我们要分类的样本的各特征,而隐藏层和输出层每个圆圈都代表了一个sigmoid单元。如下图所示,sigmoid的计算过程为:首先得到各输入与常数1的和 net=ni=0wixi ,然后用sigmoid函数计算输出 out=σ(net)=11+enet 。这便是单各单元的计算方式,而整个网络便是前一层作为输入与后一层的每各单元连接,计算出各单元的输出后,再把这一层做为输入传递到后一层。。。
sigmoid单元

算法过程

下面先给出BP神经网络的算法过程
bpNN算法
算法中的终止条件可以是到达一定的迭代次数或到达一定的准确度。可以看出算法本身并不复杂,后面要讲的推导也不会比高等数学的题难推,就是几个数学公式比较复杂。可是算法却有十分神奇的能力,所以有机会一定把数学学好

算法推导

梯度下降

可以从上面的算法过程看出,BP算法其实就是不断根据输出与实际值的误差来更新各连接权值。那么什么样的权值是最好的,我们中学应该都学过最小二乘法,即每一个输出与实际值差值的平方的和最小,即误差函数最小。在BP网络中,误差这样定义:

E(w⃗ )=12dDkoutputs(tkdokd)2

所以我们的目的是使误差值最小。那么如何使该误差函数取的最小值,通过计算E关于w的导数,列方程使所有导数都为零?显然是不可行的。所以我们要利用计算机的计算能力去一步步逼近最小值,这里用的方法就是梯度下降(Gradient descend),即朝着梯度下降的方向逐步偏移。

迭代方法推导

在正式推导前我先说明:其实BP算法有中形式:标准形随机梯度下降方法。这一点刚开始也让我疑惑了很久:为什么上面每一个样例都要迭代一次?不是应该一次扫一遍数据库,计算出总误差再迭代吗?后面那种其实就是标准形式的梯度下降方法,可是这样计算量大,收敛的速度慢。而每一个样例都迭代速度快,而且还可以避免陷入局部最优。下面我介绍的就是随机梯度下降方式的推导过程。可以看出这种方式的误差函数和上面那个有区别。但后面推导过程其实非常相似。

误差函数:
误差函数

正式推导前先明确几个标记,可以使后面推导更简洁:
标记含义

输出单元的误差:
输出

隐藏单元的误差:
隐藏单元

可以看出误差更新的过程是从后往前传播,所以算法的名字为反向传播算法

Java实现

算法理解起来可能比较困难,但是整体过程并不复杂,用Java实现,总代码不会超过100行即可实现,但是代码也比较绕,理解起来也会费点劲。下面给出我的实现,或者可以下载我的整个工程,我这个项目是用BPNN做手写体识别,最后效果并不好,不过算法本身是正确的,我用它做过习题。

public double layer[][];//store the output of every unit
    public double layerErr[][];//store the error of every unit
    public double layerWeight[][][];//store weights
    public double layerWeightDelta[][][];//store number of weights adjust
    public double mobp;// momentum rate
    public double rate;// adjust steps length

    /**
     * initialize the BPNN
     * @param layernum units number of every layer
     * @param rate adjust steps length
     * @param mobp momentum rate
     */
    public BpNN(int[] layernum,double rate,double mobp){
        this.rate=rate;
        this.mobp=mobp;
        layer=new double[layernum.length][];
        layerErr=new double[layernum.length][];
        layerWeight=new double[layernum.length][][];
        layerWeightDelta=new double[layernum.length][][];
        for(int l=0;l<layernum.length;l++){
            layer[l]=new double[layernum[l]];
            layerErr[l]=new double[layernum[l]];
//          layerWeight[l]=new double[layernum[l]+1][];
//          layerWeightDelta[l]=new double[layernum[l]+1][];
            if(l+1<layernum.length){
                    layerWeight[l]=new double[layernum[l]+1][layernum[l+1]];
                    layerWeightDelta[l]=new double[layernum[l]+1][layernum[l+1]];
                    for(int x=0;x<layernum[l]+1;x++){
                        for(int y=0;y<layernum[l+1];y++){
                            layerWeight[l][x][y]=0.1;
                        }
                    }
            }
        }
    }
    /**
     * compute the output from front to back
     * @param in input vector
     * @return output vector
     * 
     */
    public double[] compute(double[] in){
        for(int l=1;l<layer.length;l++){
            for(int j=0;j<layer[l].length;j++){
                double z=layerWeight[l-1][layer[l-1].length][j];//constant multiply weights
                for(int x=0;x<layer[l-1].length;x++){
                    layer[l-1][x]=l==1?in[x]:layer[l-1][x];
                    z+=layerWeight[l-1][x][j]*layer[l-1][x];
                }
                layer[l][j]=1/(1+Math.exp(-z));
            }
        }
        return layer[layer.length-1];
    }
    /**
     * update weight from back to front
     * @param tar target values (instances)
     */
    public void updateWeight(double[] tar){
        int l=layer.length-1;
        for(int y=0;y<layer[l].length;y++){
            layerErr[l][y]=layer[l][y]*(1-layer[l][y])*(tar[y]-layer[l][y]);
        }
        while(l-->0){
                for(int j=0;j<layerErr[l].length;j++){
                    double z=0;
                    for(int i=0;i<layerErr[l+1].length;i++){
                        z+=l>0?layerErr[l+1][i]*layerWeight[l][j][i]:0;//0 layer is input layer, need not calculate error
                        layerWeightDelta[l][j][i]=mobp*layerWeightDelta[l][j][i]+rate*layerErr[l+1][i]*layer[l][j];//delta weight with momentum
                        layerWeight[l][j][i]+=layerWeightDelta[l][j][i];//update weight of units
                        if(j==layerErr[l].length-1){//update weights of constant
                            layerWeightDelta[l][j+1][i]=mobp*layerWeightDelta[l][j+1][i]+rate*layerErr[l+1][i];
                            layerWeight[l][j+1][i]+=layerWeightDelta[l][j+1][i];
                        }
                    }
                    layerErr[l][j]=z*layer[l][j]*(1-layer[l][j]);//compute error
                }
        }
    }

    public void train(double[] in, double[] tar) {
        compute(in);
        updateWeight(tar);
    }
    public double[][][] getWeight(){
        return layerWeight;
    }
    public void setWeight(double[][][] weight){
        layerWeight=weight;
    }

猜你喜欢

转载自blog.csdn.net/jiafgn/article/details/68947874