BP神经网络概念
首先从名称中可以看出,Bp神经网络可以分为两个部分,bp和神经网络。bp是 Back Propagation 的简写 ,意思是反向传播。
BP网络能学习和存贮大量的输入-输出模式映射关系,而无需事前揭示描述这种映射关系的数学方程。它的学习规则是使用最速下降法,通过反向传播来不断调整网络的权值和阈值,使网络的误差平方和最小。
其主要的特点是:信号是正向传播的,而误差是反向传播的。
举一个例子,某厂商生产一种产品,投放到市场之后得到了消费者的反馈,根据消费者的反馈,厂商对产品进一步升级,优化,一直循环往复,直到实现最终目的——生产出让消费者更满意的产品。产品投放就是“信号前向传播”,消费者的反馈就是“误差反向传播”。这就是BP神经网络的核心。
单隐层前馈神经网络,与多隐层前馈神经网络(最下层为输入层,最上层为输出层)
BP神经网络公式推导
第
j
个输出神经元的输入:
y
^
j
=
f
(
β
j
−
θ
j
)
其中
θ
j
为输出层的阈值
第j个输出神经元的输入:\hat{y}_{j}=f(\beta _{j}-\theta _{j}) 其中\theta _{j}为输出层的阈值
第j个输出神经元的输入:y^j=f(βj−θj)其中θj为输出层的阈值
第
h
个隐层神经元的输出:
b
h
=
f
(
α
h
−
γ
h
)
其中
γ
h
为隐藏层的阈值
第h个隐层神经元的输出:b_{h}=f(\alpha _{h}-\gamma _{h})其中\gamma _{h}为隐藏层的阈值
第h个隐层神经元的输出:bh=f(αh−γh)其中γh为隐藏层的阈值
假设隐层和输出层神经元都使用Sigmoid函数
其中BP神经网络采用梯度下降法来调整参数。梯度下降在机器学习中应用十分广泛,它的主要目的是通过迭代找到目标函数的最小值,或者收敛到最小值。梯度下降的基本过程就和下山的场景很类似。首先我们有一个可微分的函数,这个函数就代表着一座山。目标是找到这个函数的最小值,也就是山底。那最快的下山的方式就是找到当前位置最陡峭的方向,然后沿着此方向向下走,对应到函数中,就是找到给定点的梯度,然后朝着梯度相反的方向,就能让函数值下降的最快。
所以,我们重复利用这个方法,反复求取梯度,最后就能到达局部的最小值
标准BP算法伪代码如下
学习率
η
\eta
η控制着算法每一轮迭代中的更新步长,若
η
\eta
η太大可能会错过 最低点,容易产生震荡;若
η
\eta
η太小则收敛速度可能过慢.
BP神经网络的应用
针对鸢尾花数据集构建一个神经网络
数据预处理后得到(其中Iris-setosa:0.000000,Iris-versicolor:0.500000,Iris-virginica:1.000000)
针对鸢尾花数据集构建一个神经网络
因数据有四个属性,所以输入层为4个神经元加1个“哑结点”,
鸢尾花是三分类问题,输出层用一个神经元
隐层用3个神经元加1个“哑结点” ,隐层神经元数量是可以调整的
BP算法的实现过程
S1: 从文件读入训练样本,存放到 x[N][M+1] 和 y[N]中
S2: 初始化权重 v[M+1][H] 和 w[H+1]
S3: 开始训练神经网络
3-1:对每一个样本进行以下计算(最外层循环)
3-2:计算隐层的输出,结果存放在b[H+1]中
3-3:计算最后的输出,结果存放在ty[N]中
3-4:按公式,调整权重w[H+1]
3-5:按公式,调整权重v[M+1][H]
3-6:计算这个样本的误差
3-7:累计所有样本的总误差
S4: 判断总误差是否小于某个给定值(如1e-4),
如果误差大于该值,则重复S3,否则结束训练
S5: 将训练好的权重 v[M+1][H] 和 w[H+1] 输出并存入文件
S6: 用测试样本集考查神经网络分类的正确率
具体代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N=15;//训练样本总数
const int M=4;//一个样本的属性个数
const int H=3;//隐藏神经元个数
const double E=0.9;//学习率
double x[N][M+1];//x为样本输入,x[i][0]=-1
double w[H+1];//隐层到输出层的权重,用w[0]表示阈值
double v[M+1][H+1];//输入层到隐层的权重,v[0][i]表示阈值
double y[N];//训练样本的已知分类标记
double b[H+1];//隐层的计算输出
double ty[N];//神经网络计算输出
double sigmoid(double x) {
return 1.0/(1.0+exp(-1*x));
}
void input() {
freopen("data3.txt","r",stdin);//将测试样本数据放入data3.txt文件中,并打开读入
int k=0;
while(cin>>x[k][1]>>x[k][2]>>x[k][3]>>x[k][4]>>y[k])k++;//输入样本
for(int i=0; i<=H; i++) {//参数随机初始化
int r=rand()%101;
w[i]=r*1.0/100;
}
for(int i=0; i<N; i++)x[i][0]=-1;//哑结点初始化
b[0]=-1;
for(int i=0; i<=M; i++) {
for(int j=1; j<=H; j++) {
int r=rand()%101;
v[i][j]=r*1.0/100;
}
}
}
double g(int j) {
return ty[j]*(1-ty[j])*(y[j]-ty[j]);
}
double e(int h,int j) {
return b[h]*(1-b[h])*w[h]*g(j);
}
double dw(int h,int j) {//计算w的调整度
return E*g(j)*b[h];
}
double dv(int i,int h,int j) {//计算v的调整度
return E*e(h,j)*x[j][i];
}
void BP() {
double sume=100;
while(sume>1e-2) {
sume=0;
for(int k=0; k<N; k++) {
for(int i=1; i<=H; i++) {//计算每一隐层输出
b[i]=0;
for(int j=0; j<=M; j++) {
b[i]+=x[k][j]*v[j][i];
}
b[i]=sigmoid(b[i]);
}
ty[k]=0;
for(int i=0; i<=H; i++) {//计算每一输出层输出
ty[k]+=b[i]*w[i];
}
ty[k]=sigmoid(ty[k]);
for(int i=0; i<=H; i++) {//计算权重w
w[i]+=dw(i,k);
}
for(int i=0; i<=M; i++) { //计算权重v
for(int j=1; j<=H; j++) {
v[i][j]+=dv(i,j,k);
}
}
//计算误差
sume+=abs(ty[k]-y[k]);
}
cout<<sume<<endl;
}
}
void test() {//测试样本结果
vector<vector<double>> v= {
{ 0,13.2526,1.82329,-5.42751},
{ 0,-13.4099,-2.62366,-1.10806},
{ 0,3.30317,-5.1121,-13.1777},
{ 0,14.5049,0.292959,-3.93443},
{ 0,14.4629,1.01794,4.40286}
};
vector<double> w= {
8.47695,
14.7531,
4.77114,
12.343
};
double sume=0;
for(int k=0; k<N; k++) {
for(int i=1; i<=H; i++) {//计算每一隐层输出
b[i]=0;
for(int j=0; j<=M; j++) {
b[i]+=x[k][j]*v[j][i];
}
b[i]=sigmoid(b[i]);
}
ty[k]=0;
for(int i=0; i<=H; i++) {//计算每一输出层输出
ty[k]+=b[i]*w[i];
}
ty[k]=sigmoid(ty[k]);
sume+=abs(ty[k]-y[k]);
cout<<ty[k]<<endl;//测试输出
}
cout<<sume<<endl;//误差总和输出
}
int main() {
input();//输入数据并且初始化
BP();
for(int i=0; i<=M; i++) {
for(int j=0; j<=H; j++) {
cout<<" "<<v[i][j];
}
cout<<endl;
}
cout<<endl;
for(int i=0; i<=H; i++) {
cout<<w[i]<<endl;
}
test();
return 0;
}
输出结果(w[M+1][H+1]和v[H+1])
测试结果(每个测试样本的模拟输出,以及最后的误差总和)
可以看到模拟输出与样本结果误差相似