AutoML 与 Bayesian Optimization 概述
1. AutoML 概述
AutoML是指對于一個超參數優化任務(比如規定計算資源內,調整網絡結構找到準確率最高的網絡),盡量減少人為干預,使用某種學習機制,來調節這些超參數,使得目標問題達到最優。
這些學習機制包括最基本的
- Grid Search
- Random Search
也有比較傳統的
- 貝葉斯優化
- 多臂老虎機(multi-armed bandit)
還有比較新穎的
- 進化算法
- 強化學習
在AutoML中有一個分支專注于自動學習深度神經網絡的網絡架構,叫做Neural architecture search (NAS),當下分類網絡中效果最好的EfficientNets就是這個領域的產物。
2. Bayesian Optimization
2.1 Grid Search & Random Search
神經網絡訓練是由許多超參數決定的,例如網絡深度,卷積類型,卷積核大小,channel數量等等。為了找到一個好的超參數組合,最直觀的想法就是Grid Search,其實也就是對離散化的參數取值進行窮舉搜索
不同于傳統AutoML相關的問題,比如決策樹相關的RandomForest,GBDT等。對于NAS領域,隨著網絡越來越復雜,超參數也越來越多,要想將每種可能的超參數組合都實驗一遍(即Grid Search)明顯不現實。
另一種比較樸素的想法是進行隨機搜索,但顯然也不是一種高效的策略。
2.2 Bayesian Optimization
貝葉斯優化是一種近似逼近的方法,用一個代理函數來擬合超參數與模型評價結果之間的關系,在每次迭代中,采集函數 根據當前代理函數的反饋選擇最有潛力的超參數進行實驗。實驗得到了一組真實的超參數-模型評價結果的pair對,將這個結果喂回給代理函數進行修正,如此反復迭代,期望其在過程中以更高的效率找到比較好的超參數組合。
上面介紹的是貝葉斯優化中最簡單的一種形式Sequential model-based optimization (SMBO),這段描述中涉及了3個概念:
- 代理函數
代理函數本質上就是一個概率模型,對超參數與模型效果背后對應的黑箱映射關系進行建模。
常用的代理函數有高斯過程,高斯混合模型,還有基于樹的模型,比如回歸隨機森林
首先,我們用f代表超參數與模型效果背后對應的真實映射關系
我們用代理函數,記為模型M,來建模f代表的映射關系。
也就是通過已知的采樣數據集D,擬合模型M,來得到一個近似表達f的概率模型
FITMODEL(M,D) -> p(y|x, D)
在后面的介紹中,都假設f服從高斯過程,也就是使用高斯過程作為代理函數
即f~GP(μ,K)。(GP:高斯過程,μ:均值,K:協方差kernel)。
所以預測也是服從正態分布的,即有p(y|x,D)=N(y|μ,σ^2)
- 采集函數
采集函數被用來根據當前的代理函數,選出本次迭代要嘗試的超參數。
采集函數的選擇有許多種,包括Probability of improvement、Expected improvement、Entropy search、 Upper confidence bound等。
這里給出Expected improvement的詳細定義
假設f′=minf,這個f′表示目前已知的f的最小值。
utility function定義如下(代表超參數為x時的收益/提升):
u(x)=max(0,f′?f(x))
Expected improvement的含義是收益/提升的期望
因此 EI(x) = E[u(x)|x,D]
當帶入我們的假設,即f服從高斯過程時,EI的計算公式如下
于是遍尋所有的超參數取值x,便可以通過采集函數獲取本次迭代需要嘗試的超參數。
- exploitation-and-exploration (利用與探索)
在學習貝葉斯優化的過程中,我思考了兩個問題:
Grid Search & Random Search 不是很高效的結論很容易接受,究其根本原因是什么?
為什么不直接選擇能夠使代理函數取最優值的超參數組合,還要使用采集函數呢?
我發現很多資料使用exploitation-and-exploration的概念對此進行了解釋。
上面兩個問題,代表了搜索過程中的兩種極端。
第一個問題,即 Random Search 對應了exploration,搜索過程只是一味的在探索位置區域,并沒有對已經獲得的信息進行任何的利用。
第二個問題,如果我們直接使用代理函數對應的概率模型進行搜索,對應了exploitation,即充分的對已知信息進行挖掘利用,但是缺乏對位置超參數區域的探索。
因此我們需要在利用與探索之間,進行一些權衡,來達到更高的搜索效率和更好的搜索結果。
Expected improvement這個采集函數就能很好的體現這一點,我們回到它的公式
當均值μ(x)較小時,EI(x)取值會因為左式較大而增大
當協方差K(x,x)較大時,EI(x)取值會因為右式較大而增大
μ(x)較小對應的是已經探索過的點中哪些比較好的超參數組合
K(x,x)較大對應的是那些為探索過的區域
因此,Expected improvement 公式從原理上對exploitation-and-exploration進行了很好權衡,當發現了一個點結果不錯時,會更傾向于在它附近進行深入挖掘,但同時這個鄰域內的點的方差不斷變小,采集函數又會去探索那些完全未知的區域。
觀察下面兩幅圖可以更直觀的感受一下。其中藍色曲線是要擬合的函數,紅色點是已經探索過的采樣點,橙色曲線對應在這些采樣點上學習出來的概率模型的均值,淺藍色陰影是置信區間。
3. Bayesian Optimization 練習
為了更好的學習貝葉斯優化的使用,這里做了一個練習。
首先,我選擇的是scikit- optimization庫的ask-and-tell方法實現我的貝葉斯優化器。
使用peaks函數加上一些隨機噪聲來作為待優化的目標函數。函數定義如下:
def cost_function(x, y):z = 3 * (1-x)**2 * np.exp(-(x**2) - (y+1)**2) \- 10 * (x/5 - x**3 - y**5) * np.exp(-x**2 - y**2) \- 1/3 * np.exp(-(x+1)**2 - y**2) + np.random.randn()return z函數可視化如下:
編寫 Bayesian Optimization 尋優代碼如下:
使用高斯過程作為代理函數的優化過程可視化如下:(前5次隨機,共迭代100次,進行了3次實驗均能收斂到最優值附近)
圖中,白點是全局最優值,當前迭代的采樣點顯示為黑色,如果當前采樣點是歷史最優值,會進行放大顯示。(圖片大小原因gif圖沒傳上來,只能放個靜態圖片在這里,感興趣可以運行下代碼看下具體效果)
使用隨機森林作為代理函數(前5次隨機,共迭代100次),進行了3次實驗只有一次收斂到最優值附近),優化過程分別可視化如下:
實驗1:成功
實驗2:失敗
實驗3:失敗
通過實驗,我發現,在低維問題的優化中,高斯過程要比基于樹模型的代理函數好用。有材料提到像隨機森林,GBDT這樣的樹模型在高維空間中的表現要好很多(這個我沒有驗證)。
此外,隨機森林這類的模型有個突出優勢,就是能夠處理離散輸入,這使得在很多情況下它是唯一選擇。
因此,我又修改了下代碼,嘗試讓RF在本實驗上的效果大幅提升。
思路是使用RandomForest+Random結合的方式進行并行的搜索。每次搜索3個點,一個點使用采集函數獲得,兩個點進行隨機,然后將3個點的采樣值都喂回給RF進行學習,這樣就很好的解決了RF不喜歡探索未知區域,進而無法尋找到最優值的問題。
改進的代碼如下:
# -*- coding: utf-8 -*- """ Created on Mon Oct 14 15:51:56 2019skopt(Scikit Optimize) 練習3練習2的優化版本使用RandomForest+Random結合的方式進行并行的策略搜索 RF模型結合隨機,試圖解決RF模型探索未知區域能力差的缺點@author: zyb_as """from mpl_toolkits.mplot3d import Axes3D import imageio import numpy as np import matplotlib.pyplot as plt import matplotlib as mpldef cost_function(x, y):z = 3 * (1-x)**2 * np.exp(-(x**2) - (y+1)**2) \- 10 * (x/5 - x**3 - y**5) * np.exp(-x**2 - y**2) \- 1/3 * np.exp(-(x+1)**2 - y**2) + np.random.randn()return zdef transform_coordinate2index(coordinate, interval=0.01):index = int((coordinate + 3)/interval)return index# 計算目標函數Z(用于畫圖) interval = 0.01 x=np.arange(-3, 3, interval) y=np.arange(-3, 3, interval) X, Y = np.meshgrid(x, y) Z = np.zeros(X.shape) for i in range(X.shape[0]):for j in range(X.shape[1]):xij = X[i][j]yij = Y[i][j]Z[i][j] = cost_function(xij, yij)# 計算理論最優值,用于判斷優化效果 min_z = np.min(Z) min_idx = np.where(Z==min_z) x_min_idx = int(min_idx[1]) y_min_idx = int(min_idx[0])# define the skopt optimizer import skopt opt = skopt.Optimizer([skopt.space.Real(-3.0, 3.0, name='x'),skopt.space.Real(-3.0, 3.0, name='y'),],n_initial_points=1,base_estimator='RF', # can choose "GP", "RF", "ET", "GBRT"acq_func='EI',acq_optimizer='auto',random_state=0,)# use these api as a random augment polices loader rand_opt = skopt.Optimizer([skopt.space.Real(-3.0, 3.0, name='x'),skopt.space.Real(-3.0, 3.0, name='y'),],n_initial_points=1e10,base_estimator='RF', # no matter to choose what valueacq_func='EI',acq_optimizer='auto',random_state=3, # better to be different with opt's random_state)cmap = mpl.cm.get_cmap('jet', 1000) # color mapimages = [] total_epochs = 30 point_x_list = [] point_y_list = [] best_cost = 1e100 best_hyperparams = None parallel_level = 3 for i in range(total_epochs):fig = plt.figure()plt.imshow(Z, cmap=cmap)# plot the hyperparams find beforeplt.scatter(point_x_list, point_y_list, s=30, c='slategray')# plot the theoretical best hyperparamsplt.scatter(x_min_idx, y_min_idx, s=50, c='white')# ask for a list of new hyperparamsrand_hyperparams_list = rand_opt.ask(parallel_level - 1)new_hyperparams = opt.ask()new_hyperparams_list = rand_hyperparams_listnew_hyperparams_list.append(new_hyperparams)# parallel distribute taskscost_list = []for parallel_index in range(parallel_level):cur_hyperparams = new_hyperparams_list[parallel_index]# use the hyperparams to train the model# ...# evaluate the costcur_cost = cost_function(cur_hyperparams[0], cur_hyperparams[1])print("epoch{}_parallel{}".format(i+1, parallel_index+1), cur_cost, cur_hyperparams)cost_list.append(cur_cost) if cur_cost < best_cost:best_cost = cur_costbest_hyperparams = cur_hyperparamsprint('find new best hyperparams')# tell the optimizer the cost for parallel_index in range(parallel_level):opt.tell(new_hyperparams_list[parallel_index], cost_list[parallel_index])rand_opt.tell(new_hyperparams_list[parallel_index], cost_list[parallel_index])# 繪制本次并行探索的超參的位置new_x_list = []new_y_list = []for parallel_index in range(parallel_level):cur_hyperparams = new_hyperparams_list[parallel_index]new_x = transform_coordinate2index(cur_hyperparams[0])new_y = transform_coordinate2index(cur_hyperparams[1])new_x_list.append(new_x)new_y_list.append(new_y)point_x_list.append(new_x)point_y_list.append(new_y)plt.scatter(new_x_list, new_y_list, s=30, c='black') # 顯示plt.savefig('temp.png')plt.close()#plt.show()# 記錄一幀圖像images.append(imageio.imread('temp.png'))# 生成gif圖 imageio.mimsave('parallel_randRF_rand5.gif', images, duration=1)改進后的效果如下:
可以看到,改進的效果還是挺明顯的,對于exploitation-and-exploration有了很好的權衡。
借助 Bayesian Optimization 自動搜索數據增強方法的探索
借鑒Insight研究員的一個工作:
AutoML數據增廣
GitHub
我探索了使用 Bayesian Optimization 自動搜索數據增強方法的實驗。
在上面工作的基礎上,我主要做了如下的修改:
1 將代理任務的網絡由一個5層CNN改為mobilenet。
這樣做雖然明顯提高了整個算法搜索過程的耗時,但是單個評估實驗的結論會更可靠,基于mobilenet的規模,也不至于使得搜索過程的耗時完全不可接受。
2 對架構進行修改,使其支持并行訓練
實際使用中經常面臨可用的gpu資源比較零碎,要想提高實驗速度,需要解決多卡多機器的問題。
因此我對程序框架進行了修改,采用單服務端,多客戶端的模式。服務端負責學習貝葉斯優化器,并根據學習結果分發任務給客戶端;客戶端領任務,對收到的數據增強組合進行評估,然后把結果返回給服務端。并且簡化了使用過程,僅需要修改一個類似如下的配置文件:
這個實驗由于比較消耗資源,有很多細節需要實驗和調整,暫時還沒有得到很好的成果。再加上最近又出了PBA,Fast AutoAugment,RandAugment這三篇自動搜索數據增強方法的paper,個人認為更有價值去研究,畢竟時間有限,本實驗對應的這種思路后面可能就不再繼續跟進了。
不過我感覺這個嘗試還是很有意義的,因為這份代碼可以作為一個autoML的通用并行框架使用,稍微改改就可以跑其它任務。
整個項目的代碼整理到了我維護的AI工具箱中,具體位置bayesian-autoaugment,感興趣的小伙伴可以看看
參考文獻
寫作本文閱讀的資料及文中部分配圖出自以下地址:
AutoML數據增廣
AutoML總結
貝葉斯優化(Bayesian Optimization)深入理解
機器學習貝葉斯超參數優化
貝葉斯優化(BayesianOptimization)
Bayesian Optimization Primer
總結
以上是生活随笔為你收集整理的AutoML 与 Bayesian Optimization 概述的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle查询本身字符集,Oracle
- 下一篇: PID控制器改进笔记之二:改进PID控制