文章目录
- 1.概念
- 2.遍历搜索
- 3.优化算法
- 3.1.一维搜索原则
- 3.2.黄金分割法
- Code Block
- 3.3.斐波拉契法
- Code Block
1.概念
\qquad一维搜索是最优化方法最简单的一种,即求一个在(a,b)内,连续下单峰函数f(x)f(x)f(x)的极小值。所谓下单峰函数就是只有一个极小值的函数。
2.遍历搜索
\qquad这个问题看似简单,我们只需要指定步长,从区间左端点搜索到右端点,每次根据步长新产生一个值,比对它和前两个值的大小。记录三个值从左到由依次为x1x_1x1,x2x_2x2,x3x_3x3,若出现x1>x2<x3x_1>x_2<x_3x1>x2<x3的情况,则说明极小值点必定落在(x1,x3)(x_1,x_3)(x1,x3)之间。因此如果指定了最终区间不得超过eps的话,那么搜索步长不得大于0.5eps。
我们很快就会发现,这种遍历法虽然算法简单,但是迭代次数多,浪费资源严重,因此我们需要更快地得到最终区间的方法。在最优化方法这个数学分支中,可以证明斐波拉契法是压缩比(在同样迭代次数下开始区间和最终区间的比值)最高的方法,黄金分割比相比它压缩比小一些,但是算法会比较简单,占用计算资源也会略少。
3.优化算法
3.1.一维搜索原则
\qquad首先在初始区间[a,b]中取两个点x1,x2(x1<x2)x_1,x_2(x_1<x_2)x1,x2(x1<x2),计算f(a),f(x1),f(x2),f(b)f(a),f(x_1),f(x_2),f(b)f(a),f(x1),f(x2),f(b)的值。由于f(x)f(x)f(x)在(a,b)为下单峰函数,即有唯一的极小值,因此a或b必定不是极小值点。
1.若f(x1)<f(x2)f(x_1)<f(x_2)f(x1)<f(x2)则说明(x1,x2)(x_1,x_2)(x1,x2)我下降段,极小值x∗x^*x∗点应该在x1x_1x1的右侧,因此修改查找区间为(x1,b)(x_1,b)(x1,b)
2.若f(x1)>f(x2)f(x_1)>f(x_2)f(x1)>f(x2)则说明(x1,x2)(x_1,x_2)(x1,x2)我上升段,极小值x∗x^*x∗点应该在x2x_2x2的左侧,因此修改查找区间为(a,x2)(a,x_2)(a,x2)
一维搜索算法的核心就是解决取x1和x2x_1和x_2x1和x2的问题
3.2.黄金分割法
\qquad设黄金分割比为k,取x1=b−(b−a)∗k,x2=a+(b−a)∗kx_1=b-(b-a)*k,x_2=a+(b-a)*kx1=b−(b−a)∗k,x2=a+(b−a)∗k。
在一维搜索中,如果满足条件1,那么需要在(x1,b)(x_1,b)(x1,b)取值。设ρ=1−k\rho=1-kρ=1−k则
CD‾=1−ρ,DE‾=ρ,EF‾=1−ρ\overline{CD}=1-\rho,\overline{DE}=\rho,\overline{EF}=1-\rhoCD=1−ρ,DE=ρ,EF=1−ρ由于在黄金分割比的作用下,1−ρ1=1−2ρ1−ρ\frac{1-\rho}{1}=\frac{1-2\rho}{1-\rho}11−ρ=1−ρ1−2ρ,因此DEGH=DEDF=DFCF=k≈0.618\frac{DE}{GH}=\frac{DE}{DF}=\frac{DF}{CF}=k≈0.618GHDE=DFDE=CFDF=k≈0.618
因此若将E点平移至GH线段,将G的横坐标当做a,H的横坐标当做b,它就相当于是x1x_1x1的位置,因此只需要计算x2=a+(b−a)∗k=b−(b−a)∗ρx_2=a+(b-a)*k=b-(b-a)*\rhox2=a+(b−a)∗k=b−(b−a)∗ρ即可
同理,如果满足条件2,DEIJ=DECE=CECF=k≈0.618\frac{DE}{IJ}=\frac{DE}{CE}=\frac{CE}{CF}=k≈0.618IJDE=CEDE=CFCE=k≈0.618D点相当于IJ线段的x2x_2x2的位置,因此只需要计算x1=b−(b−a)∗k=a+(b−a)∗ρx_1=b-(b-a)*k=a+(b-a)*\rhox1=b−(b−a)∗k=a+(b−a)∗ρ
Code Block
编写用户自定义函数的代码讲解参见以下链接:
[动态创建数学函数(CSDN])
以下为黄金分割法进行一维搜索的代码:
from math import *
import matplotlib.pyplot as plt
from pylab import *
# 通用函数f(x)靠用户录入
def function(x):fx = str_fx.replace("x", "%(x)f") # 所有的"x"换为"%(x)function"return eval(fx % {"x": x}) # 字典类型的格式化字符串,将所有的"x"替换为变量x# 绘图函数:给定闭区间(绘图间隔),绘图间隔默认为0.05,若区间较小,请自行修改
def drawf(a,b,interp=0.05):x = [a+ele*interp for ele in range(0, int((b-a)/interp))]y = [function(ele) for ele in x]plt.figure(1)plt.plot(x, y)xlim(a, b)title(init_str, color="b")plt.show()# 黄金分割法进行一维搜索的函数
def gold_div_search(a,b,esp):data=list()x1=a+rou*(b-a)x2=b-rou*(b-a)data.append([a,x1,x2,b])while(b-a>esp):if function(x1)>function(x2): #如果f(x1)>function(x2),则在区间(x1,b)内搜索a=x1x1=x2x2=b-rou*(b-a)plt.plot(x2,function(x2),'r*')elif function(x1)<function(x2): #如果f(x1)<function(x2),则在区间(a,x2)内搜索b=x2x2=x1x1=a+rou*(b-a)plt.plot(x1,function(x1),'r*')else: #如果f(x1)=function(x2),则在区间(x1,x2)内搜索a=x1b=x2x1=a+rou*(b-a)x2=b-rou*(b-a)plt.plot(x1,function(x1),'r*',x2,function(x2),'r*')data.append([a,x1,x2,b])with open("一维搜索(黄金分割法).txt",mode="w",encoding="utf-8")as a_file: # 保存的txt文件在程序的同目录下for i in range(0,len(data)):a_file.write("%d:\t"%(i+1))for j in range(0,4):a_file.write("function(%.3f)=%.3f\t"%(data[i][j],function(data[i][j])))a_file.write("\n")print("写入文件成功!")return [a,b]rou = 1-(sqrt(5)-1)/2 # 1-rou为黄金分割比
init_str = input("请输入一个函数,默认变量为x:\n") # 输入的最初字符串
para=input("请依次输入一维搜索的区间a,b和最终区间的精确值(用空格分隔)").split() # 导入区间
para=[float(ele) for ele in para]
a,b,esp=para
str_fx = init_str.replace("^", "**") # 将所有的“^"替换为python的幂形式"**"
gold_div_search(a,b,esp) # 调用黄金分割法并保存文件
drawf(a,b,(b-a)/2000) # 绘制函数图形
以下为交互式界面的效果:(请在Shell或Pycharm中的Python Console里面运行)
请输入一个函数,默认变量为x:atan(x)-log(2*x+1)+4*x**2-3*x请依次输入一维搜索的区间a,b和最终区间的精确值(用空格分隔)0 1 0.001写入文件成功!
文本文档数据:(如用此程序运行,保存的文本文档与程序同目录)
1: f(0.000)=0.0000000 f(0.382)=-0.7649875 f(0.618)=-0.5773825 f(1.000)=0.6867859
2: f(0.000)=0.0000000 f(0.236)=-0.6401822 f(0.382)=-0.7649875 f(0.618)=-0.5773825
3: f(0.236)=-0.6401822 f(0.382)=-0.7649875 f(0.472)=-0.7485370 f(0.618)=-0.5773825
4: f(0.236)=-0.6401822 f(0.326)=-0.7399126 f(0.382)=-0.7649875 f(0.472)=-0.7485370
5: f(0.326)=-0.7399126 f(0.382)=-0.7649875 f(0.416)=-0.7669244 f(0.472)=-0.7485370
6: f(0.382)=-0.7649875 f(0.416)=-0.7669244 f(0.438)=-0.7630202 f(0.472)=-0.7485370
7: f(0.382)=-0.7649875 f(0.403)=-0.7673941 f(0.416)=-0.7669244 f(0.438)=-0.7630202
8: f(0.382)=-0.7649875 f(0.395)=-0.7669382 f(0.403)=-0.7673941 f(0.416)=-0.7669244
9: f(0.395)=-0.7669382 f(0.403)=-0.7673941 f(0.408)=-0.7673906 f(0.416)=-0.7669244
10: f(0.395)=-0.7669382 f(0.400)=-0.7672874 f(0.403)=-0.7673941 f(0.408)=-0.7673906
11: f(0.400)=-0.7672874 f(0.403)=-0.7673941 f(0.405)=-0.7674185 f(0.408)=-0.7673906
12: f(0.403)=-0.7673941 f(0.405)=-0.7674185 f(0.406)=-0.7674176 f(0.408)=-0.7673906
13: f(0.403)=-0.7673941 f(0.404)=-0.7674129 f(0.405)=-0.7674185 f(0.406)=-0.7674176
14: f(0.404)=-0.7674129 f(0.405)=-0.7674185 f(0.406)=-0.7674196 f(0.406)=-0.7674176
15: f(0.405)=-0.7674185 f(0.406)=-0.7674196 f(0.406)=-0.7674194 f(0.406)=-0.7674176
16: f(0.405)=-0.7674185 f(0.405)=-0.7674193 f(0.406)=-0.7674196 f(0.406)=-0.7674194
可以看出我们的最终区间为(0.405,0.406),精度为0.001.
黄金分割法查找的点会被记录在matplotlib绘制的函数图像上,一目了然,可以看出,在最终区间精度为0.001的情况下,我们的算法收敛速度很快。
\qquad绘图可以看出函数在极小点附近非常平坦,极小值点差不多在0.405的位置,与文本文档的数据一致。
3.3.斐波拉契法
\qquad斐波拉契法是一维搜索中压缩比最高的搜索算法。斐波拉契法基于斐波拉契数列产生比例值,斐波拉契数列{Fn}\lbrace F_n \rbrace{Fn}的定义如下:
F1=F2=1,Fn+2=Fn+1+FnF_1=F_2=1,F_{n+2}=F_{n+1}+F_{n}F1=F2=1,Fn+2=Fn+1+Fn
取分点时,x1=a+Fn−2Fn(b−a),x2=a+Fn−1Fn(b−a)x_1=a+\frac{F_{n-2}}{F_n}(b-a),x_2=a+\frac{F_{n-1}}{F_n}(b-a)x1=a+FnFn−2(b−a),x2=a+FnFn−1(b−a)
{x1−a=Fn−2Fn(b−a)x2−a=Fn−1Fn(b−a)x2−x1=Fn−1−Fn−2Fn(b−a)=Fn−3Fn(b−a)b−x2=Fn−Fn−1Fn(b−a)=Fn−2Fn(b−a)\begin{cases} x_1-a=\frac{F_{n-2}}{F_n}(b-a) \\x_2-a=\frac{F_{n-1}}{F_n}(b-a) \\x_2-x_1=\frac{F_{n-1}-F_{n-2}}{F_n}(b-a)=\frac{F_{n-3}}{F_n}(b-a) \\b-x_2=\frac{F_{n}-F_{n-1}}{F_n}(b-a)=\frac{F_{n-2}}{F_n}(b-a) \end{cases}⎩⎪⎪⎪⎨⎪⎪⎪⎧x1−a=FnFn−2(b−a)x2−a=FnFn−1(b−a)x2−x1=FnFn−1−Fn−2(b−a)=FnFn−3(b−a)b−x2=FnFn−Fn−1(b−a)=FnFn−2(b−a)
1.若x1<x2x_1<x_2x1<x2,则取新区间为[a,x2][a,x_2][a,x2],则DE‾=Fn−3Fn(b−a),IJ‾=Fn−1Fn(b−a),CD‾=Fn−2Fn(b−a),CDIJ=Fn−2Fn−1,因此D相当于IJ中的x2的位置只需要再取x1=a+Fn−3Fn−1(b−a)即可\overline{DE}=\frac{F_{n-3}}{F_n}(b-a),\overline{IJ}=\frac{F_{n-1}}{F_n}(b-a),\overline{CD}=\frac{F_{n-2}}{F_n}(b-a), \\ \frac{CD}{IJ}=\frac{F_{n-2}}{F_{n-1}},因此D相当于IJ中的x_2的位置\\只需要再取x_1=a+\frac{F_{n-3}}{F_{n-1}}(b-a)即可DE=FnFn−3(b−a),IJ=FnFn−1(b−a),CD=FnFn−2(b−a),IJCD=Fn−1Fn−2,因此D相当于IJ中的x2的位置只需要再取x1=a+Fn−1Fn−3(b−a)即可
2.若x1>x2x_1>x_2x1>x2,则取新区间为[x1,b][x_1,b][x1,b],则DE‾=Fn−3Fn(b−a),GH‾=Fn−1Fn(b−a),CD‾=Fn−2Fn(b−a),DEGH=Fn−3Fn−1,因此D相当于GH中的x1的位置只需要再取x2=a+Fn−2Fn−1(b−a)即可\overline{DE}=\frac{F_{n-3}}{F_n}(b-a),\overline{GH}=\frac{F_{n-1}}{F_n}(b-a),\overline{CD}=\frac{F_{n-2}}{F_n}(b-a), \\ \frac{DE}{GH}=\frac{F_{n-3}}{F_{n-1}},因此D相当于GH中的x_1的位置\\只需要再取x_2=a+\frac{F_{n-2}}{F_{n-1}}(b-a)即可DE=FnFn−3(b−a),GH=FnFn−1(b−a),CD=FnFn−2(b−a),GHDE=Fn−1Fn−3,因此D相当于GH中的x1的位置只需要再取x2=a+Fn−1Fn−2(b−a)即可
对于要求精度esp,取压缩比C=b−aesp,Fn=min{x∣x≥C,x∈N+}C=\frac{b-a}{esp},F_n=min\lbrace x|x≥C,x∈N^+\rbraceC=espb−a,Fn=min{x∣x≥C,x∈N+},按照上述方法迭代至分母为F2F_2F2为止,最后按照以下方法进行:
Code Block
首先我们需要一个生产斐波那契数列(Fabonaci)的函数:
# 在本案例中,使用一次性生成斐波拉契列表的方法算法复杂度更低
# 生成的斐波拉契数列F(0)=0,F(1)=F(2)=1,剩余项和普通斐波拉契数列类似
def Fab_list(Fmax):a,b=0,1Fablist = [a,b] # 返回一个列表while Fablist[-1]< Fmax:a,b = b,a+bFablist.append(b)return Fablist
再按照斐波那契法迭代编写算法即可:
通用函数的编写仍然参考以下链接:
[动态创建数学函数(CSDN)]
注:需要安装matplotlib模块、pylab模块和Pillow模块,版本为python3
步骤:
Windows键+R,在【运行】窗口输入cmd并回车,在命令提示行下输入:
pip install matplotlib
pip install pylab
pip install Pillow
即可
from math import *
import matplotlib.pyplot as plt # 绘图模块
from pylab import * # 绘图辅助模块# 通用函数f(x)靠用户录入
def function(x):fx = str_fx.replace("x", "%(x)f") # 所有的"x"换为"%(x)function"return eval(fx % {"x": x}) # 字典类型的格式化字符串,将所有的"x"替换为变量x# 绘图函数:给定闭区间(绘图间隔),绘图间隔默认为0.05,若区间较小,请自行修改
def drawf(a, b, interp=0.05):x = [a + ele * interp for ele in range(0, int((b - a) / interp))]y = [function(ele) for ele in x]plt.figure(1)plt.plot(x, y)xlim(a, b)title(init_str, color="b") # 标注函数名plt.show()return "绘图完成!"# 在本案例中,使用一次性生成斐波拉契列表的方法算法复杂度更低
# 生成的斐波拉契数列F(0)=0,F(1)=F(2)=1,剩余项和普通斐波拉契数列类似
def Fab_list(Fmax):a,b=0,1Fablist = [a,b] # 返回一个列表while Fablist[-1]< Fmax:a,b = b,a+bFablist.append(b)Fablist.pop() # 舍掉最后一个元素return Fablist def Fabonaci_search(a, b, Fab):n = len(Fab)-1 # 获取斐波那契数列的长度if n < 3:return "精度过低,无法进行斐波那契一维搜索"data = list()data.append([a, b])x1 = a + Fab[n - 2]/Fab[n] * (b - a)x2 = a + Fab[n - 1]/Fab[n] * (b - a)t = nwhile (t > 3):if function(x1) > function(x2): # 如果f(x1)>f(x2),则在区间(x1,b)内搜索a = x1x1 = x2x2 = a + Fab[t - 1]/Fab[t] * (b - a)plt.plot(x2, function(x2), 'r*')data.append([x1, b])elif function(x1) < function(x2): # 如果f(x1)<(x2),则在区间(a,x2)内搜索b = x2x2 = x1x1 = a + Fab[t - 2]/Fab[t]* (b - a)plt.plot(x1, function(x1), 'r*')data.append([a, x2])else: # 如果f(x1)=function(x2),则在区间(x1,x2)内搜索a = x1b = x2x1 = a + Fab[t - 2]/Fab[t] * (b - a)x2 = a + Fab[t - 1]/Fab[t] * (b - a)plt.plot(x1, function(x1), 'r*', x2, function(x2), 'r*')data.append([x1, x2])t -= 1x1 = a + 0.5 * (b - a) # 斐波那契数列第3项和第2项的比x2 = x1 + 0.1 * (b - a) # 偏离一定距离,人工构造的点if function(x1) > function(x2): # 如果f(x1)>function(x2),则在区间(x1,b)内搜索plt.plot(x2, function(x2), 'r*')data.append([x1, b])a = x1elif function(x1) < function(x2): # 如果f(x1)<function(x2),则在区间(a,x2)内搜索plt.plot(x1, function(x1), 'r*')data.append([a, x2])b = x2else: # 如果f(x1)=function(x2),则在区间(x1,x2)内搜索plt.plot(x1, function(x1), 'r*', x2, function(x2), 'r*')data.append([x1, x2])with open(r"C:\Users\ouni\桌面\一维搜索(黄金分割法).txt", mode="w", encoding="utf-8")as a_file:for i in range(0, len(data)):a_file.write("%d:\t" % (i + 1))for j in range(0, 2):a_file.write("f(%.3f)=%.7f\t" % (data[i][j], function(data[i][j])))a_file.write("\n")print("写入文件成功!")return [a, b]init_str = input("请输入一个函数,默认变量为x:\n") # 输入的最初字符串
para = input("请依次输入一维搜索的区间a,b和最终区间的精确值(用空格分隔)").split() # 导入区间
para = [float(ele) for ele in para] # 将输入的字符串转换为浮点数
low, high, esp = para # 输入参数列表(最小值、最大值和最终精度)
str_fx = init_str.replace("^", "**") # 将所有的“^"替换为python的幂形式"**"
print(Fabonaci_search(low, high, Fab_list(int(ceil((high-low)/esp))))) # 传入区间和斐波拉契列表
drawf(low, high, (high - low) / 2000) # 默认精度是2000个点
以下代码请在交互式界面运行:
请输入一个函数,默认变量为x:
e^(-x)+x^2
请依次输入一维搜索的区间a,b和最终区间的精确值(用空格分隔)0 1 0.01
写入文件成功!
[0.3402663805075117, 0.35279320792829183]
关闭绘图框以继续交互式命令
文本文档如下:
1: f(0.000)=1.0000000 f(1.000)=1.3678794
2: f(0.000)=1.0000000 f(0.382)=0.8284208
3: f(0.236)=0.8454509 f(0.382)=0.8284208
4: f(0.382)=0.8284208 f(0.618)=0.9209301
5: f(0.382)=0.8284208 f(0.472)=0.8465897
6: f(0.236)=0.8454509 f(0.382)=0.8284208
7: f(0.326)=0.8280571 f(0.382)=0.8284208
8: f(0.236)=0.8454509 f(0.326)=0.8280571
9: f(0.292)=0.8320851 f(0.326)=0.8280571
10:f(0.326)=0.8280571 f(0.382)=0.8284208
11: f(0.326)=0.8280571 f(0.347)=0.8272109
12: f(0.347)=0.8272109 f(0.382)=0.8284208
13: f(0.347)=0.8272109 f(0.361)=0.8273036
14: f(0.326)=0.8280571 f(0.347)=0.8272109
15: f(0.340)=0.8273620 f(0.347)=0.8272109
16: f(0.347)=0.8272109 f(0.361)=0.8273036
17: f(0.347)=0.8272109 f(0.354)=0.8271921
18: f(0.340)=0.8273620 f(0.353)=0.8271855
}
图形如下:
可以看出函数的极小值确实在0.35附近,与文本文档标定的一致。
注:以上程序请在Shell或Cmd中运行,如需当做模块运行,请在主要程序的前面加上if __name__=="__main__"
。