这里开始介绍神经网络方面的知识(Neural Networks)。首先我们会介绍几个监督式学习的算法,随后便是非监督式的学习。
一、感知器学习算法基本介绍
1.神经网络
就像进化计算,神经网络又是一个类似的概念。神经网络由一个或者多个神经元组成。而一个神经元包括输入、输出和“内部处理器”。神经元从输入端接受信息,通过“内部处理器”将这些信息进行一定的处理,最后通过输出端输出。
2.感知器
感知器(Perceptron),是神经网络中的一个概念,在1950s由Frank Rosenblatt第一次引入。
基本概念
线性可分:在特征空间中可以用一个线性分界面正确无误地分开两 类样本;采用增广样本向量,即存 在合适的增广权向量 a 使得:
则称样本是线性可分的。如下图中左图线性可分,右图不可分。所有满足条件的权向量称为解向量。权值空间中所有解向量组成的区域称为解区。
通常对解区限制:引入余量b,要求解向量满足:
使解更可靠(推广性更强),防止优化算法收敛到解区的边界。
感知准则函数及求解
对于权向量a,如果某个样本yk被错误分类,则
。我们可以用对所有错分样本的求和来表示对错分样本的惩罚:
其中Yk是被a错分的样本集合。当且仅当JP(a*) = min JP(a) = 0 时,a*是解向量。这就是Rosenblatt提出的感知器(Perceptron)准则函数。
感知器准则函数的最小化可以使用梯度下降迭代算法求解:
其中,k为迭代次数,η为调整的步长。即下一次迭代的权向量是把当前时刻的权向量向目标函数的负梯度方向调整一个修正量。
因此,迭代修正的公式为:
即在每一步迭代时把错分的样本按照某个系数叠加到权向量上。
通常情况,一次将所有错误样本进行修正不是效率最高的做法,更常用是每次只修正一个样本或一批样本的固定增量法:
收敛性讨论:可以证明,对于线性可分的样本集,采用这种梯度下降的迭代算法:
经过有限次修正后一定会收敛到一个解向量。
理论结论:只要训练样本集是线性可分的,对于任意的初值 a(1) ,经过有限次叠代,算法必定收敛。
感知器是最简单可以“学习”的机器,可以解决线性可分的问题。当样本线性不可分时,感知器算法不会收敛。实际应用中直接使用感知器的场合并不多,但他是很多复杂算法的基础。
3.单层感知器
单层感知器(Single Layer Perceptron)是最简单的神经网络。它包含输入层和输出层,而输入层和输出层是直接相连的。
图1.1
图1.1便是一个单层感知器,很简单一个结构,输入层和输出层直接相连。
接下来介绍一下如何计算输出端。
利用公式1计算输出层,这个公式也是很好理解。首先计算输入层中,每一个输入端和其上的权值相乘,然后将这些乘机相加得到乘机和。对于这个乘机和做如下处理,如果乘机和大于临界值(一般是0),输入端就取1;如果小于临界值,就取-1。
以下就给出一段单层感知器的代码。
[cpp] view plaincopyprint?
- //////////////////////////////////////////////////////////////////////////
- //singlelayer perceptrons(SLP)
- bool slp_calculate_output(constdouble * inputs,constdouble * weights,intnInputs,int & output)
- {
- if(NULL ==inputs || NULL == weights)
- return false;
- double sum =0.0;
- for (int i = 0 ; i < nInputs ; ++i)
- {
- sum += (weights[i] * inputs[i]);
- }
- //这里我们对乘机和的处理:如果大于0,则输出值为1;其他情况,输出值为-1
- if(sum >0.0)
- output = 1;
- else
- output = -1;
- }
- //////////////////////////////////////////////////////////////////////////
单层感知器其简单的特性,可以提供快速的计算。它能够实现逻辑计算中的NOT、OR、AND等简单计算。
但是对于稍微复杂的异或就无能无力。下面介绍的多层感知器,就能解决这个问题。
4.多层感知器
多层感知器(Multi-Layer Perceptrons),包含多层计算。
相对于单层感知器,输出端从一个变到了多个;输入端和输出端之间也不光只有一层,现在又两层:输出层和隐藏层。
图2.2
图2.2就是一个多层感知器。
单个感知器能够完成线性可分数据的分类问题,是一种最简单的可以“学习”的机器。但他无法解决非线性问题。比如下图中的XOR问题:即(1,1)(-1,-1)属于同一类,而(1,-1)(-1,1)属于第二类的问题,不能由单个感知器正确分类。
即在Minsky和Papert的专著《感知器》所分析的:感知器只能解决所谓一阶谓词逻辑问题:与(AND),或(OR)等,而不能解决异或(XOR)等高阶谓词罗辑问题。
用多个感知器实现非线性
单个感知器虽然无法解决异或问题,但却可以通过将多个感知器组合,实现复杂空间的分割。如下图:
将两层感知器按照一定的结构和系数进行组合,第一层感知器实现两个线性分类器,把特征空间分割,而在这两个感知器的输出之上再加一层感知器,就可以实现异或运算。
也就是,由多个感知器组合:
来实现非线性分类面,其中θ(·)表示阶跃函数或符号函数。
多层感知器神经网络
实际上,上述模型就是多层感知器神经网络(Multi-layer perceptron neural networks,MLP neural netwoks)的基础模型。神经网络中每个节点为一个感知器,模型生物神经网络中神经元的基础功能:来自外界(环境或其他细胞)的电信号通过突触传递给神经元,当细胞收到的信号总和超过一定阈值后,细胞被激活,通过轴突向下一个细胞发送电信号,完成对外界信息的加工。
但是,感知器的学习算法并不能直接应用到多层感知器模型的参数学习上。因此,最初提出的学习方案是:除了最后一个神经元之外,事先固定其他所有神经元的权值,学习过程只是用感知器学习算法学习最后一个神经元的权系数。实际上,这相当于通过第一层神经元把原始的特征空间变换到一个新的特征空间,第一层的每个神经元构成新空间的一维,然后在新的特征空间用感知器学习算法构造一个线性分类器。显然,由于第一层的神经元权值需要人为给定,模型的性能很大程度取决于能否设计出恰当的第一层神经元模型,而这取决于对所面临的的问题和数据的了解,并没有针对任意问题求解第一层神经元参数的方法。
对于多层感知器的计算也是比较简单易懂的。首先利用公式1计算每一个。
看一下它代码,就能明白它的工作原理。
[cpp] view plaincopyprint?
- //////////////////////////////////////////////////////////////////////////
- //Multi-Layerperceptrons(MLP)
- const unsignedint nInputs =4;
- const unsignedint nOutputs = 3;
- const unsignedint nHiddens = 4;
- struct mlp
- {
- doubleinputs[nInputs+1];//多一个,存放的bias,一般存放入1
- doubleoutputs[nOutputs];
- doublehiddens[nHiddens+1]; //多一个,存放的bias,一般存放入1
- doubleweight_hiddens_2_inputs[nHiddens+1][nInputs+1];
- doubleweight_outputs_2_hiddens[nOutputs][nHiddens+1];
- };
- //这里我们对乘机和的处理:如果大于0,则输出值为1;其他情况,输出值为-1
- double sigmoid (double val)
- {
- if(val >0.0)
- return1.0;
- else
- return-1.0;
- }
- //计算输出端
- bool mlp_calculate_outputs(mlp * pMlp)
- {
- if(NULL ==pMlp)
- return false;
- double sum =0.0;
- //首先计算隐藏层中的每一个结点的值
- for (int h = 0 ; h < nHiddens ; ++h)
- {
- doublesum = 0.0;
- for (int i = 0 ; i < nInputs + 1 ; ++i)
- {
- sum += pMlp->weight_hiddens_2_inputs[h][i]*pMlp->inputs[i];
- }
- pMlp->hiddens[h] = sigmoid (sum);
-
- }
- //利用隐藏层作为“输入层”,计算输出层
- for (int o = 0 ; o < nOutputs ; ++o)
- {
- doublesum = 0.0;
- for (int h = 0 ; h < nHiddens + 1 ; ++h)
- {
- sum += pMlp->weight_outputs_2_hiddens[o][h]*pMlp->hiddens[h];
- }
- pMlp->outputs[o] = sigmoid (sum);
- }
- return true;
- }
- //////////////////////////////////////////////////////////////////////////
二、感知器学习算法
1.感知器学习
其实感知器学习算法,就是利用第一节介绍的单层感知器。首先利用给的正确数据,计算得到输出值,将输出值和正确的值相比,由此来调整每一个输出端上的权值。
公式2便是用来调整权值,首先 是一个“学习参数”,一般我将它设置成小于1的正数。T便是训练数据中的正确结果,
便是第i个输入端的输入值,
便是第i个输入端上面的权值。
2.代码
对于其介绍,我还是附上代码。
[cpp] view plaincopyprint?
- //////////////////////////////////////////////////////////////////////////
- //PerceptronLearning Algorithm(PLA)
- const unsignedint nTests =4; //训练数据的数量
- const unsignedint nInputs =2; //输入端的数量
- const double alpha =0.2; //学习参数
- struct slp
- {
- doubleinputs[nInputs];
- doubleoutput;
- }; //单层感知器
- //计算输出值
- int compute(double *inputs,double * weights)
- {
- double sum =0.0;
- for (int i = 0 ; i < nInputs; ++i)
- {
- sum += weights[i]*inputs[i];
- }
- //bias
- sum += 1.0 * weights[nInputs];
- if(sum >0.0)
- return1;
- else
- return-1;
- }
- //
- int _tmain(int argc,_TCHAR* argv[])
- {
- //正确的训练数据
- slp slps[nTests] = {
- {-1.0,-1.0,-1.0},
- {-1.0, 1.0, 1.0},
- { 1.0,-1.0, 1.0},
- { 1.0, 1.0, 1.0}
- };
- doubleweights[nInputs + 1] = {0.0};
- boolbLearningOK = false;
- //感知器学习算法
- while(!bLearningOK)
- {
- bLearningOK = true;
- for (int i = 0 ; i < nTests ; ++i)
- {
- intoutput = compute(slps[i].inputs,weights);
- if(output!= (int)slps[i].output)
- {
- for(int w = 0 ; w < nInputs ; ++w)
- {
- weights[w] += alpha *slps[i].output * slps[i].inputs[w];
- }
- weights[nInputs] += alpha *slps[i].output ;
- bLearningOK = false;
- }
- }
- }
- for(int w = 0 ; w < nInputs + 1 ; ++w)
- {
- cout<<"weight"<<w<<":"<<weights[w] <<endl;
- }
- cout<<"\n";
- for (int i = 0 ;i < nTests ; ++i)
- {
- cout<<"rightresult:"<<slps[i].output<<"\t";
- cout<<"caculateresult:" << compute(slps[i].inputs,weights)<<endl;
- }
-
- //
- char temp ;
- cin>>temp;
- return 0;
- }
2.效果图
下面附上运行效果图
感知器学习算法,算是神经网络中的最简单的学习算法。但是通过这个进入学习神经网络学习算法,是个不错的选择。