當前位置:
首頁 > 知識 > Python 環境下的自動化機器學習超參數調優

Python 環境下的自動化機器學習超參數調優

選自Medium

作者William Koehrsen

機器之心編譯

機器學習演算法的性能高度依賴於超參數的選擇,對機器學習超參數進行調優是一項繁瑣但卻至關重要的任務。本文介紹了一個使用「Hyperopt」庫對梯度提升機(GBM)進行貝葉斯超參數調優的完整示例,並著重介紹了其實現過程。

由於機器學習演算法的性能高度依賴於超參數的選擇,對機器學習超參數進行調優是一項繁瑣但至關重要的任務。手動調優佔用了機器學習演算法流程中一些關鍵步驟(如特徵工程和結果解釋)的時間。網格搜索和隨機搜索則不會幹涉這些步驟,但是需要大量的運行時間,因為它們浪費了時間去評估搜索空間中並不太可能找到最優點的區域。如今越來越多的超參數調優過程都是通過自動化的方法完成的,它們旨在使用帶有策略的啟發式搜索(informed search)在更短的時間內找到最優超參數,除了初始設置之外,並不需要額外的手動操作。

貝葉斯優化是一種基於模型的用於尋找函數最小值的方法。近段時間以來,貝葉斯優化開始被用於機器學習超參數調優,結果表明,該方法在測試集上的表現更加優異,但需要的迭代次數小於隨機搜索。此外,現在一些 Python 庫的出現使得對任意的機器學習模型實現貝葉斯超參數調優變得更加簡單。

本文將介紹一個使用「Hyperopt」庫對梯度提升機(GBM)進行貝葉斯超參數調優的完整示例。在本文作者早先的一篇文章中,他已經對這個方法背後的概念進行了概述,所以本文將著重介紹實現過程。和大多數機器學習的主題類似,讀者並不需要理解所有的細節,但是了解基本的原理可以幫助讀者更有效地使用這項技術!

本文所有的代碼都可以以 Jupyter Notebook 的形式在 GitHub 上獲取(https://github.com/WillKoehrsen/hyperparameter-optimization)。

本文目錄:

貝葉斯優化方法

優化問題的四個組成部分

目標函數

域空間

優化演算法

結果的歷史數據

優化

搜索結果

搜索結果的可視化

搜索的演化過程

繼續搜索

結語

貝葉斯優化方法

簡單地說,貝葉斯優化通過基於過去對目標的評估結果建立一個代理函數(概率模型)找到使得目標函數最小的值。代理函數比目標函數更易於優化,因此下一個待評估的輸入值是通過對代理函數應用某種標準(通常為預期提升)來選擇的。貝葉斯方法不同於隨機搜索或網格搜索,後兩者都使用了過去的評估結果來選擇接下來待評估的值。它們的思想是:通過根據過去表現良好的值選擇下一個輸入值來限制評價目標函數的高昂開銷。

對於超參數優化來說,其目標函數為使用一組超參數的機器學習模型的驗證誤差。它的目標是找出在驗證集上產生最小誤差的超參數,並希望將這些結果泛化到測試集上去。對目標函數評估的開銷是巨大的,因為它需要訓練帶有一組特定超參數的機器學習模型。理想情況下,我們希望找到這樣一方法,它既能探索搜索空間,又能限制耗時的超參數評估。貝葉斯超參數調優使用一個不斷更新的概率模型,通過從過去的結果中進行推理,使搜索過程「專註」於有可能達到最優的超參數。

Python 環境下有一些貝葉斯優化程序庫,它們目標函數的代理演算法有所區別。在本文中,我們將使用「Hyperopt」庫,它使用樹形 Parzen 評估器(TPE,https://papers.nips.cc/paper/4443-algorithms-for-hyper-parameter-optimization.pdf)作為搜索演算法,其他的 Python 庫還包含「Spearmint」(高斯過程代理)和「SMAC」(隨即森林回歸)。目前在這個領域有大量有趣的研究,所以如果你對某一個庫不是很滿意,你可以試試其他的選項!針對某個問題的通用結構(本文將使用的結構)可以在各個庫間進行轉換,其句法差異非常小。

優化問題的四個組成部分

貝葉斯優化問題有四個組成部分:

1. 目標函數:我們想要最小化的對象,這裡指帶超參數的機器學習模型的驗證誤差

2. 域空間:待搜索的超參數值

3. 優化演算法:構造代理模型和選擇接下來要評估的超參數值的方法

4. 結果的歷史數據:存儲下來的目標函數評估結果,包含超參數和驗證損失

通過以上四個步驟,我們可以對任意實值函數進行優化(找到最小值)。這是一個強大的抽象過程,除了機器學習超參數的調優,它還能幫我們解決其他許多問題。

數據集

在本文的示例中,我們將使用 Caravan Insurance 數據集(https://www.kaggle.com/uciml/caravan-insurance-challenge),它的目標是預測客戶是否會購買一份保險產品。這是一個監督分類問題,帶有 5800 個用於訓練的觀測值和 4000 個測試點。由於這是一個不平衡的分類問題,本文使用的評價性能的指標是受試者工作特徵曲線下的面積(ROC AUC),ROC AUC 的值越高越好,其值為 1 代表模型是完美的。數據集如下所示:

數據集(CARAVAN)是帶標籤的

由於 Hyperopt 要做的是取最小值,我們將從目標函數中返回「1-ROC AUC」,從而提高 ROC AUC。

梯度提升模型

在本文中,我們對梯度提升機(GBM)的細節知識不做過多探討,下面是我們需要理解的基本知識:GBM 是一種基於使用依次訓練的弱學習器(多為決策樹)構建強學習器的集成增強方法。GBM 中有許多超參數,它們控制著整個集成結構和單棵決策樹。我們在這裡使用的一種最有效的選擇決策樹數量的方法(稱為評估器)是早停止(early stopping)。LightGBM 提供了一種 Python 環境下的快速簡單的 GBM 實現。

想了解更多 GBM 的細節,這裡有一篇高屋建瓴的文章:https://medium.com/mlreview/gradient-boosting-from-scratch-1e317ae4587d

一篇技術論文:https://brage.bibsys.no/xmlui/bitstream/handle/11250/2433761/16128_FULLTEXT.pdf

拋開必要的背景知識不談,讓我們將用於超參數調優的貝葉斯優化問題的四個組成部分一一列舉出來。

目標函數

我們試圖最小化目標函數。其輸入為一組值——在本例中為 GBM 的超參數,輸出為需要最小化的實值——交叉驗證損失。Hyperopt 將目標函數作為黑盒處理,因為這個庫只關心輸入和輸出是什麼。為了找到使損失最小的輸入值,該演算法不需要知道目標函數的內部細節!從一個高度抽象的層次上說(以偽代碼的形式),我們的目標函數可以表示為:

在對最終的模型進行評價時,我們需要注意的是,不要使用測試集上的損失,因為我們只能使用一次測試集。相對地,我們對驗證集上的超參數進行評估。此外,我們使用 K 折交叉驗證而不是將數據劃分到一個獨立的驗證集中,這種驗證方法除了保留了有價值的訓練數據外,還能讓我們在測試集上獲得偏差更小的誤差估計。

不同模型超參數調優的目標函數的基本結構是相同的:函數接收超參數作為輸入,並返回使用這些超參數的交叉驗證誤差。儘管本文的示例是針對 GBM 的,但該結構同樣可以被應用於其他的方法。

下圖為 GBM 的完整目標函數,該 GBM 使用帶早停止機制的 10 折交叉驗證:

核心的代碼為「cv_results = lgb.cv(...)」。為了實現帶早停止的交叉驗證,我們使用了 LightGBM 的函數「cv」,向該函數傳入的參數包含超參數、一個訓練集、交叉驗證中使用的許多折,以及一些其它的參數。我們將評估器的數量(num_boost_round)設置為 10000,但是由於我們使用了「early_stopping_rounds」,當 100 個評估器的驗證得分沒有提高時訓練會被停止,所以實際上使用的評估器不會達到這個數量。早停止是一種有效的選擇評估器數量的方法,而不是將其設置為另一個需要調優的超參數!

當交叉驗證完成後,我們將得到最高得分(ROC AUC)。之後,由於我們想要得到的是最小值,我們將採用「1-最高得分」。該值將在返回的字典數據結構中作為「loss」關鍵字返回。

這個目標函數實際上比它所需的結構複雜一些,因為我們將返回一個值的字典。對於 Hyperopt 中的目標函數,我們可以返回一個單一的值(即損失),或者返回一個帶有最小值的關鍵字「loss」和「status」的字典。返回超參數的值使我們能夠查看每組超參數得到的損失。

域空間

域空間表示我們想要對每個超參數進行評估的值的範圍。在每一輪搜索迭代中,貝葉斯優化演算法將從域空間中為每個超參數選定一個值。當我們進行隨機搜索或網格搜索時,域空間就是一個網格。貝葉斯優化中也是如此,只是這個域空間對每個超參數來說是一個概率分布而不是離散的值。

然而,在貝葉斯優化問題中,確定域空間是最難的部分。如果有機器學習方法的相關經驗,我們可以將更大的概率賦予我們認為最佳值可能存在的點,以此來啟發對超參數分布的選擇。但是,不同的數據集之間的最佳模型設定是不同的,並且具有高維度的問題(大量的超參數),這會使我們很難弄清超參數之間的相互作用。在不確定最佳值的情況下,我們可以使用更大範圍的概率分布,通過貝葉斯演算法進行推理。

首先,我們應該了解一個 GBM 中所有的超參數:

我不確定世界上是否真有人知道所有的這些超參數是如何相互作用的!而其中有一些超參數是不需要調優(如「objective」和「random_state」)。我們將使用早停止方法找到最佳的評估器數量「n_estimators」。儘管如此,我們仍然需要優化 10 個超參數!當我們第一次對一個模型進行調優時,我通常創建一個以預設值為中心的大範圍域空間,然後在接下來的搜索中對其進行優化。

舉個例子,我們不妨在 Hyperopt 中定義一個簡單的域——一個離散均勻分布,其中離散點的數量為 GBM 中每棵決策樹的葉子結點數:

這裡使用的是一個離散均勻分布,因為葉子結點的數量必須是一個整數(離散的)並且域中的每個值出現的概率是均等的(均勻)。

概率分布的另一種選項是對數均勻分布,在對數尺度上其值的分布是均勻的。我們將對學習率使用一個對數均勻分布(域空間從 0.005 到 0.2),因為它的值往往在不同的數量級之間變化:

由於這是一個對數均勻分布,所以我們在 exp(low)和 exp(high)之間繪製其值的示意圖。下面左側的示意圖顯示了離散均勻分布,右側的示意圖則顯示了對數均勻分布。它們是核密度估計示意圖,所以 y 軸坐標為密度而不是計數。

現在讓我們定義整個域:

此處我們使用許多不同種類的域分布:

choice:類別變數

quniform:離散均勻分布(在整數空間上均勻分布)

uniform:連續均勻分布(在浮點數空間上均勻分布)

loguniform:連續對數均勻分布(在浮點數空間中的對數尺度上均勻分布)

當定義提升(boosting)的類型時,有一個要點需要我們注意:

在這裡,我們使用一個條件域,它意味著一個超參數的值依賴於另一個超參數的值。對於「goss」類型的提升演算法,GBM 不能使用下採樣技術(選擇一個訓練觀測數據的子樣本部分用於每輪迭代)。因此,如果提升的類型為「goss」,則下採樣率設置為 1.0(不使用下採樣),否則將其設置為 0.5-1.0。這個過程是使用嵌套域實現的。

當我們使用參數完全不同的機器學習模型時,條件嵌套往往是很有用的。條件嵌套讓我們能根據「choice」的不同值使用不同的超參數集。

現在已經定義了域空間,我們可以從中提取一個樣本來查看典型樣本的形式。當我們進行採樣時,因為子樣本最初是嵌套的,所以我們需要將它分配給頂層的關鍵字。這個操作是通過 Python 字典的「get」方法實現的,預設值為 1.0。

重新分配嵌套的關鍵字是必要的,因為梯度提升機不能處理嵌套的超參數字典。

優化演算法

儘管從概念上來說,這是貝葉斯優化最難的一部分,但在 Hyperopt 中創建優化演算法只需一行代碼。使用樹形 Parzen 評估器(Tree Parzen Estimation,以下簡稱 TPE)的代碼如下:

這就是優化演算法的所有代碼!Hyperopt 目前只支持 TPE 和隨機搜索,儘管其 GitHub 主頁聲稱將會開發其它方法。在優化過程中,TPE 演算法從過去的搜索結果中構建出概率模型,並通過最大化預期提升(EI)來決定下一組目標函數中待評估的超參數。

結果歷史數據

跟蹤這些結果並不是絕對必要的,因為 Hyperopt 會在內部為演算法執行此操作。然而,如果我們想要知道這背後發生了什麼,我們可以使用「Trials」對象,它將存儲基本的訓練信息,還可以使用目標函數返回的字典(包含損失「loss」和參數「params」)。創建一個「Trials」對象也僅需一行代碼:

另一個讓我們能夠監控長期訓練進度的做法是,在每輪迭代中向 csv 文件寫入一行。這樣做將所有的搜索結果存儲到了磁碟上,以防意外情況發生使得我們丟失「Trails」對象(根據經驗來說)。我們可以使用「csv」庫做到這一點。在開始訓練之前,我們打開一個新的 csv 文件並且寫入文件頭(hearder):

然後在目標函數中,我們可以添加幾行代碼,在每輪迭代中寫入 csv 文件(完整的目標函數可以在 notebook 中獲取。

寫入 csv 文件意味著我們可以在訓練時通過打開文件來檢查進度(不是在 Excel 文件中,因為 Excel 會在 Python 環境下導致錯誤。在 bash 中使用「tail out_file.csv」操作來查看文件的最後一行。)

優化

當我們完成了上述四個部分的工作,我們可以通過「fmin」進行優化:

在每一輪迭代中,優化演算法從基於先前結果構造的代理函數中選擇新的超參數值,並在目標函數中對這些值進行計算。接著,對目標函數進行「MAX_EVALS」評估,代理函數會不斷根據新的結果進行更新。

結果

從「fmin」返回的最佳對象包含在目標函數上產生最小損失的超參數:

擁有這些超參數之後,我們可以使用它們在完整的訓練數據上訓練模型,然後對測試數據進行評估(記住我們只能在評估最終的模型時使用一次測試集)。對於評估器的數量,我們可以使用在交叉驗證中提前停止時返回最低損失的評估器數量。最終結果如下:

作為參考,500 輪隨機搜索返回一個在測試集上 ROC AUC 得分為 0.7232、在交叉驗證中得分為 0.76850 的模型。一個沒有經過優化的預設模型在測試集上的 ROC AUC 得分則為 0.7143.

當我們查看結果時,需要將以下幾點重要事項牢記於心:

最優的超參數在交叉驗證中表現最好,但並不一定在測試數據上表現最好。當我們使用交叉驗證時,我們希望這些結果能夠泛化至測試數據上。

即使使用 10 折交叉驗證,超參數調優還是會對訓練數據過度擬合。交叉驗證取得的最佳得分遠遠高於在測試數據上的得分。

隨機搜索可能由於運氣好而返回更好的超參數(重新運行 notebook 就可能改變搜索結果)。貝葉斯優化不能保證找到更好的超參數,並且可能陷入目標函數的局部最小值。

貝葉斯優化雖然十分有效,但它並不能解決我們所有的調優問題。隨著搜索的進行,該演算法將從探索——嘗試新的超參數值,轉向開發——利用使目標函數損失最低的 超參數值。如果演算法找到了目標函數的一個局部最小值,它可能會專註於搜索局部最小值附近的超參數值,而不會嘗試域空間中相對於局部最小值較遠的其他值。隨機搜索則不會受到這個問題的影響,因為它不會專註於搜索任何值!

另一個重點是,超參數優化的效果將隨著數據集的變化而有所差異。本文使用的是一個相對較小的數據集(大約 6000 條訓練觀測數據),因此對超參數進行調優的回報較小(獲取更多的數據將更好地利用時間!)考慮到所有這些注意事項,在這種情況下,通過貝葉斯優化我們可以得到:

在測試集上更好的性能

更少的超參數調優迭代次數

貝葉斯方法可以(儘管不是經常)獲得比隨機搜索更好的調優結果。在接下來的幾節中,我們將查看貝葉斯超參數搜索的演化過程,並且將其與隨機搜索進行對比,從而理解貝葉斯優化的工作原理。

搜索結果可視化

將結果通過圖表繪製出來可以直觀地理解在超參數搜索過程中發生了什麼。此外,將貝葉斯優化和隨機搜索進行對比有助於我們看到這些方法之間的差異。如果你想知道這些圖是如何繪製的,以及隨機搜索是如何實現的,請查閱項目 notebook。但是在這裡我們將直接顯示結果。(請注意,實際的結果會隨著迭代次數增加而發生變化,所以如果你運行了這個 notebook,得到了不同的圖也無需驚訝。本節所有的圖像都是經過了 500 輪迭代所得到的)。

首先我們可以繪製隨機搜索和貝葉斯優化中採樣得到的學習率「learning_rate」的核密度估計圖。作為參考,我們還可以顯示採樣的分布。垂直的虛線表示學習率最優值(根據交叉驗證得到)。

我們將學習率定義為 0.005 到 0.2 之間的對數正態分布形式,貝葉斯優化的結果與採樣分布的結果看起來相類似。這說明,我們定義的分布看上去很適合這個任務,儘管最優值比我們放置最大概率的位置略高。這個結果可以用於通知域空間進一步搜索。

另一個超參數是提升類型,下圖為在隨機搜索和貝葉斯優化的過程中對每種類型進行評估的直方圖。由於隨機搜索不關心過去的搜索結果,我們預計每種提升類型的使用次數大致相同。

根據貝葉斯演算法的評估結果直方圖,「gbdt」提升比「dart」或「goss」更有可能找到最優值。同樣地,這有助於為進一步搜索提供信息,無論是貝葉斯方法或網格搜索。如果我們想做一個更精確的網格搜索,我們可以用這些結果來定義一個更小的網格,集中在最有可能找到最優超參數的值周圍。

既然我們已經有了這些結果,我們可以看看所有參考分布、隨機搜索、以及貝葉斯優化中數值化的超參數。垂直的虛線表示每次搜索是超參數的最優值:

在大多數情況下(「subsample_for_bin」除外),貝葉斯優化搜索傾向於專註搜索(設置更大的概率)能夠在交叉驗證中得到最小損失的超參數值附近的值。這體現了使用貝葉斯方法進行超參數調優的基本思想:將更多的時間用於評估更有可能達到最優的超參數值。

有些結果很有趣,可能會幫助我們以後定義一個用於搜索的域空間。舉例來說,「reg_alpha」和「reg_lambda」看起來是互補的:如果其中的一個值很高(接近於 1.0),另一個值則會變得較低。不能保證這一點在不同的問題中都適用,但是通過研究這些結果,我們可以得到一些可能在未來的機器學習問題中能夠用到的觀點!

搜索的演化過程

隨著優化的推進,我們期望貝葉斯方法能夠專註於更有希望達到最優超參數的值:那些在交叉驗證中產生最低誤差的值。我們可以繪製超參數域迭代次數的關係圖,看看是否存在明顯的趨勢。

黑色的星星代表最優值。「colsample_bytree」和「learning_rate」隨著時間的推移而下降,這可以在未來的搜索中為我們提供指導。

最後,如果貝葉斯優化有效,我們預計平均的驗證分數會隨著時間推移越來越高(相反,損失將會越來越小):

隨著時間的推移,貝葉斯超參數優化的驗證得分會越來越高,這說明該方法正在嘗試「更好」的超參數值(值得注意的是,只是根據驗證分數說明這些值是更好的)。隨機搜索並不會隨著迭代次數增加顯示出性能提升。

繼續搜索

如果我們對模型的性能不太滿意,可以使用 Hyperopt 從我們上次結束的地方繼續搜索。我們只需要傳入相同的「Trials」對象,演算法就會繼續進行搜索。

隨著演算法的運行,它會進行更多的「利用」操作——選用在過去表現得很好的值,進行更少的「探索」操作——選用新的值。因此,與其從上次結束的地方開始,還不如從頭開始搜索。如果第一次搜索到的最佳超參數確實是「最優值」,我們預計接下來的搜索會收斂到同樣的值上。考慮到這個高維度問題,以及超參數之間複雜的相互作用,另一次搜索並不太可能產生一組類似的超參數。

再次進行了 500 輪迭代的訓練後,最終模型在測試集上的 ROC AUC 得分為 0.72736。(我們真的不應該在測試集上對第一個模型進行評估,並且僅僅以驗證得分作為依據。理想情況下,測試集只應使用一次,在將演算法部署在新數據上時測試其性能)。同樣的,由於數據集規模較小,這個問題可能會在未來的超參數優化中得到逐漸減小的返回值,並且最終會在驗證誤差上達到一個趨近於穩定不變的值(數據集上任何模型的性能都有一個固有的限制,因為隱藏的變數沒有被測量,並且有雜訊數據,這被稱為貝葉斯誤差)。

結語

我們可以使用貝葉斯優化完成機器學習模型的自動超參數調優。不同於隨機搜索方法,貝葉斯優化通過啟發式方法選擇下一個超參數,從而能夠花更多時間評估可能達到最優的值。最終的結果可能是,與隨機搜索或網格搜索相比,貝葉斯優化對於目標函數評估的次數更少,並且在測試集上泛化的能力更強。

在本文中,我們使用 Hyperopt 一步一步地實現了 Python 環境下的貝葉斯超參數優化。儘管對於訓練數據的過擬合問題需要多加小心,但我們提升了梯度提升機在測試集上的性能,超過了對比基線和隨機搜索方法。此外,我們還通過查看結果示意圖看到了隨機搜索和貝葉斯優化的區別,該圖說明了貝葉斯方法會將更大的概率賦予在交叉驗證中獲得更低損失的超參數值。

通過利用最優化問題的四個組成部分,我們可以使用 Hyperopt 解決各種各樣的問題。貝葉斯優化的基本組成部分也適用於大量的實現其他演算法的 Python 庫。從手動調優到隨機搜索或網格搜索只是一個小的進步,但如果想要將你的機器學習技術提升到一個新的水平,則需要自動化的超參數調優。貝葉斯優化是一種能夠在 Python 環境下使用的方法,並且能返回比隨機搜索更好的結果。希望你能滿懷信心地開始將這種強大的技術應用到自己的機器學習問題中!

原文地址:https://towardsdatascience.com/automated-machine-learning-hyperparameter-tuning-in-python-dfda59b72f8a

本文為機器之心編譯,轉載請聯繫本公眾號獲得授權。

------------------------------------------------


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 機器之心 的精彩文章:

Waymo自動駕駛車每天行駛25000英里,但CEO卻告訴我們距離成功還遠
深度神經網路為什麼不易過擬合?傅里葉分析發現固有頻譜偏差

TAG:機器之心 |