终于放假啦,于是就想着把前一段时间学习的知识给整理一下,希望能够对小伙伴们有所帮助。
数学原理
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;
}