使用C语言在VS下完成标准BP算法对一个双层神经网络(隐藏层神经元个数不确定)进行模型训练从而确定相应参数(即各层神经元阙值及相关权值矩阵)。通过查阅机器学习书籍了解了各相应参数每次微调的变化量相应的公式推导过程从而实现编码过程中参数的调整更新过程。
公式推导细节参考周志华机器学习第五章第三节5.3误差逆传播算法。
对于完成的标准BP算法,其用户手册如下:
输入神经元个数,即训练样本维数为3,输出神经元个数为2,中间隐藏层节点数可根据用户需求自动调整。这里给出了12个训练样本进行训练从而确定参数模型。
训练样本集和其对应输出样本:{0.4, 0.3, 0.8}, {1, 1}
{0.8,0.6, 0.2}, {1, 0}
{0.3,0.7, 0.3}, {0, 0}
{0.2,0.9, 0.7}, {0, 1}
{0.9,0.5, 0.6}, {1, 1}
{0.2,0.1, 0.4}, {1, 0}
{0.6,0.8, 0.1}, {0, 0}
{0.5,0.7, 0.9}, {0, 1}
{0.5,0.4, 0.4}, {1, 0}
{0.45,0.4, 0.6}, {1, 1}
{0.4,0.55, 0.6}, {0, 1}
{0.5,0.6, 0.45}, {0, 0}
具体输入与输出相对应关系:
输入样本可以看作是单位体积的正方体空间内的一个点坐标,输出向量的第一个元素代表平面y=x将正方体分成的两个部分,其中x>=y部分则输出结果为1,否则结果就是0;
输出向量的第二个元素代表平面z=0.5将正方体分成的上下两个部分,其中z>=0.5部分输出结果为1;否则就为0.
// ConsoleApplication3.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
/*
object:标准BP算法对双层神经网络分类(C语言)
Author: Zetn.liu
*/
# define N_out 2
# define N_in 3
# define N_sample 12
//BP人工神经网络结构体定义
typedef struct
{
int hidden; //隐藏层神经元数目
float V[N_in][10]; //输入到隐藏层权值矩阵(10表示隐藏层神经元个数最多为10)
float W[10][N_out]; //隐藏层到输出层神经元权值矩阵
float H[10]; //隐藏层神经元阙值
float O[N_out]; //输出层神经元阙值
float rate; //学习率
float accuracy; //精确度
// int max_loop; //最大循环次数(即全部训练集训练的最大次数)
}BPNet;
// sigmoid funchion achive
// function: 1/(1+pow(e,-x))
float sigmoid(float x)
{
return 1/(1 + exp(-x));
}
//初始化神经网络权重矩阵&&阙值
int InitBPNet(BPNet *BP) ///////////////////////
{
printf("请输入中间隐藏层节点数,不超过10个:\n");
scanf_s("%d", &(*BP).hidden);
printf("请输入学习率:\n");
scanf_s("%f", &(*BP).rate);
printf("请输入精确度:\n");
scanf_s("%f", &(*BP).accuracy);
// printf("请输入最大循环次数:\n");
// scanf_s("%f", &(*BP).max_loop);
int i,j;
srand((unsigned)time(NULL));//初始化随机种子
//初始化神经网络权值矩阵以及阙值
for(i = 0; i < N_in; i++)
for(j = 0; j < (*BP).hidden; j++)
(*BP).V[i][j] = rand()/(RAND_MAX+1.0);
for(i = 0; i < (*BP).hidden; i++)
for(j = 0; j < N_out; j++)
(*BP).W[i][j] = rand()/(RAND_MAX+1.0);
for(i = 0; i < (*BP).hidden; i++)
(*BP).H[i] = rand()/(RAND_MAX+1.0);
for(i = 0; i < N_out; i++)
(*BP).O[i] = rand()/(RAND_MAX+1.0);
return 1;
}
//带标记训练样本集训练神经网络, x为样本,y为理想输出。
int TrainBPNet(BPNet *BP,float x[N_sample][N_in],int y[N_sample][N_out])
{
float rate = (*BP).rate;
float accuracy = (*BP).accuracy;
int hidden = (*BP).hidden;
//int maxloop = (*BP).max_loop;
float v[N_in][10],w[10][N_out];
float H[10],O[N_out];
float dealt_h[10],dealt_O[N_out];//训练过程中权变化量
float out1[10],out2[N_out];
int i,j,k,n;
float temp;
//复制结构体中输入到中间层的权值矩阵
for(i = 0; i < N_in; i++)
for(j = 0; j < hidden; j++)
v[i][j] = (*BP).V[i][j];
//复制结构体中中间层到输出层的权值矩阵
for(i = 0; i < hidden; i++)
for(j = 0; j < N_out; j++)
w[i][j] = (*BP).W[i][j];
//复制结构体隐藏层和输出层阙值
for(i = 0; i < hidden; i++)
H[i] = (*BP).H[i];
for(i = 0; i < N_out; i++)
O[i] = (*BP).O[i];
float e = accuracy + 1;//确保首次循环能够进行
//for(n= 0; e > accuracy && n < maxloop; n++)
for(n= 0; e > accuracy; n++)
{
e = 0;//初始化e值
for(i = 0; i < N_sample; i++)
{
//计算中间层输出向量
for(k = 0; k < hidden; k++)
{
temp = 0;
for(j = 0; j < N_in; j++)
{
temp += x[i][j] * v[j][k];
}
out1[k] = sigmoid(temp - H[k]);
}
//计算输出层输出向量
for(k = 0; k < N_out; k++)
{
temp = 0;
for(j = 0; j < hidden; j++)
temp += out1[j] * w[j][k];
out2[k] = sigmoid(temp - O[k]);
}
//计算输出层的权修改量,即输出层神经元梯度项(dealt_O[])、
for(j = 0; j < N_out; j++)
dealt_O[j] = out2[j]*(1-out2[j])*(y[i][j]-out2[j]);
//计算隐藏层的权修改量,即隐藏层神经元梯度项(dealt_h[])
for(j = 0; j < hidden; j++)
{
temp = 0;
for(k = 0; k < N_out; k++)
temp += dealt_O[k]*w[j][k];
dealt_h[j] = out1[j]*(1-out1[j])*temp;
}
//输出层权值矩阵的修改
for(j = 0; j < hidden; j++)
for(k = 0; k < N_out; k++)
w[j][k] += rate * out1[j]*dealt_O[k];
//输出层阙值修改
for(j = 0; j < N_out; j++)
O[j] -= rate * dealt_O[j];
//中间层权值矩阵修改
for(j = 0; j < N_in; j++)
for(k = 0; k < hidden; k++)
v[j][k] += rate * x[i][j] * dealt_h[k];
//中间层阙值修改
for(j = 0; j < hidden; j++)
H[j] -= rate * dealt_h[j];
//计算输出误差以比较和精度的大小从而终止循环
//训练集一次训练完成后累积误差
for(j = 0; j < N_out; j++)
e += (y[i][j] - out2[j]) * (y[i][j] - out2[j]);
}
if((n % 10) == 0)
printf("累计误差为:%f\n", e);
}
printf("总共循环次数:%d\n", n);
//修改后的隐藏层权值矩阵&&隐藏层阙值
printf("修改后的隐藏层权值矩阵:\n");
for(i = 0; i < N_in; i++)
{
for(j = 0; j < hidden; j++)
printf("%f ", v[i][j]);
printf("\n");
}
printf("修改后的隐藏层阙值:\n");
for(i = 0; i < hidden; i++)
printf("%f ", H[i]);
printf("\n");
//修改后输出层权值矩阵&&输出层阙值
printf("修改后的输出层权值矩阵:\n");
for(i = 0; i < hidden; i++)
{
for(j = 0; j < N_out; j++)
printf("%f ", w[i][j]);
printf("\n");
}
printf("修改后的输出层阙值数组:\n");
for(i = 0; i < N_out; i++)
printf("%f ", O[i]);
printf("\n");
//将结果复制回结构体
for(i = 0; i < N_in; i++)
for(j = 0; j < hidden; j++)
(*BP).V[i][j] = v[i][j];
for(i = 0; i < hidden; i++)
for(j = 0; j < N_out; j++)
(*BP).W[i][j] = w[i][j];
for(i = 0; i < hidden; i++)
(*BP).H[i] = H[i];
for(i = 0; i < N_out; i++)
(*BP).O[i] = O[i];
printf("BP神经网络训练结束!\n");
return 1;
}
//测试数据集进行分类过程
int UseBPNet(BPNet *BP,float x[N_sample][N_in])
{
float input[N_in];
float out1[10]; //中间隐藏层输出
float out2[N_out];//输出层输出 ********* output type!!! ************
while(1)
{
printf("输入三个数作为输入向量:\n");
int i,j;
for(i = 0; i < N_in; i++ )
scanf_s("%f", &input[i]);
float temp;
//中间层输出模块
for(i = 0; i < (*BP).hidden; i++)
{
temp = 0;
for(j = 0; j < N_in; j++)
temp += input[j] * (*BP).V[j][i];
out1[i] = sigmoid(temp - (*BP).H[i]);
}
//输出层输出模块
for(i = 0; i < N_out; i++)
{
temp = 0;
for(j = 0; j < (*BP).hidden; j++ )
temp += out1[j] * (*BP).W[j][i];
out2[i] = sigmoid(temp - (*BP).O[i]);
}
printf("输出为:");
for(i = 0; i < N_out; i++ )
printf("%d ", int(out2[i] + 0.1));
//printf("%.6f ", out2[i]);
printf("\n");
}
return 1;
}
//****************************
int main()
{
//训练样本
float x[N_sample][N_in] = {
{0.4,0.3,0.8},
{0.8,0.6,0.2},
{0.3,0.7,0.3},
{0.2,0.9,0.7},
{0.9,0.5,0.6},
{0.2,0.1,0.4},
{0.6,0.8,0.1},
{0.5,0.7,0.9},
{0.5,0.4,0.4},
{0.45,0.4,0.6},
{0.4,0.55,0.6},
{0.5,0.6,0.45},
};
int y[N_sample][N_out] = {
{1,1},{1,0},{0,0},{0,1},{1,1},{1,0},{0,0},{0,1},{1,0},{1,1},{0,1},{0,0}};
BPNet BP;
InitBPNet(&BP); //初始化过程
TrainBPNet(&BP, x, y); //训练过程
UseBPNet(&BP, x); //测试过程
return 1;
}