神经网络概念及基础:https://www.cnblogs.com/maybe2030/p/5597716.html
本例具体公式使用按步推导参照:https://www.cnblogs.com/charlotte77/p/5629865.html
激活函数(激励函数)理解与总结:https://blog.csdn.net/tyhj_sf/article/details/79932893
废话不多说上代码!(代码来源参考至:https://www.cnblogs.com/semen/p/6883438.html)
#include <iostream>
#include<stdlib.h>
#include <math.h>
#include <string>
using namespace std;
//简单 输入-隐藏-输出 三层Bp网络
#define innode 2 // 输入层节点数
#define hiddennode 10 // 隐层节点数
#define outnode 1 // 输出层节点数
#define sample 4 // 训练样本数
#define speed 0.1 // 训练速率
#define Kernel 1 // 传递函数类型
class BP_Net
{
private:
double W_Hid[hiddennode]; //隐藏层数据存储
double W_Out[outnode]; //输出层数据存储
double W_InToHid [hiddennode][innode]; //输入层到隐藏层的网络权值
double W_HidToOut[outnode][hiddennode]; //隐藏层到输出层的网络权值
double W_Rand(double low, double high); //权值随机填充
public:
double Err; //临时误差
double E_Sum; //总误差
double R_Predict[outnode]; //预测结果
BP_Net(const char Name[]); //构造
~BP_Net(); //反构造
void BP_Init(double low, double high); //网络权值初始化
void BP_Predict(double TestData[]); //模型数据预测
//数据模型训练
void BP_Train(double InData[sample][innode], double Target[sample][outnode]);
//激活函数选择 传递方向选择(前向、后向)
double T_Kernel(unsigned char method,unsigned char T_Direction, double Value);
};
BP_Net::BP_Net(const char Name[]):E_Sum(1),Err(0){
cout<<Name<<" :"<<endl;
}
BP_Net::~BP_Net(){}
void BP_Net::BP_Init(double low, double high)
{
unsigned int N_W_InToHid = hiddennode*innode;
unsigned int N_W_HidToOut = hiddennode*outnode;
unsigned int m = max(N_W_InToHid,N_W_HidToOut);
double* P_Hid = W_Hid;
double* P_Out = W_Out;
double* P_InToHid = (double*)W_InToHid;
double* P_HidToOut = (double*)W_HidToOut;
for (int i = 0; i < m; i++)
{
if(i < hiddennode) P_Hid[i] = W_Rand(low, high);
if(i < outnode) P_Out[i] = W_Rand(low, high);
if(i < N_W_InToHid) P_InToHid[i] = W_Rand(low, high);
if(i < N_W_HidToOut) P_HidToOut[i] = W_Rand(low, high);
}
}
double BP_Net::T_Kernel(unsigned char method,unsigned char T_Direction, double Value)
{
if(method == 1){ //sigmod
if(T_Direction<0.5)//前向
return 1.0 / (1 + exp(-Value));
else //反向
return Value*(1 - Value);
}
else{ //Tanh
if(T_Direction<0.5)//反向
return (exp(Value) - exp(-Value)) / (exp(Value) + exp(-Value));
else //反向
return pow((exp(Value) - exp(-Value)) / (exp(Value) + exp(-Value)), 2);
}
}
double BP_Net::W_Rand(double low, double high)
{
double val;
val = ((double)rand() / (double)RAND_MAX)*(high - low) + low;
return val;
}
void BP_Net::BP_Train(double InData[sample][innode], double Target[sample][outnode])
{
double I_Out[outnode]; //输出点输入
double O_Out[outnode]; //输出点输出
double E_Out[outnode]; //输出点误差
double I_Hid[hiddennode];
double O_Hid[hiddennode];
double E_Hid[hiddennode];
double Once_In[innode]; //输入结点值
double Once_Target[outnode]; //输出目标值
unsigned int M = max(innode,outnode);
unsigned int M1 = max(outnode,hiddennode);
for (int i = 0; i < sample; i++) // 监督数据训练
{
for (int j = 0; j < M; j++) //前期初值准备
{
if(j < innode) //提取一次输入结点数据
Once_In[j] = InData[i][j];
if(j < outnode)
Once_Target[j] = Target[i][j]; //提取一次拟合目标数据
}
/*前向传播*/
for (int j = 0; j < hiddennode; j++) //输入层到隐藏层的传播
{
I_Hid[j] = 0; // 结点累加和 初始化
for (int k = 0; k < innode; k++) //计算每个隐藏结点的权值和
I_Hid[j] += Once_In[k] * W_InToHid[j][k];
O_Hid[j] = T_Kernel(Kernel, 0, I_Hid[j] + W_Hid[j]); //用激活函数得结点输出值
}
for (int j = 0; j < outnode; j++) //隐藏层到输出层的传播
{
I_Out[j] = 0;
for (int k = 0; k < hiddennode; k++)
I_Out[j] += O_Hid[k] * W_HidToOut[j][k];
O_Out[j] = T_Kernel(Kernel, 0, I_Out[j] + W_Out[j]);
}
/*后向传播*/
for (int j = 0; j < outnode; j++) //输出层到隐藏层的传播
{
double E_Temp = Once_Target[j] - O_Out[j]; //每个输出节点的误差
Err += pow(E_Temp,2); //模型总输出误差
// 偏导 (总误差E_Sum)/(某输出结点O_Out) * 偏导 (某输出结点O_Out)/(激发函数运算Net)
E_Out[j] = (E_Temp) * T_Kernel(Kernel, 1, O_Out[j]);
for (int k = 0; k < hiddennode; k++) //输出层到隐藏层每条网络权值更新
// E_Out[j] * 偏导 (激发函数运算Net)/(某输条网络权值W)
W_HidToOut[j][k] += E_Out[j] * O_Hid[k] * speed;
}
E_Sum = Err*0.5;
for (int j = 0; j < hiddennode; j++)
{
E_Hid[j] = 0;
for (int k = 0; k < outnode; k++) //输出层到隐藏层的传播
// 偏导 (某输出结点E_Out)/(激发函数运算Net) * 偏导 (激发函数运算Net)/(某输条网络新输入值)
E_Hid[j] += E_Out[k] * W_HidToOut[k][j];
// 偏导 (某输出结点E_Out)/(某隐层结点O_Hid) * 偏导 (某隐层结点O_Hid)/(激发函数运算Net)
E_Hid[j] = E_Hid[j] * T_Kernel(Kernel, 1, O_Hid[j]);
for (int k = 0; k < innode; k++)//隐藏层到输入层每条网络权值更新
// E_Hid[j] * 偏导 (激发函数运算Net)/(某输条网络权值W)
W_InToHid[j][k] += E_Hid[j] * Once_In[k] * speed;
}
//更新各个节点本身的值
for (int j = 0; j < M1; j++)
{
if(j < outnode) W_Out[j] = E_Out[j] * speed;
if(j < hiddennode) W_Hid[j] = E_Hid[j] * speed;
}
}
}
void BP_Net::BP_Predict(double TestData[])
{
double I_Out[outnode]; //输出点输入
double O_Out[outnode]; //输出点输出
double I_Hid[hiddennode];
double O_Hid[hiddennode];
/*前向传播*/
for (int j = 0; j < hiddennode; j++) //输入层到隐藏层的传播
{
I_Hid[j] = 0; // 结点累加和 初始化
for (int k = 0; k < innode; k++) //计算每个隐藏结点的权值和
I_Hid[j] += TestData[k] * W_InToHid[j][k];
O_Hid[j] = T_Kernel(Kernel, 0, I_Hid[j] + W_Hid[j]); //用激活函数得结点输出值
}
for (int j = 0; j < outnode; j++) //隐藏层到输出层的传播
{
I_Out[j] = 0;
for (int k = 0; k < hiddennode; k++)
I_Out[j] += O_Hid[k] * W_HidToOut[j][k];
O_Out[j] = T_Kernel(Kernel, 0, I_Out[j] + W_Out[j]);
R_Predict[j] = O_Out[j];
cout<<R_Predict[j]<<endl;
}
}
/***************<使用示例>*****************/
//训练样本
double X[sample][innode] = {
{1,1},
{1,0},
{0,1},
{0,0}
};
//训练样本对应标签
double Y[sample][outnode] = {
{1},
{0},
{0},
{1}
};
int main(int argc, char*argv[])
{
BP_Net bp("BP1");
bp.BP_Init(-1.0,1.0);
int t = 0;
while (bp.E_Sum > 0.001 && t <30000)
{
bp.Err = 0.0;
bp.BP_Train(X, Y);
t++;
}
double m[2] = { 0,1 };
bp.BP_Predict(m);
return 0;
}