單隱層BP神經網路C++實現
這幾天抽時間學習了一下很久之前就想學習的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
不說了,接下來上程式碼。
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 啟用函式
函式原型:
//啟用函式,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