带权重与base_margin的XGBRegressor如何实现样本外验证?
带样本权重与base_margin的XGBoost交叉验证/样本外验证实现方案
首先纠正两个常见认知偏差:
GridSearchCV等sklearn交叉验证工具不需要你在模型初始化阶段传入sample_weight、base_margin,这类属于fit()方法的参数可以在调用交叉验证的fit时统一传入,框架会自动按折索引拆分对应的数据片段,不会错位。- 1.3.0及以上版本的XGBoost sklearn接口已经支持给验证集单独传权重和base_margin,不需要切换原生接口就能实现eval_set的独立参数配置。
方案1:基于sklearn网格搜索/交叉验证工具快速实现
适合旧GLM模型固定、不需要随交叉验证折重训的场景,传入的权重、base_margin数组长度只要和全量输入X、y长度完全对齐即可,sklearn会自动按拆分索引匹配每折的训练、验证子集对应数值。
from sklearn.model_selection import GridSearchCV import xgboost as xgb # 初始化模型,不需要在该阶段传入权重、base_margin xgb_model = xgb.XGBRegressor( n_estimators=50, learning_rate=0.01, n_jobs=4, eval_metric="poisson-nloglik", objective="count:poisson" # 泊松回归必须显式指定目标函数,默认平方误差和评估指标不匹配 ) # 参数搜索网格 param_grid = { "max_depth": [3, 5, 7], "max_leaves": [20, 50, 100] } grid = GridSearchCV( estimator=xgb_model, param_grid=param_grid, cv=5, scoring="neg_mean_poisson_deviance" ) # 传入全量数据对应的权重、base_margin数组 grid.fit( X=X_all, y=y_all, sample_weight=_ALL_WEIGHT, base_margin=_ALL_BASE_MARGIN )
重要提醒:如果你的base_margin来自旧GLM模型的输出,禁止用全量数据训练GLM后输出所有样本的预测值当base_margin做交叉验证,会造成严重数据泄露。这种场景请用下面的手动实现CV方案。
方案2:手动实现K折交叉验证(严格规避数据泄露)
如果你需要在每折交叉验证中单独训练旧GLM模型、生成对应折的base_margin(避免泄露),可以手动切分折逐次训练,同时支持自定义每折验证集的参数传入:
from sklearn.model_selection import KFold import numpy as np kf = KFold(n_splits=5, shuffle=True, random_state=42) cv_scores = [] for train_idx, val_idx in kf.split(X_all): # 拆分当前折的训练、验证子集 X_tr, X_val = X_all.iloc[train_idx], X_all.iloc[val_idx] y_tr, y_val = y_all.iloc[train_idx], y_all.iloc[val_idx] w_tr, w_val = _ALL_WEIGHT[train_idx], _ALL_WEIGHT[val_idx] # 若需规避泄露:在该位置只用当前折训练集数据训练GLM,再输出两个子集的base_margin # 示例:glm.fit(X_tr, y_tr, sample_weight=w_tr) # bm_tr = glm.predict(X_tr) # bm_val = glm.predict(X_val) # 若旧GLM为固定离线模型,直接按索引取对应margin即可 bm_tr = _ALL_BASE_MARGIN[train_idx] bm_val = _ALL_BASE_MARGIN[val_idx] model = xgb.XGBRegressor( n_estimators=50, max_depth=100, max_leaves=100, learning_rate=0.01, n_jobs=4, eval_metric="poisson-nloglik", objective="count:poisson" ) model.fit( X_tr, y_tr, sample_weight=w_tr, base_margin=bm_tr, eval_set=[(X_val, y_val)], # 按eval_set的顺序传入对应验证集的权重、margin即可 sample_weight_eval_set=[w_val], base_margin_eval_set=[bm_val], verbose=False ) # 计算当前折验证集指标 val_pred = model.predict(X_val, base_margin=bm_val) fold_score = poisson_nloglik(y_val, val_pred, sample_weight=w_val) cv_scores.append(fold_score) print(f"交叉验证平均泊松负对数似然: {np.mean(cv_scores):.4f}, 标准差: {np.std(cv_scores):.4f}")
方案3:XGBoost原生DMatrix接口实现(最灵活)
如果习惯用XGBoost原生训练接口,可以为每个数据集单独创建DMatrix对象,绑定对应权重、base_margin,训练时会自动读取,不需要额外传参:
import xgboost as xgb # 训练集绑定对应权重、margin dtrain = xgb.DMatrix( X_train, label=y_train, weight=_WEIGHT_TRAIN, base_margin=_BASE_MARGIN_TRAIN ) # 验证集单独绑定自己的权重、margin dval = xgb.DMatrix( X_val, label=y_val, weight=_WEIGHT_VAL, base_margin=_BASE_MARGIN_VAL ) params = { "max_depth": 100, "max_leaves": 100, "eta": 0.01, "objective": "count:poisson", "eval_metric": "poisson-nloglik", "nthread": 4 } # 训练时直接传入验证集即可,自动读取绑定的权重和margin model = xgb.train( params, dtrain, num_boost_round=50, evals=[(dtrain, "train"), (dval, "val")] )
原有代码的注意事项
- 你同时传入了
n_estimators=25和nrounds=50,sklearn接口中nrounds是无效参数,实际生效的轮次是n_estimators,重复传参会导致参数不符合预期。 max_depth=100的设置树深过大,搭配小学习率非常容易过拟合,建议把树深、叶子节点数作为交叉验证的重点调优参数。
内容的提问来源于stack exchange,提问作者Berthrand Eros
相关产品推荐
相关产品推荐

