一、介绍
在开始讨论三种算法之前,我们需要做一些假设,来使我们的讨论简洁。我们假设所讨论的函数是单谷函数,并假定在区间内有最小值。我们要求解任意给定的一阶连续可微的非线性方程在给定区间的最小值,可以采用以下三种算法。
牛顿-拉夫森算法(简称牛顿法Newton-Raphson)是非线性方程数值求根的算法,它因迭代算法简洁且收敛速度快而被广泛使用。在用于求解函数的最小值点的时候,我们利用初始点的泰勒展开来拟合原函数,由于拟合的泰勒多项式展开到二次项,其有最小值,将该二次泰勒多项式的最小值点作为下一个迭代点来不断逼近函数的最值点,其结束条件可以用极值点的一阶必要性条件来实现。理论上可以推导出,牛顿法是二阶收敛的,但牛顿法的缺点也很明显,因为要用到函数的二阶导数,要求函数本身性质很好(是二阶连续可微函数),但在大多数情况下,函数本身性质没有这么好,所以衍生出了用割线近似替代切线的算法。
双割线法,是基于牛顿法求函数最小值的思想产生的,可以看作函数在没有二阶连续可微性质的一个妥协处理。我们利用函数一阶导,使用两个点求出函数割线斜率,来近似代替牛顿法的切线斜率。因为没有牛顿法使用切线那么强的条件支持,割线法的收敛速度会稍稍慢些,理论上证明出,双割线法的是1.618阶收敛的。虽然收敛速度慢了一些,但对函数的要求降低了,因此适用更多的非线性函数求最小值的问题。
单割线法,是双割线法的更进一步简化。在双割线法中,两个迭代点是不断更新的,而单割线法中,仅有一个迭代点是参与更新。从代码复杂度角度来说,单割线法很容易实现,但笔者认为,单割线法远远不如双割线法,原因在于单割线法与双割线法都是用到了函数一阶连续可微的性质,在对函数的要求上没有改变,但单割线法的收敛速度较双割线法更慢,这样简化得不偿失。在实验对比图示中也能很清楚得看到二者收敛速度的快慢。
二、原理推导
牛顿法:设给出一个初始迭代点x0,x0处可以用泰勒展开到第三项,因为泰勒展开是对函数的近似,所以在x0处的小邻域内,函数趋势和泰勒展开式一致,利用泰勒展开的二次函数极小值点来逼近函数最小值,可以得到逼近点列{xk}。首先利用泰勒展开原函数到二阶加余项的形式:
根据假设,此函数二阶可微。且都已知。构造过点的二次函数,满足:
得到函数:
易知构造的二次函数在附近近似等于,由于,故此二次函数有最小值点,求得的最小值点再作为迭代变量,构造二次函数,不断进行则可不断逼近函数最小值点。
代入系数得到牛顿法的核心迭代公式:
当 的时候,,此时满足函数极小值一阶必要性条件,结束迭代。
需要注意的是,这里的讨论函数默认是具有最小值的单谷函数,于是,若有时,迭代点会往极大值点靠近,这里不再讨论,对于此类函数可以取负号变为求极小值,公式一样成立。
收敛性证明手稿如图:
牛顿法二阶收敛性证明:
双割线法收敛证明:同牛顿法,仅将以代替,故证明过程同牛顿法收敛性证明一致。
双割线法收敛速度证明手稿如图,证明过程较复杂,中间用约等于是为了更快得到结论,正常推导需带余项,读者略过证明过程不影响本文阅读体验:
由上述推导可知,双割线法的收敛速度是1.618,在不用二阶导函数信息的情况下,收敛速度依然很好,故双割线法适用性更广。
单割线法收敛速度证明手稿如图:
由上述推导可知,单割线法收敛速度是线性的,与上文的区间分割法收敛速度相差无几,并不能将可以求一阶导的信息充分利用上,故不推荐使用单割线法。
综上所述,双割线法的适用性更广,而牛顿法的代码简洁,收敛速度也更快,但在多元函数的优化问题中,牛顿法的运算量会非常巨大,届时我们会用其它的算法来实现多元函数的极值求解。
三、实验对比
以下是三种方法在matlab实验中的表现,会附上每个方法的代码。
1.牛顿法代码:
function [minimizer,fval]=N_L1(f,x0)
df=sym(f); %转换成符号函数进行求导
df=diff(df); %使用diff将f转化为一阶导函数df
ddf=diff(df); %ddf为f的二阶导函数
df=matlabFunction(df); %将符号函数转化为匿名函数,方便运算
ddf=matlabFunction(ddf);
x=x0;
p1=zeros(1,1); %储存每次迭代的xk值
j=1; %计数用,计算该方法的迭代次数
while df(x)~=0 %不满足一阶必要性条件时,进入循环
p1(1,j)=x; %将xk的值储存进向量p1中
j=j+1;
if ddf(x)~=0 %起保护作用,防止被零除
x=x-df(x)/ddf(x); %牛顿法的迭代公式
else
disp('x处的二阶导函数为零,输出x');
break
end
end
disp(['迭代了' num2str(j-1) '次']);
minimizer=x; %最小值点
fval=f(x); %最小值
xx=min(x0,x)-5:0.05:max(x0,x)+2; %由初始点和最终收敛点向两边延拓函数,判断是否为最小值点
plot(xx,f(xx),'b');
hold on
plot(p1,f(p1),'r*');
legend 'image of f(x)' 'estimate points';
hold off
end
2.双割线法代码:
function [minimizer,fval]=N_L2(f,x0,x1)
x2=x1; %迭代点x2储存用户输入的x1的值
x1=x0; %迭代点x1储存用户输入的x0的值
df=sym(f); %同上操作,目的为求出f一阶导函数df
df=diff(df);
df=matlabFunction(df);
x=x2;
delta=x2-x1; %计算x2-x1的值,记为delta
p2=zeros(1,1); %p2储存每次迭代的xk的值
j=1; %计数,记忆迭代进行了几次
while df(x)~=0 %当不满足一阶必要性条件时,进入循环迭代
p2(1,j)=x;
j=j+1;
if df(x2)-df(x1)~=0 %起保护作用,保证不被零除
x=x2-df(x2)*delta/(df(x2)-df(x1)); %双割线法的迭代公式
x1=x2;
x2=x;
delta=x2-x1;
else
disp('x1,x2的导函数值相减为零')
break
end
end
disp(['迭代了' num2str(j-1) '次']);
minimizer=x;
fval=f(x);
xx=min(x0,x)-5:0.05:max(x0,x)+5;
plot(xx,f(xx),'b');
hold on
plot(p2,f(p2),'r*');
legend 'image of f(x)' 'estimate points';
hold off
end
3.单割线法代码:
function[minimizer,fval]=N_L3(f,x0,x1)
df=sym(f);
df=diff(df);
df=matlabFunction(df); %同前两个方法的步骤
x2=x1;
x1=x0;
delta=x2-x1;
p=zeros(1,1);
x=x2;
j=1;
while df(x)~=0
p(1,j)=x;
j=j+1;
if df(x2)-df(x1)~=0 %保护函数不被零除
x=x2-df(x2)*delta/(df(x2)-df(x1)); %注意与双割线法不同的是,每次迭代只有一个点xk改变,另外一个初始点不变
x2=x;
delta=x2-x1;
else
disp('x1,x2的导函数值相减为零');
break
end
end
disp(['迭代了' num2str(j-1) '次']);
minimizer=x;
fval=f(x);
xx=min(x0,x)-5:0.05:max(x0,x)+5;
plot(xx,f(xx),'b');
hold on
plot(p,f(p),'r*');
legend 'image of f(x)' 'estimate points';
hold off
end
三种方法的实验对比:
测试函数: (注意在写函数时需加上.*,保证向量运算有效)
1.牛顿法:
2.双割线法:
3.单割线法:
最后,需要说明的是,本文主要是以数学推导的角度,对三种方法进行了收敛性证明和收敛速度的推导。最后用MatLab代码来做实验对比三者的差异。最后通过图像和迭代次数进一步验证牛顿法的收敛速度最快,而双割线法次之,单割线法在极小值点附近发生了震荡,收敛速度最慢。综合对函数的要求和收敛速度来说,双割线法是三种方法中最好的方法;从代码简洁度和收敛速度来说,牛顿法最优。