一、算法背景及概述
人工神经网络是由具有适应性的简单单元组成的广泛并行互连的网络,它的组织能够模拟生物神经系统对真实世界物体所做出的交互反应。它是一个由大量简单的处理单元广泛连接组成的非线性系统,具有良好的非线性映射能力、自适应学习能力和并行信息处理能力。人工神经网络早在上世纪就被提出,但并不受关注。近年来,随着计算机硬件性能的提高,人工神经网络的实现变得越来越简单,且实现复杂的人工神经网络也并不困难。尤其是近几年机器学习的兴起,人工神经网络将在智能控制、模式识别、自适应滤波和信号处理、传感技术和机器人、非线性优化、知识处理、生物医学工程、金融预测和管理等方面起着越来越大的作用。
人工神经网络中最基本的模型是神经元模型,每个神经元与其它神经元相连,当它接受到的点位值高于它的阈值时它就会产生“兴奋”,即向相连的神经元发送信号。人工神经网络中的神经元即从生物神经元中抽象出来,作为一个接受多个参数的激活函数,而其函数值则作为神经元的输出。目前被广为采用的神经元模型是由心理学家McCulloch和数学家Pitts在1943年提出的阈值加权模型,简称MP模型。它接受多个参数的输入,在经过简单的线性运算后作为激活函数的加权输入值。而常见的激活函数有阶跃函数(y = sgn(x))和Sigmoid函数(y = 1/(1+exp(-x)))。前者是最为理想的激活函数,但它并不连续也不光滑,给研究带来诸多困扰。后者则是典型的激活函数,它连续且处处可导,而它也是本文中将要采用的激活函数。多个这样的神经元按一定的层次连接起来,就形成了神经网络。
BP算法则由Rumelhart,Hinton和Williams在1986年提出,通常称为反向传播算法(back-propagation algorithm)。它是一种被广泛采用的训练神经网络的算法,基于梯度最速下降和delta法则。它主要思想即信号的正向传播与误差的反向传播。正向传播时,样本作为输入层的输入,经隐层传导后进入输出层处理输出。若输出层的输出结果与期望不符或者不满足误差的要求,接下来则进入误差的反馈阶段。误差反馈即将输出误差通过某种方式从输出层反向传导,各层根据接收的误差值进行调整以符合要求。经过反复迭代后最终达到期望要求。
BP算法可以根据模型参数的不同可以锁定极小值,稳定预测网络的准确性,为多层神经网络的实现与应用提供了有力支持。
#define INPUTDIMENSION (28*28)
#define HIDDENDIMENSION 15
#define OUTPUTDIMENSION 10
#define STUDYRATE 0.05
double weightInHiddenLayer[HIDDENDIMENSION + 1][INPUTDIMENSION + 1];
double weightInOutLayer[OUTPUTDIMENSION + 1][HIDDENDIMENSION + 1];
double baisInHiddenLayer[HIDDENDIMENSION + 1];
double baisInOutLayer[OUTPUTDIMENSION + 1];
double outputInHiddenLayer[HIDDENDIMENSION + 1];
double outputInOutLayer[OUTPUTDIMENSION + 1];
double deltaInHiddenLayer[HIDDENDIMENSION + 1];
double deltaInOutLayer[OUTPUTDIMENSION + 1];
void train(dataset& s); //训练
void initialize(); //初始化各数组
void forward(vector<double>& v); //信号向前传播
void backprop(int expect); //误差向后传播
void updateWeightAndBais(vector<double>& v); //更新权值与偏移量
double sigmoid(double x); //sigmoid函数
int assumpt(vector<double>& v); //根据输入给出既有模型下的输出
double getError(int expOutput); //计算方差
void forward(vector<double>& v)
{
for (int i = 1; i <= HIDDENDIMENSION; ++i)
{
double sum = 0;
for (unsigned int j = 1; j < v.size(); ++j)
sum += weightInHiddenLayer[i][j] * v[j];
sum -= baisInHiddenLayer[i];
outputInHiddenLayer[i] = sigmoid(sum);
}
for (int i = 1; i <= OUTPUTDIMENSION; ++i)
{
double sum = 0;
for (int j = 1; j <= HIDDENDIMENSION; ++j)
sum += outputInHiddenLayer[j] * weightInOutLayer[i][j];
sum -= baisInOutLayer[i];
outputInOutLayer[i] = sigmoid(sum);
}
}
void backprop(int expect)
{
for (int i = 1; i <= OUTPUTDIMENSION; ++i)
{
deltaInOutLayer[i] = ((i - 1) == expect ? 0.9 : 0.1) - outputInOutLayer[i];
deltaInOutLayer[i] *= (1.0 - outputInOutLayer[i])*outputInOutLayer[i];
}
for (int i = 1; i <= HIDDENDIMENSION; ++i)
{
double sum = 0;
for (int j = 1; j <= OUTPUTDIMENSION; ++j)
sum += deltaInOutLayer[j] * weightInOutLayer[j][i];
sum *= (1.0 - outputInHiddenLayer[i])*outputInHiddenLayer[i];
deltaInHiddenLayer[i] = sum;
}
}
void updateWeightAndBais(vector<double>& v)
{
for (int i = 1; i <= HIDDENDIMENSION; ++i)
{
baisInHiddenLayer[i] -= STUDYRATE*deltaInHiddenLayer[i];
for (int j = 1; j <= INPUTDIMENSION; ++j)
weightInHiddenLayer[i][j] += STUDYRATE*deltaInHiddenLayer[i] * v[j];
}
for (int i = 1; i <= OUTPUTDIMENSION; ++i)
{
baisInOutLayer[i] -= STUDYRATE*deltaInOutLayer[i];
for (int j = 1; j <= HIDDENDIMENSION; ++j)
weightInOutLayer[i][j] += STUDYRATE*deltaInOutLayer[i] * outputInHiddenLayer[j];
}
}