详细对比 colsample_bytree/bylevel/bynode 三个列采样参数,分类任务在0.7时ACC达峰值0.9082,回归任务建议保留1.0。
快速回答:colsample_bytree、colsample_bylevel 和 colsample_bynode 是 XGBoost 的三个特征采样(列采样)旋钮。三者默认均为 1.0(使用全部特征)。colsample_bytree 每次构建一棵树时采样一次;colsample_bylevel 在树的每一层(深度)重新采样;colsample_bynode 在每个分裂节点重新采样。三者相乘,因此一次分裂实际见到的列数 = bytree × bylevel × bynode × 总特征数。在我们的 5 折测试中,丢弃列有助于分类(峰值 ACC 0.9082 于 0.7),但损害了回归(最佳 RMSE 0.4093 于全量 1.0)。
免责声明:Shakti Tiwari 持有 NISM-Series-XII 认证,并非 SEBI 注册研究分析师。内容仅供教育目的。
如果 subsample 是 XGBoost 的行采样(参见前文 A5),那么 colsample_* 三兄弟就是其特征采样——这正是让 Random Forests 有效工作的核心理念。subsample 问的是"这一轮我看哪些训练行?",而 colsample_* 家族问的是"此刻我被允许在哪些特征上做分裂?"
大多数初学者只接触 colsample_bytree,因为每个教程都会提到它。但 XGBoost 实际上提供了三种不同作用域的列采样,而且它们的行为差异很大:
colsample_bytree —— 在一棵新树开始时采样一次。该树中每次分裂都从同一个缩减后的特征池中选取。
colsample_bylevel —— 在每个深度层级重新采样。因此第 0 层(根节点)可能看到特征 {A, C, F},第 1 层看到 {B, D, G},第 2 层看到 {A, E, H},以此类推。随着树变深,特征池会被刷新。
colsample_bynode —— 最激进的方式。它在每个节点重新采样,即每次独立分裂决策时都重新采样。
这是 XGBoost 参数文档中最容易被误解的角落,我见过生产环境的交易模型因为有人将三者都设为 0.8,实际采样率为 0.8³ = 0.512 却浑然不知而悄悄失调。下面我们将详细解析这个乘法陷阱,因为这才是本文的核心教训。
双层引擎说明(此参数在我们的架构中的位置):我们的实时系统是双层的:第 1 层 = Dhan WebSocket 实时采集(影子/仅预测,未经券商授权从不交易);第 2 层 = 日终审计的 XGBoost/LightGBM 训练核心(walk-forward,成本与滑点模型)。本文讨论的参数——colsample_bytree 及其扩展参数——在第 2 层即日终审计的训练核心中调优,绝不会在实时影子馈送中调优。
Random Forest 类比(帮助理解)
在 Random Forest 中,每棵树都是在行的 bootstrap 样本和每次分裂时特征随机子集上生长的。这种特征随机性正是解相关树的关键,也是 RF 泛化良好的主要原因。XGBoost 是一种提升算法(树是顺序的,不是独立的),所以 naive 的特征采样不那么核心——但它仍然作为正则化器起作用。colsample_bytree 是最接近 RF 的 max_features 的类比:它给予每棵树一个不同且受限的视野。colsample_bylevel 和 colsample_bynode 比经典 RF 更进一步,在每棵树内部更深层次重新随机化,这可以挤出更多多样性(如果过度使用,也会带来更多噪声)。
这三个参数都在官方 dmlc/xgboost 仓库(github.com/dmlc/xgboost,~28.7k stars)的 doc/parameter.rst 和调优指南 doc/tutorials/param_tuning.rst 中有文档记录。关键文档引述,如实转述:列的子采样每树一次(colsample_bytree)、每层一次(colsample_bylevel)、每节点一次(colsample_bynode),且三个比例以乘法方式组合。这就是我们构建的规范陈述。
研究问题 / 假设
假设:将 colsample_bytree 降到 1.0 以下可以解相关树,并应改善样本外泛化——即更低的回归 RMSE 和更高的分类准确率——直到某个点,之后特征过少导致模型饿死,性能崩溃。
测试设计:对 colsample_bytree ∈ {0.3, 0.5, 0.7, 0.8, 0.9, 1.0} 进行扫描,同时保持 colsample_bylevel = colsample_bynode = 1.0(因此我们先隔离 bytree 效应)。我们同时运行回归(reg:squarederror)和分类(binary:logistic)目标,因为特征采样对两种任务的影响可能不同——而且如你所见,确实不同。
在实验之后会从概念上解释两个兄弟参数,因为测得的数据来自单独的 colsample_bytree。
数据与方法框
数据:合成、确定性(seed=42)。回归:20,000 行 × 24 特征。分类:20,000 行 × 24 特征。信号是中等稠密的(多个特征携带真实信号),这对下面的解读很重要。
CV:5 折交叉验证,fold seed = 0。报告的数字是每次对held-out折的样本外(OOS)结果。
模型:gbtree booster。eta=0.1,max_depth=4,subsample=1.0(我们隔离的是列采样,不是行采样),colsample_bylevel=1.0,colsample_bynode=1.0。500 轮 boosting,提前停止(patience=30)在验证折上。
指标:回归 → OOS RMSE(越低越好,↓)。分类 → OOS 准确率(越高越好,↑)。
可复现:完整运行代码位于 xgb_articles/_utils/a2_a7_experiment.py;缓存结果位于 xgb_articles/_utils/a2_a7_results.json(键为 A6_col_reg 和 A6_col_clf)。
主表 —— colsample_bytree vs OOS(bylevel = bynode = 1.0)
回归想要全部列。RMSE 随着 colsample_bytree 上升单调下降(略有波动):0.6010 → 0.4288 → 0.4144 → 0.4194 → 0.4134 → 默认值 1.0 时的 0.4093。在每个步骤中丢弃特征都损害了回归任务。0.3 设置是一场灾难——0.6010 比 0.4093 差约 47%。当稠密信号分布在多个特征上时,隐藏列只会丢弃有用信息。
分类在 0.7 处有一个真正的最佳点。准确率从 0.3 时的 0.9057 攀升到 0.7 时的峰值 0.9082,然后在 0.8 时下降(0.9065),在 0.9 时恢复(0.9072),在默认值 1.0 时为 0.9071。从 1.0 → 0.7 的提升为 +0.0011 准确率,幅度不大但是真实的,与分类边界"特征采样减少过拟合"的理论一致。
两种任务结论不一致——这就是教训。同样的数据族、同样的深度、同样的行;只有目标不同。回归用更多特征效果更好,分类用更少特征效果更好。这就是为什么你必须根据目标和数据集来调优 colsample_bytree,而不是照搬论坛上的"0.8 很好用"的值。
默认值 1.0 并不自动最优,但也鲜少是灾难性的。对于分类,1.0(0.9071)距离峰值仅差 0.0011——所以保持默认值几乎没什么代价。对于回归,1.0 是最优的。危险区域是低端(0.3),而不是顶端。
0.8" magic number"神话受到打击。许多博客文章声称"设置 colsample_bytree=0.8"是通用正则化器。在我们的回归运行中,0.8(0.4194)实际上比 0.7(0.4144)和 0.9(0.4134)都差,更远差于 1.0。这个数字是数据集相关的,不是神圣不可侵犯的。
特征饥饿是不对称且突然的。在回归任务中,从 0.5 降到 0.3 会使 RMSE 从 0.4288 飙升至 0.6010——比例只变化了 0.2,却带来了 40% 的跳升。特征饥饿存在悬崖,而非坡度。在生产环境中,你感受到的是模型突然"忘记"如何定价。
特征饥饿是不对称且突然的。在回归任务中,从 0.5 降到 0.3 会使 RMSE 从 0.4288 飙升至 0.6010——比例只变化了 0.2,却带来了 40% 的跳升。特征饥饿存在悬崖,而非坡度。在生产环境中,你感受到的是模型突然"忘记"如何定价。
如何读懂这条曲线(这样你就能在自己的数据上操作)
将 RMSE 和 ACC 与 colsample_bytree 的值作图。你需要寻找三种形状之一:
单调下降型(本文回归任务):特征越多越好;保持在 1.0 附近,如果想要一点正则化效果可以设为 0.9。
驼峰型(本文分类任务):上升到峰值后下降——那个峰值(0.7)就是你的调优目标。
U 型:太少和太多都不好;U 型底部就是最优点。
不要逐行读表格。要读形状。形状告诉你特征采样是否有帮助。如果曲线在整个扫描范围内都很平,那么 colsample_bytree 不是你的杠杆——去调优 max_depth 或 subsample。
import xgboost as xgb
from sklearn.model_selection import KFold
import numpy as np
## Load your X (n_samples, 24) and y
## dtrain = xgb.DMatrix(X, label=y)
def cv_colsample(colsample, objective, num_boost=500):
kf = KFold(n_splits=5, shuffle=True, random_state=0)
scores = []
for tr, te in kf.split(X):
p = dict(
max_depth=4,
eta=0.1,
subsample=1.0, # isolating COLUMN sampling
colsample_bytree=colsample,
colsample_bylevel=1.0, # siblings held at default
colsample_bynode=1.0,
objective=objective, # 'reg:squarederror' or 'binary:logistic'
verbosity=0,
)
# early-stop on a validation slice of tr
bst = xgb.train(p, dtrain_sub, num_boost)
pred = bst.predict(dtest_sub)
# RMSE for reg, ACC for clf
scores.append(score(pred, y_te))
return np.mean(scores)
for c in [0.3, 0.5, 0.7, 0.8, 0.9, 1.0]:
print(c, cv_colsample(c, 'reg:squarederror'),
cv_colsample(c, 'binary:logistic'))
要将实验扩展到两个兄弟参数,只需改变 colsample_bylevel 或 colsample_bynode(或者同时改变两者),同时将其他参数保持在 1.0,然后观察有效采样的复合效果。
什么失败了 / 反面证据
我们预期 colsample_bytree < 1 也会对回归任务有帮助。理论上说"特征采样 = 正则化 = 更好的 OOS"。现实:回归 RMSE 随列数减少而恶化,一直恶化到默认值。正则化收益只出现在分类任务中。这是最重要的反直觉结果,也是我们同时报告两个目标函数而不是隐藏回归线的原因。
0.8 的民间传说站不住脚。多份调优指南将 0.8 视为安全的正则化手段。我们的数据显示 0.8 对分类是一个局部低谷(0.9065,低于 0.7 的峰值,甚至低于 0.9),对回归则严格劣于 1.0。
三个参数叠加没有免费午餐。理论上,将 bytree=bylevel=bynode=0.8 会得到有效采样率 0.512——我们没有在扫描中测量这个,但回归 0.3 的结果(有效饥饿)是强烈警告:三重采样很可能在这个数据集上同时摧毁两个任务的表现。
合成数据。密集信号的合成数据集是一个干净的实验环境。真实的、稀疏的金融特征集(例如订单流碎片、期权 Greeks)可能从列采样显示出更强的正则化收益,因为噪声特征占主导,隐藏它们有帮助。
单一深度 / 单一学习率。我们固定了 max_depth=4, eta=0.1。在更深的树上,每个节点的采样(colsample_bynode)影响更大,因为有更多的分裂需要随机化。我们的扫描只隔离了 bytree;兄弟参数是从概念上描述的。
单一随机种子族。5 折 CV seed=0 对趋势是稳健的,但单个数据集不能证明回归与分类的分化是普适的。将其作为一个强假设,用你自己的数据重新测试。
实践要点(生产检查清单)
先隔离再调优。将 bylevel = bynode = 1.0,用 colsample_bytree 单独调优。只有在理解 bytree 曲线之后才触碰兄弟参数。
扫描而非猜测。始终运行 {0.3, 0.5, 0.7, 0.8, 0.9, 1.0} 网格并读出形状(单调下降 / 驼峰 / U 型)。我们的回归案例证明了默认值 1.0 可以是赢家。
注意乘法陷阱。有效采样 = bytree × bylevel × bynode。如果你将三个都设为 0.8,你得到的是 0.512,而不是 0.8。在调优追踪器中记录这个乘积。
按目标函数分别调优。如果你的流水线同时训练回归模型和分类模型(在期权策略栈中很常见:连续目标的回归,方向判断的分类),分别为每个模型独立调优 colsample_*。没有一种设置能适合所有人。
用兄弟参数处理顽固过拟合。如果单独用 colsample_bytree 无法买到足够的泛化能力,先升级到 colsample_bylevel(在每个深度重新随机化),再考虑 colsample_bynode(在每个分裂重新随机化)。bynode 是最强也是最嘈杂的;留到最后使用。
与 subsample 配合使用(A5)。行采样和列采样组合成强大的正则化搭档——但要在不同的网格上分别调优,这样才能归因收益。
这属于第二层。记住两层架构的注释:这些是 EOD 训练核心设置,通过成本和滑点模型进行walk-forward验证。Dhan WebSocket shadow feed 只消费训练好的模型;它从不在运行时重新采样特征。
兄弟参数解析器(bytree 已完成,现在说 bylevel 和 bynode)
既然 colsample_bytree 的实测效果已经清晰,以下是另外两个参数如何配合:
colsample_bylevel(默认 1.0):在树的每个深度层级重新采样列池。可以这样理解:bytree 决定了整个树的书库中有哪些书;bylevel 则是你每下一层楼就重新补给书架。它在不像低 bytree 那样严重饥饿单棵树的情况下,为浅层分裂和深层分裂之间增加了多样性。
colsample_bynode(默认 1.0):最细粒度——在每个分裂节点进行新鲜的列采样。这比经典随机森林更随机(RF 也是每个分裂采样,但它没有更深层的再采样)。它最大化树的多样性,是三个参数中最强的正则化器,但也最有可能注入噪声,因为本该使用特征 X 的分裂可能永远看不到它。
组合方式:因为每个阶段都是从上一阶段已经缩小过的集合中采样,所以在给定分裂时实际符合条件的列数 = colsample_bytree × colsample_bylevel × colsample_bynode 乘以原始特征数。用 (0.9, 0.9, 0.9) 构建的树在每个分裂时实际看到的是 0.729 的特征——而不是 0.9。这就是为什么激进的三重采样能悄悄饿死模型,就像我们的 bytree=0.3 回归实验那样。
一个好的心智模型:bytree = "这整棵树可以使用哪些特征?",bylevel = "这个深度可以使用哪些特征?",bynode = "这个具体分裂可以使用哪些特征?" 每个问题进一步缩小候选集,而乘积才是分裂算法实际优化的对象。
Q: subsample 和 colsample_bytree 的区别是什么?A: subsample 在每个 boosting 轮次采样行(训练样本)——对数据做 bagging。colsample_bytree 为每棵树采样列(特征)——对输入做 bagging。它们从两个正交方向攻击过拟合,组合起来效果很好。(关于 subsample 见 A5。)
Q: 默认值是什么?A: 三个 colsample_* 参数都默认为 1.0——即不进行列采样,每个特征始终都合格。只有当你把它们降到 1.0 以下时正则化才会生效。
Q: 我应该用 bytree、bylevel 还是 bynode?A: 从 colsample_bytree 开始——它是最粗粒度、最可解释的。如果仍然过拟合,下一步尝试 colsample_bylevel。将 colsample_bynode 留作处理顽固情况,因为它最嘈杂。而且永远不要忘记它们是相乘的关系。
Q: 为什么我降低列数后回归反而变差了?A: 当密集信号分布在许多特征上时,隐藏列只是在丢弃真实信息——没有什么噪声特征可以被"淹没"。相比之下,分类从去相关中获益,因为它的决策边界对任何单一主导特征在每棵树上都占主导地位更敏感。务必为你的目标函数检查曲线形状。
Q: 这属于我们系统的哪一层?A: EOD 审计的训练核心(第二层)。Dhan WebSocket shadow feed(第一层)只在已训练好的模型上运行推理。
colsample_bytree、colsample_bylevel 和 colsample_bynode 是 XGBoost 三个特征采样旋钮,均默认 1.0,且会相互叠加。在我们的 5 折测试中,colsample_bytree 对回归有害(最佳 RMSE 0.4093,值为 1.0),但对分类有帮助(最佳 ACC 0.9082,值为 0.7)。请按任务目标单独调优,观察曲线形态,并警惕乘法叠加陷阱。
Profiles: about.me · optiontradingwithai.in · github · whatsapp · x/twitter
参考来源
dmlc/xgboost repository — doc/parameter.rst(colsample_bytree / colsample_bylevel / colsample_bynode 定义;乘法叠加说明)。github.com/dmlc/xgboost(~28.7k stars)。
dmlc/xgboost — doc/tutorials/param_tuning.rst(将列采样作为正则化策略)。
实验来源:xgb_articles/_utils/a2_a7_experiment.py;缓存结果 xgb_articles/_utils/a2_a7_results.json(键为 A6_col_reg、A6_col_clf)。
作者 / 规范署名
Shakti Tiwari — Nifty Option Trader,XGBoost 专家。NISM-Series-XII 认证;非 SEBI 注册研究分析师。内容仅供教育目的。
关于我:https://about.me/shaktitiwari
网站:https://optiontradingwithai.in
WhatsApp:https://wa.me/919169650895
上一篇:A5 subsample | 下一篇:A7 lambda / alpha
著作:《Option Trading with AI》(B0H9ZNTBPK)| 《The AI Opportunity》(B0H9BBFKDQF)