这几天抽时间学习了一下很久之前就想学习的BP神经网络。通过阅读西瓜书的神经网络部分的原理和参考了网上几篇博客,我自己用C++编写、实现了一个单隐层BP神经网络。
简单画了个示意图,好理解下面给出的公式:(注意:图中省略了其他的节点之间的连线)
西瓜书上的BP神经网络训练流程:
训练流程:
输入:训练集D={(xk,yk)},学习率η
过程:
在(0,1)范围内随机初始化网络中的所有连接权和阈值
repeat
for all (xk,yk)ⅭD do
计算当前样本的输出yk;
计算输出层神经元的梯度项gj;
计算隐藏层神经元梯度项ek;
更新连接权值wh,j,vi,j与阈值θj,ϒh;
end for
until 达到停止条件
输出:连接权值与阈值确定的单隐层前馈神经网络
在这里,我就不解释BP神经网络了,西瓜书上写得十分详细和明白,我直接上程序。
关于矩阵处理,C++不如python来得快和方便,但是只要是算法都是能够运用任何一种语言描述出来的。这里,为了方便矩阵的运算,我加入了Eigen库。Eigen库不用安装,直接下载好然后再配置一下就好。关于Eigen配置和使用可以参照博客:https://blog.csdn.net/fengbingchun/article/details/47378515
不说了,接下来上代码。
代码中的注释已经很详细了,我就不解释了。
#pragma once
#include
using namespace Eigen;
//单隐层BP神经网络
class BP
{
public:
//构造函数
BP(int input,int output,int hide,double eta);
//激活函数
double sigmoid(double x);
//训练网络
void BPtrain(MatrixXd input,MatrixXd output,int time);
//对数据进行预测,输出结果
void BPpredict(MatrixXd input);
//获取输出节点的输出值
MatrixXd getOutputValues(MatrixXd input);
//获取隐藏节点的输出值
MatrixXd getHidenValues(MatrixXd input);
virtual ~BP();
private:
//学习率
double eta;
//输入层个数
int input_size;
//输出层个数
int output_size;
//隐藏层个数
int hide_size;
//隐藏层节点阈值
MatrixXd hide_threshold;
//输出层节点阈值
MatrixXd output_threshold;
//输入到隐藏层的权值
MatrixXd hide_w;
//隐藏层到输出层的权值
MatrixXd output_w;
};
//构造函数,对输入层节点个数、输出层节点个数、隐藏层节点个数、学习率、隐藏层阈值、输出层阈值、隐藏层权值、输出层权值进行初始化
BP::BP(int input, int output, int hide, double eta):input_size(input),output_size(output),hide_size(hide),eta(eta)
{
//将以下值随机初始化为-1~1之间的值
//初始化隐藏层阈值
hide_threshold = MatrixXd::Random(1, hide_size);
//初始化输出层阈值
output_threshold = MatrixXd::Random(1, output_size);
//初始化隐藏层权值,行为隐藏节点数,列为输入节点数
hide_w = MatrixXd::Random(hide_size, input_size);
//初始化输出层权值,行为输出节点数,列为隐藏节点数
output_w = MatrixXd::Random(output_size, hide_size);
}
函数原型:
//激活函数,sigmoid函数
double BP::sigmoid(double x)
{
return 1 / (1 + exp(x*(-1)));
}
公式:
输出:
v为隐层权值,x为网络的输入,γ为隐层阈值,α为隐层输入,b为隐层输出。
//获取隐藏层的输出
MatrixXd BP::getHidenValues(MatrixXd input)
{
MatrixXd alpha, hide_output(1,hide_size);
//求隐藏节点的输入,即 隐藏层权值*输入值的累加
alpha = input * hide_w.transpose();
for (int h = 0; h < hide_size; h++)
{
//调用激活函数,获得隐藏节点的输出值
hide_output(0, h) = sigmoid(alpha(0, h) - hide_threshold(0, h));
}
return hide_output;
}
w为输出层权值,b为隐层输出,β为输出层输入,θ为输出层阈值,y为网络计算的输出。
//获取输出
MatrixXd BP::getOutputValues(MatrixXd input)
{
MatrixXd beta, output(1, output_size), hide_output;
//获取隐藏层输出
hide_output = getHidenValues(input);
//求输出层的输入值,即 隐藏层输出*权值的累加
beta = hide_output * output_w.transpose();
for (int j = 0; j < output_size; j++)
{
//求得最终的输出
output(0, j) = sigmoid(beta(0, j) - output_threshold(0, j));
}
return output;
}
公式:
,,其中
w为输出层连接权值,η为学习率,b为隐层输出,g为输出层梯度项,θ为隐层阈值,为网络输出,y为给定输出,v为隐层连接权值,e为隐层梯度项,x为网络输入,γ为隐层阈值。
//训练神经网络
void BP::BPtrain(MatrixXd input, MatrixXd output, int time)
{
MatrixXd train_output, hide_output;
//输出神经元梯度项g
MatrixXd output_gradient(1,output_size);
//隐藏层神经元梯度项e
MatrixXd hide_gradient(1,hide_size);
//训练time次
while (time>0)
{
--time;
//对每次输入
for (int t = 0; t < input.rows(); t++)
{
train_output = getOutputValues(input.row(t));
hide_output = getHidenValues(input.row(t));
//更新输出权值和阈值
//计算输出层神经元梯度项
for (int j = 0; j < output_size; j++)
{
output_gradient(0, j) = train_output(0, j) * (1 - train_output(0, j)) * (output(t, j) - train_output(0, j));
}
//修改输出权值
MatrixXd temp = output_w; //暂存原值,以便计算后面的 sum
output_w = output_w + eta * output_gradient.transpose() * hide_output;
//修改输出神经元阈值
output_threshold = output_threshold - eta * output_gradient;
//计算隐藏层的神经元梯度
for (int h = 0; h < hide_size; h++)
{
double sum = 0;
for (int j = 0; j < output_size; j++)
{
sum += temp(j, h)*output_gradient(0, j);
}
hide_gradient(0, h) = hide_output(0, h) * (1 - hide_output(0, h)) * sum;
}
//修改隐藏层权值
hide_w = hide_w + eta * hide_gradient.transpose() * input.row(t);
//修改隐藏层神经元的阈值
hide_threshold = hide_threshold - eta * hide_gradient;
}
}
}
测试输出函数其实就是在训练好的网络上输入数据,调用getOutputValues函数得出网络的预测输出值。
//对给定输入预测神经元的输出
void BP::BPpredict(MatrixXd input)
{
MatrixXd result;
cout << "预测结果:" << endl;
for (int t = 0; t < input.rows(); t++)
{
//即以输入的值获取输出值
result = getOutputValues(input.row(t));
cout << t << ": " << result << endl;
}
}
int main()
{
//输入5个点进行训练
MatrixXd x(5, 2);
x << 0, 1,
1, 2,
2, 1,
2, 3,
3, 0;
MatrixXd y(5, 1);
y << 1, 1, 0, 1, 0;
//设置BP网络参数:输入节点数:2,输出节点数:1,隐藏层节点数:3,学习率:0.8
BP test(2, 1, 3, 0.8);
//进行训练,这里训练次数设为了1000次,更新各权值和阈值
test.BPtrain(x, y, 1000);
//输入3个点进行预测,点(4,2)对应的输出应该为0,(-1,2)为1,(1,-2)为0
MatrixXd t(3, 2);
t << 4, 2,
-1, 2,
1, -2;
//输出预测结果
test.BPpredict(t);
system("pause");
return 0;
}
放上输出结果:
输入3个点进行预测(4,2)(-1,2)(1,-2),对应的预计结果为0, 1, 0
结果为:
随着训练次数的增加和隐层节点数的增加,预测结果的误差将会越来越小。
给出源代码下载地址:https://download.csdn.net/download/m0_37543178/10674861