先从回归(Regression)问题说起。我在本吧已经看到不少人提到如果想实现强AI,就必须让机器学会观察并总结规律的言论。具体地说,要让机器观察什么是圆的,什么是方的,区分各种颜色和形状,然后根据这些特征对某种事物进行分类或预测。其实这就是回归问题。
如何解决回归问题?我们用眼睛看到某样东西,可以一下子看出它的一些基本特征。可是计算机呢?它看到的只是一堆数字而已,因此要让机器从事物的特征中找到规律,其实是一个如何在数字中找规律的问题。
例:假如有一串数字,已知前六个是1、3、5、7,9,11,请问第七个是几?你一眼能看出来,是13。对,这串数字之间有明显的数学规律,都是奇数,而且是按顺序排列的。那么这个呢?前六个是0.14、0.57、1.29、2.29、3.57、5.14,请问第七个是几?这个就不那么容易看出来了吧!我们把这几个数字在坐标轴上标识一下,可以看到如下图形:
用曲线连接这几个点,延着曲线的走势,可以推算出第七个数字——7。由此可见,回归问题其实是个曲线拟合(CurveFitting)问题。那么究竟该如何拟合?机器不可能像你一样,凭感觉随手画一下就拟合了,它必须要通过某种算法才行。假设有一堆按一定规律分布的样本点,下面我以拟合直线为例,说说这种算法的原理。
其实很简单,先随意画一条直线,然后不断旋转它。每转一下,就分别计算一下每个样本点和直线上对应点的距离(误差),求出所有点的误差之和。这样不断旋转,当误差之和达到最小时,停止旋转。说得再复杂点,在旋转的过程中,还要不断平移这条直线,这样不断调整,直到误差最小时为止。这种方法就是著名的梯度下降法(GradientDescent)。为什么是梯度下降呢?在旋转的过程中,当误差越来越小时,旋转或移动的量也跟着逐渐变小,当误差小于某个很小的数,例如0.0001时,我们就可以收工(收敛,Converge)了。啰嗦一句,如果随便转,转过头了再往回转,那就不是梯度下降法。
切线每次旋转的幅度叫做学习率(LearningRate),加大学习率会加快拟合速度,但是如果调得太大会导致切线旋转过度而无法收敛。[学习率其实是个预先设置好的参数,不会每次变化,不过可以影响每次变化的幅度。]
注意:对于凹凸不平的误差函数曲线,梯度下降时有可能陷入局部最优解。下图的曲线中有两个坑,切线有可能在第一个坑的最底部趋于水平。
直线方程y=kx+b改为二次曲线方程y=ax^2+bx+c时,参数(Parameter)由2个(分别是k、b)变为3个(分别是a、b、c),特征(Feature)由1个(x)变为2个(x^2和x)。三次曲线和复杂的多项式回归会增加更多的参数和特征。
前面讲的是总结一串数字的规律,现实生活中我们往往要根据多个特征(多串数字)来分析一件事情,每个原始特征我们都看作是一个维度(Dimension)。例如一个学生的学习成绩好坏要根据语文、数学、英语等多门课程的分数来综合判断,这里每门课程都是一个维度。当使用二次曲线和多变量(多维)拟合的情况下,特征的数量会剧增,特征数=维度^2/2这个公式可以大概计算出特征增加的情况,例如一个100维的数据,二次多项式拟合后,特征会增加到100*100/2=5000个。
这么小的一张图片,就有这么巨大的特征量,可以想像一下我们的数码相机拍下来的照片会有多大的特征量!而我们要做的是从十万乃至亿万张这样的图片中找规律,这可能吗?很显然,前面的那些回归方法已经不够用了,我们急需找到一种数学模型,能够在此基础上不断减少特征,降低维度。
于是,“人工神经网络(ANN,ArtificialNeuralNetwork)”就在这样苛刻的条件下粉墨登场了,神经科学的研究成果为机器学习领域开辟了广阔的道路。
神经元
下图是单个神经元(Neuron),或者说一个脑细胞的生理结构:
X1*w1+X2*w2+…+Xn*wn这种计算方法称为加权求和(WeightedSum)法,此方法在线性代数里极为常用。加权求和的标准数学符号是,不过为了简化,我在教程里使用女巫布莱尔的符号表示,刚好是一个加号和一个乘号的组合。
这个数学模型有什么意义呢?下面我对照前面那个y=kx+b直线拟合的例子来说明一下。
公式中的e叫自然常数,也叫欧拉数,e=2.71828…。e是个很神秘的数字,它是“自然律”的精髓,其中暗藏着自然增长的奥秘,它的图形表达是旋涡形的螺线。
e是怎么来的?e=1+1/1!+1/2!+1/3!+1/4!+1/5!+1/6!+1/7!+…=1+1+1/2+1/6+1/24+1/120+…≈2.71828(!代表阶乘,3!=1*2*3=6)
再举个通俗点的例子:从前有个财主,他特别贪财,喜欢放债。放出去的债年利率为100%,也就是说借1块钱,一年后要还给他2块钱。有一天,他想了个坏主意,要一年算两次利息,上半年50%,下半年50%,这样上半年就有1块5了,下半年按1块5的50%来算,就有1.5/2=0.75元,加起来一年是:上半年1.5+下半年0.75=2.25元。用公式描述,就是(1+50%)(1+50%)=(1+1/2)^2=2.25元。可是他又想,如果按季度算,一年算4次,那岂不是更赚?那就是(1+1/4)^4=2.44141,果然更多了。他很高兴,于是又想,那干脆每天都算吧,这样一年下来就是(1+1/365)^365=2.71457。然后他还想每秒都算,结果他的管家把他拉住了,说要再算下去别人都会疯掉了。不过财主还是不死心,算了很多年终于算出来了,当x趋于无限大的时候,e=(1+1/x)^x≈2.71828,结果他成了数学家。
神经网络
前面讲过,使用梯度下降的方法,要不断的修改k、b两个参数值,使最终的误差达到最小。神经网络可不只k、b两个参数,事实上,网络的每条连接线上都有一个权重参数,如何有效的修改这些参数,使误差最小化,成为一个很棘手的问题。从人工神经网络诞生的60年代,人们就一直在不断尝试各种方法来解决这个问题。直到80年代,误差反向传播算法(BP算法)的提出,才提供了真正有效的解决方案,使神经网络的研究绝处逢生。
输出层→隐藏层:残差=-(输出值-样本值)*激活函数的导数隐藏层→隐藏层:残差=(右层每个节点的残差加权求和)*激活函数的导数
如果输出层用Purelin作激活函数,Purelin的导数是1,输出层→隐藏层:残差=-(输出值-样本值)
如果用Sigmoid(logsig)作激活函数,那么:Sigmoid导数=Sigmoid*(1-Sigmoid)输出层→隐藏层:残差=-(Sigmoid输出值-样本值)*Sigmoid*(1-Sigmoid)=-(输出值-样本值)输出值(1-输出值)隐藏层→隐藏层:残差=(右层每个节点的残差加权求和)*当前节点的Sigmoid*(1-当前节点的Sigmoid)
如果用tansig作激活函数,那么:tansig导数=1-tansig^2
残差全部计算好后,就可以更新权重了:输入层:权重增加=当前节点的Sigmoid*右层对应节点的残差*学习率隐藏层:权重增加=输入值*右层对应节点的残差*学习率偏移值的权重增加=右层对应节点的残差*学习率学习率前面介绍过,学习率是一个预先设置好的参数,用于控制每次更新的幅度。
此后,对全部数据都反复进行这样的计算,直到输出的误差达到一个很小的值为止。以上介绍的是目前最常见的神经网络类型,称为前馈神经网络(FeedForwardNeuralNetwork),由于它一般是要向后传递误差的,所以也叫BP神经网络(BackPropagationNeuralNetwork)。
BP神经网络的特点和局限:-BP神经网络可以用作分类、聚类、预测等。需要有一定量的历史数据,通过历史数据的训练,网络可以学习到数据中隐含的知识。在你的问题中,首先要找到某些问题的一些特征,以及对应的评价数据,用这些数据来训练神经网络。-BP神经网络主要是在实践的基础上逐步完善起来的系统,并不完全是建立在仿生学上的。从这个角度讲,实用性>生理相似性。-BP神经网络中的某些算法,例如如何选择初始值、如何确定隐藏层的节点个数、使用何种激活函数等问题,并没有确凿的理论依据,只有一些根据实践经验总结出的有效方法或经验公式。-BP神经网络虽然是一种非常有效的计算方法,但它也以计算超复杂、计算速度超慢、容易陷入局部最优解等多项弱点著称,因此人们提出了大量有效的改进方案,一些新的神经网络形式也层出不穷。
这里介绍的是计算完一条记录,就马上更新权重,以后每计算完一条都即时更新权重。实际上批量更新的效果会更好,方法是在不更新权重的情况下,把记录集的每条记录都算过一遍,把要更新的增值全部累加起来求平均值,然后利用这个平均值来更新一次权重,然后利用更新后的权重进行下一轮的计算,这种方法叫批量梯度下降(BatchGradientDescent)。
p=[25;36;122;16;92;812;47;79]’;%特征数据X1,X2t=[101824618962863];%样本值net=newff(p,t,20);%创建一个BP神经网络ff=FeedForwardnet=train(net,p,t);%用p,t数据来训练这个网络
你也许会问,计算机难道这样就能学会乘法规则吗?不用背乘法口诀表了?先随便选几个数字,试试看:
s=[37;69;45;57]’;%准备一组新的数据用于测试y=sim(net,s)%模拟一下,看看效果%结果是:25.102961.588229.584837.5879
你测试的结果也许和我的不同,这是因为初始化的权重参数是随机的,可能会陷入局部最优解,所以有时预测的结果会很不理想。
例2:下面测试一下拟合正弦曲线,这次我们随机生成一些点来做样本。
p=rand(1,50)*7%生成1行50个0~7之间的随机数t=sin(p)%计算正弦曲线s=[0:0.1:7];%生成0~7的一组数据,间隔0.1,用于模拟测试plot(p,t,‘x’)%画散点图
net=newff(p,t,20);%创建神经网络net=train(net,p,t);%开始训练
y=sim(net,s);%模拟plot(s,y,‘x’)%画散点图
下面的设置是一种标准的批量梯度下降法的配置。
%创建3层神经网络[隐藏层10个节点->logsig,输出层1个节点->purelin]traingd代表梯度下降法net=newff(p,t,10,{‘logsig’‘purelin’},‘traingd’);%10不能写成[101]
net=train(net,p,t);%开始训练
注意:newff的第三个参数10不能写成[101],否则就是4层网络,两个隐藏层,分别是10个和1个节点。这个很容易弄错。(输出层的节点数程序会自动根据t的维度自动判断,所以不用指定)
这时的效果显然更差了。
%创建2层神经网络[隐藏层10个节点->logsig,输出层1个节点->purelin]traingd代表梯度下降法net=newff(p,t,10,{‘logsig’‘purelin’},‘traingd’);
标准的批量梯度下降法的速度确实够慢,这次计算花了一分多钟。
效果比上次稍好一点。不过这条曲线显得坑坑洼洼的很难看,这是一种过拟合(Overfitting)现象,与之相反的是欠拟合(Underfitting)。
net=newff(p,t,10,{‘logsig’‘purelin’},‘trainlm’);…后面的代码不变
下面解决过拟合问题,把隐藏层的节点数目设少一点就行了。
net=newff(p,t,3,{‘logsig’‘purelin’},‘trainlm’);…后面的代码不变
net=newff(p,t,3,{‘logsig’‘logsig’});%创建神经网络net=train(net,p,t);%开始训练y=sim(net,s);%模拟plot(s,y,‘x’)%画散点图
可以看出,-1~0范围之间的点都变为0了。使用logsig输出时要想得到完整数值范围的效果,必须先对数据进行归一化才行。
归一化(Normalization),也叫标准化,就是把一堆数字按比例缩放到0~1或-1~1的范围。虽然用Purelin输出可以不必归一化,但归一化能在一定程度上加快收敛速度,因此被许多教程定为训练前的必须步骤。
Matlab的归一化命令为:mapminmax注:网上的不少教程里用premnmx命令来归一化,要注意Matlab版本R2007b和R2008b,premnmx在处理单列数据时有bug,Matlab已给出了警告,R2009a版才修正。因此推荐使用mapminmax。mapminmax的输入输出值和premnmx是行列颠倒的,使用时要注意代码中是否添加转置符号。
a=[5,2,6,3];
b=mapminmax(a,0,1)%归一化到0~1之间%b=0.750001.00000.2500
c=mapminmax(a)%归一化到-1~1之间%c=0.5000-1.00001.0000-0.5000
反归一化(Denormalization)就是按归一化时的比例还原数值。
a=[5,2,6,3];[c,PS]=mapminmax(a);%PS记录归一化时的比例mapminmax(‘reverse’,c,PS)%利用PS反归一化%ans=5263
神经网络的归一化(0~1范围)代码:
p=rand(1,50)*7;%特征数据t=sin(p);%样本值s=[0:0.1:7];%测试数据
[pn,ps]=mapminmax(p,0,1);%特征数据归一化[tn,ts]=mapminmax(t,0,1);%样本值归一化sn=mapminmax(‘apply’,s,ps);%测试数据,按ps比例缩放
net=newff(pn,tn,[51],{‘logsig’‘logsig’});%创建神经网络net=train(net,pn,tn);%开始训练
yn=sim(net,sn);%模拟y=mapminmax(‘reverse’,yn,ts);%按ps的比例还原plot(s,y,‘x’)%画散点图
关于Sigmoid的由来,中文的网站上很少有提及的。下面简单讲一下,希望能给大家拓展一下思路。
PS:这里的公式我都给出了求解过程,但如今这个年头,用手工解题的人越来越少了,一般的方程用软件来解就行了。例如解Sigmoid微分方程,可以用Matlab去解:
dsolve(‘Dx=x*(1-x)’)%ans=1/(1+exp(-t)*C1)
logsig
Sigmoid函数(S形函数,LogisticFunction)是受统计学模型的启发而产生的激活函数。基于生物学的神经元激活函数是这样的:
导数的形式知道了,那么它的原函数是什么样子呢?已知导数求原函数,用统计学的话来讲,即根据概率密度函数(PDF)求累积分布函数(CDF),不定积分(IndefiniteIntegral)就是专门用来做这个的工具。根据不定积分的知识可知,由于常数项是可变的,所以存在无数个原函数的可能。让我们先用图解法看一下:既然导数是函数曲线的斜率,那么可以把一定数值范围内的斜率,都画成一根根的短斜线,组成斜率场(SlopeFields,DirectionFields),然后根据这些斜线的走势,画出积分曲线。Matlab可以用quiver命令来画斜率场。
从上图中可以看出,在y轴的0~1之间是个分水岭,0和1处的方向趋于水平。下面放大0~1的范围看看是什么样子的。