在擬合一個單分類器時不需要花費太多時間,但是在擬合上百個分類器時卻需要大量時間。在尋找超參數時我們必須要擬合多個分類器,那我們應該怎麼解決這種問題呢?
本文研究了Bayesian optimisation的算法的內部工作,你可以通過使用它來減少設定的超參數集的數量並獲得最優的超參數集。如果你正在尋找一個已經實現的算法工具,Yelp提供了 MOE, metric optimisation
通常高斯過程回歸是一個有效的工具同時被大量使用在這裡。如果想獲得關於高斯過程回歸更多的信息可以參考Gaussian processes with george。
本文將從一個事前知道的得分方程的真實形式的案例開始,通過比較random grid search和Bayesian optimisation兩種算法,來發現對於實際分類器的最優的超參數。
首先從安裝和導入模塊開始。
%matplotlib inline
import randomimport numpy as npnp.random.seed(9)from scipy.stats import randint as sp_randintimport matplotlib.pyplot as pltimport seaborn as snssns.set_style('whitegrid')sns.set_context("talk")
By George!Bayesian optimisation對之前超參數空間中的評估點使用高斯過程來擬合回歸模型。同時這個模型將提供超參數空間中下一個用於評估模型的點(可能是最優的)。為了選擇最優的點我們需要定義一個準則,在這種情況下我們使用「提高期望」的準則。就像我們所知的那樣,在一個有精準的得分函數下,我們並不想簡單的選擇獲得最高分的點。相反我們選擇那些在能最大增加期望的點。這種方法允許我們將得分方程的不確定性納入流程,並引導參數空間的搜索。下面我們設置一個得分函數(-x sin x),從中抽取兩個點,並擬合我們的高斯過程模型。
import georgefrom george.kernels import ExpSquaredKernelscore_func = lambda x: -x*np.sin(x)x = np.arange(0, 10, 0.1)
xp = 10 * np.sort(np.random.rand(2))yerr = 0.2 * np.ones_like(xp)yp = score_func(xp) + yerr * np.random.randn(len(xp))
kernel = ExpSquaredKernel(1)gp = george.GP(kernel)gp.compute(xp, yerr)mu, cov = gp.predict(yp, x)std = np.sqrt(np.diag(cov))def basic_plot(): fig, ax = plt.subplots() ax.plot(x, mu, label="GP median") ax.fill_between(x, mu-std, mu+std, alpha=0.5) ax.plot(x, score_func(x), '--', label=" True score function (unknown)") ax.errorbar(xp, yp, yerr=yerr, fmt='ok', zorder=3, label="samples") ax.set_ylim(-9,6) ax.set_ylabel("score") ax.set_xlabel('hyper-parameter X') ax.legend(loc='best') return fig,axbasic_plot()
(<matplotlib.figure.Figure at 0x109dd82d0>, <matplotlib.axes._subplots.AxesSubplot at 0x10b945590>)
其中綠色虛線代表得分函數(以超參數X為函數)的真實值。圖中黑的的點(以及他們的誤差線)是我們用於評估分類器的點同時我們也計算出其得分。在藍色線是我們利用回歸模型所試圖預測的得分函數,並用深藍色陰影區域代表得分函數值的不確定性。
下面我們計算在超參數X每個位置的提高的期望。我們也建立了一個多點優化程序(next_sample),它通過使用提高期望的方法來選擇哪個點將在下一步中使用。
from scipy.optimize import minimizefrom scipy import stats
def expected_improvement(points, gp, samples, bigger_better=False): if bigger_better: best_sample = samples[np.argmax(samples)] mu, cov = gp.predict(samples, points) sigma = np.sqrt(cov.diagonal()) Z = (mu-best_sample)/sigma ei = ((mu-best_sample) * stats.norm.cdf(Z) + sigma*stats.norm.pdf(Z)) return -ei else: best_sample = samples[np.argmin(samples)] mu, cov = gp.predict(samples, points) sigma = np.sqrt(cov.diagonal()) Z = (best_sample-mu)/sigma ei = ((best_sample-mu) * stats.norm.cdf(Z) + sigma*stats.norm.pdf(Z)) return -eidef next_sample(gp, samples, bounds=(0,10), bigger_better=False): """Find point with largest expected improvement""" best_x = None best_ei = 0 for rand_x in np.random.uniform(bounds[0], bounds[1], size=30): res = minimize(expected_improvement, rand_x, bounds=[bounds], method='L-BFGS-B', args=(gp, samples, bigger_better)) if res.fun < best_ei: best_ei = res.fun best_x = res.x[0] return best_xfig, ax = basic_plot()ax.plot(x, 10*np.abs(expected_improvement(x, gp, yp)), label='expected improvement')ax.legend(loc='best')
<matplotlib.legend.Legend at 0x109de4490>
print "The algorithm suggests sampling at X=%.4f"%(next_sample(gp, yp))
The algorithm suggests sampling at X=1.5833
其中紅色的線代表期望的提高。通過對比藍色實心線和陰影區域,優化算法建議將期望提高最大的點X=1.58作為下一個點來計算我們的得分函數。
現在讓我們看看一些實際的案例。
以隨機格點搜索的效果作為基準為了讓這個簡單的示例更有趣,我們使用一個來自(Friedman1)的回歸問題以及一個決策樹回歸模型(它在這個數據集下面擬合大量分類器相當的快)。你可以在實際問題中用它來代替兩者。
為了評估在搜索超參數中誰比較快,我們將對bayesian optimisation和 隨機格點法進行對比。隨機格點搜索法已經比枚舉的格點法提升了很多。我通過一個來自Gilles Louppe’s的博士論文Understanding Random Forests: From Theory to Practice的特殊回歸問題來做實驗。
from sklearn.grid_search import GridSearchCVfrom sklearn.grid_search import RandomizedSearchCVfrom sklearn.datasets import make_friedman1from sklearn.tree import DecisionTreeRegressorfrom operator import itemgetterX, y = make_friedman1(n_samples=5000)clf = DecisionTreeRegressor()param_dist = {"min_samples_split": sp_randint(1, 101), }n_iterations = 8random_grid = RandomizedSearchCV(clf, param_distributions=param_dist, n_iter=n_iterations, scoring='mean_squared_error')random_grid = random_grid.fit(X, y)
from scipy.stats import semparams_ = []scores_ = []yerr_ = []for g in random_grid.grid_scores_: params_.append(g.parameters.values()[0]) scores_.append(g.mean_validation_score) yerr_.append(sem(g.cv_validation_scores))fig, ax = plt.subplots()ax.errorbar(params_, scores_, yerr=yerr_, fmt='ok', label='samples')ax.set_ylabel("score")ax.set_xlabel('min samples leaf')ax.legend(loc='best')
<matplotlib.legend.Legend at 0x10a52ebd0>
def top_parameters(random_grid_cv): top_score = sorted(random_grid_cv.grid_scores_, key=itemgetter(1), reverse=True)[0] print "Mean validation score: {0:.3f} +- {1:.3f}".format( top_score.mean_validation_score, np.std(top_score.cv_validation_scores)) print random_grid_cv.best_params_top_parameters(random_grid)
Mean validation score: -4.304 +- 0.161{'min_samples_split': 8}
最大的得分參數大概在8附近。現在讓我們來試試bayesian方法。
Bayesian optimisation你已經準備好你的先驗函數了麼?讓我開始用bayesian的方法來試一下!那麼問題就是我們是否可以找到和 min_smples_split至少一樣的值或者更好的一個或者減少計算步驟。
首先我們使用在擬合高斯過程模型中的三個點來評估模型。而下一個點我們將採用能讓期望增加最大的點。
下面兩個圖展現了Bayesian optimisation在使用之前三個點,並且依據提高期望的準則,又納入了5個點後的情形
from sklearn.cross_validation import cross_val_scoredef plot_optimisation(gp, x, params, scores, yerr): mu, cov = gp.predict(scores, x) std = np.sqrt(np.diag(cov)) fig, ax = plt.subplots() ax.plot(x, mu, label="GP median") ax.fill_between(x, mu-std, mu+std, alpha=0.5) ax_r = ax.twinx() ax_r.grid(False) ax_r.plot(x, np.abs(expected_improvement(x, gp, scores, bigger_better=True)), label='expected improvement', c=sns.color_palette()[2]) ax_r.set_ylabel("expected improvement") ax.errorbar(params, scores, yerr=yerr, fmt='ok', zorder=3, label='samples') ax.set_ylabel("score") ax.set_xlabel('min samples leaf') ax.legend(loc='best') return gpdef bayes_optimise(clf, X,y, parameter, n_iterations, bounds): x = range(bounds[0], bounds[1]+1) params = [] scores = [] yerr = [] for param in np.linspace(bounds[0], bounds[1], 3, dtype=int): clf.set_params(**{parameter: param}) cv_scores = cross_val_score(clf, X,y, scoring='mean_squared_error') params.append(param) scores.append(np.mean(cv_scores)) yerr.append(sem(cv_scores)) kernel = ExpSquaredKernel(1000) gp = george.GP(kernel, mean=np.mean(scores)) gp.compute(params, yerr) plot_optimisation(gp, x, params, scores, yerr) for n in range(n_iterations-3): gp.compute(params, yerr) param = next_sample(gp, scores, bounds=bounds, bigger_better=True) clf.set_params(**{parameter: param}) cv_scores = cross_val_score(clf, X,y, scoring='mean_squared_error') params.append(param) scores.append(np.mean(cv_scores)) yerr.append(sem(cv_scores)) plot_optimisation(gp, x, params, scores, yerr) return params, scores, yerr, clfparams, scores, yerr, clf = bayes_optimise(DecisionTreeRegressor(), X,y, 'min_samples_split', 8, (1,100))
print "Best parameter:"print params[np.argmax(scores)], 'scores', scores[np.argmax(scores)]
Best parameter:14.8626450189 scores -4.26649894389
你可以看到通過抽樣所得的點都非常接近最大值。當隨機格點搜索法抽樣到的點遠離最高點的區域時(40或者以上),baiyesian optimization方法所抽得的點集中於最大值附近(大約20左右)。這樣快速有效的找到最大值。有時候我們甚至可以提前停止計算後續的五個點,因為他們都十分接近彼此。
利器—MOE雖然它很容易為你自己建立一個小的bayesian optimisation過程,我依然建議你看看MOE。它是一個用於做全局,黑箱優化的一個非常優秀的產品,由Yelp的專業學者們開發,因此比我們自己做的方案更加強大。
結論bayesian optimisation並不可怕,通過兩個例子,我們可以相信通過使用像這樣的聰明方法可以獲得比格點搜索法更快的解決問題(即使在高維的情況下),但實際上並沒有任何魔法發生。
作者:betatim
翻譯:董安瀾