【发布时间】:2022-01-19 15:04:21
【问题描述】:
这里,n_estimators 表示 xgboost 中树的数量(周学习者)。我定义了一个自定义目标函数,我必须在其中计算一阶和二阶梯度。而且我还在自定义目标函数中添加了一个打印函数,这样我就可以计算自定义目标函数被调用(调用)的数量。最后,我发现该函数仅被 n_estimators 次调用。
以前,我认为在进行叶分裂时应该调用自定义目标函数,甚至一棵树也可以有许多叶分裂。所以现在我很困惑。
【问题讨论】:
这里,n_estimators 表示 xgboost 中树的数量(周学习者)。我定义了一个自定义目标函数,我必须在其中计算一阶和二阶梯度。而且我还在自定义目标函数中添加了一个打印函数,这样我就可以计算自定义目标函数被调用(调用)的数量。最后,我发现该函数仅被 n_estimators 次调用。
以前,我认为在进行叶分裂时应该调用自定义目标函数,甚至一棵树也可以有许多叶分裂。所以现在我很困惑。
【问题讨论】:
在 XGBoost 和 LightGBM 中,梯度在每个提升轮开始时计算一次。
然后构建每棵树以尝试根据候选特征拆分来解释梯度。您可以将其想象为“每棵树都是经过训练的决策树,用于预测模型的 残差”,但“残差”的值由所选目标确定功能。这就是为什么每次将节点添加到模型树之一时都不需要重新计算梯度的原因。
这是一个minimal, reproducible example,展示了您所询问的行为。我今天使用来自 conda-forge 的 lightgbm==3.3.2 和 xgboost==1.5.0 在 macOS 上使用 Python 3.9.7 运行此代码。
我提供了 lightgbm 和 xgboost 示例,因为原始帖子具有这两个标签。
import lightgbm as lgb
import numpy as np
import xgboost as xgb
from sklearn.datasets import make_regression
from typing import Tuple, Union
COUNTER = 0
def _objective_least_squares(
y_pred: np.ndarray,
train_data: Union[lgb.Dataset, xgb.DMatrix]
) -> Tuple[np.ndarray, np.ndarray]:
global COUNTER
print(f"iteration: {COUNTER}")
y_true = train_data.get_label()
grad = y_pred - y_true
hess = np.ones(len(y_true))
COUNTER += 1
return grad, hess
X, y = make_regression(n_samples=1_000, n_features=10, n_informative=8, random_state=708)
#--- xgboost example ---#
COUNTER = 0
xgb_dtrain = xgb.DMatrix(data=X, label=y)
xgb_model = xgb.train(
params={'tree_method': 'hist', 'verbosity': 0},
dtrain=xgb_dtrain,
num_boost_round=10,
obj=_objective_least_squares
)
print(f"Model has {len(xgb_model.trees_to_dataframe())} total tree nodes")
#--- lightgbm example ---#
COUNTER = 0
lgb_dtrain = lgb.Dataset(data=X, label=y)
lgb_model = lgb.train(
params={'seed': 1994, 'verbosity': -1},
train_set=lgb_dtrain,
num_boost_round=10,
fobj=_objective_least_squares
)
print(f"Model has {len(lgb_model.trees_to_dataframe())} total tree nodes")
这两个示例都显示了原始帖子中报告的行为...目标函数在每次迭代时被评估一次,而不是每个树节点一次。
iteration: 0
iteration: 1
iteration: 2
iteration: 3
iteration: 4
iteration: 5
iteration: 6
iteration: 7
iteration: 8
iteration: 9
训练后添加的print() 语句确认本示例中生成的模型总共有超过 10 个树节点,只是为了确认这是显示目标函数调用频率的准确方法。
# xgboost example
Model has 1100 total tree nodes
# lightgbm example
Model has 610 total tree nodes
【讨论】: