单隐层BP神经网络C++实现

    这几天抽时间学习了一下很久之前就想学习的BP神经网络。通过阅读西瓜书的神经网络部分的原理和参考了网上几篇博客,我自己用C++编写、实现了一个单隐层BP神经网络。

    简单画了个示意图,好理解下面给出的公式:(注意:图中省略了其他的节点之间的连线)

    西瓜书上的BP神经网络训练流程:

训练流程:

输入:训练集D={(xkyk)},学习率η

过程:

在(0,1)范围内随机初始化网络中的所有连接权和阈值

repeat

      for all (xkyk)ⅭD do

           计算当前样本的输出yk

扫描二维码关注公众号,回复: 4148950 查看本文章

           计算输出层神经元的梯度项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

    不说了,接下来上代码。

    1、单隐层BP神经网络类设计

    代码中的注释已经很详细了,我就不解释了。

#pragma once
#include <Eigen/dense>
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;
};

 2、类的实现

 2.1构造函数

//构造函数,对输入层节点个数、输出层节点个数、隐藏层节点个数、学习率、隐藏层阈值、输出层阈值、隐藏层权值、输出层权值进行初始化
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);
}

 2.2 激活函数

    函数原型:

        y = \frac{1}{(1 + e^{-x})}

//激活函数,sigmoid函数
double BP::sigmoid(double x)
{
	return 1 / (1 + exp(x*(-1)));
}

 2.3隐藏层的输出

    公式:

        

       输出: 

       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;
}

 2.4 输出层输出

  

   

  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;
}

  2.5对神经网络进行训练

 公式:

 输出均方误差: ,需要通过修改权值和阈值将其变为最小。

 ,,其中 

 ,其中 

 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;
			}
		}
		
}

 2.6测试输出函数 

    测试输出函数其实就是在训练好的网络上输入数据,调用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;
	}
}

 3、测试主函数

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

猜你喜欢

转载自blog.csdn.net/m0_37543178/article/details/82761084