3.1 核心参数 (Core Parameters) 本节摘要:核心参数决定一棵棵决策树长成什么样、模型一共学多少步。其中 numleaves、maxdepth、learningrate 是控制模型容量的三只"复杂度旋钮",lambdal1、lambdal2、minsplitgain、minchildsamples 与特征采样则是抑制过拟合的"闸门"。本节先解释为什么 leaf-wise 生长的 LightGBM 尤其怕树长得太野,再逐一拆解这些旋钮与闸门各自的作用点、默认值以及典型取值区间,最后给出一个"先判断欠拟合还是过拟合,再决定往哪边拧"的调参顺序。
本节摘要:核心参数决定一棵棵决策树长成什么样、模型一共学多少步。其中 num_leaves、max_depth、learning_rate 是控制模型容量的三只"复杂度旋钮",lambda_l1、lambda_l2、min_split_gain、min_child_samples 与特征采样则是抑制过拟合的"闸门"。本节先解释为什么 leaf-wise 生长的 LightGBM 尤其怕树长得太野,再逐一拆解这些旋钮与闸门各自的作用点、默认值以及典型取值区间,最后给出一个"先判断欠拟合还是过拟合,再决定往哪边拧"的调参顺序。
阅读完本节,你应当能够:
先承认一件事:LightGBM 的参数表里,真正每天都要碰的其实就那么十来个,其余大半是留给分布式训练、IO 细节或者冷门分布的。但麻烦在于,这十来个核心参数不是各自为政的独立开关,而是一张互相拉扯的网。你把 num_leaves 调大,树变复杂了,过拟合风险跟着上升,于是又得去动 min_child_samples 或正则化来兜底;你把 learning_rate 调小,每步走得更稳了,却要更多棵树才能收敛,迭代次数又得跟上。
这种耦合,根源在 LightGBM 的树生长方式。传统 GBDT 通常按层生长,整棵树一层层往下铺,深度和叶子数天然绑在一起。LightGBM 用的是 leaf-wise 策略,每次只挑"收益最大"的那个叶子去分裂。带来的结果是:同样给 31 个叶子,leaf-wise 的树可能长得又深又偏,某些分支一路钻到底,另一些分支早早停下。树深了,容量上去了,但过拟合的通道也被悄悄打开。所以在这一节里,我会把核心参数分成两拨来讲——一拨是"复杂度旋钮",负责把模型容量往上抬;另一拨是"防过拟合闸门",负责把容量往下压。理解了这两拨力量怎么对抗,你就抓住了 LightGBM 调参的主线。
先看 num_leaves,它是 LightGBM 里我最常动的第一个参数。它控制的是"每棵树最多允许长多少个叶子"。默认值是 31,看起来不大,但别忘了 leaf-wise 的树不是对称铺开的,31 个叶子已经能形成一棵相当深的树。经验上,num_leaves 每翻一倍,模型的表达能力就上一个台阶,同时过拟合的风险也上一个台阶。我见过不少新手一上来就把 num_leaves 设成 127、255,理由是"让模型学得更细",结果训练集指标漂亮得离谱,验证集一塌糊涂。
max_depth 则是另一个约束维度的开关,默认值是 -1,也就是不限制深度。它与 num_leaves 的关系近似为"叶子数约等于 2 的深度次方":深度为 3 的满树大约有 8 个叶子,深度为 7 大约有 128 个叶子。既然两者都能限制复杂度,为什么通常优先调 num_leaves 而不是 max_depth?因为 num_leaves 对复杂度的控制更直接、更贴合 leaf-wise 的实际生长方式——它数的是"终点的数量",而 max_depth 数的是"路径的长度"。对 leaf-wise 而言,两个模型叶子数相同,一棵深一棵浅,它们表达能力的差异远比"深度差一层"更显著。所以 LightGBM 官方也建议:优先用 num_leaves 控制复杂度,max_depth 只在需要"硬性截断极端深度"时才显式设置,比如当你担心某棵树叶子里钻进去的噪声特征形成一条极长的路径时。

learning_rate 也叫收缩率,默认 0.1,控制的是"每棵新树对最终预测的贡献占比"。把它想象成每次只迈出预测误差的一小步:learning_rate 越大,每棵树迈得越狠,收敛快,但容易一步踩过头,在训练集上震荡甚至过拟合;learning_rate 越小,每棵树只修正一点点,走得稳,泛化往往更好,代价是要走很多步。
于是它天然和 num_iterations(也就是树的总棵数,默认 100)构成一对跷跷板。learning_rate 减半,往往需要把树的棵数翻倍,才能补回总的前进距离。这也解释了为什么很多竞赛方案里会出现 learning_rate 等于 0.01、0.02 这类"看起来很慢"的配置——它们几乎都配着几千上万棵树,外加早停来收尾。反过来,如果你时间预算很紧,想快速出一个可用基线,0.1 甚至 0.2 配上几百棵树也完全够用。
这里必须把早停拿出来单独强调。手调 num_iterations 是个无底洞:设少了欠拟合,设多了过拟合还浪费时间。正确的做法是给足一个上限,再打开早停,让验证集指标连续若干轮不涨就自动停下来。LightGBM 里的 early_stopping_rounds 干的就是这件事,它和 learning_rate、num_iterations 三者配合,能把"该训多少棵树"这个最烦人的问题交给数据自己回答。若想让学习率随训练逐渐衰减、后期步长越来越小,框架也提供了对应的衰减开关,但对多数任务而言,固定一个小学习率再加早停就已经足够稳。
复杂度旋钮负责把容量抬上去,闸门负责把容量压下来。LightGBM 里最常用的四道闸门,作用点各不相同。
第一道是 lambda_l1 与 lambda_l2,也就是 L1、L2 正则化系数,默认都是 0。它们在损失函数上追加对叶子输出值的惩罚项,L1 倾向于把部分叶子权重压到接近零、起到稀疏化作用,L2 则把整体权重往小里拉。数据量小、特征又多时,把这俩从 0 调到 0.1、1.0 量级,通常能明显压住过拟合。
第二道是 min_split_gain,别名 gamma,默认 0。它规定:一次分裂带来的增益必须超过这个阈值才允许发生。增益不够的分裂直接被拒绝,等于给树的生长设了一道"收益门槛",能挡住那些纯粹拟合噪声的微小分裂。
第三道是 min_child_samples(默认 20)与 min_child_weight(默认 0.001),它们限制的是"叶子至少得装下多少东西"。min_child_samples 数样本个数,min_child_weight 数样本权重和(对二阶梯度意义上的权重)。叶子被逼着保持一定的最小规模,就不容易在末端长出只覆盖三五个样本的细碎叶子。这是最直观的一道闸门,也是大数据集上调大它、小数据集上调小它的高频操作。
第四道其实是"随机化"闸门,放到下一节单独讲,因为它和前面三道"惩罚型"闸门思路不同——它靠引入随机性来降低方差,而不是靠约束来降低容量。
⚠️ 常见坑:同时把多道闸门都拧到最大,等于把模型五花大绑,最后从过拟合直接摔进欠拟合。闸门要一道一道加、一格一格调,每加一道都回验证集看一眼。我的习惯是:先调 min_child_samples 或 min_split_gain 里的一个,再看要不要补正则化。
feature_fraction(别名 colsample_bytree)和 bagging_fraction(别名 subsample)是两道"随机化"闸门,思想都来自随机森林。feature_fraction 默认 1.0,控制每棵树随机抽取多大比例的特征;bagging_fraction 默认 1.0,配合 bagging_freq 控制每隔几轮按多大比例随机抽取样本。它们削减的不是单棵树的容量,而是树与树之间的相关性——每棵树看到的特征或样本都略有不同,各自学到的模式就不那么雷同,集成起来方差更低、泛化更好。
这俩通常比正则化更"温和",也更好上手。特征维度很高时,把 feature_fraction 设到 0.7、0.8 往往能同时拿到提速和防过拟合两个收益;样本量很大时,把 bagging_fraction 设到 0.8、bagging_freq 设成 1,效果也很稳。要注意 bagging_freq 为 0 表示关闭样本采样,只有设成正整数才生效,这个默认值坑过不少人。
下面这张流程图,把"先判断再调参"的顺序串了起来:
| 参数 | 默认值 | 归属 | 一句话作用 | 典型调整方向 |
|---|---|---|---|---|
| num_leaves | 31 | 复杂度旋钮 | 每棵树最多叶子数 | 过拟合调小,欠拟合调大 |
| max_depth | -1 | 复杂度旋钮 | 树的最大深度,-1 不限 | 需要硬截断时再设 |
| learning_rate | 0.1 | 复杂度旋钮 | 每棵树的贡献步长 | 调小需同步加迭代 |
| num_iterations | 100 | 复杂度旋钮 | 树的棵数 | 配合早停,给足上限 |
| lambda_l1 / lambda_l2 | 0 | 防过拟合闸门 | 叶子权重正则化 | 小数据多特征时上调 |
| min_split_gain | 0 | 防过拟合闸门 | 分裂的最低增益门槛 | 过拟合时小幅上调 |
| min_child_samples | 20 | 防过拟合闸门 | 叶子最少样本数 | 大数据上调,小数据下调 |
| feature_fraction | 1.0 | 随机化闸门 | 每棵树特征采样比例 | 高维特征降到 0.7 上下 |
| bagging_fraction | 1.0 | 随机化闸门 | 样本采样比例 | 配 bagging_freq 生效 |
💡 关键直觉:判断"欠拟合还是过拟合"比记住每个参数的默认值更重要。训练集与验证集差距大、验证集不涨反跌,是过拟合,去加闸门;两者都差,是欠拟合,去拧大旋钮。方向对了,调参才是有意义的搜索,而不是碰运气。
如果懒得从零起步,下面这份配置可以当作一个稳妥的默认起点,先跑通再按方向微调:
# 一个可落地的默认起点 params = { 'objective': 'binary', 'metric': 'auc', 'boosting_type': 'gbdt', 'learning_rate': 0.05, 'num_leaves': 31, 'min_child_samples': 20, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 1, 'lambda_l1': 0.0, 'lambda_l2': 0.0, } # 训练时把树的棵数给足,交给早停去截断
这段配置的思路是:用小学习率配足够多的迭代次数,把树复杂度留在默认的 31 个叶子,用特征与样本双层采样先压住方差,正则化先不动、留作过拟合出现时的后手。等验证集曲线稳定下来,再针对性地去拧某一个旋钮,而不是一次性把所有开关都改满。
我常说的调参心法是"先保守起步,再单点试探"——一次只动一个变量,才能清楚是哪一步起了作用。这比拿着网格搜索漫无目的地扫一遍效率高得多,也更容易积累出对每个参数的手感。等哪天你能凭验证集曲线的形状猜出该动哪个参数,就说明这一节的内容真正内化了。
下一节我们把视角从"模型长什么样"转到"模型往哪个方向学"——目标函数。正是它决定了你填进 objective 的那个字符串,究竟该是 regression 还是 binary、multiclass、lambdarank。