BP神经网络的简易C++实现(非矩阵)

看了很多BP神经网络原理,也看到很多python的代码,我就想用C++来徒手撕一撕。

因为我习惯用C++写程序了,不妨就用C++啃下这个神经网络吧。

BP(back propagation)神经网络是最基础的神经网络,很多变种,例如RNN,LSTM,GRU,HNN等,都基于这个神经网络的训练过程。

BP包括三个神经元层,输入神经元,隐藏层神经元,输出神经元。上一层神经元的输出会被下一层的神经元“选择性地”以一定强度接受,并且进行下一步的传递,这里就有权重和偏置来影响。而输出会有激励函数来加以限制,将数据压缩在一定的范围。这里我选择的激励函数是最基础的sigmoid

sigmoid=1/(1+exp(-x))

exp()函数在cmath库中有,表示e^x

而sigmoid的导数dif-sigmoid

dif-sigmoid=sigmoid(x)*(1-sigmoid(x))

#include<bits/stdc++.h>
#define inum 10
#define hnum 20
#define onum 10
using namespace std;
double input[inum],expect[onum],error,n=0.5;
struct hidden
{
    double in,out;
    double w[inum],bia;
}hide[hnum];
struct outputlayer
{
    double in,out;
    double w[hnum],bia;
    double tr;
}output[onum];
double f(double x)
{
    return 1.0/(1.0+exp(-x));
}
double dif(double x)
{
    double trans=f(x);
    return trans*(1-trans);
}

不难看出我在开头这些乱七八糟的定义里面把东西都定全了

inum输入层神经元数,hnum隐藏层神经元数,onum输出层神经元数,通常是符合hnum=random(1~10)+根号下inum*onum(打不出根号蛋疼)

expect就是期望得到的结果,最后在反向传播时有用

n是学习率,error是误差,f是激励函数,dif就是它的导数了

在定义hide和output时用了结构体,有的人觉得不一定需要这样,也可以直接用数组,二维数组来代替。我是觉得这样比较直观吧,于是就这么写了。

必然是要有in输入,out输出,w权重,bia偏置的,in和out都要参与运算,所以不可少,尤其是in,这个要参与反向传播。

首先是要初始化权重和偏置的,对吧。

void init()
{
    error=10;
    srand(unsigned(time(NULL)));
    for(int i=0;i<hnum;i++)
    {
        hide[i].bia=(rand()%10)/200.0;
        for(int j=0;j<inum;j++)
            hide[i].w[j]=(rand()%10)/200.0;
    }
    for(int i=0;i<onum;i++)
    {
        output[i].bia=(rand()%10)/200.0;
        for(int j=0;j<hnum;j++)
            output[i].w[j]=(rand()%10)/200.0;
    }
}

先把error给整成了10,主要是下一步运算的时候进while不要出问题。然后采用了随机数生成,种子是时间,不过这个有缺陷,因为。。。。。。基本上下面生成的随机数都是一样的,因为电脑在一秒钟内能运算太多了。。。希望有大佬能帮帮我解决这个问题。(不过对运算影响不是特别大啦,不过这种权重和偏置设定也可以手动调,或者采用遗传算法,选择出最佳的初始设定)

主函数内容大概如此,灵活应变吧,这里只是稍微做个演示

int main()
{
    init();
    for(int i=0;i<inum;i++)cin>>input[i];
    for(int i=0;i<onum;i++)cin>>expect[i];
    while(error>=0.01)
    {
        mainwork();
        cout<<error<<endl;
    }
    for(int i=0;i<onum;i++)cout<<output[i].out<<" ";
    return 0;
}

最后我要看看训练结果如何,就让他输出output[i].out了。在实际训练中,训练集肯定是不止一组的,一定要记住,训练的时候把整个训练集过下来才能算一次训练,如果一组训练完再进行下一组训练,那么BP神经网络比较蠢,最后只能逼近最后一组数据。(想想为什么)

接下来就是主要运算过程啦!

首先前向传播

hide[i].in=sigma(hide[i].w[j]*input[j])+hide[i].bia

hide[i].out=sigmoid(hide[i].in)

output[i].in+=sigmoid(output[i].w[j]*hide[j].out)+output[i].bia

output[i].out=sigmoid(output[i].in)

for(int i=0;i<hnum;i++)
    {
        hide[i].in=0;
        for(int j=0;j<inum;j++)
            hide[i].in+=hide[i].w[j]*input[j];
        hide[i].in+=hide[i].bia;
        hide[i].out=f(hide[i].in);
    }
    for(int i=0;i<onum;i++)
    {
        output[i].in=0;
        for(int j=0;j<hnum;j++)
            output[i].in+=output[i].w[j]*hide[j].out;
        output[i].in+=output[i].bia;
        output[i].out=f(output[i].in);
    }

然后算一下误差

error=0;
    for(int i=0;i<onum;i++)
    {
        double trans=expect[i]-output[i].out;
        error+=trans*trans;
    }
    error*=0.5;

最后开始反向传播,具体反向传播的求偏导过程不赘述。

tr用于储存对expect求output[i].bia的偏导(其实是对output[i].in的求偏导,因为bia求偏导时前面乘常数1,所以没有变化),这样在进行下一步求偏导的时候可以减少运算时间

for(int i=0;i<onum;i++)
        output[i].tr=(expect[i]-output[i].out)*dif(output[i].in);

接下来是要先训练隐藏层的权重和偏置的,因为先训练输出层的权重会影响到训练隐藏层的权重(隐藏层权重偏导数内含有输出层的权重)

for(int i=0;i<hnum;i++)
    {
        double trans=0;
        for(int j=0;j<onum;j++)
            trans+=output[j].tr*output[j].w[i];
        trans*=dif(hide[i].in);
        hide[i].bia+=n*trans;
        for(int j=0;j<inum;j++)
            hide[i].w[j]+=n*trans*input[j];
    }

最后攻克输出层训练,一次训练结束,但是error值可能并没有满足要求(我在while里面设定了这个条件)

for(int i=0;i<onum;i++)
    {
        output[i].bia+=n*output[i].tr;
        for(int j=0;j<hnum;j++)
            output[i].w[j]+=n*output[i].tr*hide[j].out;
    }

对整个运算过程包装一下,放入mainwork()函数中~

void mainwork()
{
    for(int i=0;i<hnum;i++)
    {
        hide[i].in=0;
        for(int j=0;j<inum;j++)
            hide[i].in+=hide[i].w[j]*input[j];
        hide[i].in+=hide[i].bia;
        hide[i].out=f(hide[i].in);
    }
    for(int i=0;i<onum;i++)
    {
        output[i].in=0;
        for(int j=0;j<hnum;j++)
            output[i].in+=output[i].w[j]*hide[j].out;
        output[i].in+=output[i].bia;
        output[i].out=f(output[i].in);
    }
    error=0;
    for(int i=0;i<onum;i++)
    {
        double trans=expect[i]-output[i].out;
        error+=trans*trans;
    }
    error*=0.5;
    for(int i=0;i<onum;i++)
        output[i].tr=(expect[i]-output[i].out)*dif(output[i].in);
    for(int i=0;i<hnum;i++)
    {
        double trans=0;
        for(int j=0;j<onum;j++)
            trans+=output[j].tr*output[j].w[i];
        trans*=dif(hide[i].in);
        hide[i].bia+=n*trans;
        for(int j=0;j<inum;j++)
            hide[i].w[j]+=n*trans*input[j];
    }
    for(int i=0;i<onum;i++)
    {
        output[i].bia+=n*output[i].tr;
        for(int j=0;j<hnum;j++)
            output[i].w[j]+=n*output[i].tr*hide[j].out;
    }
}

如果对反向传播不了解的话,可以参考下面的两个链接

BP神经网络讲解(简书)

还有一个是另外一个大佬的C++实现,里面有更好的解说过程

基于C++实现简单的BP神经网络

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值