本文將介紹一種快速有效的方法用於實現機器學習模型的調參。有兩種經常使用的調參方法:網格搜索和隨機搜索。每一種都有本身的優勢和缺點。網格搜索速度慢,但在搜索整個搜索空間方面效果很好,而隨機搜索很快,但可能會錯過搜索空間中的重要點。幸運的是,還有第三種選擇:貝葉斯優化。本文咱們將重點介紹貝葉斯優化的一個實現,一個名爲hyperopt的Python模塊。html
使用貝葉斯優化進行調參可讓咱們得到給定模型的最佳參數,例如邏輯迴歸模型。這也使咱們可以執行最佳的模型選擇。一般機器學習工程師或數據科學家將爲少數模型(如決策樹,支持向量機和K近鄰)執行某種形式(網格搜索或隨機搜索)的手動調參,而後比較準確率並選擇最佳的一個來使用。該方法可能比較的是次優模型。也許數據科學家找到了決策樹的最優參數,但卻錯過了SVM的最優參數。這意味着他們的模型比較是有缺陷的。若是SVM參數調整得不好,K 近鄰可能每次都會擊敗SVM。貝葉斯優化容許數據科學家找到全部模型的最佳參數,並所以比較最佳模型。這會獲得更好的模型選擇,由於你比較的是最佳的k近鄰和最佳的決策樹。只有這樣你才能很是自信地進行模型選擇,確保選擇並使用的是實際最佳的模型。python
本文涵蓋的主題有:git
假設你有一個定義在某個範圍內的函數,而且想把它最小化。也就是說,你想找到產生最低輸出值的輸入值。下面的簡單例子找到\(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)
輸出結果:dom
{'x': 0.000269455723739237}
這有一個更復雜的目標函數:
\(lambda\ x: (x-1)^2\)。此次咱們試圖最小化一個二次方程\(y(x)=(x-1)^2\)。因此咱們改變搜索空間以包括咱們已知的最優值\((x=1)\)加上兩邊的一些次優範圍:\(hp.uniform('x', -2, 2)\)。機器學習
best = fmin( fn=lambda x: (x-1)**2, space=hp.uniform('x', -2, 2), algo=tpe.suggest, max_evals=100) print(best)
輸出結果:分佈式
{'x': 0.997369045274755}
hyperopt模塊包含一些方便的函數來指定輸入參數的範圍。咱們已經見過\(hp.uniform\)。最初,這些是隨機搜索空間,但隨着hyperopt更多的學習(由於它從目標函數得到更多反饋),經過它認爲提供給它最有意義的反饋,會調整並採樣初始搜索空間的不一樣部分。函數
如下內容將在本文中使用:學習
import hyperopt.pyll.stochastic space = { 'x': hp.uniform('x', 0, 1), 'y': hp.normal('y', 0, 1), 'name': hp.choice('name', ['alice', 'bob']), } print(hyperopt.pyll.stochastic.sample(space))
輸出結果:測試
{'y': -1.4012610048810574, 'x': 0.7258615424906184, 'name': 'alice'}
若是能看到hyperopt黑匣子內發生了什麼是極好的。Trials對象使咱們可以作到這一點。咱們只須要導入一些東西。
from hyperopt import fmin, tpe, hp, STATUS_OK, Trials fspace = { 'x': hp.uniform('x', -5, 5) } def f(params): x = params['x'] val = x**2 return {'loss': val, 'status': STATUS_OK} trials = Trials() best = fmin(fn=f, space=fspace, algo=tpe.suggest, max_evals=50, trials=trials) print('best:', best) print 'trials:' for trial in trials.trials[:2]: print(trial)
\(STATUS_OK\)和Trials是新導入的。Trials對象容許咱們在每一個時間步存儲信息。而後咱們能夠將它們打印出來,並在給定的時間步查看給定參數的函數評估值。
輸出結果:
best: {'x': 0.014420181637303776} trials: {'refresh_time': None, 'book_time': None, 'misc': {'tid': 0, 'idxs': {'x': [0]}, 'cmd': ('domain_attachment', 'FMinIter_Domain'), 'vals': {'x': [1.9646918559786162]}, 'workdir': None}, 'state': 2, 'tid': 0, 'exp_key': None, 'version': 0, 'result': {'status': 'ok', 'loss': 3.8600140889486996}, 'owner': None, 'spec': None} {'refresh_time': None, 'book_time': None, 'misc': {'tid': 1, 'idxs': {'x': [1]}, 'cmd': ('domain_attachment', 'FMinIter_Domain'), 'vals': {'x': [-3.9393509404526728]}, 'workdir': None}, 'state': 2, 'tid': 1, 'exp_key': None, 'version': 0, 'result': {'status': 'ok', 'loss': 15.518485832045357}, 'owner': None, 'spec': None}
Trials對象將數據存儲爲BSON對象,其工做方式與JSON對象相同。BSON來自pymongo模塊。咱們不會在這裏討論細節,這是對於須要使用MongoDB進行分佈式計算的hyperopt的高級選項,所以須要導入pymongo。回到上面的輸出。
咱們看看損失vs值的圖
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)
在本節中,咱們將介紹4個使用hyperopt在經典數據集Iris上調參的完整示例。咱們將涵蓋K近鄰(KNN),支持向量機(SVM),決策樹和隨機森林。須要注意的是,因爲咱們試圖最大化交叉驗證的準確率(acc請參見下面的代碼),而hyperopt只知道如何最小化函數,因此必須對準確率取負。最小化函數f與最大化f的負數是相等的。
對於這項任務,咱們將使用經典的Iris數據集,並進行一些有監督的機器學習。數據集有有4個輸入特徵和3個輸出類別。數據被標記爲屬於類別0,1或2,其映射到不一樣種類的鳶尾花。輸入有4列:萼片長度,萼片寬度,花瓣長度和花瓣寬度。輸入的單位是釐米。咱們將使用這4個特徵來學習模型,預測三種輸出類別之一。由於數據由sklearn提供,它有一個很好的DESCR屬性,能夠提供有關數據集的詳細信息。嘗試如下代碼以得到更多細節信息。
咱們如今將使用hyperopt來找到K近鄰(KNN)機器學習模型的最佳參數。KNN模型是基於訓練數據集中k個最近數據點的大多數類別對來自測試集的數據點進行分類。下面的代碼結合了咱們所涵蓋的一切。
from sklearn import datasets iris = datasets.load_iris() X = iris.data y = iris.target def hyperopt_train_test(params): clf = KNeighborsClassifier(**params) return cross_val_score(clf, X, y).mean() space4knn = { 'n_neighbors': hp.choice('n_neighbors', range(1,100)) } 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:',best)
如今讓咱們看看輸出結果的圖。y軸是交叉驗證分數,x軸是k近鄰個數。下面是代碼和它的圖像:
f, ax = plt.subplots(1)#, figsize=(10,10)) xs = [t['misc']['vals']['n'] 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'\)的值限制爲較小的值來進一步探索。
from sklearn import datasets iris = datasets.load_iris() X = iris.data y = iris.target def hyperopt_train_test(params): clf = KNeighborsClassifier(**params) return cross_val_score(clf, X, y).mean() space4knn = { 'n_neighbors': hp.choice('n_neighbors', range(1,50)) } 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:',best)
如今咱們能夠清楚地看到k有一個最佳值,k=4。
上面的模型沒有作任何預處理。因此咱們來歸一化和縮放特徵,看看是否有幫助。用以下代碼:
# now with scaling as an option from sklearn import datasets iris = datasets.load_iris() X = iris.data y = iris.target def 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:',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)能作什麼。
因爲這是一個分類任務,咱們將使用sklearn的SVC類。代碼以下:
iris = datasets.load_iris() X = iris.data y = iris.target def 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:',best) parameters = ['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,gamma的最佳值是15.04230279483486。這組參數獲得了99.3%的分類準確率。
自動調整一個模型的參數(如SVM或KNN)很是有趣而且具備啓發性,但同時調整它們並取得全局最佳模型則更有用。這使咱們可以一次比較全部參數和全部模型,所以爲咱們提供了最佳模型。代碼以下:
digits = datasets.load_digits() X = digits.data y = digits.target print X.shape, y.shape def 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 0 return 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, count count += 1 acc = hyperopt_train_test(params.copy()) if acc > best: print 'new best:', acc, 'using', params['type'] best = acc if count % 50 == 0: print 'iters:', count, ', acc:', acc, 'using', params return {'loss': -acc, 'status': STATUS_OK} trials = Trials() best = fmin(f, space, algo=tpe.suggest, max_evals=1500, trials=trials) print('best:',best)
因爲咱們增長了評估數量,此代碼須要一段時間才能運行完:\(max\_evals=1500\)。當找到新的最佳準確率時,它還會添加到輸出用於更新。好奇爲何使用這種方法沒有找到前面的最佳模型:參數爲kernel=linear,C=1.416,gamma=15.042的SVM。
咱們已經介紹了簡單的例子,如最小化肯定的線性函數,以及複雜的例子,如調整SVM的參數。後面讀者須要根據本身的需求再去調整選擇的參數,也能夠基於深度學習模型進行調參。