摘要
在生产过程、科学实验以及日常生活中,人们总希望用最少的人力、物力、财力和时间去办更多的事,获得最大的效益,,所以最优化理论和方法日益受到重视。
无约束最优化计算方法是数值计算领域中十分活跃的研究课题之一,快速的求解无约束最优化问题,除了自身的重要性以外,还体现在它也构成一些约束最优化问题的子问题。因此,对于无约束最优化问题,如何快速高精度的求解一直是优化工作者十分关心的事。本文验证求解一维无约束最优化问题的三种线性搜索方法,分别是牛顿法、黄金分割法,二次插值法。
问题
min f(x)=3x4 - 4x3-12x2
1. 一维牛顿法
1.1. 概念
牛顿迭代法(Newton’s method)又称为牛顿-拉夫逊方法(Newton-Raphson method),它是牛顿在17世纪提出的一种在实数域和复数域上近似求解方程的方法,其基本思想是利用目标函数的二次Taylor展开,并将其极小化。牛顿法使用函数 f(x)的泰勒级数的前面几项来寻找方程 "f(x)=0" 的根。牛顿法是求方程根的重要方法之一,其最大优点是在方程 "f(x)=0" 的单根附近具有平方收敛,而且该法还可以用来求方程的重根、复根,此时非线性收敛,但是可通过一些方法变成线性收敛。
1.2. 牛顿法的几何解释
方程 f(x)=0 的根 x*可解释为曲线y=f(x)轴的焦点的横坐标。如下图:
设 x[k]是根 x*的某个近似值,过曲线 y=f(x)上横坐标为 x[k]的点P[k]引切线,并将该切线与 x轴的交点 的横坐标 x_{k+1}_作为 x*的新的近似值。鉴于这种几何背景,牛顿法亦称为切线法。
1.3. 原理
将f(xk+1)在x=xk处一阶泰勒展开:
公式不打了
令函数趋于0,求与x轴交点:
1.4. 程序主体流程图
1.5. 运行效果
分别取-1.5,0.4,1.0,1.6,2.8
1.6. 评价
1.6.1. 优点
在凸函数很初始点选取好的情况下,收敛快。
1.6.2. 缺点
Ø 计算二阶导数,计算量大
Ø 要求函数二阶可微
Ø 收敛性与初始点选取有关
2. 黄金分割法
2.1. 基本思路
黄金分割法适用于[a,b]区间上的任何单股函数求极小值问题,对函数除要求“单峰”外不做其他要求,甚至可以不连续。因此,这种方法的适应面非常广。黄金分割法也是建立在区间消去法原理基础上的试探方法,即在搜索区间 ?内适当插入两点 ?,并计算其函数值。 ?将区间分成三段,应用函数的单峰性质,通过函数值大小的比较,删去其中一段,是搜索区间得以缩小。然后再在保留下来的区间上作同样的处理,如此迭代下去,是搜索区间无限缩小,从而得到极小点的数值近似解。
2.2. 基本原理与步骤
一维搜索是解函数极小值的方法之一,其解法思想为沿某一已知方向求目标函数的极小值点。一维搜索的解法很多,这里主要采用黄金分割法(0.618法)。如图所示
黄金分割法是用于一元函数 ?在给定初始区间 ?内搜索极小点?的一种方法。其基本原理是:依照“去劣存优”原则、对称原则、以及等比收缩原则来逐步缩小搜索区间。
具体步骤是:在区间 ?内取点: ?分为三段。如果 ?,令?;如果 ?,令 <?,如果 ?都大于收敛精度?重新开始。因为 ?为单峰区间,这样每次可将搜索区间缩小0.618倍或0.382倍,处理后的区间都将包含极小点的区间缩小,然后在保留下来的区间上作同样的处理,如此迭代下去,将使搜索区 ?逐步缩小,满足预先给定的精度时,即获得一维优化问题的近似最优解。
2.3. 算法流程图
2.4. 运行效果
[-2,0],[1,3]
2.5. 评价
很巧妙地使每次分割点都为上次的黄金分割点,可以复用上次运算的结果,简化计算。它是优化计算中的经典算法,以算法简单、收敛速度均匀、效果较好而著称,是许多优化算法的基础,但它只适用于一维区间上的凸函数,即只在单峰区间内才能进行一维寻优,其收敛效率较低。
3. 二次插值法
3.1. 基本思路
在求解一元函数?的极小点时,在搜索区间中用低次(通常不超过三次)插值多项式?来近似目标函数,后求该多项式的极小点(比较容易计算),并以此作为目标函数?的近似极小点。如果其近似的程度尚未达到所要求的精度时,反复使用此法,逐次拟合,直到满足给定的精度时为止。
3.2. 设计思路
考虑二次多项式
则
令 ,得 。这意味着我们要求a,b。
今考虑在包含 的极小点 的搜索区间 中,给定三个点 , , ,满足
< <
> <
利用三点处的函数值 , , 构造二次函数,并要求插值条件满足
令 ,i=1,2,3。解上述方程组得
于是,二次函数 的极小点为
设 。求得 和 以后,如果
≤ ,当 > 时,
或者如果
≤ ,当 < 时。
则我们认为收敛准则满足。如果 < ,则极小点估计为 ,否则为 。
若终止准则不满足,则利用 提供的信息,从 , , 和 中选出相邻的三个点,将原来的搜索区间缩小,然后重复上述过程,直到终止准则满足为止。
3.3. 算法设计
初始步 给出?,?,?,满足上述设计步骤。
步1 由上述设计步骤计算?。
步2 比较?和?的大小,如果?>?,则转步3;否则转步4。
步3 如果?≤?,则
?,?,?,?,
转步5;否则?,?,转步5。
步4 若?,则
?,?,?,?,
转步5;否则?,?,转步5。
步5 如果收敛准则满足,停止迭代;否则转步1,在新的搜索区间[?,?]上按公式计算二次插值函数的极小点?。
3.4. 程序框图
3.5. 运行结果
-2.5,-1.4,-0.3
-0.3,1.5,3.3.
3.6. 评价
3.6.1. 优点
插值法仅需计算函数值,不涉及导数、Hesse矩阵等的计算,计算起来相对比较简单,能够适用于非光滑和导数表达式复杂或表达式写不出等种种情形。
3.6.2. 缺点
当迭代步数较多时,计算过程比较复杂,计算量较大,计算起来比较麻烦。当迭代点离目标函数的最优解较远时,追求线性搜索的精度反而会降低整个算法的效率。
程序
用符号推导画图
picture.py
import matplotlib.pyplot as plt
import numpy as np
import sympy
t = sympy.symbols('t')
y=3*pow(t,4)-4*pow(t,3)-12*pow(t,2)
y_diff1 = sympy.diff(y, t)
y_diff2 = sympy.diff(y, t, 2)
f=sympy.lambdify(t,y,"numpy")
f1=sympy.lambdify(t,y_diff1,"numpy")
f2=sympy.lambdify(t,y_diff2,"numpy")
# print(sympy.solveset(sympy.Eq(y_diff1,0)))
station=sympy.solveset(sympy.Eq(y_diff1,0))
station=list(station)
station=list(map(int,station))
# 去除鞍点
for i,a in enumerate(station):
if f2(np.array(a))==[0]:
station=station.remove(a)
pass
print("所有驻点",station)
station.sort()
region=0.2*(station[-1]-station[0])
t=np.arange(station[0]-region,station[-1]+region,0.01)
fg,axes=plt.subplots(3)
axes[0].plot(t,f(t))
axes[0].set_title("original function")
axes[1].plot(t,f1(t))
axes[1].plot(station,f1(np.array(station)),'ro')
# axes[1].set_title("一阶导数")
axes[2].plot(t,f2(t))
# axes[2].set_title("二阶导数")
for i in range(3):
axes[i].grid()
pass
plt.show()
Gold_ratio.py
import math
import numpy as np
import matplotlib.pyplot as plt
def draw_dot(x,y,tx):
plt.plot([x],[y],'o')
plt.text(x,y,tx,color='blue',fontsize=15)
pass
Epsilon=1e-2#极限
# y=lambda t:t*(t+2)#函数
y=lambda t:3*pow(t,4)-4*pow(t,3)-12*pow(t,2)
(a,b)=[-2,0]# 确定a,b
draw_dot(a,y(a),'a')
draw_dot(b,y(b),'b')
Belta=(3-math.sqrt(5))/2 # 0.381
t1=a+Belta*(b-a)
y1=y(t1)
t2=a+(1-Belta)*(b-a)
y2=y(t2)
n=0
t = np.arange(a-0.1*(b-a), b+0.2*(b-a), 0.01)
s = y(t)
plt.plot(t, s)
# draw_dot(t2,y(t2)+1*n,'r'+str(n))
# draw_dot(t1,y(t1)+1*n,'l'+str(n))
while (t2-t1)>Epsilon:
n+=1
if y1>y2:
a=t1
t1=t2
t2=a+b-t1
y1=y2
y2=y(t2)
# draw_dot(a,y(a),'a'+str(n))
# draw_dot(t2,y(t2),'t2+'+str(n))
draw_dot(t2,y(t2)+2*n,'r'+str(n))
draw_dot(t1,y(t1)+2*n,'l'+str(n))
pass
else:
b=t2
t2=t1
y2=y1
t1=a+b-t2
y1=y(t1)
# draw_dot(b,y(b),'b'+str(n))
draw_dot(t2,y(t2)+1*n,'r'+str(n))
draw_dot(t1,y(t1)+1*n,'l'+str(n))
#绘制函数图像
t_ = (t1 + t2) / 2
y_ = y(t_)
print('t*=%f,y*=%f' % (t_, y_))
def printFunc(x, y):
plt.plot([x], [y], 'ro')
plt.text(x,y,'(%0.3f,%0.3f)'%(x,y), color='red', fontsize=15)
printFunc(t_,y_)
plt.show()
Newton.py
import sympy
import matplotlib.pyplot as plt
import numpy as np
Epsilon = 0.01
t = sympy.symbols('t')
# y=t*(t+2)
y = t ** 3 - 2 * t + 1
f=lambda t:t ** 3 - 2 * t + 1
T0 = [0.5]#初始值
# y = 3 * pow(t, 4) - 4 * pow(t, 3) - 12 * pow(t, 2)
# f = lambda t: 3 * pow(t, 4) - 4 * pow(t, 3) - 12 * pow(t, 2)
# T0 = [-1.5,0.4,1.0,1.6,2.8]
y_diff1 = sympy.diff(y, t)
y_diff2 = sympy.diff(y, t, 2)
k = 1
def printFunc(f, a, b):
t = np.arange(a, b, 0.01)
s = f(t)
plt.plot(t, s)
plt.show()
def Next_t(t0):
global k
delta = (y_diff1 / y_diff2).subs(t, t0)
t1 = (t - delta).subs(t, t0)
# print(t1)
if abs(delta) < Epsilon:
print('第%d次迭代:%f=%f%+f' % (k, t1, t0, -delta))
plt.plot([t1], [f(t1)], marker='o', markersize=8, markerfacecolor="red")
plt.text(t1, f(t1), k, color='red', fontsize=k * 2 + 10)
return t1
else:
print('第%d次迭代:%f=%f%+f' % (k, t1, t0, -delta))
plt.plot([t1], [f(t1)], marker='o', markersize=8, markerfacecolor="green")
plt.text(t1, f(t1), k, color='green', fontsize=k * 2 + 10)
k += 1
return Next_t(t1)
for t0 in T0:
Next_t(t0)
printFunc(f, -3, 3)
# printFunc(f,t0-1,t0+1)
Polynomial_interpolation.py
'''
二次插值法python实现
f(x)=x^4 - 4x^3 - 6x^2 -16x +4极值
区间[-1,6] e=0.05
'''
import numpy as np
import matplotlib.pyplot as plt
from time import sleep
from threading import Thread
'''
函数表达式
'''
def f(x):
# return 1.0 * (pow(x, 4) - 4 * pow(x, 3) - 6 * pow(x, 2) - 16 * x + 4)
return 3*pow(x,4)-4*pow(x,3)-12*pow(x,2)
# 定义变量们
X2,Y=list(),list()
k=0
a = -2.5# 左点
b = -0.3# 右点
e = 0.001# 精度
'''
绘制函数图像
'''
def close(time=1):
sleep(time)
# plt.savefig('./img/'+name)
plt.close()
pass
def printFunc():
t = np.arange(a, b, 0.01)
s = f(t)
plt.plot(t, s)
def update_point(x,y):
global k
printFunc()
plt.plot(x,y,'ro')
plt.text(x[-1], y[-1], k, color='red', fontsize=k + 10)
# else:
# plt.plot([x], [y], 'ro')
# plt.text(x, y, k, color='red', fontsize=k + 10)
thread1 = Thread(target=close, args=())
thread1.start()
# print('打开')
plt.show()
# print("close")
def final_fun(x,y):
global k
printFunc()
plt.plot(x, y, 'ro')
for i in range(1,k+1):
plt.text(x[i-1], y[i-1], i, color='red', fontsize=i + 10)
# thread1 = Thread(target=close, args=())
# thread1.start()
plt.show()
'''
e为精度
'''
def search(f, x1, x2, x3):
global k
k+=1
if f(x2) > f(x1) or f(x2) > f(x3):
print("不满足两头大中间小的性质")
return 0
# 系数矩阵
A = [[pow(x1, 2), x1, 1], [pow(x2, 2), x2, 1], [pow(x3, 2), x3, 1]]
b = [f(x1), f(x2), f(x3)]
X = np.linalg.solve(A, b)
a0,a1,_ = X
x = - a1 / (2 * a0)
# 达到精度退出
if abs(x - x2) < e:
if f(x) < f(x2):
y = f(x)
print('最后的x:',x)
X2.append(x)
Y.append(y)
final_fun(X2,Y)
return (X2, Y)
else:
y = f(x2)
print("最后的x2",x2)
X2.append(x2)
Y.append(y)
final_fun(X2, Y)
return (X2, Y)
arr = [x1, x2, x3, x]
arr.sort()
# 在x2和新算出的x中找最小值
if f(x2) > f(x):
index = arr.index(x)
x2 = x
else:
index = arr.index(x2)
x1 = arr[index - 1]
x3 = arr[index + 1]
X2.append(x2)
Y.append(f(x2))
print('运行中的第%d次:%f' % (k, x2))
update_point(X2,Y)
return search(f, x1, x2, x3)
def regre(f, a, b):
x1 = a
x3 = b
x2 = (a + b) / 2.0
search(f, x1, x2, x3)
regre(f, a, b)