用於梯度提升的自定義損失函數

用於梯度提升的自定義損失函數

介紹

梯度提升技術在工業中得到廣泛應用,並贏得了許多Kaggle 比賽。互聯網已經對梯度提升有了很多很好的解釋,但我們注意到缺乏關於自定義損失函數的信息:why,when and where。這篇文章是我們嘗試總結自定義損失函數在許多實際問題中的重要性 - 以及如何使用LightGBM梯度提升包實現它們。

訓練機器學習算法以最小化訓練數據的損失函數。常見的機器學習(ML)庫中有許多常用的損失函數。在現實世界中,這些“現成的”損失函數通常無法很好地適應我們試圖解決的業務問題。

自定義損失函數

用於梯度提升的自定義損失函數

自定義損失函數方便的一個例子是機場準時的不對稱風險。問題是決定何時離開房子,以便在合適的時間到達機場。我們不想太早去機場,在機場等幾個小時。與此同時,我們不想錯過我們的航班。任何一方的損失都是非常不同的:如果我們提前到達機場,那真的不是那麼糟糕; 如果我們來得太晚而錯過航班,那真的很糟糕。如果我們使用機器學習來決定何時離開房子,我們可能希望直接在我們的模型中處理這種風險不對稱,通過使用自定義損失函數來懲罰 late errors而不是early errors。

另一個常見的例子是分類問題。例如,對於疾病檢測,我們可能認為假陰性比假陽性要差得多,因為給予健康人的藥物通常比未治療病人的危害小。在這種情況下,我們可能希望優化F-beta評分,其中β取決於我們想要給予誤報的權重。這有時被稱為Neyman-Pearson標準。

我們最近遇到了一個問題,需要一個自定義損失函數。我們的客戶Cortex Building Intelligence提供了一個應用程序,可以幫助工程師更精確地操作一棟建築的供暖、通風和空調系統。大部分商業樓宇有“租契業務”,在辦公時間內,例如在上午九時至下午六時的工作時間,把樓宇的室內溫度調至“舒適”的溫度範圍,例如在70至74度之間。同時,HVAC是建築最大的運營成本。高效的HVAC運行的關鍵是在不需要的時候關閉系統,比如在晚上,然後在清晨再次打開,以履行“租賃業務”。為此,建立了一個預測模型,以推薦在建築物中打開HVAC系統的確切時間。

然而,錯誤預測的懲罰是不對稱的。如果我們預測的啟動時間早於實際需要的啟動時間,那麼建築就會過早地達到舒適的溫度,一些能量就會被浪費掉。但如果預測的時間晚於實際需要的開始時間,那麼建築溫度就會過晚,租戶就會不高興——沒有人願意在冰冷/沸騰的建築裡工作、購物或學習。所以遲到比早退要糟糕得多,因為我們不希望租戶(支付$$的租金)不開心。我們通過創建一個自定義的非對稱Huber損失函數將業務知識編碼到我們的模型中,該函數在殘差為正或為負時具有更高的誤差

用於梯度提升的自定義損失函數

Ignore what recovery time error means

摘要:找到一個與您的業務目標緊密匹配的損失函數。通常,這些損失函數在流行的機器學習庫中沒有默認實現。定義自己的損失函數並用它來解決問題並不難。

定製訓練損失和驗證損失

在進一步討論之前,讓我們在定義中明確一點。機器學習(ML)文獻中使用了許多術語來指代不同的東西。我們將選擇一組我們認為最清晰的定義:

  • 訓練損失。這是在訓練數據上優化的函數。例如,在神經網絡二元分類器中,這通常是二進制交叉熵。對於隨機森林分類器,這是基尼雜質。訓練損失通常也稱為“目標函數”。
  • 驗證損失。這是我們用來評估我們訓練模型在看不見的數據上的性能的函數。這通常與訓練損失不同。例如,在分類器的情況下,這通常是受試者工作特徵曲線(ROC)下的面積 - 雖然這從來沒有直接優化,因為它是不可微的。這通常被稱為“性能或評估度量”。

在許多情況下,定製這些損失對於構建更好的模型非常有效。這對於梯度提升特別簡單,如下所示。

訓練損失

在訓練過程中,訓練損失得到優化。對於某些算法來說很難定製,比如隨機森林(見這裡),但是對於其他算法來說相對容易,比如梯度提升和神經網絡。由於梯度下降的某些變體通常是優化方法,訓練損失通常需要一個具有凸梯度(一階導數)和hessian (二階導數)的函數。最好是連續的,有限的,非零的。最後一個很重要,因為函數為0的部分可以凍結梯度下降。

在梯度提升的情況下,訓練損失是使用梯度下降優化的函數,例如,gradient boosting模型的“梯度”部分。具體地,訓練損失的梯度用於改變每個連續樹的目標變量。請注意,即使訓練損失定義了“梯度”,每個樹仍然使用貪婪分割算法生長,而不是綁定到這個自定義損失函數。

定義一個定製的訓練損失通常需要我們做一些微積分來找到梯度和hessian。正如我們接下來將看到的,首先更改驗證損失通常更容易,因為它不需要太多的開銷。

驗證損失

驗證損失用於調整超參數。它通常更容易定製,因為它沒有像訓練損失那樣多的要求。驗證損失可以是非凸的,不可微分的和不連續的。因此,從定製開始通常是一個更容易的地方。

例如,在LightGBM中,一個重要的超參數是boosting rounds數。驗證損失可用於找到最佳數量的boosting rounds。調用LightGBM中的驗證損失eval_metric。我們可以使用庫中可用的驗證損失之一,也可以定義我們自己的自定義函數。由於它非常簡單,因此您應該自定義是否對您的業務問題很重要。

具體來說,我們通常使用early_stopping_rounds變量,而不是直接優化num boosting rounds。當 early stopping rounds數量開始增加時,驗證損失就停止增加。實際上,它通過監視樣本驗證集上的驗證損失來防止過度擬合。如下圖所示,設置更高的stopping rounds 會導致模型運行更多的boosting rounds。

用於梯度提升的自定義損失函數

藍色:訓練損失。橙色:驗證損失 訓練和驗證都使用相同的自定義損失函數

用於梯度提升的自定義損失函數

k-fold交叉驗證。每個測試fold都有驗證損失

請記住,驗證策略也非常重要。上面列出的驗證/驗證是許多可能的驗證策略之一。它可能不適合您的問題。其他包括k-fold交叉驗證和嵌套交叉驗證,我們在HVAC啟動時建模問題中使用了這些驗證。

如果適用於業務問題,我們希望使用自定義函數來進行訓練和驗證損失。在某些情況下,由於自定義損失的函數形式,可能無法將其用作訓練損失。在這種情況下,僅更新驗證損失並使用像MSE這樣的默認訓練損失可能是有意義的。您仍然可以獲益,因為超級參數將使用所需的自定義損失進行調整。

在LightGBM中實現自定義損失函數

讓我們來看看它在實踐中的樣子,並對模擬數據進行一些實驗。首先,讓我們假設過高估計比低估更糟糕。另外,假設平方損失是我們在任一方向上的誤差的良好模型。為了對其進行編碼,我們定義了一個自定義MSE函數,它對正殘差的懲罰比負殘差多10倍。下圖說明了我們的自定義損失函數與標準MSE損失函數的對比情況。

用於梯度提升的自定義損失函數

正如定義的那樣,不對稱MSE很好,因為它很容易計算梯度和hessian,它們被繪製在下面。注意,hessian在兩個不同的值上是常量,左邊是2,右邊是20,儘管在下面的圖中很難看到這一點。

用於梯度提升的自定義損失函數

LightGBM提供了一種直接的方式來實現自定義訓練和驗證損失。其他梯度提升包,包括XGBoost和Catboost,也提供此選項。但在較高的層次上,實現略有不同:

  • 訓練損失:在LightGBM中自定義訓練損失需要定義一個函數,該函數包含兩個數組,即目標及其預測。反過來,該函數應該返回梯度的兩個數組和每個觀測值的hessian數組。如上所述,我們需要使用微積分來派生gradient和hessian,然後在Python中實現它。
  • 驗證損失:自定義LightGBM中的驗證損失需要定義一個函數,該函數接受相同的兩個數組,但返回三個值:一個字符串,其名稱為要打印的度量,損失本身,以及關於whether higher is better的布爾值。

用於在LightGBM中實現自定義損失的Python代碼

定義自定義驗證和培訓損失函數,Python代碼如下:

def custom_asymmetric_train(y_true, y_pred):
residual = (y_true - y_pred).astype("float")
grad = np.where(residual<0, -2*10.0*residual, -2*residual)
hess = np.where(residual<0, 2*10.0, 2.0)
return grad, hess

def custom_asymmetric_valid(y_true, y_pred):
residual = (y_true - y_pred).astype("float")
loss = np.where(residual < 0, (residual**2)*10.0, residual**2)
return "custom_asymmetric_eval", np.mean(loss), False
用於梯度提升的自定義損失函數

在LightGBM中結合訓練和驗證損失(包括Python和scikit-learn API示例)
import lightgbm

********* Sklearn API **********
# default lightgbm model with sklearn api
gbm = lightgbm.LGBMRegressor()

# updating objective function to custom
# default is "regression"
# also adding metrics to check different scores
gbm.set_params(**{'objective': custom_asymmetric_train}, metrics = ["mse", 'mae'])

# fitting model
gbm.fit(
X_train,
y_train,
eval_set=[(X_valid, y_valid)],
eval_metric=custom_asymmetric_valid,
verbose=False,
)

y_pred = gbm.predict(X_valid)

********* Python API **********
# create dataset for lightgbm
# if you want to re-use data, remember to set free_raw_data=False
lgb_train = lgb.Dataset(X_train, y_train, free_raw_data=False)
lgb_eval = lgb.Dataset(X_valid, y_valid, reference=lgb_train, free_raw_data=False)

# specify your configurations as a dict
params = {
'objective': 'regression',
'verbose': 0
}

gbm = lgb.train(params,
lgb_train,
num_boost_round=10,
init_model=gbm,
fobj=custom_asymmetric_train,
feval=custom_asymmetric_valid,
valid_sets=lgb_eval)

y_pred = gbm.predict(X_valid)
用於梯度提升的自定義損失函數

自定義損失函數的實驗

Jupyter notebook(https://github.com/manifoldai/mf-eng-public/blob/master/notebooks/custom_loss_lightgbm.ipynb)還對默認隨機森林,默認LightGBM和MSE以及LightGBM與自定義培訓和驗證損失函數進行了深入比較。我們使用Friedman 1合成數據集,進行了8,000次訓練觀察,2,000次驗證觀察和5,000次測試觀察。驗證集用於查找優化驗證損失的最佳超參數集。下面報告的分數在測試觀察結果上進行評估,以評估我們模型的普遍性。

下表中總結的一系列實驗。請注意,我們關心的最重要的分數是非對稱MSE,因為它明確定義了我們的不對稱懲罰問題。

用於梯度提升的自定義損失函數

實驗和結果

讓我們詳細看一些比較。

隨機森林→LightGBM

使用默認設置,LightGBM在此數據集上的性能優於Random Forest。隨著更多樹和超參數的更好組合,隨機森林也可能會給出好的結果,但這不是重點。

LightGBM→LightGBM,具有定製的訓練損失

這表明我們可以使我們的模型優化我們關心的內容。默認的LightGBM正在優化MSE,因此它可以降低MSE損失(0.24對0.33)。具有自定義訓練損失的LightGBM優化了非對稱MSE,因此對於非對稱MSE(1.31 vs. 0.81)表現更好。

LightGBM → LightGBM with tuned early stopping rounds using MSE

兩種LightGBM模型都在優化MSE。我們看到默認的MSE分數有了很大改善,只需稍微調整一下使用early stopping rounds(MSE:0.24 vs 0.14)。因此,我們應該讓模型使用early stopping超參數來確定最佳提升次數,而不是將提升次數限制為默認值(即 100)。超參數優化很重要!

LightGBM with tuned early stopping rounds using MSE → LightGBM with tuned early stopping using custom MSE

這兩個模型的得分非常接近,沒有任何實質性差異。這是因為驗證損失僅用於決定何時停止提升。梯度是在兩種情況下優化默認MSE。每個後續樹為兩個模型生成相同的輸出。唯一的區別是具有自定義驗證損失的模型在742次增強迭代時停止,而另一次運行多次。

LightGBM with tuned early stopping using custom MSE → LightGBM trained on custom loss and tuned early stopping with MSE

僅在不改變驗證損失的情況下定製訓練損失會損害模型性能。只有自定義訓練損失的模型比其他情況增加了更多輪次(1848)。如果我們仔細觀察,這個模型的訓練損失非常低(0.013)並且在訓練集上非常過度擬合。每個梯度提升迭代使用訓練誤差作為目標變量來創建新樹,但僅當驗證數據的損失開始增加時,提升停止。當模型開始過度擬合時,驗證損失通常開始增加,這是停止構建更多樹木的信號。在這種情況下,由於驗證和訓練損失彼此不一致,因此模型似乎沒有“得到消息”而導致過度擬合。

LightGBM with tuned early stopping rounds with MSE → LightGBM trained on custom training loss and tuned early stopping rounds with customized validation loss

最終模型使用自定義訓練和驗證損失。它通過相對較少的boosting迭代次數給出最佳的非對稱MSE分數。損失與我們關心的一致!

讓我們仔細看看殘差直方圖以獲得更多細節。

用於梯度提升的自定義損失函數

不同模型預測殘差的直方圖

請注意,使用LightGBM(即使使用默認的超參數),與隨機森林模型相比,預測性能也有所提高。具有自定義驗證損失的最終模型似乎在直方圖的右側進行更多預測,即實際值大於預測值。這是由於非對稱自定義損失函數的緣故。使用殘差的核密度圖可以更好地顯示殘差的right sided shift。

用於梯度提升的自定義損失函數

LightGBM模型的預測與對稱和非對稱評估的比較

結論

所有的機器模型都有誤差,但是許多業務問題並不平等地對待低估和高估。有時,我們有意地希望我們的模型將誤差偏向某個方向,這取決於哪些誤差代價更高。因此,我們不應該侷限於普通及其學學習(ML)庫中的“現成的”對稱損失函數。

LightGBM提供了一個簡單的界面,可以包含自定義訓練和驗證損失函數。在適當的時候,我們應該利用這個函數來做出更好的預測。同時,您不應立即跳轉到使用自定義損失函數。採用lean,迭代的方法總是最好的,首先從簡單的基線模型開始,如隨機森林。在下一次迭代中,您可以用更復雜的模型,如LightGBM,並進行超參數優化。只有在這些基線穩定後,才有必要進行定製驗證和訓練損失。


分享到:


相關文章: