BP神经网络(3层)

终于放假啦,于是就想着把前一段时间学习的知识给整理一下,希望能够对小伙伴们有所帮助。

数学原理

BP(Back Propagation)(Rummelhart D, McClelland J.,1986)神经网络是一种按照误差逆向传播算法训练的多层前馈神经网络。
神经网络图示
三层BP神经网络的组成主要分为三个部分:输入层、隐含层、输出层。其中层与层之间的连接是全连接。其数学原理主要分为三个部分:1)正向传播过程;2)误差反向传递过程;3)权重更新。因为其公式有点多,所以就直接把写的实验报告的截图给贴上来啦,如下所示:

  • 正向传播过程
    正向传播
    其中 p 为输出层的神经元个数,b 为截距,w 为权重
  • 误差反向传递
    在反向误差传递的过程中,利用随机梯度下降法,对每个训练样本都使得往权重负梯度方向变化,从而调整权重使得误差最小。其中误差可以用损失函数求得,最小化均方根差损失函数 L(e)如下所示:
    误差反向传递
    每个权重的梯度都等于与其相连的前一层节点的输出与其相连的后一层的
    反向传播的输出。
  • 权重更新
    权重更新即在原来权重基础上加上权重梯度*rate_w 即可。

实验数据

1981年生物学家格若根(W.Grogan)和维什(W.Wirth)发现了两类蚊子(或飞蠓midges),他们测量了这两类蚊子每个个体的翼长和触角长,数据如下:
- 训练样本

翼长 触角长 类别
1.78 1.14 Apf
1.96 1.18 Apf
1.86 1.20 Apf
1.72 1.24 Af
2.00 1.26 Apf
2.00 1.28 Apf
1.96 1.30 Apf
1.74 1.36 Af
1.64 1.38 Af
1.82 1.38 Af
1.90 1.38 Af
1.70 1.40 Af
1.82 1.48 Af
1.82 1.54 Af
2.08 1.56 Af

  • 测试样本
    如果抓到三只新的蚊子,它们的翼长和触角长分别为(1.80,1.24);(1.84,1.28);(2.04,1.40),问它们应分别属于哪一个种类?

源代码


  • main.cpp
//
//  main.cpp
//  test
//
//  Created by Deemo on 2018/5/17.
//  Copyright © 2018年 Star. All rights reserved.
//

#include <iostream>
#include <stdio.h>
#include "bp_Net.hpp"

int main(int argc, const char * argv[])
{
    // insert code here...
    FILE *fou = fopen("/Users/Star/Desktop/test/BP训练结果.txt","w");
    //输入蚊子的翼长和触角长数据
    double m_Insample[trainsample][BpInNode]=
    {
        {1.78,1.14},
        {1.96,1.18},
        {1.86,1.20},
        {1.72,1.24},
        {2.00,1.26},
        {2.00,1.28},
        {1.96,1.30},
        {1.74,1.36},
        {1.64,1.38},
        {1.82,1.38},
        {1.90,1.38},
        {1.70,1.40},
        {1.82,1.48},
        {1.82,1.54},
        {2.08,1.56}
    };
    //输入蚊子的类别数据
    double m_Outsmaple[trainsample][BpOutNode]
    {
        1,
        1,
        1,
        0,
        1,
        1,
        1,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0
    };
    double m_Tsample[testsample][BpInNode]=
    {
        {1.80,1.24},
        {1.84,1.28},
        {2.04,1.40}
    };
    if(!fou)
        return 0;
    BpNet  bp;

    //初始化
    bp.BpInitNetFunc();

    //设定迭代次数
    int times = 0;
    while( bp.totalErr > 0.0001 && times < 5000)
    {
        times++;
        bp.BpNetTrainFunc(m_Insample,m_Outsmaple);
        if(times + 1 == (times + 1) / 100*100)
            fprintf(fou,"BP %5d  DT:%10.5f\n",times+1,bp.totalErr);
    }
    int    jj,isamp;
    double m[BpInNode];
    for(isamp = 0;isamp < testsample;isamp++)
    {
        for(jj = 0;jj < BpInNode; jj++)
            m[jj] = m_Tsample[isamp][jj];  //输入的样本
        //进行识别
        bp.BpNetRecognizeFunc(m);
        for(jj = 0;jj < BpInNode; jj++)
            fprintf(fou," %5.2f",bp.In_x0[jj]);
        fprintf(fou," is ");
        for(jj = 0;jj < BpOutNode;jj++)
            fprintf(fou," %5.2f",bp.Outy0[jj]);
        fprintf(fou,"\n");

    }
    fclose(fou);
    return 0;
}

  • bp_Net.hpp
//
//  bp_Net.hpp
//  test
//
//  Created by Deemo on 2018/5/17.
//  Copyright © 2018年 Star. All rights reserved.
//

#ifndef bp_Net_hpp
#define bp_Net_hpp

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#define   trainsample        15             //BP训练样本数
#define   testsample         3              //BP测试样本数
#define   BpInNode           2             //BP输入结点数
#define   BpHideNode         10             //BP隐含结点数
#define   BpOutNode          1            //BP输出结点数

#define   RAND_MAX           100


class BpNet
{ public:
    BpNet();
    virtual ~BpNet();
public:
    double    ih_w[BpInNode][BpHideNode];  //隐含结点权值
    double    ho_w[BpHideNode][BpOutNode]; //输出结点权值
    double    Hideb0[BpHideNode];          //隐含结点阀值
    double    Out_b0[BpOutNode];           //输出结点阀值
    double    rate_ih_w;                   //权值学习率(输入层-隐含层)
    double    rate_ho_w;                   //权值学习率 (隐含层-输出层)
    double    rate_Hideb0;                 //隐含层阀值学习率
    double    rate_Out_b0;                 //输出层阀值学习率
    double    In_x0[ BpInNode];            //输入向量
    double    Hides0[BpHideNode];          //隐含结点状态值
    double    Outy0[ BpOutNode];           //输出结点状态值
    double    Outyd[BpOutNode];            //希望输出值
    double    out_Er[BpOutNode];           //输出结点的校正误差 希望输出值与实际输出值的偏差
    double    hideEr[BpHideNode];          //隐含结点的校正误差
    double    totalErr;                    //允许的总误差
public:
    void    __fastcall winit(double w[],int nn);                                //权值初始化
    void    __fastcall BpInitNetFunc();                                         //参数初始化
    void    __fastcall BpNetTrainFunc(double InSam[trainsample][BpInNode],
                                      double OutSam[trainsample][BpOutNode]);   //Bp训练
    void    __fastcall BpNetRecognizeFunc(double *p);                           //Bp识别
};


#endif /* bp_Net_hpp */
  • bp_Net.cpp
//
//  bp_Net.cpp
//  test
//
//  Created by Deemo on 2018/5/17.
//  Copyright © 2018年 Star. All rights reserved.
//

#include "bp_Net.hpp"
#include <stdio.h>

//产生[low,high)之间的随机数
double __fastcall  Genc_randval(double low,double high)
{
    double val =((double) ( rand() % RAND_MAX) / (float) RAND_MAX) * (high-low) + low;
    return(val);
}

//sigmoid 激励函数
double __fastcall  SigmoidFunc(  double t0)
{
    t0 = exp(-t0);
    return 1. / (1 + t0);
}

BpNet :: BpNet()
{
    totalErr    = 1.0;  //允许的总误差
    rate_ih_w   = 0.1;  //权值学习率(输入层--隐含层)
    rate_ho_w   = 0.1;  //权值学习率 (隐含层--输出层)
    rate_Hideb0 = 0.1;  //隐含层阀值学习率
    rate_Out_b0 = 0.1;  //输出层阀值学习率
}

BpNet :: ~BpNet() {}

//初始权值:-0.01,0.01  随机数
void __fastcall BpNet :: winit(double w[],int nn)
{
    for(int  ii = 0;ii < nn;ii++)
        w[ii] = Genc_randval(-0.01,0.01);
}

//参数初始化
void __fastcall BpNet :: BpInitNetFunc()
{ winit((double*) ih_w,  BpInNode  * BpHideNode);
    winit((double*) ho_w,  BpHideNode* BpOutNode);
    winit((double*) Hideb0,BpHideNode);
    winit((double*) Out_b0,BpOutNode);
}

//训练样本
void __fastcall BpNet :: BpNetTrainFunc(double InSam[trainsample][BpInNode],double OutSam[trainsample][BpOutNode])
{
    double  sum,z0;
    int     isamp;
    int     ii,jj;
    totalErr = 0.;
    //1.循环训练样品
    for(isamp = 0;isamp < trainsample;isamp++)
    {
        for(jj = 0;jj < BpInNode; jj++)
            In_x0[jj] = InSam[isamp][jj];             //输入的样本
        for(jj = 0;jj < BpOutNode;jj++)
            Outyd[jj] = OutSam[isamp][jj];            //希望输出的样本

        //2.正向传播 :: 构造每个样品的输入和输出标准
        //2.1 输入->隐含层
        for(jj = 0;jj < BpHideNode;jj++)
        {
            sum = 0.0;
            for(ii = 0;ii < BpInNode;ii++)
                sum += ih_w[ii][jj]*In_x0[ii];         //隐含层各单元输入激活值
            z0= sum + Hideb0[jj];                                              //隐含层激活值
            Hides0[jj] = SigmoidFunc(z0);              //隐含层各单元的输出  1.0/( 1.0 + exp(-z0));
        }

        //2.2 隐含层->输出层
        for(jj = 0;jj < BpOutNode;jj++)
        {
            sum= 0.0;
            for(ii = 0;ii < BpHideNode;ii++)
                sum += ho_w[ii][jj]*Hides0[ii];      //输出层各单元输入激活值
            z0=sum + Out_b0[jj];                                            //输出层激活值
            Outy0[jj] = SigmoidFunc(z0);            //输出层各单元输出    1.0/(1.0 + exp(-z0)
        }

        //3.误差反向传播:: 对于网络中每个输出单元,计算误差项,并更新权值
        //3.1 输出层->隐含层[第2层]
        sum = 0;
        //计算总均方差
        for(jj = 0;jj < BpOutNode;jj++)
        {
            z0 = Outyd[jj] - Outy0[jj];
            out_Er[jj] = z0;
            sum += z0*z0;
        }
        totalErr += sum / 2.0;
        for(jj = 0;jj < BpOutNode;jj++)
        {
            out_Er[jj] = out_Er[jj] * Outy0[jj] * (1. - Outy0[jj]);  //输出层δ2 = ei * θ'(si2)  期望误差*输出层Outy0激励函数导数
            for(ii = 0;ii < BpHideNode;ii++)
                ho_w[ii][jj] += rate_ho_w * out_Er[jj]*Hides0[ii];  //更新隐含层和输出层之间的连接权

        }
        //更新隐含层和输出层之间的阈值
        for(jj = 0;jj < BpOutNode; jj++)
            Out_b0[jj] += rate_Out_b0 * out_Er[jj];

        //3.2 隐含层->输入层[第1层]
        for(jj = 0;jj < BpHideNode;jj++)
        {
            sum = 0.0;
            for(ii = 0;ii < BpOutNode;ii++)
                sum += out_Er[ii] * ho_w[jj][ii];
            hideEr[jj] = sum * Hides0[jj]*(1. - Hides0[jj]);   //隐含层δ1 = (∑out_Er *ho_w) * θ'(si2)  隐含层误差*隐含层Hides0激励函数导数
            for(ii = 0;ii < BpInNode;ii++)
                ih_w[ii][jj] += rate_ih_w * hideEr[jj] * In_x0[ii];  //更新输入层和隐含层之间的连接权[权重梯度]
        }
        for(jj = 0;jj < BpHideNode;jj++)
            Hideb0[jj] += rate_Hideb0 * hideEr[jj];  //更新输入层和隐含层之间的阈值
    }
    return;
}

//识别模块
void __fastcall BpNet :: BpNetRecognizeFunc(double*p)
{
    int    jj,ii;
    double sum,z0;
    for(jj = 0;jj < BpInNode; jj++)
        In_x0[jj] = p[jj];                        //输入的样本
    //2.正向传播 :: 构造每个样品的输入和输出标准
    //2.1 输入->隐含层
    for(jj = 0;jj < BpHideNode;jj++)
    {
        sum    = 0.0;
        for(ii = 0;ii < BpInNode;ii++)
            sum += ih_w[ii][jj]*In_x0[ii]; //隐含层各单元输入激活值
        z0     = sum + Hideb0[jj];                                    //隐含层激活值
        Hides0[jj] = SigmoidFunc(z0);   //隐含层各单元的输出1.0/( 1.0 + exp(-z0));
    }
    //2.2 隐含层->输出层
    for(jj = 0;jj < BpOutNode;jj++)
    {
        sum    = 0.0;
        for(ii = 0;ii < BpHideNode;ii++)
            sum += ho_w[ii][jj]*Hides0[ii];//输出层各单元输入激活值
        z0     = sum + Out_b0[jj];                                      //输出层激活值
        Outy0[jj] = SigmoidFunc(z0);    //输出层各单元输出1.0/(1.0 + exp(-z0)

    }
    return;

}

猜你喜欢

转载自blog.csdn.net/weixin_42071896/article/details/81476746