这个学期我学习是神经网络课程,有很多的知识国内都不是很完善,而国外就有很大的进步,下面就是来自一本
<AI for Game>的电子版英文书.其中我就拿用面向对象写的C++类进行说明怎样编写神经网络程序.
神经网络的基本思想就是怎样去改变权值.
神经网络层类
class NeuralNetworkLayer
{
public:
int NumberOfNodes;
int NumberOfChildNodes;
int NumberOfParentNodes;
double** Weights;
double** WeightChanges;
double* NeuronValues;
double* DesiredValues;
double* Errors;
double* BiasWeights;
double* BiasValues;
double LearningRate;
bool LinearOutput;
bool UseMomentum;
double MomentumFactor;
NeuralNetworkLayer* ParentLayer;
NeuralNetworkLayer* ChildLayer;
NeuralNetworkLayer();
void Initialize(int NumNodes,
NeuralNetworkLayer* parent,
NeuralNetworkLayer* child);
void CleanUp(void);
void RandomizeWeights(void);
void CalculateErrors(void);
void AdjustWeights(void);
void CalculateNeuronValues(void);
};
NumberOfNodes 层中神经元数目
NumberOfChildNodes 子层神经元数目
NumberOfParentNodes 父层神经元数目
Weights权值数组
WeightChanges 权值改变数组
NeuronValues 神经元值
DesiredValues 导师信号
Errors 误差
BiasWeights 偏差权值
LearningRate 学习效率
LinearOutput 是否线性输出
UseMomentum 是否有动力因素
MomentumFactor有动力因素的话,则动力因素大小值
ParentLayer 父层
ChildLayer 子层
构造函数
NeuralNetworkLayer::NeuralNetworkLayer()
{
ParentLayer = NULL;
ChildLayer = NULL;
LinearOutput = false;
UseMomentum = false;
MomentumFactor = 0.9;
}
初始化类
void NeuralNetworkLayer::Initialize(int NumNodes,
NeuralNetworkLayer* parent,
NeuralNetworkLayer* child)
{
int i, j;
// 分配内存
NeuronValues = (double*) malloc(sizeof(double) *
NumberOfNodes);
DesiredValues = (double*) malloc(sizeof(double) *
NumberOfNodes);
Errors = (double*) malloc(sizeof(double) * NumberOfNodes);
if(parent != NULL)
{
ParentLayer = parent;
}
if(child != NULL)
{
ChildLayer = child;
Weights = (double**) malloc(sizeof(double*) *
NumberOfNodes);
WeightChanges = (double**) malloc(sizeof(double*) *
NumberOfNodes);
for(i = 0; i<NumberOfNodes; i++)
{
Weights[i] = (double*) malloc(sizeof(double) * NumberOfChildNodes);
WeightChanges[i] = (double*) malloc(sizeof(double) * NumberOfChildNodes);
}
BiasValues = (double*) malloc(sizeof(double) *
NumberOfChildNodes);
BiasWeights = (double*) malloc(sizeof(double) *
NumberOfChildNodes);
} else {
Weights = NULL;
BiasValues = NULL;
BiasWeights = NULL;
WeightChanges = NULL;
}
// 确保所有归 0
for(i=0; i<NumberOfNodes; i++)
{
NeuronValues[i] = 0;
DesiredValues[i] = 0;
Errors[i] = 0;
if(ChildLayer != NULL)
for(j=0; j<NumberOfChildNodes; j++)
{
Weights[i][j] = 0;
WeightChanges[i][j] = 0;
}
}
// Initialize the bias values and weights
if(ChildLayer != NULL)
for(j=0; j<NumberOfChildNodes; j++)
{
BiasValues[j] = -1;
BiasWeights[j] = 0;
}
}
这个构造函数用于初始化层,为变量分配空间。这个函数共三个参数:一个是确定层是神经元的个数,一个是父层,一个是子层。如果这个层是一个输入层则父层为null,同理,如果这个层是一个输出层,则子层为null,而当这个层是一个隐层,则父层和子层都不可能为null。
清空层函数
void NeuralNetworkLayer::CleanUp(void)
{
int i;
free(NeuronValues);
free(DesiredValues);
free(Errors);
if(Weights != NULL)
{
for(i = 0; i<NumberOfNodes; i++)
{
free(Weights[i]);
free(WeightChanges[i]);
}
free(Weights);
free(WeightChanges);
}
if(BiasValues != NULL) free(BiasValues);
if(BiasWeights != NULL) free(BiasWeights);
}
这个函数主要是用于清空已经不再需要利用的层。悉放内存空间。
随机产生初始权值函数
void NeuralNetworkLayer::RandomizeWeights(void)
{
int i,j;
int min = 0;
int max = 200;
int number;
srand( (unsigned)time( NULL ) );
for(i=0; i<NumberOfNodes; i++)
{
for(j=0; j<NumberOfChildNodes; j++)
{
number = (((abs(rand())%(max-min+1))+min));
if(number>max)
number = max;
if(number<min)
number = min;
Weights[i][j] = number / 100.0f - 1;
}
}
for(j=0; j<NumberOfChildNodes; j++)
{
number = (((abs(rand())%(max-min+1))+min));
if(number>max)
number = max;
if(number<min)
number = min;
BiasWeights[j] = number / 100.0f - 1;
}
}
这个方法产生一个值大小在-1到1之间的值并把值作为初始权值。同时也产生偏值,并储存在BiasWeights数组中。你可以在训练网络前调用这个方法。
计算神经元值的方法
void NeuralNetworkLayer::CalculateNeuronValues(void)
{
int i,j;
double x;
if(ParentLayer != NULL)
{
for(j=0; j<NumberOfNodes; j++)
{
x = 0;
for(i=0; i<NumberOfParentNodes; i++)
{
x += ParentLayer->NeuronValues[i] *
ParentLayer->Weights[i][j];
}
x += ParentLayer->BiasValues[j] *
ParentLayer->BiasWeights[j];
if((ChildLayer == NULL) && LinearOutput)
NeuronValues[j] = x;
else
NeuronValues[j] = 1.0f/(1+exp(-x));
}
}
}
所有的权值调整都通过嵌套的for循环来实现。j循环访问所有子层的所有结点,i循环访问所有父层的所有结点。在这些循环中,网络的输入被计算并且储存在变量x中。父层中各神经元所有相联的输入加权和被反馈到各个元结点。当你计算输入层中所有神经元,神经元值被输入激活函数。除了输出层,其它各层都会用logistic activation function,当然logistic activation function是否为线性或非线性取决于LinearOutput的值.
计算误差函数
void NeuralNetworkLayer::CalculateErrors(void)
{
int i, j;
double sum;
if(ChildLayer == NULL) // output layer
{
for(i=0; i<NumberOfNodes; i++)
{
Errors[i] = (DesiredValues[i] - NeuronValues[i]) *
NeuronValues[i] *
(1.0f - NeuronValues[i]);
}
} else if(ParentLayer == NULL) { // 输入层
for(i=0; i<NumberOfNodes; i++)
{
Errors[i] = 0.0f;
}
} else { // 隐层
for(i=0; i<NumberOfNodes; i++)
{
sum = 0;
for(j=0; j<NumberOfChildNodes; j++)
{
sum += ChildLayer->Errors[j] * Weights[i][j];
}
Errors[i] = sum * NeuronValues[i] *
(1.0f - NeuronValues[i]);
}
}
}
如果层中没有子层,这个函数仅仅在输出层有效。但如果层中没有父层,则这个函数会在输入层被调用。这个误差被设置为0。如是神经网络有三层:输入层、隐层、输出层,则这个函数会被应用于隐层。
权值调整方法
void NeuralNetworkLayer::AdjustWeights(void)
{
int i, j;
double dw;
if(ChildLayer != NULL)
{
for(i=0; i<NumberOfNodes; i++)
{
for(j=0; j<NumberOfChildNodes; j++)
{
dw = LearningRate * ChildLayer->Errors[j] *
NeuronValues[i];
if(UseMomentum)
{
Weights[i][j] += dw + MomentumFactor *
WeightChanges[i][j];
WeightChanges[i][j] = dw;
} else {
Weights[i][j] += dw;
}
}
}
for(j=0; j<NumberOfChildNodes; j++)
{
BiasWeights[j] += LearningRate *
ChildLayer->Errors[j] *
BiasValues[j];
}
}
}
权值只有一个神经网络有子层的时候才会被调整,换句话说:输入层和隐层才会出现权值的调整。输出层没有子层,因此没有连接到下一层,更不会有权值可以调整。For嵌套遍历层和子层中的所有节点。由神经网络定义可知:每个神经元会和子层中各个神经元相连。在这些嵌套循环中,权值会用规则进行调整。如果动力要素被应用,动力要素值会被添加到权值的改变之中。当次的权值变化值会被储存在变量WeightChanges中,为下次的利用做好准备。如果动力因素不被使用,则不必将其添加到权值改变函数中进行计算。
一、神经网络类(The Neural Network Class)
class NeuralNetwork
{
public:
NeuralNetworkLayer InputLayer;
NeuralNetworkLayer HiddenLayer;
NeuralNetworkLayer OutputLayer;
void Initialize(int nNodesInput, int nNodesHidden,
int nNodesOutput);
void CleanUp();
void SetInput(int i, double value);
double GetOutput(int i);
void SetDesiredOutput(int i, double value);
void FeedForward(void);
void BackPropagate(void);
int GetMaxOutputID(void);
double CalculateError(void);
void SetLearningRate(double rate);
void SetLinearOutput(bool useLinear);
void SetMomentum(bool useMomentum, double factor);
void DumpData(char* filename);
};
这个类共有三个变量,分别为输入层、隐层、输出层。但方法有很多,一共13个方法。下面我们将会详细地介绍。
初始化函数
void NeuralNetwork::Initialize(int nNodesInput,
int nNodesHidden,
int nNodesOutput)
{
InputLayer.NumberOfNodes = nNodesInput;
InputLayer.NumberOfChildNodes = nNodesHidden;
InputLayer.NumberOfParentNodes = 0;
InputLayer.Initialize(nNodesInput, NULL, &HiddenLayer);
InputLayer.RandomizeWeights();
HiddenLayer.NumberOfNodes = nNodesHidden;
HiddenLayer.NumberOfChildNodes = nNodesOutput;
HiddenLayer.NumberOfParentNodes = nNodesInput;
HiddenLayer.Initialize(nNodesHidden,&InputLayer,&OutputLayer);
HiddenLayer.RandomizeWeights();
OutputLayer.NumberOfNodes = nNodesOutput;
OutputLayer.NumberOfChildNodes = 0;
OutputLayer.NumberOfParentNodes = nNodesHidden;
OutputLayer.Initialize(nNodesOutput, &HiddenLayer, NULL);
}
初始化函数,用于初始化类中的变量,并且分配空间。
清空网络方法
void NeuralNetwork::CleanUp()
{
InputLayer.CleanUp();
HiddenLayer.CleanUp();
OutputLayer.CleanUp();
}
很简单的一个方法,只调用了神经网络层类(NeuralNetworkLayer)中的清空方法。
设置输入方法
void NeuralNetwork::SetInput(int i, double value)
{
if((i>=0) && (i<InputLayer.NumberOfNodes))
{
InputLayer.NeuronValues[i] = value;
}
}
设置输入层第i个神经元的输入值。你可以使用这个函数在训练期间和初始输入值。
取得输出值函数
double NeuralNetwork::GetOutput(int i)
{
if((i>=0) && (i<OutputLayer.NumberOfNodes))
{
return OutputLayer.NeuronValues[i];
}
return (double) INT_MAX;
}
这个函数有一个输入参数i,返回的是输出层第i个神经元的输出值。
设置导师信号
void NeuralNetwork::SetDesiredOutput(int i, double value)
{
if((i>=0) && (i<OutputLayer.NumberOfNodes))
{
OutputLayer.DesiredValues[i] = value;
}
}
在训练过程中你需要把实现的输出和导师信号进行比较。而我们这个类提供的方法SetDesiredOutput可以轻易地对神经网络的导师信号进行设置。这个方法有两个输入参数,对应的含义为:输出层第i个神经元的导师信号。
信号前馈函数
void NeuralNetwork::FeedForward(void)
{
InputLayer.CalculateNeuronValues ();
HiddenLayer.CalculateNeuronValues (); OutputLayer.CalculateNeuronValues ();
}
这个函数很简单。只不过调用了神经网络的层类中的CalculateNeuronValues进行对网络输入层、隐层、输出层的信号前馈。当函数调用完毕,则输出层将包含前馈神经网络的输出。
反向传播方法
void NeuralNetwork::BackPropagate(void)
{
OutputLayer.CalculateErrors();
HiddenLayer.CalculateErrors();
HiddenLayer.AdjustWeights();
InputLayer.AdjustWeights();
}
反向传播函数首先调用输出层和隐层的函数CalculateErrors。之后再隐层的和输入层的AdjustWeights函数。当然,这些调用次序是不可以改变的。
取得输出值最大神经元的ID
int NeuralNetwork::GetMaxOutputID(void)
{
int i, id;
double maxval;
maxval = OutputLayer.NeuronValues[0];
id = 0;
for(i=1; i<OutputLayer.NumberOfNodes; i++)
{
if(OutputLayer.NeuronValues[i] > maxval)
{
maxval = OutputLayer.NeuronValues[i];
id = i;
}
}
return id;
}
这个函数用于返回输出层输出神经元输出值最大的神经元ID。
计算误差方法
double NeuralNetwork::CalculateError(void)
{
int i;
double error = 0;
for(i=0; i<OutputLayer.NumberOfNodes; i++)
{
error += pow(OutputLayer.NeuronValues[i] --
OutputLayer.DesiredValues[i], 2);
}
error = error / OutputLayer.NumberOfNodes;
return error;
}
这个函数主要是返回输出值和导师信号联合的均方,用于了解训练效果和进行权值调整。
设置学习效率
void NeuralNetwork::SetLearningRate(double rate)
{
InputLayer.LearningRate = rate;
HiddenLayer.LearningRate = rate;
OutputLayer.LearningRate = rate;
}
很简单,主要是调用神经网络层类的方法LearningRate进行设置。
设置输出是否线性
void NeuralNetwork::SetLinearOutput(bool useLinear)
{
InputLayer.LinearOutput = useLinear;
HiddenLayer.LinearOutput = useLinear;
OutputLayer.LinearOutput = useLinear;
}
这个函数有一个参数,当参数为false时则为线性输出。反之就为非线性。
设置动力因素
void NeuralNetwork::SetMomentum(bool useMomentum, double factor)
{
InputLayer.UseMomentum = useMomentum;
HiddenLayer.UseMomentum = useMomentum;
OutputLayer.UseMomentum = useMomentum;
InputLayer.MomentumFactor = factor;
HiddenLayer.MomentumFactor = factor;
OutputLayer.MomentumFactor = factor;
}
用于设置是不具有动力因素,当有动力因素时,动力因素大小就是factor。