1.概念
一維搜索是最優化方法最簡單的一種,即求一個在(a,b)內,連續下單峯函數的極小值。所謂下單峯函數就是隻有一個極小值的函數。
2.遍歷搜索
這個問題看似簡單,我們只需要指定步長,從區間左端點搜索到右端點,每次根據步長新產生一個值,比對它和前兩個值的大小。記錄三個值從左到由依次爲,,,若出現的情況,則說明極小值點必定落在之間。因此如果指定了最終區間不得超過eps的話,那麼搜索步長不得大於0.5eps。
我們很快就會發現,這種遍歷法雖然算法簡單,但是迭代次數多,浪費資源嚴重,因此我們需要更快地得到最終區間的方法。在最優化方法這個數學分支中,可以證明斐波拉契法是壓縮比(在同樣迭代次數下開始區間和最終區間的比值)最高的方法,黃金分割比相比它壓縮比小一些,但是算法會比較簡單,佔用計算資源也會略少。
3.優化算法
3.1.一維搜索原則
首先在初始區間[a,b]中取兩個點,計算的值。由於在(a,b)爲下單峯函數,即有唯一的極小值,因此a或b必定不是極小值點。
1.若則說明我下降段,極小值點應該在的右側,因此修改查找區間爲
2.若則說明我上升段,極小值點應該在的左側,因此修改查找區間爲
一維搜索算法的核心就是解決取的問題
3.2.黃金分割法
設黃金分割比爲k,取。
在一維搜索中,如果滿足條件1,那麼需要在取值。設則
由於在黃金分割比的作用下,,因此
因此若將E點平移至GH線段,將G的橫座標當做a,H的橫座標當做b,它就相當於是的位置,因此只需要計算即可
同理,如果滿足條件2,D點相當於IJ線段的的位置,因此只需要計算
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=x1
x1=x2
x2=b-rou*(b-a)
plt.plot(x2,function(x2),'r*')
elif function(x1)<function(x2): #如果f(x1)<function(x2),則在區間(a,x2)內搜索
b=x2
x2=x1
x1=a+rou*(b-a)
plt.plot(x1,function(x1),'r*')
else: #如果f(x1)=function(x2),則在區間(x1,x2)內搜索
a=x1
b=x2
x1=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的情況下,我們的算法收斂速度很快。
繪圖可以看出函數在極小點附近非常平坦,極小值點差不多在0.405的位置,與文本文檔的數據一致。
3.3.斐波拉契法
斐波拉契法是一維搜索中壓縮比最高的搜索算法。斐波拉契法基於斐波拉契數列產生比例值,斐波拉契數列的定義如下:
取分點時,
1.若,則取新區間爲,則
2.若,則取新區間爲,則
對於要求精度esp,取壓縮比,按照上述方法迭代至分母爲爲止,最後按照以下方法進行:
Code Block
首先我們需要一個生產斐波那契數列(Fabonaci)的函數:
# 在本案例中,使用一次性生成斐波拉契列表的方法算法複雜度更低
# 生成的斐波拉契數列F(0)=0,F(1)=F(2)=1,剩餘項和普通斐波拉契數列類似
def Fab_list(Fmax):
a,b=0,1
Fablist = [a,b] # 返回一個列表
while Fablist[-1]< Fmax:
a,b = b,a+b
Fablist.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,1
Fablist = [a,b] # 返回一個列表
while Fablist[-1]< Fmax:
a,b = b,a+b
Fablist.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 = n
while (t > 3):
if function(x1) > function(x2): # 如果f(x1)>f(x2),則在區間(x1,b)內搜索
a = x1
x1 = x2
x2 = 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 = x2
x2 = x1
x1 = 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 = x1
b = x2
x1 = 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 -= 1
x1 = 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 = x1
elif function(x1) < function(x2): # 如果f(x1)<function(x2),則在區間(a,x2)內搜索
plt.plot(x1, function(x1), 'r*')
data.append([a, x2])
b = x2
else: # 如果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__"
。