跳转至


课程  因子投资  机器学习  Python  Poetry  ppw  tools  programming  Numpy  Pandas  pandas  算法  hdbscan  聚类  选股  Algo  minimum  numpy  回测  数据标准化  algo  FFT  模式识别  配对交易  GBDT  LightGBM  XGBoost  statistics  CDF  KS-Test  monte-carlo  VaR  过拟合  algorithms  machine learning  strategy  python  sklearn  pdf  概率  数学  面试题  量化交易  策略分类  风险管理  Info  interview  career  强化学习  监督学习  AI量化  复权  数据  tushare  akshare  xgboost  PCA  wavelet  时序事件归因  SHAP  深度学习  归一化  BN  LN  WN  machine-learning  quant-trading  dropout  交易实战  避坑指南  quant  deep-learning  tcn  time-series  causal-convolution  Figures  Behavioral Economics  graduate  arma  garch  人物  职场  Quantopian  figure  Banz  金融行业  买方  卖方  story  量化传奇  rsi  zigzag  穹顶压力  因子  ESG  因子策略  投资  策略  pe  ORB  Xgboost  Alligator  Indicator  factor  alpha101  alpha  技术指标  wave  algorithm  pearson  spearman  套利  LOF  白银  因子分析  Alphalens  涨停板  herd-behaviour  momentum  因子评估  review  SMC  聪明钱  trade  history  indicators  zscore  波动率  lightgbm  顶背离  另类数据  freshman  resources  others  AI  DeepSeek  network  量子计算  金融交易  IBM  weekly  进化论  logic-factor  Agent Skills  Skills Marketplace  VS Code  Tushare  XtQuant  BaoStock  A股  量化  neutralization  basics  LLT  backtest  backtrader  研报  papers  UBL  quantlib  jupyter-notebook  scikit-learn  pypinyin  qmt  xtquant  blog  static-site  duckdb  工具  colors  free resources  barra  world quant  Alpha  openbb  risk-management  llm  prompt  CANSLIM  Augment  arsenal  copilot  vscode  code  量化数据存储  hdf5  h5py  cursor  augment  trae  Jupyter  jupysql  pyarrow  parquet  数据源  quantstats  几何收益  实盘  clickhouse  polars  滑动窗口  notebook  sqlite  sqlite-utils  fastlite  大数据  PyArrow  UV  Pydantic  Engineering  redis  remote-agent  AI-tools  Moonshot  回测,研报,tushare  dividend 

Kaggle 比赛为什么 XGBoost 能赢

最后更新: 2026-03-30


Kaggle 比赛为什么 XGBoost 能赢

Kaggle 的表格赛有个很有意思的现象。

很多模型都能把分数做上去,但真到后半程,最常留在牌桌上的,往往还是 XGBoost 这一类树模型。你当然可以说,这是因为表格数据天然适合树去切条件、拆人群。这个判断没有问题,但还没说到它真正难缠的地方。

XGBoost 的厉害,不只是“会切”,而是每往前补一步,都尽量先把账算清楚。方向错了,它不该修;方向对了,它也不会默认往死里修。它先看这一步该往哪边走,再看这一步应该走多大,最后才决定值不值得把这次修正真正写进模型里。

问题也恰好出在这里。很多人知道 XGBoost 会用一阶和二阶信息,但一旦离开公式,脑子里就容易只剩几个标签:梯度、Hessian、二阶优化。标签是记住了,训练时到底在算什么,却没真正连起来。

我想换一个讲法。

不要先把它当成一组名词,而是把它当成模型每一步都要回答的两个问题:

这次修正,应该往哪个方向动。

如果方向没错,这次应该修得多猛。

把这两个问题看清楚以后,再回头看泰勒展开和 XGBoost,很多原来看着抽象的东西就会突然变得很具体。

Kaggle 的表格赛,放到量化里其实就是因子表

如果把 Kaggle 里最典型的 tabular 比赛翻译成量化语言,其实很像你手里的横截面因子表。

每一行是一只股票,每一列是一个特征,比如过去 20 日收益率、波动率、换手率、市值、PB、PE、行业变量,目标可能是下周收益、超额收益,或者某种风险状态。

这类问题难的地方,不是你找不到特征,而是同一个特征在不同组合下,含义会变。

小市值配高换手,可能是一种交易拥挤下的短线博弈;大市值配低波动,可能又是另一套逻辑。单看波动率可能没什么,但一旦叠上“最近 20 日已经涨了很多”,味道就可能完全变掉。

这本来就是树模型擅长的活。它不是拟合一条很光滑的大曲线,而是不断问条件:市值是不是低于某个阈值,换手率是不是高于某个阈值,最近收益是不是已经偏高。它做的事,说白了就是把混在一起的人群慢慢拆开。

所以 XGBoost 能在 Kaggle 这种比赛里常赢,第一层原因确实是树模型适合表格数据。

但真正把差距拉开的,不是它会拆人群,而是它知道每拆一步到底有没有赚到。这就回到了梯度、曲率和泰勒展开。

先别看树,先看误差是怎么动的

先不要急着回到树模型。把损失函数单独拎出来看,会更容易懂。你可以把它想成一张局部地形图。横轴不是时间,也不是特征,而是当前模型给出的预测值;纵轴是这个预测值对应的损失。

模型当前的预测,对应着这张图上的一个位置。训练做的事情其实很单纯,就是让这个位置一点点往更低的区域靠。

这时候,一阶和二阶信息就不是抽象名词了,而是当前位置附近的两种局部信息。

第一种是坡往哪边斜。

如果当前位置往右一点,损失会下降,那说明更新应该往右走。

如果当前位置往右一点,损失反而上升,那更新就该往左走。

这就是一阶信息真正干的事。它不是神秘地“给你答案”,而是在当前位置告诉你,朝哪边挪更可能把误差压下去。

第二种信息是这块地方到底陡不陡、弯不弯。

如果局部地形变化很快,你这一步就不能迈太大,不然很容易一下跨过去。反过来,如果这一带本来就很平,你每次都只蹭一点,收敛就会特别慢。

所以训练时真正要决定的,从来都不是一个问题,而是两个问题一起决定:

  • 往哪边修
  • 修多少

所谓“更稳定”,说的其实就是第二件事没有失控。模型不会一脚油门冲过头,也不会永远只肯挪半步。

用最短的泰勒展开,把这件事钉住

现在再回到数学,但只看最短的一段。

把损失函数记成 \(l(\hat y)\),我们只关心当前预测值 \(\hat y\) 附近,往旁边挪一小步 \(\Delta\) 会发生什么。那它在局部可以写成:

\[ l(\hat y + \Delta) \approx l(\hat y) + g \Delta + \frac{1}{2} h \Delta^2 \]

这里的 \(g\) 是一阶导数,\(h\) 是二阶导数。

这个式子真正有用的地方,不在于它漂亮,而在于它刚好把上面那两个问题写进去了。

\(l(\hat y)\) 是当前位置本来的损失。

\(g \Delta\) 这一项负责方向感。它决定了你往左还是往右更合理。

\(\frac{1}{2} h \Delta^2\) 这一项负责约束更新幅度。它反映的是局部曲率,也就是这一步到底适不适合走得太猛。

如果你把二阶项拿掉,局部近似其实只剩一条直线。直线可以给方向,但它自己不会给出一个自然的“合适步长”。

二阶项一进来,局部近似就不再只是线性外推,而是变成了一个有弯度的局部二次问题。到这一步,更新幅度才开始变得可算,而不是纯靠拍学习率。

如果把这个局部二次式继续往下推一步,它的最低点其实就在

\[ \Delta^* = -\frac{g}{h} \]

这个式子很短,但信息已经够用了。\(g\) 的符号告诉你修正方向,\(h\) 的大小决定你敢不敢走大步。\(h\) 越大,说明这一带曲率越强,更新就该更保守。

所以更准确的说法不是“二阶更高级”,而是:一阶负责决定修正的朝向,二阶负责给修正幅度加刹车。

曲率稳定的,其实是更新节奏

还是盯着那条曲线看。

如果某一段曲线弯得很厉害,说明你站在当前点附近时,地形变化很快。这个时候你就算方向判断没错,步子迈太大,也很容易直接冲过最低点,跑到另一侧去。接下来下一轮又发现方向反了,再往回修,于是开始来回摆。

这就是很多人说的过冲和震荡。

反过来,如果某一段曲线很平,说明局部地形比较缓。这时候你每次都特别保守,只挪一点点,当然不会炸,但会收敛得很慢。

所以二阶梯度提供的,不是什么额外装饰,而是局部风险提示。

曲率大,说明路急,要慢一点。

曲率小,说明路平,可以放开一点。

量化里这个感觉其实很好懂。你在做横截面收益预测时,经常会碰到一小撮极端样本,比如几只最近突然暴涨、换手也极高的小盘股。它们会把局部误差拉得很大。如果你只看一阶信息,很容易顺着这点误差猛冲过去,把这段行情硬记成规律。二阶项的作用,就是让模型意识到:这里虽然有误差,但这块地形很可能比较险,别一下修太狠。

如果翻成量化里的话,一阶更像是在说:这批股票当前整体该往上修还是往下修。二阶更像是在说:这个判断到底稳不稳,修的时候要不要留余地。

你甚至可以把它想成调仓时的两个动作。先判断该不该加减仓,再判断这次是重手调,还是试探性地挪一点。XGBoost 里的二阶信息,管的主要就是后面这半步。

回到 XGBoost,它为什么非要把这件事算进去

讲到这里,再回头看 XGBoost 的训练过程,就顺了。

XGBoost 不是一口气长成一棵大树,而是一轮一轮往现有模型上补小树。第 \(t\) 轮时,模型已经有了当前预测 \(\hat y_i^{(t-1)}\),现在它准备再加一棵树 \(f_t(x_i)\),于是新预测变成:

\[ \hat y_i^{(t)} = \hat y_i^{(t-1)} + f_t(x_i) \]

真正的问题不是“我能不能再长一棵树”,而是:

这棵树加进去以后,损失到底会下降多少。

原始损失函数通常不好直接和树结构一起处理,所以 XGBoost 就在当前预测点附近做二阶泰勒展开:

\[ l\left(y_i, \hat y_i^{(t-1)} + f_t(x_i)\right) \approx l\left(y_i, \hat y_i^{(t-1)}\right) + g_i f_t(x_i) + \frac{1}{2} h_i f_t(x_i)^2 \]

这一步一做,事情就变了。

原来“加一棵树后损失怎么变”这个问题,是一团不太好直接处理的东西。现在它被改写成了一个局部二次目标。于是树的每个叶子节点,不再只是拍脑袋给一个输出值,而是可以根据当前落进这个叶子的样本的一阶、二阶信息,一起算出一个更合适的修正幅度。

如果第 \(j\) 个叶子的样本集合是 \(I_j\),记

\[ G_j = \sum_{i \in I_j} g_i, \qquad H_j = \sum_{i \in I_j} h_i \]

那么这个叶子的最优输出就是:

\[ w_j^* = - \frac{G_j}{H_j + \lambda} \]

这条式子其实就是上面那句口号在 XGBoost 里的落地版本。

\(G_j\) 这一坨一阶信息告诉模型,这个叶子里的样本整体上该往哪边修。

\(H_j\) 这一坨二阶信息告诉模型,这个叶子里的局部地形到底陡不陡,修正幅度该不该保守一点。

\(\lambda\) 再加一道 L2 正则,把叶子输出继续往回压一把,避免修得太凶。

所以 XGBoost 的强,不是它会补树,而是它每补一棵树,都在问同一件事:方向有没有看错,修正会不会过头。

把它翻译成量化研究里的话,其实就是:

这批股票当前整体是被低估了,还是高估了。

如果要修正,这次修正应该是大胆一点,还是保守一点。

这组样本看起来像是一个稳定结构,还是只是最近一段行情碰巧凑出来的噪音。

XGBoost 比较厉害的地方,就是它不是凭感觉回答这些问题,而是把这些问题都塞回目标函数里一起算。

分裂 gain 为什么也能算出来

叶子值能算,只是第一步。

树接下来还要决定一个更现实的问题:这个叶子要不要继续分裂。

如果不分裂,这批样本共用一个叶子值;如果分裂,左右两边就可以各用各的值。问题是,这一刀切下去,收益到底有没有大到值得付出更复杂的代价。

有了上面的局部二次目标,这件事也能继续算。于是 XGBoost 才会有那条经典的 gain 公式:

\[ ext{Gain} = \frac{1}{2} \left( \frac{G_L^2}{H_L + \lambda} + \frac{G_R^2}{H_R + \lambda} - \frac{G^2}{H + \lambda} \right) - \gamma \]

它本质上就是在算一笔账。

分裂以后,左右两个叶子各自能把损失往下压多少。

减去不分裂时原来那个叶子已经能做到多少。

再减去多长一个分支本身的复杂度代价 \(\gamma\)

如果最后 gain 很大,说明这一刀值得切。如果很小,说明这刀带来的改进不够;如果是负的,就别切。

这就是为什么我说,XGBoost 很少在训练里浪费动作。它不是“反正能切就切”,而是每切一步都在算这一步有没有回报。

放到量化里也一样。你拿着一张因子表,模型准备按“市值 < 某个阈值”再切一刀。真正的问题不是“这个条件有没有故事”,而是“切完以后,预测误差是不是实打实地下去了”。gain 做的就是这件事。

比如现在一整个叶子里混着两类股票。一类是小市值、高换手、近期强势;另一类是大市值、低波动、机构持仓稳定。如果切开以后,两边的最优叶子值明显不一样,而且带来的损失下降足够大,那这刀就值得切。反过来,如果切开以后只是把样本分得更碎,但误差没真正降多少,那这一刀在量化里多半就对应着一句很熟的话:你是在制造解释,不是在提高预测。

在代码里,哪些参数在控制这件事

如果再往工程实现靠一点,你会发现上面这些直觉在代码里其实都有落点。

先看方向是怎么定义的。

你选的是平方误差、逻辑损失还是别的目标函数,决定了每个样本的 \(g_i\)\(h_i\) 怎么算。说白了,你先规定什么叫误差,模型才能知道该怎么纠偏。

再看更新幅度和稳定性主要被谁控制。

  • etalearning_rate:整体缩放每一轮叶子输出,越小越稳,但也越慢。
  • lambda:L2 正则,直接压缩叶子权重,让更新更保守。
  • min_child_weight:要求一个叶子里累计的二阶信息量足够大,才允许继续分裂。这个值越大,模型越不容易为了少数样本就激进地下刀。
  • gamma:要求一次分裂带来的收益足够大,才值得多长一个分支。
  • alpha:L1 正则,进一步压叶子输出,让模型别太激进。

你如果把这些参数和上面的图连起来看,会很清楚。

一阶和二阶提供的是局部方向感和路况信息。

etalambdamin_child_weightgamma 这些参数,决定的是模型最后愿意把这一步走得多快、多猛、多冒险。

量化里调这些参数时,直觉上也可以这样理解。

  • eta 小一点,相当于你承认市场噪音很多,每次只肯小修,不敢一步修满。
  • lambda 大一点,相当于你不太相信某个局部模式值得给出特别激进的预测分数。
  • min_child_weight 大一点,相当于你要求一组样本里得先有足够多、足够稳的“证据”,才允许模型单独给它开一个叶子。
  • gamma 大一点,相当于你告诉模型,不要为了讲一个边际很弱的小故事,就再多切一层树。

如果你做过因子挖掘,会发现这套直觉其实很顺手。很多候选模式在样本内看起来都有点道理,但真正难的是分清楚:它到底是一个能反复出现的结构,还是一段行情刚好配合出来的形状。XGBoost 这些参数,本质上就是在控制模型对“局部故事”的轻信程度。

再回到标题

很多 Kaggle 表格赛里,XGBoost 之所以能赢,当然有树模型擅长处理表格条件结构这层原因。

但更关键的原因是,它不是在盲目地一棵一棵往上叠树。它会在当前点附近先看斜率,再看曲率,把“往哪边修”和“该修多大”一起算进去,然后才决定叶子值和分裂值不值得做。

放到量化里看,这套逻辑尤其重要。因为量化数据最怕的从来不是模型不够复杂,而是模型太容易把阶段性行情、少数极端样本、偶然凑出来的条件组合,当成稳定规律。XGBoost 用一阶、二阶和正则化把每一步都卡得比较严,本质上是在尽量避免这种“修着修着就开始贴噪音”的事。

所以如果把整篇文章压成一句话,我会这样说:

XGBoost 常赢,不只是因为它会长树,而是因为它用一阶信息决定纠错方向,用二阶信息约束修正步子,让每一轮更新都更像一次算过账的修正,而不是盲目的试错。