前面的文章已经介绍过了2种经典的机器学习算法:线性回归和logistic回归,并且在后面的练习中也能够感觉到这2种方法在一些问题的求解中能够取得很好的效果。现在开始来看看另一种机器学习算法——神经网络。线性回归或者logistic回归问题理论上不是可以解决所有的回归和分类问题么,那么为什么还有其它各种各样的机器学习算法呢?比如这里马上要讲的神经网络算法。其实原因很简单,在前面的一系列博文练习中可以发现,那些样本点的输入特征维数都非常小(比如说2到3维),在使用logistic回归求解时,需要把原始样本特征重新映射到高维空间中,如果特征是3维,且指数最高为3时,得到的系数最高维数应该是20维。但是一般现实生活中的数据特征非常大,比如一张小的可怜的灰度图片50*50,本身就只有2500个特征,如果要采用logistic回归来做目标检测的话,则有可能达到上百万的特征了。这样不仅计算量复杂,而且因为特征维数过大容易是学习到的函数产生过拟合现象。总的来说,只有线性回归和logistic回归在现实生活中是远远不够的,因此,神经网络由于它特有的优势就慢慢被研究了。
神经网络模型的表达结构是比较清晰的,输入值和对应的权重相乘然后相加最终加上个偏移值就是输出了。只是数学公式比较繁琐,容易弄错。假设第j层网络有Sj个节点,而第j+1层网络有S(j+1)个节点,则第j层的参数应该是个矩阵,矩阵大小为S(j+1)*(Sj+1),当然了,此时是因为那个权值为1的那个网络节点没有算进去。很显然,为了方便公式的表达,神经网络中经常使用矢量化的数学公式。为什么神经网络最有学习功能呢?首先从生物上来讲,它模拟了人的大脑的功能,而人的大脑就有很强大的学习机制。其次从神经网络的模型中也可以看出,如果我们只看输出层已经和输出层相连的最后一层可以发现,它其实就是一个简单的线性回归方程(如果使输出在0~1之间,则是logistic回归方程),也就是说前面那么多的网络只是自己学习到了一些新的特征,而这些新的特征是很适合作为问题求解的特征的。因此,说白了,神经网络是为了学习到更适合问题求解的一些特征。
表面上看,神经网络的前一层和当前层是直接连接的,前一层的输出值的线性组合构成了当前层的输出,这样即使是有很多层的神经网络,不也只能学习到输入特征的线性组合么?那为什么说神经网络可以学习任意的非线性函数呢?其实是刚才我犯了一个本质错误,因为前一层输出的线性组合并不直接是本层的输出,而是一般还通过一个函数复合,比如说最常见的函数logistic函数(其它的函数比如双曲正切函数也是很常用的),要不然可就真是只能学习到线性的特征了。神经网络的功能是比较强大的,比如说单层的神经网络可以学习到”and”,”or”,,”not”以及非或门等,两层的神经网络可以学习到”xor”门(通过与门和非或门构成的一个或门合成),3层的神经网络是可以学习到任意函数的(不包括输入输出层)等,这些在神经网络的发展过程中有不少有趣的故事。当然了,神经网络也是很容易用来扩展到多分类问题的,如果是n分类问题,则只需在设计的网络的输出层设置n个节点即可。这样如果系统是可分的话则总有一个学习到的网络能够使输入的特征最终在n个输出节点中只有一个为1,这就达到了多分类的目的。
神经网络的损失函数其实是很容易确定的,这里以多分类的神经网络为例。当然了,这里谈到损失函数是在有监督学习理论框架下的,因为只有这样才能够知道损失了多少(最近有发展到无监督学习框架中也是可以计算损失函数的,比如说AutoEncoder等)。假设网络中各个参数均已学到,那么对于每个输入样本,就能够得出一个输出值了,这个输出值和输入样本标注的输出值做比较就能够得到一个损失项。由于多分类中的输出值是一个多维的向量,所以计算它的损失时需要每一维都求(既然是多分类问题,那么训练样本所标注的值也应该为多维的,至少可以转换成多维的)。这样的话,神经网络的损失函数表达式与前面的logistic回归中损失函数表达式很类似,很容易理解。
有了损失函数的表达式,我们就可以用梯度下降法或者牛顿法来求网络的参数了,不管是哪种方法,都需要计算出损失函数对某个参数的偏导数,这样我们的工作重点就在求损失函数对各个参数的偏导数了,求该偏导数中最著名的算法就是BP算法,也叫做反向传播算法。在使用BP算法求偏导数时,可以证明损失函数对第l层的某个参数的偏导与第l层中该节点的误差,以及该参数对应前一层网络编号在本层的输出(即l层)的输出值有关,那么此时的工作就转换成了每一层网络的每一个节点的误差的求法了(当然了,输入层是不用计算误差的)。而又可通过理论证明,每个节点的误差是可以通过下一层网络的所以节点反向传播计算得到(这也是反向传播算法名字的来源)。总结一下,当有多个训练样本时,每次输入一个样本,然后求出每个节点的输出值,接着通过输入样本的样本值反向求出每个节点的误差,这样损失函数对每个节点的误差可以通过该节点的输出值已经误差来累加得到,当所有的样本都经过同样的处理后,其最终的累加值就是损失函数对应位置参数的偏导数了。BP算法的理论来源是一个节点的误差是由前面简单的误差传递过来的,传递系数就是网络的系数。
一般情况下,使用梯度下降法解决神经网络问题时是很容易出错,因为求解损失函数对参数的偏导数过程有不少矩阵,在程序中容易弄错,如果损失函数或者损失函数的偏导数都求错了的话,那么后面的迭代过程就更加错了,导致不会收敛,所以很有必要检查一下偏导数是否正确。Andrew Ng在课程中告诉大家使用gradient checking的方法来检测,即当求出了损失函数的偏导数后,取一个参数值,计算出该参数值处的偏导数值,然后在该参数值附近取2个参数点,利用损失函数在这个两个点值的差除以这2个点的距离(其实如果这2个点足够靠近的话,这个结果就是导数的定义了),比较这两次计算出的结果是否相等,如果接近相等的话,则说明很大程度上,这个偏导数没有计算出错,后面的工作也就可以放心的进行了,这时候一定要记住不要再运行gradient checking,因为在运行gradient checking时会使用BP进行每层的误差等计算,这样很耗时(但是我感觉即使不计算gradient checking,不也要使用BP算法进行反向计算么?)。
在进行网络训练时,千万不要将参数的初始值设置成一样的,因为这样学习的每一层的参数最终都是一样的,也就是说学习到的隐含特征是一样的,那么就多余了,且效果不好。因此明智的做法是对这些参数的初始化应该随机,且一般是满足均值为0,且在0左右附近的随机。
如果采用同样的算法求解网络的参数的话(比如说都是用BP算法),那么网络的性能就取决于网络的结构(即隐含层的个数以及每个隐含层神经元的个数),一般默认的结构是:只取一个隐含层,如果需要取多个隐含层的话就将每个隐含层神经元的个数设置为相同,当然了隐含层神经元的个数越多则效果会越好。