概述
XGBoost被設計為一個可擴展的庫。通過提供自定義的訓練目標函數和相應的性能監控指標,可以擴展它。本文介紹了如何為XGBoost實現自定義的逐元評估指標和目標。
注意:
排序不能自定義
在接下來的兩個部分中,將逐步介紹如何實現平方對數誤差(Squared Log Error,SLE
)目標函數:
1 2 [ log ? ( p r e d + 1 ) ? log ? ( l a b e l + 1 ) ] 2 \frac{1}{2}[\log(pred + 1) - \log(label + 1)]^2 21?[log(pred+1)?log(label+1)]2
以及它的默認評估指標均方根對數誤差(Root Mean Squared Log Error,RMSLE
):
1 N [ log ? ( p r e d + 1 ) ? log ? ( l a b e l + 1 ) ] 2 \sqrt{\frac{1}{N}[\log(pred + 1) - \log(label + 1)]^2} N1?[log(pred+1)?log(label+1)]2?
定制目標函數
盡管XGBoost本身已經原生支持這些功能,但為了演示的目的,使用它來比較自己實現的結果和XGBoost內部實現的結果。完成本教程后,應該能夠為快速實驗提供自己的函數。最后,將提供一些關于非恒等鏈接函數的注釋,以及在scikit-learn接口中使用自定義度量和目標的示例。
如果計算所述目標函數的梯度:
g = ? o b j e c t i v e ? p r e d = log ? ( p r e d + 1 ) ? log ? ( l a b e l + 1 ) p r e d + 1 g = \frac{\partial{objective}}{\partial{pred}} = \frac{\log(pred + 1) - \log(label + 1)}{pred + 1} g=?pred?objective?=pred+1log(pred+1)?log(label+1)?
以及 hessian(目標的二階導數):
h = ? 2 o b j e c t i v e ? p r e d = ? log ? ( p r e d + 1 ) + log ? ( l a b e l + 1 ) + 1 ( p r e d + 1 ) 2 h = \frac{\partial^2{objective}}{\partial{pred}} = \frac{ - \log(pred + 1) + \log(label + 1) + 1}{(pred + 1)^2} h=?pred?2objective?=(pred+1)2?log(pred+1)+log(label+1)+1?
在模型訓練過程中,目標函數起著重要的作用:基于模型預測和觀察到的數據標簽(或目標),提供梯度信息,包括一階和二階梯度。因此,有效的目標函數應接受兩個輸入,即預測值和標簽。對于實現SLE
,定義:
import numpy as np
import xgboost as xgb
from typing import Tupledef gradient(predt: np.ndarray, dtrain: xgb.DMatrix) -> np.ndarray:'''Compute the gradient squared log error.'''y = dtrain.get_label()return (np.log1p(predt)-np.log1p(y)) / (predt+1)def hessian(predt: np.ndarray, dtrain: xgb.DMatrix) -> np.ndarray:'''Compute the hessian for squared log error.'''y = dtrain.get_label()return ((-np.log1p(predt)+np.log1p(y)+1) /np.power(predt+1, 2))def squared_log(predt: np.ndarray,dtrain: xgb.DMatrix) -> Tuple[np.ndarray, np.ndarray]:'''Squared Log Error objective. A simplified version for RMSLE used asobjective function.'''predt[predt < -1] = -1 + 1e-6grad = gradient(predt, dtrain)hess = hessian(predt, dtrain)return grad, hess
在上面的代碼片段中,squared_log
是想要的目標函數。它接受一個numpy數組predt
作為模型預測值,以及用于獲取所需信息的訓練DMatrix,包括標簽和權重(此處未使用)。然后,在訓練過程中,通過將其作為參數傳遞給xgb.train
,將此目標函數用作XGBoost的回調函數:
xgb.train({'tree_method': 'hist', 'seed': 1994}, # any other tree method is fine.dtrain=dtrain,num_boost_round=10,obj=squared_log)
注意,在定義目標函數時,從預測值中減去標簽或從標簽中減去預測值,這是很重要的。如果發現訓練錯誤上升而不是下降,這可能是原因。
定制度量函數
因此,在擁有自定義目標函數之后,還需要一個相應的度量標準來監控模型的性能。如上所述,SLE
的默認度量標準是 RMSLE
。同樣,定義另一個類似的回調函數作為新的度量標準:
def rmsle(predt: np.ndarray, dtrain: xgb.DMatrix) -> Tuple[str, float]:''' Root mean squared log error metric.'''y = dtrain.get_label()predt[predt < -1] = -1 + 1e-6elements = np.power(np.log1p(y) - np.log1p(predt), 2)return 'PyRMSLE', float(np.sqrt(np.sum(elements) / len(y)))
與目標函數類似,度量也接受 predt
和 dtrain
作為輸入,但返回度量本身的名稱和一個浮點值作為結果。將其作為 custom_metric
參數傳遞給 XGBoost:
xgb.train({'tree_method': 'hist', 'seed': 1994,'disable_default_eval_metric': 1},dtrain=dtrain,num_boost_round=10,obj=squared_log,custom_metric=rmsle,evals=[(dtrain, 'dtrain'), (dtest, 'dtest')],evals_result=results)
能夠看到 XGBoost 打印如下內容:
[0] dtrain-PyRMSLE:1.37153 dtest-PyRMSLE:1.31487
[1] dtrain-PyRMSLE:1.26619 dtest-PyRMSLE:1.20899
[2] dtrain-PyRMSLE:1.17508 dtest-PyRMSLE:1.11629
[3] dtrain-PyRMSLE:1.09836 dtest-PyRMSLE:1.03871
[4] dtrain-PyRMSLE:1.03557 dtest-PyRMSLE:0.977186
[5] dtrain-PyRMSLE:0.985783 dtest-PyRMSLE:0.93057
...
注意,參數 disable_default_eval_metric
用于禁用 XGBoost 中的默認度量。
完整可復制的源代碼參閱定義自定義回歸目標和度量的演示。
轉換鏈接函數
在使用內置目標函數時,原始預測值會根據目標函數進行轉換。當提供自定義目標函數時,XGBoost 不知道其鏈接函數,因此用戶需要對目標和自定義評估度量進行轉換。對于具有身份鏈接的目標,如平方誤差squared error
,這很簡單,但對于其他鏈接函數,如對數鏈接或反鏈接,差異很大。
在 Python 包中,可以通過 predict
函數中的 output_margin
參數來控制預測的行為。當使用 custom_metric
參數而沒有自定義目標函數時,度量函數將接收經過轉換的預測,因為目標是由 XGBoost 定義的。然而,當同時提供自定義目標和度量時,目標和自定義度量都將接收原始預測。以下示例比較了多類分類模型中兩種不同的行為。首先,我們定義了兩個不同的 Python 度量函數,實現了相同的底層度量以進行比較。其中 merror_with_transform
在同時使用自定義目標時使用,否則會使用更簡單的 merror
,因為 XGBoost 可以自行執行轉換。
import xgboost as xgb
import numpy as npdef merror_with_transform(predt: np.ndarray, dtrain: xgb.DMatrix):"""Used when custom objective is supplied."""y = dtrain.get_label()n_classes = predt.size // y.shape[0]# Like custom objective, the predt is untransformed leaf weight when custom objective# is provided.# With the use of `custom_metric` parameter in train function, custom metric receives# raw input only when custom objective is also being used. Otherwise custom metric# will receive transformed prediction.assert predt.shape == (d_train.num_row(), n_classes)out = np.zeros(dtrain.num_row())for r in range(predt.shape[0]):i = np.argmax(predt[r])out[r] = iassert y.shape == out.shapeerrors = np.zeros(dtrain.num_row())errors[y != out] = 1.0return 'PyMError', np.sum(errors) / dtrain.num_row()
僅當想要使用自定義目標并且 XGBoost 不知道如何轉換預測時才需要上述函數。多類誤差函數的正常實現是:
def merror(predt: np.ndarray, dtrain: xgb.DMatrix):"""Used when there's no custom objective."""# No need to do transform, XGBoost handles it internally.errors = np.zeros(dtrain.num_row())errors[y != out] = 1.0return 'PyMError', np.sum(errors) / dtrain.num_row()
接下來需要自定義 softprob 目標:
def softprob_obj(predt: np.ndarray, data: xgb.DMatrix):"""Loss function. Computing the gradient and approximated hessian (diagonal).Reimplements the `multi:softprob` inside XGBoost."""# Full implementation is available in the Python demo script linked below...return grad, hess
最后可以使用 obj
和 custom_metric
參數訓練模型:
Xy = xgb.DMatrix(X, y)
booster = xgb.train({"num_class": kClasses, "disable_default_eval_metric": True},m,num_boost_round=kRounds,obj=softprob_obj,custom_metric=merror_with_transform,evals_result=custom_results,evals=[(m, "train")],
)
如果不需要自定義目標,只是想提供一個XGBoost中不可用的指標:
booster = xgb.train({"num_class": kClasses,"disable_default_eval_metric": True,"objective": "multi:softmax",},m,num_boost_round=kRounds,# Use a simpler metric implementation.custom_metric=merror,evals_result=custom_results,evals=[(m, "train")],
)
使用multi:softmax
來說明轉換后預測的差異。使用softprob
時,輸出預測數組的形狀是(n_samples, n_classes)
,而對于softmax
,它是(n_samples, )
。關于多類目標函數的示例也可以在創建自定義多類目標函數的示例中找到。此外,更多解釋請參見Intercept。
Scikit-Learn 接口
XGBoost的scikit-learn接口提供了一些工具,以改善與標準的scikit-learn函數的集成,用戶可以直接使用scikit-learn的成本函數(而不是評分函數):
from sklearn.datasets import load_diabetes
from sklearn.metrics import mean_absolute_errorX, y = load_diabetes(return_X_y=True)
reg = xgb.XGBRegressor(tree_method="hist",eval_metric=mean_absolute_error,
)
reg.fit(X, y, eval_set=[(X, y)])
對于自定義目標函數,用戶可以在不訪問DMatrix的情況下定義目標函數:
def softprob_obj(labels: np.ndarray, predt: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:rows = labels.shape[0]classes = predt.shape[1]grad = np.zeros((rows, classes), dtype=float)hess = np.zeros((rows, classes), dtype=float)eps = 1e-6for r in range(predt.shape[0]):target = labels[r]p = softmax(predt[r, :])for c in range(predt.shape[1]):g = p[c] - 1.0 if c == target else p[c]h = max((2.0 * p[c] * (1.0 - p[c])).item(), eps)grad[r, c] = ghess[r, c] = hgrad = grad.reshape((rows * classes, 1))hess = hess.reshape((rows * classes, 1))return grad, hessclf = xgb.XGBClassifier(tree_method="hist", objective=softprob_obj)
參考
- https://xgboost.readthedocs.io/en/latest/tutorials/custom_metric_obj.html
- https://xgboost.readthedocs.io/en/latest/python/examples/custom_rmsle.html#sphx-glr-python-examples-custom-rmsle-py