1. 程式人生 > >單隱層BP神經網路C++實現

單隱層BP神經網路C++實現

    這幾天抽時間學習了一下很久之前就想學習的BP神經網路。通過閱讀西瓜書的神經網路部分的原理和參考了網上幾篇部落格,我自己用C++編寫、實現了一個單隱層BP神經網路。

    簡單畫了個示意圖,好理解下面給出的公式:(注意:圖中省略了其他的節點之間的連線)

    西瓜書上的BP神經網路訓練流程:

訓練流程:

輸入:訓練集D={(xkyk)},學習率η

過程:

在(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

    不說了,接下來上程式碼。

    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