BP神经网络的模块化编程(隐含层层数>=1)

之前的3层BP神经网络因为只有一层隐含层,所以较为基础,在后续的应用中,存在以下问题:

  • 1)训练样本的数据量多,需要对数据文件进行读写操作;
  • 2)隐含层数不止一层。

问题解决:

  • 读写文件的时候为了方便采用fgets函数对行进行读取,使用strtok函数来进行打断,对每行数据不同变量进行存储;
  • 在之前的数学原理中可以看到,BP神经网络包括三大部分:1)正向传播过程;2)误差反向传递过程;3)权重更新。所以可以将每一部分独立的当作一个函数体,通过对输入的参数进行调控来对BP神经网络的运算进程进行控制。

所以函数体设置如下:

public:
    void __fastcall Bp_Read_TranFile_Func(FILE *fp,double *InSam,double *OutSam);  //读取训练文件
    void __fastcall Bp_Read_TestFile_Func(FILE *fp,double *InSam);
    void __fastcall winit(double w[],int nn); //生成随机数
    void __fastcall init_Bp_LayerFunc(BP_Layer *pLay,int prenum,int curnum); //初始化
    void __fastcall Bp_Positve_Trans_Func(BP_Layer *prelay,BP_Layer *curlay); //正向传播函数
    void __fastcall Bp_Reverse_Delta2_Func(BP_Layer *curlay,double *Outyd);  //反向传播计算delta2
    void __fastcall Bp_Reverse_Deltaj_Func(BP_Layer *curlay,BP_Layer *prelay); //反向传播计算每一层的delta
    void __fastcall Bp_Update_Weight_Func(BP_Layer *curlay,BP_Layer *prelay);  //更新权值

其中BP神经网络的将层的概念利用结构体来进行构建,如下所示:

//定义每一层的结构体
typedef struct struct_AnnBP_Layer
{
    int nodeNum;   //当前层神经元数目
    int preNode;   //前一层神经元数目
    double *pData; //当前层各个神经元数值
    double *Delta; //偏差
    double *b;     //阈值
    double *W;     //前一层的神经元连接权值
    double rate_w; //权值学习速率
    double rate_b; //阈值学习速率
}BP_Layer;

其中最主要的功能函数有训练文件的读取、正向传播过程、反向误差传递、更新权值。其中反向误差传递


  • 训练文件的读取
    LeNet-5 15 2 2
    翼长 触角长 类别
    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
    因为在此处我使用的是按行读取,因为一行包含翼长、触角长和类别三个变量,前两个是输入变量,后面的类别则是输出变量的真值。需要利用到strtok函数来进行打断。关于stork函数的功能可以参考如下链接:
    百度词条:https://baike.baidu.com/item/strtok/5522728?fr=aladdin
    源码如下所示:
//读取训练文件
void __fastcall BpNet::Bp_Read_TranFile_Func(FILE *fp,double *InSam, double *OutSam)
{
    fp=fopen("/Users/Star/Desktop/sample/BP实例1_训练.txt", "r");
    if(NULL==fp)
    {
        return;
    }
    char *flag;
    char str[100]={0};
    //char *judge;

    //读取文件第一行并进行判定
    if (!feof(fp))
    {
        memset(str, 0, sizeof(str));   //初始化
        fgets(str, sizeof(str)-1, fp);  //按行读取
        //获取str的第一个字符串
        flag=strtok(str," ");
        //judge="LeNet-5";
        if (strcmp(flag, "LeNet-5")!=0)
        {
            return;
        }
    }

    //跳过文件第二行
    memset(str, 0, sizeof(str));
    fgets(str, sizeof(str)-1, fp);

    //从第三行开始输入数据
    char *data=NULL;
    for(int ii=0;ii<trainsample;ii++)
    {
        if (!feof(fp))
        {
            memset(str, 0, sizeof(str));   //初始化
            fgets(str, sizeof(str)-1, fp);  //按行读取
        }

        //利用strtok对每行数据进行切割,strtok函数怎么用????(已经解决)
        int jj=0;
        //读取第一个字段
        data = strtok(str, " ");

        while (data != NULL)
        {
            //训练样本的输入变量
            if(jj<BpInNode)
                InSam[ii*BpInNode+jj]=atof(data);
            //训练样本的输出变量
            if (jj>=BpInNode && jj<BpInNode+BpOutNode)
            {
                //进行判定
                if(strcmp(data, "Apf\r\n")==0)
                {
                    //OutSam[ii*BpOutNode+BpOutNode]=0.0;
                    OutSam[ii*BpOutNode+jj-BpInNode]=0.0;
                }
                else if (strcmp(data, "Af\r\n")==0)
                {
                    //OutSam[ii*BpOutNode+BpOutNode]=1.0;
                    OutSam[ii*BpOutNode+jj-BpInNode]=1.0;
                }
                else
                    return;
            }
            data = strtok(NULL, " ");
            jj++;
        }
    }
}

  • 正向传播过程
    正向传播过程的数学原理在前面一篇博客中已经有了详细的介绍,在此不做赘述。
//正向传播函数
void __fastcall BpNet::Bp_Positve_Trans_Func(BP_Layer *prelay, BP_Layer *curlay)
{
    int preNum=prelay->nodeNum;
    double *preS0=prelay->pData;
    int curNum=curlay->nodeNum;
    double *Wij=curlay->W;       //连接权值
    double *b0j=curlay->b;       //阈值
    double *curS0=curlay->pData;
    int jj,ii;
    double sum,z0;
    for(jj=0; jj<curNum; jj++)
    {
        sum=0.0;
        //隐含层各单元输入激活值
        for(ii=0; ii<preNum; ii++)
            sum += Wij[ii*curNum+jj]*preS0[ii];
        z0=sum+b0j[jj];   //隐含层激活值
        curS0[jj]= ActivateFunc(z0);  //隐含层各单元的输出
    }
    return;
}

  • 误差反向传递过程
    误差反向传递过程简而言之:
    每个权重的梯度=与其相连的前一层节点的输出*与其相连的后一层的反向传播的输出;
    所以对其进行模块化则需要拆分为两部分进行计算:1)对输出层的反向传播的输出进行计算;2)从后至前对其他层的权重梯度进行计算;
//反向传播计算delta2
void __fastcall BpNet::Bp_Reverse_Delta2_Func(BP_Layer *curlay, double *Outyd)
{
    int outnum = curlay->nodeNum;
    double *Outy0 = curlay->pData;      //结点状态值
    double *Delta = curlay->Delta;      //偏差 Weight
    int ii;
    int jj;
    double sum,ej;
    sum = 0;
    for(jj = 0;jj < outnum;jj++)
    {
        ej = Outyd[jj] - Outy0[jj];
        sum += ej*ej;
        Delta[jj] = ej * Outy0[jj] * (1. - Outy0[jj]); //输出层δ2 = ei * θ'(si2)  期望误差*输出层Outy0激励函数导数
    }
    AnntotalErr += sum / 2.0; //计算希望输出与实际输出的偏差ej和总均方差sum
    return;
}

//反向传播计算每一层的delta
void __fastcall BpNet::Bp_Reverse_Deltaj_Func(BP_Layer *curlay, BP_Layer *prelay)
{
    int prenum = prelay->nodeNum;
    double *ps0 = prelay->pData;      //结点状态值
    double *Deltaj = prelay->Delta;   //偏差 Deltaj
    int curnum = curlay->nodeNum;
    double *Delta2 = curlay->Delta;   //偏差 Delta2
    double *wij = curlay->W;          //连接权值
    int jj,kk;
    double sum;
    for(jj = 0;jj < prenum;jj++)
    {
        sum  = 0.0;
        for(kk = 0;kk < curnum;kk++)
            sum += Delta2[kk] * wij[jj*curnum+kk];
        Deltaj[jj]  = sum * ps0[jj] *(1. - ps0[jj]);  //隐含层δ1 = (∑curErr *wij) * θ'(si2)  隐含层误差*隐含层ps0激励函数导数
    }

    return;
}

  • 权重的更新
    权重更新即在原来权重基础上加上权重梯度*rate_w 即可。
//更新权值
void __fastcall BpNet::Bp_Update_Weight_Func(BP_Layer *curlay, BP_Layer *prelay)
{
    int prenum  = prelay->nodeNum;
    double *pS0 = prelay->pData;      //结点状态值
    int curnum  = curlay->nodeNum;
    double *Deltaj  = curlay->Delta;  //偏差
    double *wij = curlay->W;          //连接权值
    double *b0j = curlay->b;          //阈值
    float rate_w  = curlay->rate_w;   //权值学习速率
    float rate_b  = curlay->rate_b;   //阈值学习速率
    int jj,ii;
    for(jj = 0;jj < curnum;jj++)
    {
        //更新输入层和隐含层之间的连接权[权重梯度]
        for(ii = 0;ii < prenum;ii++)
            wij[ii*curnum+jj] += rate_w * Deltaj[jj] * pS0[ii];
        //更新输入层和隐含层之间的阈值
        b0j[jj] += rate_b * Deltaj[jj];
    }
    return;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值