以下為該學習地址的學習筆記
學習地址:Basic tour of the Bayesian Optimization package — Bayesian Optimization documentation
貝葉斯優化簡介
貝葉斯優化是一種基于貝葉斯推斷和高斯過程的全局優化方法,它試圖在盡可能少的迭代次數內找到一個未知函數的最大值。這種技術特別適合用于高成本函數的優化,以及需要在探索(exploration)和利用(exploitation)之間找到平衡的情況。
貝葉斯優化的基本原理
-
后驗分布構建:貝葉斯優化通過構建一個描述你要優化的函數的后驗分布(通常是高斯過程)來工作。隨著觀察數據的增加,后驗分布會不斷改進,算法會逐漸確定參數空間中的哪些區域值得探索,哪些不值得探索。
-
探索與利用的平衡:在每一步迭代中,算法都會根據已知樣本(先前探索過的點)擬合一個高斯過程。然后,后驗分布結合探索策略(如UCB(上置信界)或EI(期望改進))來確定下一個應該探索的點。
優化過程
貝葉斯優化的過程旨在最小化找到接近最優參數組合所需的步驟數。為此,這種方法使用了一個代理優化問題(尋找采集函數的最大值),雖然這仍然是一個困難的問題,但計算成本較低,并且可以使用常見的工具。因此,貝葉斯優化特別適用于采樣目標函數非常昂貴的情況。
關鍵概念
- 高斯過程:一種用于構建后驗分布的非參數貝葉斯模型,它通過已知樣本來推斷未知函數的分布。
- 采集函數:一種策略函數,用于在每次迭代中決定下一個采樣點。常見的采集函數包括上置信界(UCB)和期望改進(EI)。
- 探索(Exploration)與利用(Exploitation):探索是指尋找參數空間中未知或不確定的區域,而利用是指在已知的高潛力區域進行優化。貝葉斯優化通過平衡這兩者來提高優化效率。
應用場景
貝葉斯優化適用于以下情況:
- 目標函數的計算代價高昂,例如機器學習模型的超參數調優。
- 需要在盡量少的迭代中找到接近最優的參數組合。
- 需要在探索新的參數區域和利用已有信息之間找到平衡。
通過貝葉斯優化,可以在高成本的函數優化問題中高效地找到最優解,減少不必要的計算開銷。詳細的討論和更多的理論基礎可以參考相關文獻和資料。
1. 確定要優化的函數
這是一個函數優化軟件包,因此最重要的第一要素當然是要優化的函數。
免責聲明:我們很清楚下面函數的輸出如何取決于其參數。顯然,這只是一個示例,你不應該指望在實際場景中知道這一點。不過,你應該清楚,你并不需要知道。要使用這個軟件包(更廣泛地說,要使用這種技術),你所需要的只是一個接收已知參數集并輸出實數的函數 f。
貝葉斯優化的核心是對函數進行優化。首先,需要定義一個目標函數(即需要優化的函數)。
def black_box_function(x, y):"""定義一個我們希望優化的未知內部函數。這里僅作為示例,在實際場景中,你不應該知道該函數的具體內部實現。只需要知道這個函數接受一組參數并輸出一個實數即可。"""return -x ** 2 - (y - 1) ** 2 + 1
此函數返回一個值,該值基于輸入參數 x和 y 計算得到。優化的目標是找到使得該函數值最大的參數組合。
2. 開始貝葉斯優化
我們需要實例化一個 BayesianOptimization
對象,指定要優化的函數 f
及其參數和對應的邊界 pbounds
。貝葉斯優化是一種約束優化技術,因此必須指定每個參數的最小和最大值。
from bayes_opt import BayesianOptimization# 參數空間的有界區域
pbounds = {'x': (2, 4), 'y': (-3, 3)} # 指定參數 x 和 y 的邊界optimizer = BayesianOptimization(f=black_box_function, # 需要優化的函數pbounds=pbounds, # 參數邊界verbose=2, # verbose=1 時僅在觀察到最大值時打印,verbose=0 時不打印random_state=1, # 隨機種子,保證結果可重復
)
BayesianOptimization
對象可以直接使用,無需進行大量調優。主要需要關注的方法是 maximize
,它用于執行貝葉斯優化過程。
maximize
方法接受多個參數,最重要的有:
n_iter
:執行貝葉斯優化的步數,步數越多,找到好最大值的可能性越大。init_points
:執行隨機探索的步數,隨機探索有助于多樣化探索空間。
optimizer.maximize(init_points=2, # 初始隨機探索步數n_iter=3, # 貝葉斯優化步數
)
執行后,會顯示每次迭代的參數組合及其對應的目標值(即函數值)。
markdown復制代碼
| iter | target | x | y |
-------------------------------------------------
| 1 | -7.135 | 2.834 | 1.322 |
| 2 | -7.78 | 2.0 | -1.186 |
| 3 | -7.11 | 2.218 | -0.7867 |
| 4 | -12.4 | 3.66 | 0.9608 |
| 5 | -6.999 | 2.23 | -0.7392 |
=================================================
最佳參數組合及其對應的目標值可以通過 optimizer.max
屬性訪問。
print(optimizer.max)
# 輸出最優參數及其對應的目標值
# {'target': -6.999472814518675, 'params': {'x': 2.2303920156083024, 'y': -0.7392021938893159}}
所有被探索的參數及其目標值可以通過 optimizer.res
屬性訪問。
for i, res in enumerate(optimizer.res):print("Iteration {}: \\n\\t{}".format(i, res))
# 逐次打印每次迭代的參數及目標值
# Iteration 0:
# {'target': -7.135455292718879, 'params': {'x': 2.8340440094051482, 'y': 1.3219469606529488}}
# Iteration 1:
# {'target': -7.779531005607566, 'params': {'x': 2.0002287496346898, 'y': -1.1860045642089614}}
# Iteration 2:
# {'target': -7.109925819441113, 'params': {'x': 2.2175526295255183, 'y': -0.7867249801593896}}
# Iteration 3:
# {'target': -12.397162416009818, 'params': {'x': 3.660003815774634, 'y': 0.9608275029525108}}
# Iteration 4:
# {'target': -6.999472814518675, 'params': {'x': 2.2303920156083024, 'y': -0.7392021938893159}}
2.1 修改參數邊界
在優化過程中,可能會發現某些參數的邊界不合適。此時,可以調用 set_bounds
方法來修改它們。你可以傳遞任何現有參數及其新的邊界組合。
示例代碼
optimizer.set_bounds(new_bounds={"x": (-2, 3)}) # 修改參數 x 的邊界為 [-2, 3]optimizer.maximize( # 開始優化過程init_points=0, # 初始隨機探索步數設置為 0n_iter=5, # 進行 5 次貝葉斯優化迭代
)
執行結果會顯示每次迭代的參數組合及其對應的目標值(即函數值)。
- iter: 迭代次數,即當前是第幾次迭代。
- target: 目標值,即目標函數在該次迭代的參數組合下計算得到的值。
- x: 參數 x 的值。
- y: 參數 y 的值。
| iter | target | x | y |
-------------------------------------------------
| 6 | -2.942 | 1.98 | 0.8567 |
| 7 | -0.4597 | 1.096 | 1.508 |
| 8 | 0.5304 | -0.6807 | 1.079 |
| 9 | -5.33 | -1.526 | 3.0 |
| 10 | -5.419 | -2.0 | -0.5552 |
=================================================
3. 指導優化過程
在優化過程中,我們通常對參數空間的某些區域有一定的了解,認為這些區域可能包含函數的最大值。對于這種情況,BayesianOptimization
對象允許用戶指定特定的點進行探測。默認情況下,這些點會被懶惰地(lazy=True
)探索,即這些點將在下次調用 maximize
時才會被評估。這個探測過程發生在高斯過程接管之前。
示例代碼
可以將參數以字典形式傳遞,如下所示:
optimizer.probe( # 使用 probe 方法指定要探測的點params={"x": 0.5, "y": 0.7}, # 指定 x 和 y 的值lazy=True, # 懶惰探索,等到下次調用 maximize 時才評估這些點
)
也可以將參數作為可迭代對象傳遞。注意順序必須是按字母順序排列的。你可以使用 optimizer.space.keys
來查看順序。
print(optimizer.space.keys) # 打印參數的鍵,以確認順序
# 輸出: ['x', 'y']optimizer.probe( # 使用 probe 方法指定要探測的點params=[-0.3, 0.1], # 參數按字母順序排列,即 x=-0.3, y=0.1lazy=True, # 懶惰探索,等到下次調用 maximize 時才評估這些點
)
接下來調用 maximize
方法執行優化:
optimizer.maximize(init_points=0, n_iter=0) # 進行優化,此處 init_points 和 n_iter 均為 0 表示只評估探測點
- init_points:
- 表示在正式開始貝葉斯優化之前,進行隨機探索的步數。
- 隨機探索可以幫助多樣化探索空間,但并不利用貝葉斯優化的優點。
- n_iter:
- 表示實際進行貝葉斯優化的步數。
- 貝葉斯優化利用已有數據和高斯過程模型來選擇下一個探索點,以最大化目標函數。
- lazy=True:
- 表示指定的探測點將在下次調用
maximize
時被評估。 - 如果
lazy=False
,探測點會立即被評估。
- 表示指定的探測點將在下次調用
執行結果會顯示每次迭代的參數組合及其對應的目標值(即函數值)。
| iter | target | x | y |
-------------------------------------------------
| 11 | 0.66 | 0.5 | 0.7 |
| 12 | 0.1 | -0.3 | 0.1 |
=================================================
- 指定探測點:
- 通過
optimizer.probe
方法指定一些參數組合(探測點)用于評估。 - 設置
lazy=True
表示這些探測點不會立即被評估,而是在下次調用maximize
時才會被評估。
- 通過
- 調用
maximize
方法:- 通過
optimizer.maximize(init_points=0, n_iter=0)
,告訴優化器這次優化不進行任何額外的隨機探索(init_points=0
)和貝葉斯優化步驟(n_iter=0
)。 - 由于設置了
lazy=True
,因此這些探測點在這次maximize
調用時被評估。
- 通過
這樣做的意義在于,你可以提前指定一些感興趣的點,讓優化器在正式開始貝葉斯優化之前先評估這些點。這對于你有某些先驗知識或猜測可能有用的參數區域時非常有效。
3.1 補充
1. 探測點(Probing Points)
探測點是你認為可能重要或有趣的參數組合,提前指定這些點用于目標函數的計算。你可以使用 optimizer.probe
方法來指定這些點。
python復制代碼
optimizer.probe(params={"x": 0.5, "y": 0.7},lazy=True, # 表示這些探測點在下次調用 maximize 時才會被評估
)
2. 評估(Evaluation)
評估是指對指定的探測點進行目標函數的計算。也就是說,把這些探測點作為參數代入目標函數,計算得到對應的目標值。
3. maximize
方法的調用
optimizer.maximize(init_points=0, n_iter=0)
表示這次優化過程中不進行任何額外的隨機探索(init_points=0
)和貝葉斯優化步驟(n_iter=0
),只對之前通過 optimizer.probe
指定的探測點進行評估。
如下這個代碼示例,執行這段代碼時,maximize
方法會評估之前指定的探測點 (0.5, 0.7) 和 (-0.3, 0.1),并將評估結果記錄下來。這些結果將用于更新貝葉斯優化模型,從而幫助優化算法更好地尋找目標函數的最大值。
#示例
from bayes_opt import BayesianOptimization# 目標函數
def black_box_function(x, y):return -x ** 2 - (y - 1) ** 2 + 1# 定義參數空間的邊界
pbounds = {'x': (2, 4), 'y': (-3, 3)}# 創建貝葉斯優化對象
optimizer = BayesianOptimization(f=black_box_function,pbounds=pbounds,verbose=2, # 設置輸出級別random_state=1,
)# 指定探測點
optimizer.probe(params={"x": 0.5, "y": 0.7}, # 參數 x=0.5, y=0.7lazy=True, # 懶惰評估,這些點會在下次調用 maximize 時進行評估
)# 指定另一個探測點
optimizer.probe(params=[-0.3, 0.1], # 參數按字母順序排列,即 x=-0.3, y=0.1lazy=True, # 懶惰評估,這些點會在下次調用 maximize 時進行評估
)# 進行優化,此次 maximize 調用只評估指定的探測點
optimizer.maximize(init_points=0, n_iter=0)
4. 保存、加載和重啟
默認情況下,通過設置 verbose > 0
可以跟蹤優化的進度。如果需要更高級的日志記錄或警報控制,可以使用觀察者模式。這里我們將看到如何使用內置的 JSONLogger
對象來保存和加載進度。
4.1 保存進度
首先,導入所需的庫:
from bayes_opt.logger import JSONLogger # 導入 JSONLogger 用于記錄日志
from bayes_opt.event import Events # 導入 Events 用于觸發事件
觀察者模式的工作原理如下:
- 實例化觀察者對象。
- 將觀察者對象與優化器觸發的特定事件綁定。
貝葉斯優化對象在優化過程中會觸發多個內部事件,特別是每次探測函數并獲得新的參數-目標組合時,會觸發 Events.OPTIMIZATION_STEP
事件,日志記錄器將監聽該事件。
logger = JSONLogger(path="./logs.log") # 創建 JSONLogger 對象,指定日志文件路徑
optimizer.subscribe(Events.OPTIMIZATION_STEP, logger) # 訂閱優化步驟事件,將其與日志記錄器綁定optimizer.maximize( # 開始優化init_points=2, # 初始隨機探測點數量n_iter=3, # 貝葉斯優化的迭代次數
)
iter | target | x | y |
---|---|---|---|
13 | -12.48 | -1.266 | -2.446 |
14 | -3.854 | -1.069 | -0.9266 |
15 | -3.594 | 0.7709 | 3.0 |
16 | 0.8238 | 0.03434 | 1.418 |
17 | 0.9721 | -0.1051 | 0.87 |
4.2 加載進度
如果保存了進度,可以將其加載到一個新的貝葉斯優化實例中。最簡單的方法是調用 load_logs
函數。
- 創建一個新的貝葉斯優化實例:用于優化一個目標函數
black_box_function
。 - 加載先前保存的優化進度:從日志文件
logs.log
中恢復已經探測過的點。 - 繼續優化過程:在加載了之前的進度后,進行更多的貝葉斯優化迭代。
- 通過這段代碼,你可以在不同的會話或運行中保存和加載貝葉斯優化的進度。這對于長時間運行的優化任務特別有用,因為你可以中途暫停,然后在加載進度后繼續優化。這段代碼展示了如何使用貝葉斯優化包的日志功能來保存和加載優化進度,并在新的優化器實例中繼續優化過程。
from bayes_opt.util import load_logs # 導入 load_logs 函數new_optimizer = BayesianOptimization( # 創建新的貝葉斯優化對象f=black_box_function, # 目標函數pbounds={"x": (-2, 2), "y": (-2, 2)}, # 參數空間的邊界verbose=2, # 輸出級別random_state=7, # 隨機種子
)
#這一步創建了一個新的 BayesianOptimization 對象,并指定了要優化的函數、參數邊界、輸出級別和隨機種子。print(len(new_optimizer.space)) # 輸出當前優化器空間中的點數
# 輸出: 0 # 初始時點數為0
# 在沒有加載日志之前,優化器空間中的點數為0。load_logs(new_optimizer, logs=["./logs.log"]) # 加載之前保存的日志文件
# 這一步從指定的日志文件 logs.log 中加載先前的優化進度,包括已經探測過的參數點和對應的目標值。print("New optimizer is now aware of {} points.".format(len(new_optimizer.space))) # 輸出當前優化器空間中的點數
# 輸出: New optimizer is now aware of 5 points.
# 加載日志后,優化器現在知道5個點(假設日志中保存了5個探測點)。new_optimizer.maximize( # 繼續優化init_points=0, # 不進行初始隨機探測n_iter=10, # 貝葉斯優化的迭代次數
)
# 這一步在加載了之前的進度后,進行10次貝葉斯優化迭代,以進一步探索和優化目標函數。
iter | target | x | y |
---|---|---|---|
1 | -3.548 | -2.0 | 1.74 |
2 | -3.041 | 1.914 | 0.3844 |
3 | -12.0 | 2.0 | -2.0 |
4 | -3.969 | 2.0 | 1.984 |
5 | -0.7794 | -1.238 | 0.5022 |
6 | 0.529 | 0.685 | 0.9576 |
7 | 0.2987 | 0.1242 | 0.1718 |
8 | 0.9544 | 0.2123 | 0.9766 |
9 | 0.7157 | -0.437 | 1.305 |
10 | 0.983 | -0.06785 | 1.111 |
下一步
這部分內容應該足以覆蓋此包的大多數使用場景。如果需要了解更多,請查閱高級教程筆記本。在那里,你可以找到此包的其他高級功能,這些功能可能正是你所尋找的。此外,還可以瀏覽示例文件夾,獲取實現技巧和靈感。