BP 神经网络算法

BP神经网络算法

  简介:BP(Back Propagation)网络是1986年由Rumelhart和McCelland为首的科学家小组提出,是一种按误差逆传播算法训练的多层前馈网络,是目前应用最广泛的神经网络模型之一。BP网络能学习和存贮大量的输入-输出模式映射关系,而无需事前揭示描述这种映射关系的数学方程。它的学习规则是使用最速下降法,通过反向传播来不断调整网络的权值和阈值,使网络的误差平方和最小。BP神经网络模型拓扑结构包括输入层(input)、隐层(hide layer)和输出层(output layer)
  摘 要:BP神经网络算法是在BP神经网络现有算法的基础上提出的,是通过任意选定一组权值,将给定的目标输出直接作为线性方程的代数和来建立线性方程组,解得待求权,不存在传统方法的局部极小及收敛速度慢的问题,且更易理解。
  关键词:固定权值;gauss消元法;BP算法
  人工神经网络(artificial neural networks,ANN)系统是20世纪40年代后出现的,它是由众多的神经元可调的连接权值连接而成,具有大规模并行处理、分布式信息存储、良好的自组织自学习能力等特点,在信息处理、模式识别、智能控制及系统建模等领域得到越来越广泛的应用。尤其误差反向传播算法(Error Back-propagation Training,简称BP网络)可以逼近任意连续函数,具有很强的非线性映射能力,而且网络的中间层数、各层的处理单元数及网络的学习系数等参数可根据具体情况设定,灵活性很大,所以它在许多应用领域中起到重要作用。近年来,为了解决BP神经网络收敛速度慢、不能保证收敛到全局最小点,网络的中间层及它的单元数选取无理论指导及网络学习和记忆的不稳定性等缺陷,提出了许多改进算法。
  1 传统的BP算法简述
  BP算法是一种有监督式的学习算法,其主要思想是:输入学习样本,使用反向传播算法对网络的权值和偏差进行反复的调整训练,使输出的向量与期望向量尽可能地接近,当网络输出层的误差平方和小于指定的误差时训练完成,保存网络的权值和偏差。具体步骤如下:
  (1)初始化,随机给定各连接权[w],[v]及阀值θi,rt。
  (2)由给定的输入输出模式对计算隐层、输出层各单元输出
  bj=f(■wijai-θj) ct=f(■vjtbj-rt)
  式中:bj为隐层第j个神经元实际输出;ct为输出层第t个神经元的实际输出;wij为输入层至隐层的连接权;vjt为隐层至输出层的连接权。
  dtk=(ytk-ct)ct(1-ct) ejk=[■dtvjt] bj(1-bj)
  式中:dtk为输出层的校正误差;ejk为隐层的校正误差。
  (3)计算新的连接权及阀值,计算公式如下:
  vjt(n+1)=vjt(n)+?琢dtkbj wij(n+1)=wij(n)+?茁ejkaik
  rt(n+1)=rt(n)+?琢dtk θj(n+1)=θj(n)+?茁ejk
  式中:?琢,?茁为学习系数(0<?琢<1,0<?茁<1)。
  (4)选取下一个输入模式对返回第2步反复训练直到网络设输出误差达到要求结束训练。
  传统的BP算法,实质上是把一组样本输入/输出问题转化为一个非线性优化问题,并通过负梯度下降算法,利用迭代运算求解权值问题的一种学习方法,但其收敛速度慢且容易陷入局部极小,为此提出了一种新的算法,即高斯消元法。
  2 改进的BP网络算法
  2.1 改进算法概述
  此前有人提出:任意选定一组自由权,通过对传递函数建立线性方程组,解得待求权。本文在此基础上将给定的目标输出直接作为线性方程等式代数和来建立线性方程组,不再通过对传递函数求逆来计算神经元的净输出,简化了运算步骤。没有采用误差反馈原理,因此用此法训练出来的神经网络结果与传统算法是等效的。其基本思想是:由所给的输入、输出模式对通过作用于神经网络来建立线性方程组,运用高斯消元法解线性方程组来求得未知权值,而未采用传统BP网络的非线性函数误差反馈寻优的思想。
  2.2 改进算法的具体步骤
  对给定的样本模式对,随机选定一组自由权,作为输出层和隐含层之间固定权值,通过传递函数计算隐层的实际输出,再将输出层与隐层间的权值作为待求量,直接将目标输出作为等式的右边建立方程组来求解。
  现定义如下符号(见图1):x (p)输入层的输入矢量;y (p)输入层输入为x (p)时输出层的实际输出矢量;t (p)目标输出矢量;n,m,r分别为输入层、隐层和输出层神经元个数;W为隐层与输入层间的权矩阵;V为输出层与隐层间的权矩阵。具体步骤如下:
  (1)随机给定隐层和输入层间神经元的初始权值wij。
  (2)由给定的样本输入xi(p)计算出隐层的实际输出aj(p)。为方便起见将图1网络中的阀值写入连接权中去,令:隐层阀值θj=wnj,x(n)=-1,则:
  aj(p)=f(■wijxi(p)) (j=1,2…m-1)。
  (3)计算输出层与隐层间的权值vjr。以输出层的第r个神经元为对象,由给定的输出目标值tr(p)作为等式的多项式值建立方程,用线性方程组表示为:
  a0(1)v1r+a1(1)v2r+…+am(1)vmr=tr(1)a0(2)v1r+a1(2)v2r+…+am(2)vmr=tr(2) ……a0(p)v1r+a1(p)v2r+…+am(p)vmr=tr(p) 简写为: Av=T
  为了使该方程组有唯一解,方程矩阵A为非奇异矩阵,其秩等于其增广矩阵的秩,即:r(A)=r(A┊B),且方程的个数等于未知数的个数,故取m=p,此时方程组的唯一解为: Vr=[v0r,v2r,…vmr](r=0,1,2…m-1)
  (4)重复第三步就可以求出输出层m个神经元的权值,以求的输出层的权矩阵加上随机固定的隐层与输入层的权值就等于神经网络最后训练的权矩阵。
  3 计算机运算实例
  现以神经网络最简单的XOR问题用VC编程运算进行比较(取神经网络结构为2-4-1型),传统算法和改进BP算法的误差(取动量因子α=0.001 5,步长η=1.653)
算法C++版代码:
#ifndef BP_H
#define BP_H
class BpNet
{
private:
    int _nInput;            //输入层节点个数
    int _nHide;             //隐含层节点个数
    int _nOutput;           //输出层节点个数
   
    double **_pplfWeight1;  //输入层-隐含层权系数
    double **_pplfWeight2;  //隐含层-输出层权系数
   
    double *_plfHideIn, *_plfHideOut;       //隐含层的网络输入和输出
    double *_plfOutputIn, *_plfOutputOut;   //输出层的网络输入和输出
   
private:
    double (*f)(double);    //激活函数
   
public:
    /**
    构造函数,创建一个未经训练的Bp网络
    param nInput 输入层节点个数
    param nHide 隐含层节点个数
    param nOutput 输出层节点个数
    */
    BpNet(int nInput, int nHide, int nOutput);
   
    /**
    析构函数,销毁已有的Bp网络
    */
    virtual ~BpNet();
   
    /**
    通过多组正例,对Bp网络进行训练
    param pplfInput 正例的输入
    param pplfDesire 正例的理想输出
    返回值 训练是否收敛
    */
    bool Train(int n, double **pplfInput, double **pplfDesire);
   
    /**
    使用当前的Bp网络对指定模式进行分类,分类的结果存储到plfOutput中
    param plfInput 待分类的模式
    param plfOutput 分类的输出
    */
    void Classify(double plfInput[], double plfOutput[]);
};
#endif
#include "bp.h"
#include "memory.h"
#include "math.h"
#include "time.h"
#include "stdlib.h"
#include "stdio.h"
double sigmoid(double x)
{
    return 1 / (1 + exp(- x));
}
BpNet::BpNet(int nInput, int nHide, int nOutput)
{
    _nInput = nInput;
    _nHide = nHide;
    _nOutput = nOutput;
   
    srand((unsigned)time(NULL));
   
    _pplfWeight1 = new double *[_nInput];
    for (int i = 0; i < _nInput; i++)
    {
        _pplfWeight1[i] = new double [_nHide];
        for (int j = 0; j < _nHide; j++) _pplfWeight1[i][j] = rand() / (double)(RAND_MAX);
    }
   
    _pplfWeight2 = new double *[_nHide];
    for (int i = 0; i < _nHide; i++)
    {
        _pplfWeight2[i] = new double [_nOutput];
        for (int j = 0; j < _nOutput; j++) _pplfWeight2[i][j] = rand() / (double)(RAND_MAX);
    }
   
    _plfHideIn = new double[_nHide];
    _plfHideOut = new double[_nHide];
    _plfOutputIn = new double[_nOutput];
    _plfOutputOut = new double[_nOutput];
    f = sigmoid;    //使用sigmoid激活函数
}
BpNet::~BpNet()
{
    delete []_plfHideIn;
    delete []_plfHideOut;
    delete []_plfOutputIn;
    delete []_plfOutputOut;
    for (int i = 0; i < _nInput; i++) delete []_pplfWeight1[i];
    for (int i = 0; i < _nHide; i++) delete []_pplfWeight2[i];
    delete []_pplfWeight1;
    delete []_pplfWeight2;
}
void BpNet::Classify(double plfInput[], double plfOutput[])
{
    memset(_plfHideIn, 0, sizeof(double) * _nHide);
    memset(_plfHideOut, 0, sizeof(double) * _nHide);
    memset(_plfOutputIn, 0, sizeof(double) * _nOutput);
    memset(_plfOutputOut, 0, sizeof(double) * _nOutput);
   
    //输入层到隐含层的正向传播
    for (int i = 0; i < _nInput; i++)
        for (int j = 0; j < _nHide; j++)
            _plfHideIn[j] += plfInput[i] * _pplfWeight1[i][j];
    for (int j = 0; j < _nHide; j++) _plfHideOut[j] = (*f)(_plfHideIn[j]);
   
    //隐含层到输出层的正向传播
    for (int j = 0; j < _nHide; j++)
        for (int k = 0; k < _nOutput; k++)
            _plfOutputIn[k] += _plfHideOut[j] * _pplfWeight2[j][k];
    for (int k = 0; k < _nOutput; k++) _plfOutputOut[k] = (*f)(_plfOutputIn[k]);
   
    if (plfOutput != NULL)
        memcpy(plfOutput, _plfOutputOut, sizeof(double) * _nOutput);
}
bool BpNet::Train(int n, double **pplfInput, double **pplfDesire)
{
    const double a = 0.1;
    const double E = 0.01;
    double lfE = E + 1;
   
    //输入层-隐含层权系数增量
    double **pplfDeltaWeight1 = new double *[_nInput];
    for (int i = 0; i < _nInput; i++)
    {
        pplfDeltaWeight1[i] = new double [_nHide];
        memset(pplfDeltaWeight1[i], 0, sizeof(double) * _nHide);
    }
   
    //隐含层-输出层权系数增量
    double **pplfDeltaWeight2 = new double *[_nHide];
    for (int i = 0; i < _nHide; i++)
    {
        pplfDeltaWeight2[i] = new double[_nOutput];
        memset(pplfDeltaWeight2[i], 0, sizeof(double) * _nOutput);
    }
       
    int nCount = 0;
    while (lfE > E)
    {
        lfE = 0;
        //对每一个样本进行处理
        for (int i = 0; i < n; i++)
        {
            double *plfInput = pplfInput[i];        //样本输入
            double *plfDesire = pplfDesire[i];      //样本期望输出
       
            //计算样本实际输出plfOutput
            Classify(plfInput, NULL);
       
            //计算误差测度
            double lfEp = 0;
            for (int j = 0; j < _nOutput; j++)
                lfEp += (plfDesire[j] - _plfOutputOut[j]) * (plfDesire[j] - _plfOutputOut[j]);
            lfE += lfEp;
       
            //计算隐含层-输出层权系数增量
            double *plfChange2 = new double[_nOutput];
       
            for (int j = 0; j < _nOutput; j++)
                plfChange2[j] = _plfOutputOut[j] * (1 - _plfOutputOut[j]) * (plfDesire[j] - _plfOutputOut[j]);
            for (int j = 0; j < _nHide; j++)
                for (int k = 0; k < _nOutput; k++)
                    pplfDeltaWeight2[j][k] = a * _plfHideOut[j] * plfChange2[k];
            //计算输入层-隐含层权系数增量
            double *plfChange1 = new double[_nHide];
            memset(plfChange1, 0, sizeof(double) * _nHide);
            for (int j = 0; j < _nHide; j++)
            {
                for (int k = 0; k < _nOutput; k++)
                    plfChange1[j] += _pplfWeight2[j][k] * plfChange2[k];
                plfChange1[j] *= _plfHideOut[j] * (1 - _plfHideOut[j]);
            }
            for (int j = 0; j < _nInput; j++)
                for (int k = 0; k < _nHide; k++)
                    pplfDeltaWeight1[j][k] = a * plfInput[j] * plfChange1[k];
               
            delete []plfChange1;
            delete []plfChange2;
       
            //更新Bp网络权值
            for (int i = 0; i < _nInput; i++)
                for (int j = 0; j < _nHide; j++)
                    _pplfWeight1[i][j] += pplfDeltaWeight1[i][j];
            for (int i = 0; i < _nHide; i++)
                for (int j = 0; j < _nOutput; j++)
                    _pplfWeight2[i][j] += pplfDeltaWeight2[i][j];
        }
        nCount++;
        if (nCount % 10 == 0) printf("%d%8.5lf/n", nCount, lfE);
    }
           
    for (int i = 0; i < _nInput; i++) delete []pplfDeltaWeight1[i];
    for (int i = 0; i < _nHide; i++) delete []pplfDeltaWeight2[i];
    delete [] pplfDeltaWeight1;
    delete [] pplfDeltaWeight2;
    return true;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值