news 2026/10/22 6:03:33

XGBoost原理与Python实战:二阶泰勒展开、缺失值处理与调参

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
XGBoost原理与Python实战:二阶泰勒展开、缺失值处理与调参

聊到 XGBoost,很多人第一反应是我早就用 sklearn 做过,没什么可学的。但只要你换一批带缺失值的数据,或者把n_estimators和learning_rate的组合稍微改一改,就会发现同一个模型的表现可以天差地别。XGBoost 全称 eXtreme Gradient Boosting,是梯度提升决策树里最经典的工程实现之一。它把加法模型、二阶泰勒展开、正则化、近似分裂算法这些概念统一在同一个框架里,才让它在各类表格数据的回归和分类任务里成了默认武器。这篇文章我会从损失函数开始拆解,再给出一套完整的 Python 示例,最后把调参和踩坑记录一并整理出来。适合两类人:一类是只用过接口、想弄清内部原理的算法工程师;另一类是刚接触树模型、想尽快写出能跑结果的同学。

1. 先想清楚:XGBoost 干的到底是件什么事

1.1 从随机森林到梯度提升:思路转向

随机森林的思路是 Bagging:同时在很多棵树上并行训练,最后投票或取平均。每棵树都在同一个数据的不同抽样版本上学习,树的复杂度可以很高,靠“平均”把方差压下来。

XGBoost 走的是完全不同的路线。它属于 Boosting:树是一棵一棵串行训练出来的,后一棵树重点关注前一棵树没做好的样本。你可以把 Boosting 理解为“补差”的过程,而不是“投票”的过程。第一棵树给出一个粗糙的预测,第二棵树学习第一棵树的预测误差,第三棵再学前两棵合起来之后的误差,依此类推。

这个思路对偏差的改善特别明显。如果一个任务里每棵树都是低偏差但高方差的弱模型,Boosting 通过顺序训练逐渐逼近真实目标,代价是更容易过拟合训练集。所以 XGBoost 的实现里塞进了大量正则化手段,比如叶子权重 L2、叶子数量惩罚、列采样、行采样、收缩步长,这是它能长期作为比赛默认工具的重要原因。

1.2 加法模型:一棵树接一棵树

XGBoost 的模型输出是一系列决策树输出的加和。假设有 K 棵树,对样本 i 的预测是:

yhat_i = f_1(x_i) + f_2(x_i) + ... + f_K(x_i)

这里每一棵f_k(x)是一棵 CART 回归树。注意 CART 树和分类树的区别:每个叶子节点存放的是一个连续权重,而不是类别标签。训练过程中,XGBoost 考虑的是“当前这棵树该是多少”,而不是“重新把前面所有树一起优化一遍”。

第 t 轮优化时,模型输出可以写成:

yhat_i^(t) = yhat_i^(t-1) + f_t(x_i)

也就是说,前面 t-1 棵树已经固定,当前要解决的是第 t 棵树怎么定义:树的结构是什么,每个叶子节点该取什么值。这个“固定历史、只学当前增量”的思路是理解后续所有数学推导的前提。

1.3 为什么 XGBoost 要同时算一阶导和二阶导

初学的时候大家都会问:为什么不直接拟合残差?拟合残差其实是最简单平方损失下的特殊结果。假设损失函数是L = 0.5 * (y - yhat)^2,对 yhat 求导得到:

dL/dyhat = yhat - y

这个梯度就是预测值减真实值,也就是残差的相反数。要让损失下降,我们往负梯度方向调整,所以本质上就是去拟合y - yhat,也就是残差。

但真实业务里的损失不只是平方误差。分类交叉熵、回归中的分位数损失、排序损失,它们的梯度都不是直接等于残差。XGBoost 不满足于只用一阶梯度信息,它把损失函数做二阶泰勒展开,同时用到梯度g_i和 Hessianh_i。你可以把 Hessian 理解成损失曲线的弯曲程度:斜率大、弯曲也大的地方,步子可以迈得更稳。这样每个样本的更新步长不再是统一的学习率,而是被二阶信息自适应地缩放,收敛更稳,这也是 XGBoost 和早期 GBDT 实现的一个重要分水岭。

2. 数学推导:目标函数怎么变成树的打分规则

2.1 目标函数和正则项

XGBoost 每一轮优化的目标函数是:

Obj = Σ_i L(y_i, yhat_i^(t)) + Σ_k Ω(f_k)

第一部分是所有样本的损失之和,第二部分是所有树的复杂度之和。为什么要加复杂度?因为如果不加,模型会倾向于无限细分训练集,把每个样本都放到单独的叶子里,训练损失趋近于零,但测试集表现会崩。

单棵树的复杂度通常定义为:

Ω(f) = γ * T + 0.5 * λ * Σ_j w_j^2

其中 T 是叶子数量,w_j 是叶子 j 的权重,γ 表示“每多一个叶子就要付的代价”,λ 是叶子权重的 L2 惩罚系数。γ 越大,树越难长出新的分裂;λ 越大,单个叶子能取到的极端数值就越小,整体预测更平滑。

这里要理解一个关键点:目标函数里正则项不是“加在损失后面防过拟合那么简单”,它是分裂算法的一部分。一次分裂到底做不做,不是只看损失降低多少,而是要扣除新增叶子的成本。

2.2 二阶泰勒展开:把复杂损失变成二次函数

第 t 轮的优化对象是当前树 f_t。对第 i 个样本,损失函数可以看作关于旧预测yhat_i^(t-1)的函数,现在加上 f_t 这个增量。二阶泰勒展开得到:

L(y_i, yhat_i^(t-1) + f_t(x_i)) ≈ L(y_i, yhat_i^(t-1)) + g_i * f_t(x_i) + 0.5 * h_i * f_t(x_i)^2

这里:

g_i = ∂ L(y_i, yhat_i^(t-1)) / ∂ yhat_i^(t-1)

h_i = ∂² L(y_i, yhat_i^(t-1)) / ∂ (yhat_i^(t-1))²

g_i 是一阶梯度,h_i 是二阶梯度。L(y_i, yhat_i^(t-1))是上一轮的损失,当前轮优化时已经固定,可以暂时忽略。

如果损失是平方误差,那么 g_i 就是yhat_i - y_i,h_i 恒等于 1。如果损失是二分类交叉熵,那么 h_i 就不是常数,它会随预测概率变化。这也是为什么 XGBoost 需要样本的 Hessian 信息,而不是只给一个固定步长。

2.3 叶子的最佳权重和结构得分

现在假设树的结构已经定好了:样本通过分裂规则被分到不同的叶子上。比如第 j 个叶子里的样本集合是I_j,那么该叶子所有样本的一阶梯度之和记为G_j = Σ_{i∈I_j} g_i,二阶梯度之和记为H_j = Σ_{i∈I_j} h_i。

去掉常数项后,目标函数关于所有叶权重可以写成各个叶子累加的形式:

Obj ≈ Σ_j [ G_j * w_j + 0.5 * (H_j + λ) * w_j² ] + γ * T

这是一个关于 w_j 的二次函数,直接求导等于零,得到叶子的最优权重:

w_j* = -G_j / (H_j + λ)

注意前面是负号,因为我们求导的是最小化方向,-G_j/(H_j+λ)对应的是牛顿步。把这个最优权重代回目标函数,得到该树结构下的最小目标值:

Obj* = -0.5 * Σ_j G_j² / (H_j + λ) + γ * T

这个值越小,说明这棵树在当前结构下能更大地降低目标。它被称为结构分数,用来评价整棵树的好坏。

这里有个点值得细说:λ 放在分母上,相当于对二阶梯度之和做了一个平滑。当某个叶子里的样本量很少,H_j 很小时,单独看G_j² / (H_j + λ)会被 λ 限制住,不会因为一个极端样本就产生一个很大的叶子权重。这也是 XGBoost 处理小叶子样本的工程技巧之一,和普通的“限制叶子最少样本数”并不是一回事。

2.4 分裂收益 Gain:一个公式看懂怎么切特征

一棵树的结构不是凭空变出来的,而是从根节点开始反复寻找最优分裂得到的。假设某个节点上的样本集合为 I,一阶梯度之和为 G,二阶梯度之和为 H。如果按某个特征的阈值把它分成左右两个子节点,那么分裂前的结构得分是:

Score_before = -0.5 * G² / (H + λ) + γ

分裂后的结构得分是:

Score_after = -0.5 * [ G_L² / (H_L + λ) + G_R² / (H_R + λ) ] + 2γ

因为叶子数增加了一个,所以 γ 的项多了一个。用分裂后的分数减去分裂前的分数,整理后得到常用的 Gain 公式:

Gain = 0.5 * [ G_L² / (H_L + λ) + G_R² / (H_R + λ) - (G_L + G_R)² / (H_L + H_R + λ) ] - γ

如果 Gain 小于等于零,那这个分裂对目标没有正向贡献,不要切。这个公式在实际训练中有几个非常直接的结论:

第一,γ 是“分裂最低收益门槛”,不只是正则项。第二,如果某个候选分裂的H_L + H_R因为样本量少而很小,即使 G 看起来不小,也可能被 λ 抵消后收益不高。第三,XGBoost 不需要像普通决策树那样计算基尼系数或者信息增益,它直接以目标函数下降量为准则,这让它在不同损失函数下都能统一处理。

这就是为什么说 XGBoost 的数学推导和工程是深度耦合的:每个分裂点的收益,本质上都在回答“这一刀切下去,能否让带正则的目标函数下降”。

3. 工程实现里的隐藏细节

3.1 寻找分裂点:精确贪心与加权分位数近似

Gain 公式看起来很简单,但真正训练时的问题在于:候选分裂点从哪里来?

对精确贪心算法,每个节点都需要把特征值排序,然后逐个尝试所有可能的分裂位置。这样做的复杂度很高,每个节点都要遍历一次所有样本,数据量一大就非常慢。所以 XGBoost 实现了近似算法:对每个特征生成少量候选分裂点,然后用这些候选点去计算 Gain,而不是穷举所有值。

近似算法的关键不是简单等频分桶,而是加权分位数。样本权重用的是h_i,也就是二阶梯度。为什么用 h 加权?因为 h 代表了损失对预测值的二阶敏感度。一个样本的 h 越大,说明它在目标函数中的“影响力”越大,分位点应该更多地照顾这类样本。你可以把它理解成:不是每个样本都平等参与投票,而是按它对损失的潜在贡献来分配分桶空间。

在实际使用里,tree_method='hist'就是建立在这种思想上的直方图近似算法。它不需要精确排序全部数值,而是构建特征值的直方图,速度比默认的exact快很多,而且在大数据下性能差距会非常明显。所以“数学上精确”不等于“工程上最优”,近似算法用很小的精度损失换来了巨大的效率提升。

3.2 缺失值自动学习默认方向

表格数据基本逃不掉缺失值。很多人的第一反应是用均值、中位数填充,其实 XGBoost 原生支持缺失值,而且在分裂的时候会自动学习缺失样本该往哪个方向走。

具体做法是:在计算某个特征的分裂收益时,把缺失值样本分别尝试放入左节点和右节点,分别计算 Gain,然后选择收益大的那个方向作为缺失值默认方向。也就是说,缺失值不是被当成 0 或者均值,而是被模型当成“一条可以学习的路径”。

这个设计背后的假设是:缺失本身可能携带信息,也可能不携带信息。如果缺失是随机的,模型会找到让损失最小的那个方向;如果缺失在业务上有特定含义,例如某个采集项只有特定人群才有值,模型也能利用这个模式。实操中有一个技巧:如果缺失值代表“该特征不适用”,可以额外构造一个“是否缺失”的 0/1 特征,让模型更容易捕获这种语义。但如果缺失比例很低,XGBoost 自带的默认方向通常已经够用。

3.3 收缩、列采样与最小叶子权重

看 XGBoost 参数文档时,很多人会被一大堆参数吓到,其实核心机制并不多。

learning_rate也叫做收缩步长。每一棵新树拟合的目标不是完整残差,而是残差乘以一个很小的系数,比如 0.05。这样做的效果是让模型不会一步就冲到训练集的最优解附近,给后续的树留出更多微调空间。代价是需要更多轮数,所以这个参数必须和n_estimators一起考虑。

subsample是行采样。每轮训练只随机抽一部分样本。这和随机森林的 Bagging 采样有点像,但它的作用是防止连续几棵树都在同样的样本上过度拟合。

colsample_bytree是列采样。每棵树只用一部分特征,降低树与树之间的相关性,也顺便提升训练速度。还有更细粒度的colsample_bylevel,它是每个节点分裂时采样一部分特征,实际使用频率不高。

min_child_weight控制的是子节点中H的最小值。不是一个简单的“最少样本数”,而是所有进入子节点的样本的二阶梯度之和。默认值是 1,在平方损失下就等于最小样本数,但在交叉熵损失下,它代表的含义会随模型置信度变化。调大这个参数会让模型更保守,通常用于抑制过拟合。

4. Python 完整实战:从造数据到训练评估

4.1 环境准备与版本确认

开始之前,先把环境装好。XGBoost 的安装很直接:

pip install xgboost

如果用的是 conda,也可以:

conda install -c conda-forge xgboost

装完之后可以先确认版本:

import xgboost as xgb print(xgb.__version__)

我建议尽量用 1.7 以后的版本。新版本对tree_method='hist'的默认支持更好,GPU 相关参数也从gpu_hist逐步迁移到device参数。后面示例里的代码都基于较新版本的原生接口,如果你手里的版本很老,某些参数名可能需要微调。

4.2 构造带缺失值的回归数据集

为了同时演示数学推导里提到的概念和实际训练,我这里构造一个带缺失值的回归数据。样本数 3000,特征数 20,随机设置 5% 的值变成缺失。

import numpy as np import pandas as pd import xgboost as xgb from sklearn.datasets import make_regression from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, r2_score X, y = make_regression( n_samples=3000, n_features=20, noise=0.3, random_state=42 ) rng = np.random.default_rng(42) missing_mask = rng.random(X.shape) < 0.05 X[missing_mask] = np.nan X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) X_train, X_valid, y_train, y_valid = train_test_split( X_train, y_train, test_size=0.25, random_state=42 )

这里特意保留测试集不参与验证集选择,最后再用测试集做一次真实评估。注意 DMatrix 是 XGBoost 自己的数据结构,它会记录特征信息、缺失值模式和标签。

dtrain = xgb.DMatrix(X_train, label=y_train) dvalid = xgb.DMatrix(X_valid, label=y_valid) dtest = xgb.DMatrix(X_test, label=y_test)

4.3 用原生接口训练并早停

我比较喜欢用原生xgb.train接口做演示,因为它能更直接地展示evals和early_stopping_rounds的行为。参数先不追求最优,使用一个兼顾速度和精度的常规组合:

params = { 'objective': 'reg:squarederror', 'eval_metric': 'rmse', 'tree_method': 'hist', 'learning_rate': 0.05, 'max_depth': 4, 'min_child_weight': 1, 'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_lambda': 1.0, 'seed': 42, } bst = xgb.train( params, dtrain, num_boost_round=1000, evals=[(dvalid, 'valid')], early_stopping_rounds=50, verbose_eval=100, ) print('best_iteration:', bst.best_iteration) print('best_score:', bst.best_score) pred = bst.predict(dtest) print('test rmse:', np.sqrt(mean_squared_error(y_test, pred))) print('test r2:', r2_score(y_test, pred))

early_stopping_rounds=50的意思是连续 50 轮验证集 RMSE 没有下降,就提前停止。这样不需要真的跑满 1000 轮,也能自动选一个相对合理的迭代次数。

注意:原生接口里的best_iteration是从 0 开始计数的。如果你之后要用它继续训练,最好先加 1,或者直接参考best_score来判断模型状态。

看过这段代码你会发现,XGBoost 的 API 其实不复杂,难点在于理解每个参数背后的目标函数变化,而不是机械调参。

4.4 用交叉验证选迭代轮数

单独一个验证集有个问题:随机切分的验证结果可能不够稳定。我习惯再用xgb.cv做一次五折交叉验证,用交叉验证的 RMSE 曲线来确认迭代轮数范围。

cv_result = xgb.cv( params, dtrain, num_boost_round=1000, nfold=5, early_stopping_rounds=50, metrics='rmse', seed=42, ) best_round = cv_result['test-rmse-mean'].idxmin() + 1 print('cv best round:', best_round)

xgb.cv返回一个 DataFrame,每一行对应一轮迭代。test-rmse-mean是五折验证的平均 RMSE。直接找最小值所在的迭代轮数,比看最后一行更稳妥。这样选出来的轮数不是一个事后在测试集上挑出来的轮数,而是从验证流程里独立得到的,能减少一定程度的“测试集偷看”风险。

拿到best_round之后,我通常会在完整训练集上重新训练,也就是把原来的训练集和验证集合到一起:

X_full = np.vstack([X_train, X_valid]) y_full = np.concatenate([y_train, y_valid]) dfull = xgb.DMatrix(X_full, label=y_full) bst_final = xgb.train(params, dfull, num_boost_round=best_round) pred_final = bst_final.predict(dtest) print('final test rmse:', np.sqrt(mean_squared_error(y_test, pred_final))) print('final test r2:', r2_score(y_test, pred_final))

这样做的原因是:早停阶段使用的验证集只用来选轮数,选完之后把验证集也还给模型,让最终模型看到更多数据,通常会有小幅提升。

4.5 特征重要性与完整脚本

模型训练完,第一件事不是急着调参,而是看特征重要性。XGBoost 的get_score可以返回两种常用类型:weight表示这个特征被用来分裂的次数,gain表示这个特征带来的平均增益之和。

importance = bst_final.get_score(importance_type='gain') imp_df = pd.DataFrame( [{'feature': k, 'gain': v} for k, v in importance.items()] ).sort_values('gain', ascending=False) print(imp_df.head(10))

我更推荐用gain作为主要参考,因为weight只统计使用次数,一个无信息但取值很多的特征可能被频繁选中,误导判断。gain能反映特征在目标函数下降中的实际贡献。

把上面几段代码拼在一起,就是一个能直接运行的完整脚本。同一份代码在你的机器上跑出来的具体 RMSE 数字可能略有差别,但训练流程和结论是一致的:带正则的原生 XGBoost 在这个数据集上很快就能收敛,而且对缺失值不需要任何填充预处理。

5. 调参顺序与常见问题排查

5.1 我习惯的调参顺序

很多教程会把参数分成两页表格,然后让你网格搜索。真正做项目时,网格搜索维度太高,成本太大。我习惯的顺序是这样的:

  1. 先把learning_rate设成 0.05,max_depth设成 4,min_child_weight设成 1,用早停跑一轮,看看基线水平。
  2. 调max_depth和min_child_weight。如果训练集误差明显低、验证集误差高,就降低深度或调大min_child_weight;如果两边误差都高,说明模型太弱,先加深。
  3. 调subsample和colsample_bytree。这两个参数对验证集误差的稳定性影响很大,一般从 0.8 开始,往下试探。
  4. 调reg_lambda、reg_alpha、gamma。正则参数的收益通常不如深度和采样参数明显,但可以在最后做精修。
  5. 最后把learning_rate降到 0.02 或 0.01,同时成倍增加n_estimators或加大早停窗口,做一轮收尾。

每次只改一个参数,最好记录成一张小表。不要同时调三四个参数,否则你根本分不清验证集的变化是哪个动作带来的。

实测下来,learning_rate=0.05、max_depth=4、subsample=0.8、colsample_bytree=0.8在多数中小型表格数据上都是一个稳健的起点。它不是理论上的最优区间,但足够让你先看清问题的上界在哪里。

5.2 高频踩坑场景

第一个坑是评估指标和训练目标不一致。XGBoost 的objective决定模型优化方向,eval_metric只负责观察和早停。比如回归任务用reg:squarederror,但验证时居然设置成logloss,这不会报错,但它不能衡量回归任务的好坏。分类任务也有类似的误解:AUC 是一个不可导的排序指标,它只能当验证指标,不能直接作为训练目标。

第二个坑是时序数据随机切分。如果数据带时间属性,比如按时间顺序记录的样本,千万不要用train_test_split随机打乱。这样会引入未来信息,训练集里某个时间点的样本可能泄露到验证集里,结果会虚高。应该按时间顺序切割训练集、验证集和测试集。

第三个坑是类别特征直接塞进模型。XGBoost 对数值型特征天然友好,但直接把类别 ID 当成整数使用,会给模型一种“1 和 2 只差 1”的错误假设。类别无序时建议用 One-Hot,或者在新版本里使用enable_categorical=True配合DMatrix的 categorical 列。类别很多时,One-Hot 会让矩阵变宽,这时要权衡是否值得。

第四个坑是类别不平衡没有设置scale_pos_weight。二分类里正负样本差距悬殊时,单纯靠调max_depth很难解决问题。scale_pos_weight一般取负样本数除以正样本数,让模型对少数类的错误更加敏感。

5.3 常见问题速查表

现象可能原因处理思路
训练集 RMSE 很低,测试集偏高模型过拟合,叶子权重太大降低max_depth,调大min_child_weight,提高reg_lambda,调低learning_rate
训练集和测试集 RMSE 都比较高模型学习能力不足,轮数不够增大max_depth,先确保模型能在训练集上表现出来,再谈正则
验证集指标忽高忽低验证集太小,或随机性太强改用 K 折交叉验证,或者增大验证集比例
加了大量缺失值后效果明显下降缺失不是随机的,模型没有学到缺失方向补充“是否缺失”特征,或者结合业务理解是否要单独处理
某个特征重要性特别高该特征确实信息量大,可能是强特征结合 SHAP 分析,不要只看单一指标
轮数很多但收益很小学习率太低,模型已经收敛适当提高学习率,或者改用更深的树

这张表不是万能答案,但可以帮你快速缩小排查范围。实际项目里,模型效果不好经常是数据问题而不是参数问题,先检查特征,再怀疑超参。

6. 工程化扩展:自定义目标与可解释性

6.1 自定义损失函数示例

大多数时候内置的reg:squarederror、binary:logistic已经够用,但业务里偶尔会遇到需要自定义损失的情况,比如想对高值误差给更高惩罚,或者用分位数损失做区间预测。

自定义 objective 的套路是返回一阶梯度和二阶梯度。看一个平方误差的例子:

def squared_objective(preds, dtrain): labels = dtrain.get_label() grad = preds - labels hess = np.ones_like(labels) return grad, hess params_obj = params.copy() params_obj['objective'] = squared_objective bst_custom = xgb.train( params_obj, dtrain, num_boost_round=200, evals=[(dvalid, 'valid')], early_stopping_rounds=30, )

这里grad = preds - labels是平方损失对预测值的导数,hess = 1是二阶导数。注意和普通直觉相反:导数不是labels - preds,而是preds - labels,因为我们要做梯度下降,目标函数对预测值的导数会影响后续牛顿步的方向。如果你在自定义损失时发现效果不对,先检查符号。

自定义损失的关键不只是算对梯度,还要保证 Hessian 非负且有意义。有些损失函数的 Hessian 在极端预测下会变成负数,XGBoost 处理起来会有问题。因此实际使用前,先用小数据验证自定义损失和内置损失结果是否一致,再扩展到业务场景。

6.2 大规模数据与 GPU 训练注意事项

当数据量到了几十万甚至几百万行,CPU 单机训练会明显变慢。XGBoost 提供了 GPU 支持,新版本里的配置方式比老版本干净很多:

params_gpu = params.copy() params_gpu['tree_method'] = 'hist' params_gpu['device'] = 'cuda'

老版本里常见的tree_method='gpu_hist'在较新版本里已经被整合到device参数里,如果你还在照抄旧代码,可能会遇到报错或警告。

大规模场景下,我更建议先做特征筛选,而不是盲目上 GPU。XGBoost 虽然能处理成百上千个特征,但特征越多,分位数近似算法和直方图构建的时间越长,收益可能很低。先跑一遍特征重要性,把明显没有贡献的特征删掉,往往比硬件加速带来的收益更直接。

分布式训练也是一种选择,比如把数据分到多个节点上训练。但分布式会引入通信开销,日常项目里如果单机 GPU 能解决问题,就不要急着上分布式。

6.3 特征依赖与业务可解释性

XGBoost 常被诟病为黑盒,但表格数据场景下,它的可解释性工具其实很成熟。除了原生get_score,我最常用的是 SHAP。它可以把每个样本的预测值拆解成“这个特征对预测的贡献是多少”,能看出特征之间的交互、方向、非线性关系。

import shap explainer = shap.TreeExplainer(bst_final) shap_values = explainer.shap_values(X_test) shap.summary_plot(shap_values, X_test)

SHAP 的好处是它给的是一个特征对每个样本的具体贡献,而不是一个笼统的重要性分数。比如某个特征整体重要性靠前,你还能进一步看它在高取值时是推高预测还是压低预测,这在业务沟通里非常有用。

最后说一个我自己的经验。XGBoost 不是参数越多越好,有了早停和合理的正则,大多数表格问题都能在默认参数附近得到不错的结果。我踩过几次坑之后,发现最值钱的不是把 RMSE 压到小数点后几位,而是搞清楚每个特征为什么重要、哪些缺失模式在影响预测。先把这些想清楚,再谈调参。如果你刚上手,建议把我上面的完整脚本跑一遍,然后用get_score(importance_type='gain')和 SHAP 对同一个特征你:一个给你笼统的排名,一个给你每个样本上的贡献方向,两者对照着看,你对模型的理解会立刻上一个台阶。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 6:03:28

多模态Agent如何规划UI测试路径:从视觉理解到鲁棒执行

1. 当UI测试遇上多模态Agent&#xff1a;一个正在发生的范式转移做过UI自动化测试的人都有一个共同的痛&#xff1a;脚本写了几百行&#xff0c;页面改一个按钮位置&#xff0c;全挂。传统的UI测试方案&#xff0c;无论是基于DOM选择器、XPath定位&#xff0c;还是基于图像模板…

作者头像 李华
网站建设 2026/10/22 6:03:27

AI生成Rust代码为何频频卡壳?所有权与生命周期避坑指南

1. 从一次代码生成翻车说起&#xff1a;Rust为什么让AI模型频频失手前阵子帮一个做后端的朋友看一段自动生成的代码&#xff0c;场景很简单&#xff1a;用Rust写一个带超时控制的HTTP请求封装。生成出来的代码乍一看像模像样&#xff0c;async fn、tokio::time::timeout、Resul…

作者头像 李华
网站建设 2026/10/22 6:03:20

国庆七天AI圈全景复盘:开发者视角下的模型发布与工程落地

国庆七天假期&#xff0c;我本来计划着把之前攒下的几篇技术笔记整理一下&#xff0c;结果从一号早上开始&#xff0c;手机的通知栏就没安静过。模型发布、框架更新、开源仓库上新、技术博客连更&#xff0c;整个AI圈子像是约好了似的&#xff0c;七天长假硬是过出了“冲刺周”…

作者头像 李华
网站建设 2026/10/22 6:03:05

基于MATLAB/Simulink的NPC三电平逆变器仿真建模全解析

开门见山说一句&#xff1a;我最早接触三电平逆变器仿真的时候&#xff0c;被文档里那句"输出电平被钳位在直流母线中点"绕了很久。明明是四个开关管加两只二极管的组合&#xff0c;却在Simulink里反复出现波形乱跳、中点电位漂移的问题。等真正把NPC_3level_Inverte…

作者头像 李华
网站建设 2026/10/22 6:01:44

2026年IM选型指南:AI生成、开源与三方服务如何取舍

2026 年了&#xff0c;还在为"即时通讯&#xff08;IM&#xff09;功能到底怎么落地"纠结的团队&#xff0c;我见得太多了。打开编译器之前&#xff0c;先打开 AI 工具&#xff0c;让它"生成一个聊天系统"&#xff1b;或者花一天翻开源仓库&#xff0c;看到…

作者头像 李华
网站建设 2026/10/22 6:01:09

AI辅助论文写作:9类工具实测与边界指南

“一键生成论文”这个说法&#xff0c;每隔一阵就会在社交媒体上冒出来一次&#xff0c;配上诱人的标题和看似华丽的演示截图&#xff0c;点进去看的人一多半都是被毕业论文逼到墙角的本科生。我这些年和论文打交道不少&#xff0c;也被问过无数次类似的问题&#xff0c;所以这…

作者头像 李华