1.神经元结构
每个神经元包含权值、输入值、激活元素,误差项,阀值组成。在这里用一个结构体Nerver来表示,其中Weights_Temp[20],这个数组在批处理训练的时候会用来,保存个样本训练后权值的改变量。
typedef struct
{
float Weights[20]; //权值
float Weights_Temp[20]; //批处理时临时权值
float Activating_Element; //激活元素Iq
float OutPut; //神经元输出
float Error; //误差项
float Threshold; //阀值
}Nerver; //神经元结构体
神经元输出与前一层的输入,本层的权值,阀值之间的关系为:
其中函数f()一种非线性函数,编程中采用
2. 3层的神经网络
for (i=1;i<=Lay_Number;i++) //Lay_Number 是神经网络的层数,本例中取4层
{
for (j=0;j<Lay_Arrangement[i];j++)//Lay_Arrangement是每层中节点个数
{
for (k=0;k<Lay_Arrangement[i-1];k++)
{
Sum+=NetWeb[i-1][k].OutPut*NetWeb[i][j].Weights[k];//NetWeb是Nerver结构体的一个对象
}
NetWeb[i][j].Activating_Element=Sum+NetWeb[i][j].Threshold;
Sum=0;
NetWeb[i][j].OutPut=(a/(1+exp(-NetWeb[i][j].Activating_Element/thta)));//第i层第j个神经元的输出值y
}
} //计算每个神经元的值
在这里神经网络的正向计算已经完成,其中权值、阀值,训练样本的初始化这些工作在后面的完整代码中给出。
3. 权值的训练过程
反向传播模型采用德尔塔原则:
均方误差:
权值训练,相邻两层间误差项的递推关系:
每层每个节点误差项的计算如下:
for(Cal=0;Cal<1;Cal++)
{
NetWeb[Lay_Number][Cal].Error=(Optimal_Value[m]-NetWeb[Lay_Number][Cal].OutPut)*a/thta*NetWeb[Lay_Number][Cal].OutPut*(1-NetWeb[Lay_Number][Cal].OutPut);//输出层误差项的计算
Sum_Error+=abs(Optimal_Value[m]-NetWeb[Lay_Number][Cal].OutPut); //累计误差
} //输出层的各节点误差项
for (i=Lay_Number-1;i>0;i--)
{
for (j=0;j<Lay_Arrangement[i];j++)
{
for (k=0;k<Lay_Arrangement[i+1];k++)
{
Sum+=NetWeb[i+1][k].Error*NetWeb[i+1][k].Weights[j];
}
NetWeb[i][j].Error=Sum*a/thta*NetWeb[i][j].OutPut*(1-NetWeb[i][j].OutPut);
Sum=0;
}
} //利用递推关系计算每层每节点误差项
对一个样本训练后利用每个节点的误差项,得到每层每节点的权值变化量,由于程序中使用批处理的方式,也就是当所有样本全部训练完一次后在改变网络中的权值,所以每个样本训练后,得到的权值改变量会累加到相应的数组NetWeb[i][j].Weights_Temp[k]中:
for (i=1;i<=Lay_Number;i++)
{
for (j=0;j<Lay_Arrangement[i];j++)
{
for (k=0;k<Lay_Arrangement[i-1];k++)
{
NetWeb[i][j].Weights_Temp[k]+=Learn_Coefficient*NetWeb[i][j].Error*NetWeb[i-1][k].OutPut;
}
}
} //计算训练一个样本后得权值改变累计
所用样本都训练完一次后,对网络权值进行更新:
for (i=1;i<=Lay_Number;i++)
{
for (j=0;j<Lay_Arrangement[i];j++)
{
for (k=0;k<Lay_Arrangement[i-1];k++)
{
NetWeb[i][j].Weights[k]+=NetWeb[i][j].Weights_Temp[k];
}
}
} //一个训练批次后,更新权值
4.训练结果
程序中采用了一个输入层,两个隐层和一个输入层的结构。每次训练结果会有所差异。误差总会存在,还存在许多改进的地方,不过作为一个理解神经网络基本运作原理来说已经够了。
目标函数选择 (sin(2*3.1415926*x)+1)/2。训练范围为(0,1)。
对于样本的选取,最好将输入与输出规范化到(0,1)中这样训练效果会比较好。程序中S函数参数选取为a=1.716,thta=1。a的值要比目标函数的最大值要大,这样训练才能成功。
5.源代码
该源代码使用了opencv显示图像,需要包含opencv库,可能由于各个电脑程序库不同,并不一定可以直接运行。但各个功能部分是可以使用的,本程序可以实现任意层数与节点的神经网络。
#include <stdio.h>
#include <tchar.h>
#include "function.h"
#include <math.h>
#include "opencv2/opencv.hpp"
#include <ctime>
#include <cstdlib>
using namespace cv;#define NUM 40
double random(double,double);typedef struct
{
float Weights[20]; //权值
float Weights_Temp[20]; //批处理时临时权值
float Activating_Element; //激活元素Iq
float OutPut; //神经元输出
float Error; //误差项
float Threshold; //阀值
}Nerver; //神经元结构体
int _tmain(int argc, _TCHAR* argv[])
{
unsigned int Lay_Arrangement[10],i,j,k,m,Lay_Number=3,Learn_Num=0,Sample_Num=NUM,Cal=0;
float Sum=0,Optimal_Value[NUM+1],Learn_Coefficient=0.01,x,Sum_Error=100000,a=1.716,thta=1;
Lay_Arrangement[0]=1;
Lay_Arrangement[1]=3;
Lay_Arrangement[2]=2;
Lay_Arrangement[3]=1;
Nerver NetWeb[10][NUM];
srand(unsigned(time(0)));
IplImage *im;
CvPoint PrePoint,Point;
CvScalar m_RGB;
for (i=1;i<=Lay_Number;i++) //i层
{
for (j=0;j<Lay_Arrangement[i];j++) //第j个节点
{
NetWeb[i][j].Threshold=0; //初始化神经元阀值
}
}
for (i=1;i<=Lay_Number;i++) //i层
{
for (j=0;j<Lay_Arrangement[i];j++) //第j个节点
{
for (k=0;k<Lay_Arrangement[i-1];k++) // 第i层第j个节点与前一层第k个节点之间的权值初始值
{
NetWeb[i][j].Weights[k]=random(-0.5,0.5);
//NetWeb[i][j].Weights[k]=0.1;
NetWeb[i][j].Weights_Temp[k]=0;
}
}
}
for(x=0.0;x<PI;x+=PI/Sample_Num)
{
NetWeb[0][Cal].OutPut=x; //神经元输入值
Optimal_Value[Cal]=(sin(2*3.1415926*x)+1)/2; //神经元理想输出值
//Optimal_Value[Cal]=sin(x);
Cal++;
} //初始化神经网络输入层
while ((Sum_Error>0.0001)&&(Learn_Num<40000))
{
Sum_Error=0;
Learn_Num++;
for (i=1;i<=Lay_Number;i++) //i层
{
for (j=0;j<Lay_Arrangement[i];j++) //第j个节点
{
for (k=0;k<Lay_Arrangement[i-1];k++) // 第i层第j个节点与前一层第k个节点之间的权值初始值
{
NetWeb[i][j].Weights_Temp[k]=0;
}
}
}
for(m=0;m<Sample_Num;m++)
{
NetWeb[0][0].OutPut=NetWeb[0][m].OutPut;
for (i=1;i<=Lay_Number;i++)
{
for (j=0;j<Lay_Arrangement[i];j++)
{
for (k=0;k<Lay_Arrangement[i-1];k++)
{
Sum+=NetWeb[i-1][k].OutPut*NetWeb[i][j].Weights[k];
}
NetWeb[i][j].Activating_Element=Sum+NetWeb[i][j].Threshold;
Sum=0;
NetWeb[i][j].OutPut=(a/(1+exp(-NetWeb[i][j].Activating_Element/thta)));
}
} //计算每个神经元的值
for(Cal=0;Cal<1;Cal++)
{
NetWeb[Lay_Number][Cal].Error=(Optimal_Value[m]-NetWeb[Lay_Number][Cal].OutPut)*a/thta*NetWeb[Lay_Number][Cal].OutPut*(1-NetWeb[Lay_Number][Cal].OutPut);
Sum_Error+=abs(Optimal_Value[m]-NetWeb[Lay_Number][Cal].OutPut); //累计误差
} //输出层的各节点误差项
for (i=Lay_Number-1;i>0;i--)
{
for (j=0;j<Lay_Arrangement[i];j++)
{
for (k=0;k<Lay_Arrangement[i+1];k++)
{
Sum+=NetWeb[i+1][k].Error*NetWeb[i+1][k].Weights[j];
}
NetWeb[i][j].Error=Sum*a/thta*NetWeb[i][j].OutPut*(1-NetWeb[i][j].OutPut);
Sum=0;
}
} //计算每层每节点误差项
for (i=1;i<=Lay_Number;i++)
{
for (j=0;j<Lay_Arrangement[i];j++)
{
for (k=0;k<Lay_Arrangement[i-1];k++)
{
NetWeb[i][j].Weights_Temp[k]+=Learn_Coefficient*NetWeb[i][j].Error*NetWeb[i-1][k].OutPut;
}
}
} //计算训练一个样本后得权值改变累计
}
for (i=1;i<=Lay_Number;i++)
{
for (j=0;j<Lay_Arrangement[i];j++)
{
for (k=0;k<Lay_Arrangement[i-1];k++)
{
NetWeb[i][j].Weights[k]+=NetWeb[i][j].Weights_Temp[k];
}
}
} //一个训练批次后,更新权值
}
im=cvCreateImage(cvSize(1400,400),IPL_DEPTH_8U,3);
Mat im_Mat(im);
cvZero(im);
PrePoint=cvPoint(0,100);
m_RGB=CV_RGB(255,0,0);
x=0;
for(Cal=0.0;Cal<Sample_Num;Cal++)
{
Point=cvPoint(int(100*x),int(100*Optimal_Value[Cal]));
cvLine(im, PrePoint, Point,m_RGB,1,CV_AA,0);
PrePoint=Point;
x+=PI/Sample_Num;
}
m_RGB=CV_RGB(0,255,0);
PrePoint=cvPoint(0,int((NetWeb[Lay_Number][Cal].OutPut)*100));
x=0;
for (i=1;i<=Lay_Number;i++)
{
for (j=0;j<Lay_Arrangement[i];j++)
{
for (k=0;k<Lay_Arrangement[i-1];k++)
{
printf("%f1.4 ",NetWeb[i][j].Weights[k]);
}
}
printf("\n");
} //一个训练批次后,更新权值
for(Cal=0.0;Cal<Sample_Num;Cal++)
{
NetWeb[0][0].OutPut=x;
for (i=1;i<=Lay_Number;i++)
{
for (j=0;j<Lay_Arrangement[i];j++)
{
for (k=0;k<Lay_Arrangement[i-1];k++)
{
Sum+=NetWeb[i-1][k].OutPut*NetWeb[i][j].Weights[k];
}
NetWeb[i][j].Activating_Element=Sum+NetWeb[i][j].Threshold;
Sum=0;
NetWeb[i][j].OutPut=(a/(1+exp(-NetWeb[i][j].Activating_Element/thta)));//利用训练后网络实现对目标函数的拟合
}
}
Point=cvPoint(int(100*x),int(100*(NetWeb[Lay_Number][0].OutPut)));
cvLine(im, PrePoint, Point,m_RGB,1,CV_AA,0);
PrePoint=Point;
x+=PI/Sample_Num;
}
imshow("result",im_Mat);
waitKey(0);
return 0;
}
double random(double start, double end)
{
return start+(end-start)*rand()/(RAND_MAX + 1.0);
}