Hyperopt 参数优化
翻譯自https://districtdatalabs.silvrback.com/parameter-tuning-with-hyperopt
Parameter Tuning with Hyperopt –Kris Wright
概述
Hyperopt可以幫助快速進行機器學習模型參數調試。通常情況下有兩種類型的參數調試方法,網格搜索(grid search)和隨機搜索(random search)。網格搜索速度慢但是適用于需要隨整個參數空間進行搜索的情況;隨機搜索速度很快但是容易遺漏一些重要信息。幸運的是,我們有第三個選擇:貝葉斯優化(Bayesian optimization)。這里我們關注貝葉斯優化在Python中的實現:Hyperopt模塊。
使用貝葉斯優化進行參數調試允許我們獲得給定模型的最優參數,如,邏輯回歸( logistic regression),因此我們可以進行最優模型選擇。通常情況下,機器學習工程師或數據科學家會對一些模型進行一些形式的手動參數調試(如網格搜索或隨機搜索),如決策樹(decision tree)、支持向量機(support vector machine)、k最近鄰(k nearest neighbors)-然后比較準確度得分(accuracy score)進而選擇性能表現最好的一組參數給模型使用。這種方法可能會產生次優模型(sub-optimal models)。數據科學家可能會給決策樹選擇一組最優參數,但是對SVM卻不是最優的,這就意味著模型比較是有缺陷的。KNN每次都可以打敗SVM,如果SVM的參數選擇很差的話。貝葉斯優化允許數據科學家找到所有模型的最優參數,然后比較所有使用了最佳參數的模型。這樣的話,在模型選擇中,就可以比較最優的決策樹和最優的支持向量機。只有這樣才能保證我們選擇和使用了實際最好的模型。
本文涉及的主題包括:
- 評估函數(Objective functions)
- 搜索空間(Search spaces)
- 存儲評價試驗(Storing evaluation trials)
- 可視化(Visualization)
- 鳶尾花數據集實例(Full example on a classic dataset: Iris)
目標函數(Objective Functions - A Motivating Example)
假設有一個定義在摸個范圍內的函數,你想要使這個函數最小化,也就是說,你想要找到一個輸入值,使得函數的輸出結果是最小的。如下面的例子,找到一個x,使得線性函數y(x)=x取得最小值。
from hyperopt import fmin, tpe, hp best = fmin(fn=lambda x: x,space=hp.uniform('x', 0, 1),algo=tpe.suggest,max_evals=100) print best下面進行分解說明。
函數fmin首先要接收一個函數fn來最小化,這里我們指定fn為一個匿名函數lambda x: x,實際上這個函數可以是任何有效的帶有返回值的函數(valid value-returning function),如回歸中的平均絕對誤差(mean absolute error)。
第二個參數space指定了搜索空間(search space),在這個例子中為在0到1范圍之間的數字。hp.uniform是一個內置的( built-in)hyperopt函數,包括三個參數:name:x,范圍的上界和下屆:0和1。
參數algo接收一個搜索算法(serach algorithm),這個例子中tpe代表tree of Parzen estimators。這個話題超出了這個博客文章的范圍,更多關于tpe的內容請參考這里。algo參數也可以設置成hyperopt.random,但我們在此不做介紹,因為這是眾所周知的搜索策略。
最后,我們指定fmin函數執行的最大次數max_evals。fmin函數返回一個python字典。函數輸出的一個例子:{‘x’: 0.000269455723739237}。
下面是一張函數圖像,紅色的點是我們要找的x的位置。
另一個例子(More Complicated Examples)
這里有個更復雜的目標函數:lambda x: (x-1)**2,這次我們試著最小化一個二次方程 y(x)=(x?1)2 y ( x ) = ( x ? 1 ) 2 。因此,我們將搜索空間更改為包括我們已經知道的最優值x=1以及加上兩側的一些次優范圍:hp.uniform(‘x’, -2, 2)。
我們可以得到:
輸出為:
{'x': 0.997369045274755}函數如下圖:
除了最小化目標函數,也許我們想使函數值最大化。這種情況下我們只需要返回函數的負值,如函數y(x) = -(x**2):
如何解決這個問題呢?我們只需要將目標函數改為lambda x: (x**2),求y(x) = -(x**2)得最小值即為求y(x) = -(x**2)的最大值。
同理對于剛開始的線性函數的例子,我們將求最小化改為求最大化,將將目標函數改為lambda x: -x即可。
下面是一個有用許多(給定無窮范圍的無窮多個)局部最小值得函數,也可以求最大值。
搜索空間(Search spaces)
Hyperopt模塊包含了一些方便的函數(handy functions)來指定輸入參數的范圍。我們已經見過hp.uniform。最初,這些是隨機搜索空間,但是隨著hyperopt的學習(隨著從目標函數獲得更多的feedback),它會對初始搜索空間的不同部分進行調整和采樣,并認為這些部分會給它提供最有意義的反饋。
以下函數將在本文使用:
- hp.choice(label, options) ,其中options為 python list 或tuple
- hp.normal(label, mu, sigma) ,其中mu 和 sigma 分別為均值和標準差
- hp.uniform(label, low, high),其中low和 high分別為范圍的上界和下界
其他還有hp.normal, hp.lognormal, hp.quniform,因為本文不會使用就沒有寫,可自行了解。
下面定義搜索空間:
例子輸出:
{'y': -1.4012610048810574, 'x': 0.7258615424906184, 'name': 'alice'}獲取信息和試驗(Capturing Info with Trials)
如果能看到hyperopt黑盒里到底發生了什么,那就太好了。Trials對象允許我們這樣做。我們只需要再導入幾個項目
from hyperopt import fmin, tpe, hp, STATUS_OK, Trialsfspace = {'x': hp.uniform('x', -5, 5) }def f(params):x = params['x']val = x**2return {'loss': val, 'status': STATUS_OK}trials = Trials() best = fmin(fn=f, space=fspace, algo=tpe.suggest, max_evals=50, trials=trials)print 'best:', bestprint 'trials:' for trial in trials.trials[:2]:print trialSTATUS_OK 和 Trials是新導入的模塊,Trials允許我們存儲每一時間步長(time step)所存儲的信息。然后我們可以輸出這些函數在給定時間步長上對給定參數的求值。
輸出:
Trials對象將數據存儲為BSON對象,類似于JSON對象一樣。BSON來自pymongo模塊,著這里我們不討論細節,但是對于hyperopt有一些高級選項需要使用MongoDB進行分布式計算,因此導入pymongo。
回到上面的輸出,’tid’表示時間id,也就是時間步長,范圍0到max_evals-1,每次迭代加1;’x’在’vals’鍵中,也就是每次迭代參數存儲的地方;’loss’在’result’鍵中,是每次迭代目標函數的值。
可視化(Visualization)
這里介紹兩種類型可視化,val vs. time和loss vs. val。
首先val vs. time。下面是trials.trials數據描述可視化的代碼和樣本輸出。
輸出如下(假設我們將max_evals設置為1000):
我們可以看到,最初算法從整個范圍中均勻取值,但是隨著時間的增加以及對于參數在目標函數上效果的學習,算法搜索范圍越來越集中到最可能取得最優值的范圍-0附近。它仍然探索整個解決方案空間,但不太頻繁。
loss vs. val的可視化:
f, ax = plt.subplots(1) xs = [t['misc']['vals']['x'] for t in trials.trials] ys = [t['result']['loss'] for t in trials.trials] ax.scatter(xs, ys, s=20, linewidth=0.01, alpha=0.75) ax.set_title('$val$ $vs$ $x$ ', fontsize=18) ax.set_xlabel('$x$', fontsize=16) ax.set_ylabel('$val$', fontsize=16)
這就是我們期望的,因為函數y(x) = x**2是確定性的。
最后,讓我們嘗試一個更復雜的示例,使用更多的隨機性和更多的參數。
鳶尾花數據集(The Iris Dataset)
在本節中,我們將介紹在經典數據集Iris上使用hyperopt進行參數調優的4個完整示例。我們將介紹k近鄰(KNN)、支持向量機(SVM)、決策樹和隨機森林。注意,由于我們試圖最大化交叉驗證的準確性(下面代碼中的acc),我們必須為hyperopt對這個值進行取負數,因為hyperopt只知道如何最小化函數。最小化函數f等于最大化函數f的復數。
對于這項任務,我們將使用經典的Iris數據集,并進行一些有監督的機器學習。有4個輸入特性和3個輸出類。這些數據被標記為屬于0類、1類或2類,它們映射到不同種類的鳶尾花。輸入有4列:萼片長度(sepal length)、萼片寬度(sepal width)、花瓣長度(petal length)和花瓣寬度(pedal width)。輸入單位是厘米。我們將使用這4個特性來學習預測三個輸出類之一的模型。由于數據是由sklearn提供的,因此它有一個很好的DESCR屬性,提供了關于數據集的詳細信息。
讓我們通過可視化特性和類來更好地了解數據,使用下面的代碼。如果還沒有安裝seaborn,請不要忘記安裝pip install seaborn。
import seaborn as sns sns.set(style="whitegrid", palette="husl")iris = sns.load_dataset("iris") print iris.head()iris = pd.melt(iris, "species", var_name="measurement") print iris.head()f, ax = plt.subplots(1, figsize=(15,10)) sns.stripplot(x="measurement", y="value", hue="species", data=iris, jitter=True, edgecolor="white", ax=ax)如圖:
K最近鄰(K-Nearest Neighbors)
我們現在使用hyperopt找到k近鄰(KNN)機器學習模型的最佳參數。KNN模型根據訓練數據集中k個最近的數據點的多數類,將測試集中的數據點進行分類。
關于這個算法的更多信息可以在這里找到。下面的代碼包含了我們已經討論過的所有內容。
現在我們來看看輸出圖。y軸是交叉驗證得分,x軸是k-最近鄰中的k值。下面是代碼及其圖像:
f, ax = plt.subplots(1)#, figsize=(10,10)) xs = [t['misc']['vals']['n_neighbors'] for t in trials.trials] ys = [-t['result']['loss'] for t in trials.trials] ax.scatter(xs, ys, s=20, linewidth=0.01, alpha=0.5) ax.set_title('Iris Dataset - KNN', fontsize=18) ax.set_xlabel('n_neighbors', fontsize=12) ax.set_ylabel('cross validation accuracy', fontsize=12)
當k大于63時,準確率急劇下降。這是由于數據集中每個類的數量。這三個類中的每個類只有50個實例。因此,讓我們通過將’n_neighbors’的值限制為較小的值進行深入研究。
下面是當我們運行相同的可視化代碼時得到的結果:
現在我們可以清楚地看到k在k = 4處有一個最佳值。
上面的模型不做任何預處理。讓我們對我們的特性進行規范化和縮放看看這是否有幫助。使用這段代碼:
# now with scaling as an option from sklearn import datasets iris = datasets.load_iris() X = iris.data y = iris.targetdef hyperopt_train_test(params):X_ = X[:]if 'normalize' in params:if params['normalize'] == 1:X_ = normalize(X_)del params['normalize']if 'scale' in params:if params['scale'] == 1:X_ = scale(X_)del params['scale']clf = KNeighborsClassifier(**params)return cross_val_score(clf, X_, y).mean()space4knn = {'n_neighbors': hp.choice('n_neighbors', range(1,50)),'scale': hp.choice('scale', [0, 1]),'normalize': hp.choice('normalize', [0, 1]) }def f(params):acc = hyperopt_train_test(params)return {'loss': -acc, 'status': STATUS_OK}trials = Trials() best = fmin(f, space4knn, algo=tpe.suggest, max_evals=100, trials=trials) print 'best:' print best像這樣畫出參數:
parameters = ['n_neighbors', 'scale', 'normalize'] cols = len(parameters) f, axes = plt.subplots(nrows=1, ncols=cols, figsize=(15,5)) cmap = plt.cm.jet for i, val in enumerate(parameters):xs = np.array([t['misc']['vals'][val] for t in trials.trials]).ravel()ys = [-t['result']['loss'] for t in trials.trials]xs, ys = zip(\*sorted(zip(xs, ys)))ys = np.array(ys)axes[i].scatter(xs, ys, s=20, linewidth=0.01, alpha=0.75, c=cmap(float(i)/len(parameters)))axes[i].set_title(val)
我們發現,數據的縮放和/或規范化并不能提高預測的準確性。k的最佳值為4,準確率為98.6%。
這對于一個簡單模型KNN的參數調優非常有用。讓我們看看支持向量機(SVM)能做些什么。
支持向量機 (SVM)
由于這是一個分類任務,我們將使用sklearn的SVC類。這是代碼:
iris = datasets.load_iris() X = iris.data y = iris.targetdef hyperopt_train_test(params):X_ = X[:]if 'normalize' in params:if params['normalize'] == 1:X_ = normalize(X_)del params['normalize']if 'scale' in params:if params['scale'] == 1:X_ = scale(X_)del params['scale']clf = SVC(**params)return cross_val_score(clf, X_, y).mean()space4svm = {'C': hp.uniform('C', 0, 20),'kernel': hp.choice('kernel', ['linear', 'sigmoid', 'poly', 'rbf']),'gamma': hp.uniform('gamma', 0, 20),'scale': hp.choice('scale', [0, 1]),'normalize': hp.choice('normalize', [0, 1]) }def f(params):acc = hyperopt_train_test(params)return {'loss': -acc, 'status': STATUS_OK}trials = Trials() best = fmin(f, space4svm, algo=tpe.suggest, max_evals=100, trials=trials) print 'best:' print bestparameters = ['C', 'kernel', 'gamma', 'scale', 'normalize'] cols = len(parameters) f, axes = plt.subplots(nrows=1, ncols=cols, figsize=(20,5)) cmap = plt.cm.jet for i, val in enumerate(parameters):xs = np.array([t['misc']['vals'][val] for t in trials.trials]).ravel()ys = [-t['result']['loss'] for t in trials.trials]xs, ys = zip(\*sorted(zip(xs, ys)))axes[i].scatter(xs, ys, s=20, linewidth=0.01, alpha=0.25, c=cmap(float(i)/len(parameters)))axes[i].set_title(val)axes[i].set_ylim([0.9, 1.0])下面是我們得到的:
同樣,縮放和規范化也沒有幫助。核函數的首選為最佳(linear),最佳C值為1.4168540399911616,最佳伽瑪值為15.04230279483486。該參數集的分類準確率達到99.3%。
決策樹(Decision Trees)
我們將只嘗試對決策樹的幾個參數進行優化。這是代碼。
iris = datasets.load_iris() X_original = iris.data y_original = iris.targetdef hyperopt_train_test(params):X_ = X[:]if 'normalize' in params:if params['normalize'] == 1:X_ = normalize(X_)del params['normalize']if 'scale' in params:if params['scale'] == 1:X_ = scale(X_)del params['scale']clf = DecisionTreeClassifier(**params)return cross_val_score(clf, X, y).mean()space4dt = {'max_depth': hp.choice('max_depth', range(1,20)),'max_features': hp.choice('max_features', range(1,5)),'criterion': hp.choice('criterion', ["gini", "entropy"]),'scale': hp.choice('scale', [0, 1]),'normalize': hp.choice('normalize', [0, 1]) }def f(params): acc = hyperopt_train_test(params) return {'loss': -acc, 'status': STATUS_OK}trials = Trials() best = fmin(f, space4dt, algo=tpe.suggest, max_evals=300, trials=trials) print 'best:' print best輸出如下,準確率為97.3%:
{'max_features': 1, 'normalize': 0, 'scale': 0, 'criterion': 0, 'max_depth': 17}如圖我們可以看到,在不同的尺度值、標準化值和標準值下,性能幾乎沒有差別。
parameters = ['max_depth', 'max_features', 'criterion', 'scale', 'normalize'] # decision tree cols = len(parameters) f, axes = plt.subplots(nrows=1, ncols=cols, figsize=(20,5)) cmap = plt.cm.jet for i, val in enumerate(parameters):xs = np.array([t['misc']['vals'][val] for t in trials.trials]).ravel()ys = [-t['result']['loss'] for t in trials.trials]xs, ys = zip(\*sorted(zip(xs, ys)))ys = np.array(ys)axes[i].scatter(xs, ys, s=20, linewidth=0.01, alpha=0.5, c=cmap(float(i)/len(parameters)))axes[i].set_title(val)#axes[i].set_ylim([0.9,1.0])隨機森林(Random Forests)
讓我們看看集成分類器Random Forest發生了什么,它只是一組針對不同大小的數據分區訓練的決策樹,每個分區對一個輸出類進行投票,并選擇多數派類作為預測。
iris = datasets.load_iris() X_original = iris.data y_original = iris.targetdef hyperopt_train_test(params):X_ = X[:]if 'normalize' in params:if params['normalize'] == 1:X_ = normalize(X_)del params['normalize']if 'scale' in params:if params['scale'] == 1:X_ = scale(X_)del params['scale']clf = RandomForestClassifier(**params)return cross_val_score(clf, X, y).mean()space4rf = {'max_depth': hp.choice('max_depth', range(1,20)),'max_features': hp.choice('max_features', range(1,5)),'n_estimators': hp.choice('n_estimators', range(1,20)),'criterion': hp.choice('criterion', ["gini", "entropy"]),'scale': hp.choice('scale', [0, 1]),'normalize': hp.choice('normalize', [0, 1]) }best = 0 def f(params):global bestacc = hyperopt_train_test(params)if acc > best:best = accprint 'new best:', best, paramsreturn {'loss': -acc, 'status': STATUS_OK}trials = Trials() best = fmin(f, space4rf, algo=tpe.suggest, max_evals=300, trials=trials) print 'best:' print best同樣,我們只得到97.3%的準確率,和決策樹一樣。
下面是繪制參數的代碼:
parameters = ['n_estimators', 'max_depth', 'max_features', 'criterion', 'scale', 'normalize'] f, axes = plt.subplots(nrows=2, ncols=3, figsize=(15,10)) cmap = plt.cm.jet for i, val in enumerate(parameters):print i, valxs = np.array([t['misc']['vals'][val] for t in trials.trials]).ravel()ys = [-t['result']['loss'] for t in trials.trials]xs, ys = zip(\*sorted(zip(xs, ys)))ys = np.array(ys)axes[i/3,i%3].scatter(xs, ys, s=20, linewidth=0.01, alpha=0.5, c=cmap(float(i)/len(parameters)))axes[i/3,i%3].set_title(val)#axes[i/3,i%3].set_ylim([0.9,1.0])所有模型(All Together Now)
雖然自動調優一個模型的參數(例如SVM或KNN)既有趣又有指導意義,但更有用的是一次調優所有的參數并得到一個總體上最好的模型。這使我們能夠同時比較所有的參數和所有的模型,這給了我們最好的模型。這是代碼。
digits = datasets.load_digits() X = digits.data y = digits.target print X.shape, y.shapedef hyperopt_train_test(params):t = params['type']del params['type']if t == 'naive_bayes':clf = BernoulliNB(**params)elif t == 'svm':clf = SVC(**params)elif t == 'dtree':clf = DecisionTreeClassifier(**params)elif t == 'knn':clf = KNeighborsClassifier(**params)else:return 0return cross_val_score(clf, X, y).mean()space = hp.choice('classifier_type', [{'type': 'naive_bayes','alpha': hp.uniform('alpha', 0.0, 2.0)},{'type': 'svm','C': hp.uniform('C', 0, 10.0),'kernel': hp.choice('kernel', ['linear', 'rbf']),'gamma': hp.uniform('gamma', 0, 20.0)},{'type': 'randomforest','max_depth': hp.choice('max_depth', range(1,20)),'max_features': hp.choice('max_features', range(1,5)),'n_estimators': hp.choice('n_estimators', range(1,20)),'criterion': hp.choice('criterion', ["gini", "entropy"]),'scale': hp.choice('scale', [0, 1]),'normalize': hp.choice('normalize', [0, 1])},{'type': 'knn','n_neighbors': hp.choice('knn_n_neighbors', range(1,50))} ])count = 0 best = 0 def f(params):global best, countcount += 1acc = hyperopt_train_test(params.copy())if acc > best:print 'new best:', acc, 'using', params['type']best = accif count % 50 == 0:print 'iters:', count, ', acc:', acc, 'using', paramsreturn {'loss': -acc, 'status': STATUS_OK}trials = Trials() best = fmin(f, space, algo=tpe.suggest, max_evals=1500, trials=trials) print 'best:' print best這段代碼需要一段時間才能運行,因為我們增加了計算次數:max_evals=1500。當發現新的最佳精度時,還會增加輸出以更新。奇怪的是,為什么使用這種方法沒有找到我們在上面找到的最佳模型:SVM的kernel=linear,C=1.416,和gamma=15.042。
總結
我們已經介紹了一些簡單的例子,比如最小化確定性線性函數,以及一些復雜的例子,比如調整隨機森林參數。hyperopt的文檔在這里。另一個關于hyperopt的好博客是FastML的。hyperopt作者撰寫的SciPy會議論文是Hyperopt: A Python Library for Optimizing the Hyperparameters of Machine Learning Algorithms,附帶一個視頻教程。對工程細節的一種更科學的處理方法是Making a Science of Model Search。
這篇文章中的技術可以應用于除機器學習之外的許多領域,例如在epsilon的epsilon-greedy multi-armed bandit調優參數,或傳遞給圖形生成器的參數,以形成具有某些特性的合成網絡。稍后我們將對此進行更多的討論。
總結
以上是生活随笔為你收集整理的Hyperopt 参数优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小白不知道raw批量转换jpg怎么转?分
- 下一篇: macOS Big Sur到来,为Mac