
不少同学跑LightGBM都是直接lgb.train(params, ...)一把梭参数全靠默认然后看运气。其实LightGBM的参数体系不算复杂但它和XGBoost、传统GBDT的调参逻辑有本质区别如果拿XGBoost那套思路硬套经常会出现调了半天还不如默认参数的情况。这篇文章我把自己在实际项目中调LightGBM的经验完整梳理一遍从参数含义到底层逻辑从基础配置到rank任务和自定义目标函数尽量一篇讲透。1. 先理解LightGBM的三个底层机制否则调参就是瞎调1.1 直方图算法与Leaf-wise生长为什么LightGBM快又为什么容易过拟合LightGBM和XGBoost最大的区别不在精度而在训练机制。XGBoost用的是预排序pre-sorted算法每个特征在训练前先把特征值排序然后遍历所有可能的分裂点。LightGBM换成直方图Histogram算法把连续特征离散成固定数量的bin默认255个然后在bin上找最优分裂点。这就把分裂点查找的复杂度从“特征值数量”降到了“bin数量”训练速度提升一个量级内存占用也大幅下降。但真正影响调参思路的是它的生长策略。XGBoost默认是level-wise按层生长每层所有叶子一起分裂这会让树长得比较“平衡”不容易过拟合但代价是很多分裂其实是没有意义的浪费算力。LightGBM走的是leaf-wise路线每次只选择分裂收益最大的那个叶子来分裂同样是生长N个叶子leaf-wise的loss下降一定比level-wise快。问题也出在这里leaf-wise容易长出那种“一条道走到黑”的深树某个叶子路径上的样本越分越少噪声被学进去直接表现就是训练集分数高得离谱验证集不涨反跌。所以LightGBM官方文档里那句“leaf-wise可能过拟合建议调大num_leaves”不是客套话是真的会过拟合。1.2 GOSS采样它不是简单扔掉样本而是“嫌贫爱富”LightGBM的GOSSGradient-based One-Side Sampling基于梯度的单边采样原理是计算每个样本的梯度梯度大的样本说明当前模型对它预测很差这些样本保留并且是全部保留梯度小的样本说明模型已经学得不错了按比例随机采样一部分。这样既保留了信息量最大的样本又减少了训练数据量。用大白话说就是老师讲题的时候重点讲那些错得离谱的学生已经会的学生可以少听几句。但GOSS有个坑——它对噪声非常敏感。如果数据本身标注质量差那些“梯度大”的样本可能不是难样本而是错标签样本GOSS会把它们当宝贝反复学习模型就越学越歪。1.3 EFB互斥特征绑定自动帮你做特征融合EFBExclusive Feature Bundling互斥特征绑定的思路是把那些“几乎不同时取非零值”的特征捆成一捆当成一个特征来建直方图。比如一个用户画像里“是否是VIP”和“是否是黑名单用户”这两个特征基本是互斥的你不能同时是VIP又是黑名单EFB就把它们绑到一个bin里。这一手对稀疏特征极多的高维数据非常有效训练速度能再快一倍以上。理解了这三个机制后面的参数调优才有依据。比如你知道leaf-wise容易过拟合就明白num_leaves才是LightGBM里最核心的复杂度控制参数而不是max_depth。你知道GOSS的原理就明白为什么有时候把subsample调大反而更稳。2. 基础参数配置先把默认参数跑通再谈优化2.1 最常用参数速查表与适用场景LightGBM的参数有上百个但实际项目中天天打交道的核心参数不超过20个。我把它们按用途分组列在下面这样查起来方便。参数名作用典型取值备注objective目标函数regression,binary,multiclass,lambdarank决定了损失函数的计算方式metric评估指标rmse,auc,ndcg,map可以传多个逗号分隔learning_rate学习率/步长0.01~0.1越小越稳树越多num_leaves单棵树最大叶子数31~255核心复杂度控制不是越大越好max_depth最大深度默认-1不限制leaf-wise下不建议和num_leaves同时用力过猛min_data_in_leaf叶子最少样本数20~100防止过拟合的关键参数feature_fraction特征采样比例0.6~0.9每棵树随机选取的特征比例bagging_fraction样本采样比例0.6~0.9每轮迭代采样的样本比例bagging_freq采样频率1bagging_fraction 0时需同时设置lambda_l1/lambda_l2L1/L2正则化0~10特征多、维度高时效果明显cat_smooth类别特征平滑系数10~20处理高基数类别特征时调大n_estimators/num_round树的数量100~10000必须配合早停使用2.2 训练集验证集划分与early stopping的正确姿势LightGBM调参的第一步不是调任何参数而是先把验证集和早停机制配好。没有早停的LightGBM调参就是闭着眼开车因为你根本不知道哪个树数量是合适的。import lightgbm as lgb from sklearn.model_selection import train_test_split X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, random_state42, stratifyy ) train_data lgb.Dataset(X_train, labely_train) val_data lgb.Dataset(X_val, labely_val, referencetrain_data) params { objective: binary, metric: auc, learning_rate: 0.1, num_leaves: 31, verbose: -1 } model lgb.train( params, train_data, num_boost_round10000, valid_sets[val_data], callbacks[lgb.early_stopping(stopping_rounds100)] )这里有两个细节值得展开。第一valid_sets传的是列表如果传两个验证集LightGBM会把早停的判断依据定在最后一个验证集上所以第一个验证集用来看趋势最后一个验证集用来决策。第二lgb.early_stopping(stopping_rounds100)的意思是验证集指标连续100轮不提升就停止训练然后自动用表现最好的那棵树作为最终模型。这个“连续100轮”不是随便拍的它和学习率有关——学习率越小指标提升越平缓早停轮数就该设大一点否则容易提前停在一个次优位置。2.3 学习率与迭代次数的权衡一个被严重低估的组合先说结论学习率和num_boost_round是一对强耦合参数学习率减半达到同等效果所需的树数量差不多是翻倍的。这个关系背后的逻辑非常直接GBDT是一步步在残差方向上逼近目标学习率是步长树的数量是步数。步长太大每一步跨得太猛容易跨过最优点验证集上表现为“先快速下降后突然反弹”步长太小需要走很多步训练时间暴涨而且如果树数量没给够模型会欠拟合。我在项目里的做法是先用0.1的学习率快速跑通一个基线看val指标大概在一个什么水平然后降到0.05或者0.01把num_boost_round按比例放大配合早停重新训练。这一步带来的收益往往比调其他任何参数都大。很多竞赛选手的配置是learning_rate0.01甚至0.005配合几千棵树效果非常好代价就是训练时间被拉长。3. 核心调优实战按优先级一步步来3.1 第一步固定学习率优先调num_leaves和min_data_in_leaf我见过太多人一上来就调learning_rate这是个误区。正确的顺序应该是先把学习率固定住0.1然后去调模型复杂度相关的参数等找到合适的复杂度后再回头降学习率。num_leaves是LightGBM里最重要的单参数它控制每棵树的复杂度。num_leaves31是默认值对应深度为5的满二叉树2^5-131这个值的语义是“允许模型拟合多复杂的模式”。num_leaves127差不多是7层满二叉树的叶子数模型拟合能力大幅提升但过拟合风险也随之上升。min_data_in_leaf是一个经常被忽视但非常有效的参数。它在每次分裂前做检查如果分裂后某个叶子上的样本数小于min_data_in_leaf就放弃这次分裂。这个参数对噪声的抑制效果出奇地好特别是当你的数据里有极端离群值时。建议范围是20-100数据量大可以设更大。实操中可以这样用一个循环快速找num_leaves的合适区间for leaves in [15, 31, 63, 127, 255]: params[num_leaves] leaves cv_result lgb.cv( params, train_data, num_boost_round1000, nfold5, early_stopping_rounds50, seed42 ) print(fnum_leaves{leaves}, best_iter{cv_result[auc-iter]}, best_score{cv_result[auc-mean][-1]})lgb.cv的好处是它内置了交叉验证并且自动使用早停返回的结果里auc-mean[-1]就是最佳迭代次数的对应分数auc-iter是达到最佳分数时的迭代次数。注意观察一个现象当num_leaves增大时最佳迭代次数通常会往后推因为每棵树的拟合能力变强了但学习率不变所以需要的树反而更多了——这其实是过拟合的早期信号。3.2 第二步正则化参数lambda_l1、lambda_l2和min_gain_to_split当你的模型在训练集上表现很好但验证集跟不上除了调num_leaves还可以靠正则化来压制。LightGBM的正则化参数比XGBoost少一个gamma对应物对应min_gain_to_split但lambda_l1和lambda_l2的作用机制是一样的都是往损失函数里加模型复杂度的惩罚项。具体来说LightGBM的叶子节点权重是通过求解一个带正则项的二次规划得到的。L2正则lambda_l2会让叶子权重整体变小相当于让每棵树的输出都“温和”一些L1正则lambda_l1除了让权重变小还倾向于把某些权重直接压到0产生稀疏效果。特征特别多且大多数是无用特征时L1的效果更明显。min_gain_to_split则是设置一个分裂收益的阈值只有当某个分裂带来的loss下降大于这个阈值时才执行分裂。这个参数调大了以后树会变浅整体模型更保守但它和num_leaves一起调的时候要小心——两者都压模型可能直接欠拟合。我一般的策略是num_leaves和min_data_in_leaf先定然后微调lambda_l2最后再看min_gain_to_split要不要动。3.3 第三步采样参数是如何影响模型偏差和方差的采样参数包括feature_fraction和bagging_fraction。有的资料管feature_fraction叫colsample_bytree这是XGBoost的叫法意思都是每棵树随机选取的特征比例。bagging_fraction是每轮迭代的样本采样比例配合bagging_freq1使用相当于每棵树用不同的样本子集训练。这两个参数背后的统计意义是降低树之间的相关性。GBDT的集成要起作用前提是树之间有足够的差异性。如果每棵树都用同样的特征、同样的样本去训练那集成只是把同一个模型复制了N次方差一点没降。加入随机采样后每棵树的视角都有所不同集成后的结果才会更稳定。但要注意bagging_fraction和GOSS是两套独立的采样机制。如果你在params里同时设置了bagging_fraction和boost_from_average等相关配置并且之前在训练日志里看到“GOSS”字样说明GOSS也在生效。GOSS是从“样本重要性”角度采样bagging是从“随机性”角度采样两者不冲突但bagging_fraction不要设太低建议不低于0.5否则每棵树能看到的数据太少单棵树质量下降集成效果反而变差。我在实际项目中的经验是样本量很大几十万以上时bagging_fraction0.7~0.8能稳定提升泛化能力样本量小几万以下时这个参数别动保持默认或设0.9以上否则方差降了偏差就会升。3.4 类别特征处理category特征的两种正确打开方式很多人在LightGBM里处理类别特征的方式是OneHot编码——这其实浪费了LightGBM的一大优势。LightGBM原生支持类别特征你需要做两件事第一把类别特征列的类型设为category。用pandas的话就是df[col] df[col].astype(category)如果是lgb.Dataset可以在构造时传categorical_feature参数指定列名或索引。第二就是调cat_smooth和cat_l2。LightGBM处理类别特征的方式是把类别值映射到梯度统计量上然后对统计量排序寻找最优分裂点。cat_smooth是拉普拉斯平滑系数防止某个类别样本数太少导致统计量不稳定取值越大对低频类别的惩罚越重。如果一个类别特征的基数很高比如城市代码有几千个取值建议把cat_smooth调大一些否则模型会抓住那些“样本数极少但碰巧目标均值很高”的类别造成严重的过拟合。另一个方法是自己把高基数类别特征做target encoding再喂进去但这样做的风险是容易引入target leakage一定要在训练集上做编码并且把编码结果保存下来用于验证集和测试集。我在实际项目中尽量先尝试LightGBM原生类别特征方案卡住了再考虑target encoding。3.5 不同目标函数下的参数差异回归、二分类、多分类LightGBM的objective不同损失函数对梯度的影响不同调参的重点也有差异。回归任务objectiveregression最简单损失一般是MSE或MAE。MSE对大误差样本极其敏感如果数据里有离群点梯度会被少数样本主导这时候调大min_data_in_leaf能有效压制。MAE的梯度是常数对离群点不那么敏感但训练收敛慢早停轮数要适当加大。二分类任务objectivebinary核心指標是AUC或LogLoss。如果你的正负样本极不平衡建议先把is_unbalance设为true或者手动设置scale_pos_weight。LightGBM底层会根据这个参数调整正样本的权重相当于把少数类的错误代价调大。多分类任务objectivemulticlass配合num_class有它特有的麻烦——树的数量其实是“每类的树数量”实际模型大小是num_class倍的。所以多分类的num_leaves要往下压否则模型文件会非常大推理速度也受影响。我一般把多分类的num_leaves控制在31以内靠增加num_boost_round来补精度。还有一个容易被忽略的就是metric的选择。objective决定模型怎么学metric决定模型怎么评。早停是看metric的所以metric选得不合适早停的时机就不对。二分类我用auc做早停回归用rmse但如果回归的最终业务指标是MAE那metric用mae更贴近实际目标——LightGBM支持objectiveregression但metricmae学的时候用MSE的梯度评的时候看MAE这个组合很实用。4. 高级玩法自定义目标函数、rank任务与多模型对比4.1 LightGBM vs XGBoost什么时候该换什么时候不该换“LightGBM和XGBoost哪个好”是个老生常谈的问题但结论不是绝对的。我实测下来的经验是数据量大百万级以上、特征维度高上千维LightGBM优势明显训练速度快好几倍内存占用低一个量级。数据量中等十万级、特征少几十个两者精度差距很小XGBoost的Level-wise生长方式反而更稳不容易过拟合。样本量极小几千条建议直接用XGBoostLightGBM的leaf-wise在这种数据量下太容易过拟合你得费很大劲去压num_leaves。类别特征多LightGBM完胜XGBoost目前对类别特征的原生支持弱不少。需要精细控制每个特征的分裂方式XGBoost的预排序机制在找分裂点时的确定性更强。一个实用的做法是先跑LightGBM拿到基线如果精度不达标再跑XGBoost对比最终选精度更高的那个做线上模型。两个框架的early stopping和CV逻辑很相似切换成本并不高。4.2 自定义objective和metric的完整示例LightGBM真正强大的一点是支持自定义目标函数和评估指标。这意味着你可以为业务定制损失函数而不局限于内置的MSE、LogLoss这些。举个例子广告点击率预估里有时候我们希望模型重点关注TOP位置的排序质量直接优化AUC已经不够了可以自定义一个加权交叉熵。实现方法如下import numpy as np import lightgbm as lgb def weighted_binary_cross_entropy(preds, train_data): weights train_data.get_weight() labels train_data.get_label() preds 1.0 / (1.0 np.exp(-preds)) # sigmoid grad (preds - labels) * weights hess preds * (1.0 - preds) * weights return grad, hess def weighted_binary_error(preds, train_data): labels train_data.get_label() preds 1.0 / (1.0 np.exp(-preds)) return weighted_error, np.average((preds 0.5) ! labels, weightstrain_data.get_weight()), False model lgb.train( params, train_data, num_boost_round1000, valid_sets[val_data], fobjweighted_binary_cross_entropy, fevalweighted_binary_error, callbacks[lgb.early_stopping(50)] )这里fobj接收的是原始预测值不是概率函数内部要自己做sigmoid转换。返回的grad是一阶导数梯度hess是二阶导数海森矩阵LightGBM用这两个值来拟合下一棵树。feval返回三个值指标名、指标值、是否越大越好。自定义objective踩坑最多的地方在于preds进来的时候LightGBM还没有做任何转换如果你用的是内置binaryLightGBM会自动加sigmoid一旦换成fobj这个转换就没了你在计算grad时必须自己处理。另外自定义metric里的preds同样需要手动sigmoid否则算出来的错误率对不上。4.3 排序任务实战lambdarank与xe_ndcg指标排序学习Learning to Rank是LightGBM的一个招牌功能很多搜索引擎、推荐系统都在用。热词里提到的“rank和xendcg”其实就是lambdarank目标函数和xe_ndcg评估指标。先说明一下xe_ndcg在LightGBM里正确写法是xe_ndcg或ndcg全称是“指数版本的NDCG”Exponential NDCG它在计算NDCG时对相关度分数做了指数变换放大了头部排序的差异。当你的业务更关注“排在最前面的几个结果准不准”时用xe_ndcg比用普通ndcg更贴合需求。Lambdarank的params配置和普通任务不太一样params { objective: lambdarank, metric: ndcg, ndcg_eval_at: [1, 3, 5, 10], label_gain: [0, 1, 3, 7, 15], learning_rate: 0.05, num_leaves: 63, min_data_in_leaf: 50, lambdarank_truncation_level: 10 }这里label_gain是相关性等级对应的增益值一般配合5档相关度使用0-4增益按指数增长这正好和xe_ndcg的指数变换逻辑呼应。ndcg_eval_at是评估时取TOP几个位置实际业务里如果只关心首屏设[1, 3]就够了设太多会让模型花精力去优化那些用户根本看不到的位置。排序任务和分类回归有个很大的区别样本之间不是独立的它们以query为单位分组。所以构造Dataset时必须传group参数表示每个query下的样本数。这个group信息不需要归一化但顺序必须和数据集顺序一致否则模型学到的排序关系完全是错的。train_data lgb.Dataset( X_train, labely_train, groupgroup_train # 例如 [3, 5, 2] 表示第一个query有3条样本第二个有5条... )4.4 树个数与早停的关系为什么设置5000棵不是鲁莽热词里有一个“lightgbm的树个数”这个参数num_boost_round / n_estimators是新手最容易纠结的。我的观点是树个数本身不值得反复调真正值得调的是学习率树个数只是学习率的“配套设施”。道理很简单只要你有早停树个数设10000和设50000的结果是一样的——模型会在最优迭代次数自动停下来。所以我的建议是num_boost_round设一个足够大的值5000到10000学习率从0.05开始配合早停跑然后根据早停时的“experience iteration”来反推学习率是否合适。举个例子如果early stopping在500轮就停了说明模型很快收敛你可以把学习率降到0.02再跑预计最佳迭代会推到1200轮左右精度大概率会提升。反过来如果到4000轮还没停说明学习率太小或者数据太复杂这时候不能再等了应该把学习率调回0.05甚至0.1先把基线拿到再说。但是要注意num_boost_round设太大而早停不生效会导致训练时间被白白浪费。我曾经在一个任务里因为忘记在callbacks里传入lgb.early_stopping模型硬生生跑了6000轮训练集AUC到了0.999验证集从第800轮开始就没涨过——白白烧了一个小时GPU。4.5 特征重要性与模型可解释性调参之外的另一半工作参数调得再好如果不知道模型到底学了什么规律业务方很难信服。LightGBM提供了一套完整的特征重要性分析工具。importance model.feature_importance(importance_typegain) feature_names model.feature_name() for name, imp in sorted(zip(feature_names, importance), keylambda x: x[1], reverseTrue)[:20]: print(f{name}: {imp:.2f})importance_type有两个选择split是特征被用来分裂的次数gain是特征带来的总信息增益。推荐用gain因为它直接反映特征对模型预测的贡献大小。split次数容易被高基数特征刷上去——类别取值多的特征天然更容易被选中做分裂但实际贡献可能没那么大。更直观的可解释性工具是lgb.plot_importance直接把重要性画成条形图。另外shap库可以配合LightGBM画出每个特征的SHAP依赖图看某个特征在不同取值下对预测结果的正负影响这在做业务分析和风控解释时非常有用。还有一个小技巧利用LightGBM可以输出树结构的特性用model.dump_model()把模型导出成JSON然后写脚本分析一些特定样本的决策路径。这在排查“某个ID为什么被模型打高分”的场景下特别好用。5. 常见问题与排查技巧实录5.1 过拟合训练集分数远高于验证集这是LightGBM调参时最常遇到的问题。排查顺序是先看数据量再看参数。如果是小样本几千条到几万条leaf-wise天生容易过拟合优先调小num_leaves到15左右调大min_data_in_leaf到50以上这两个组合下去基本能压住。如果数据量不小但还是过拟合检查是不是特征数远大于样本数比如基因数据、文本稀疏特征这时开lambda_l1或lambda_l2同时调低feature_fraction到0.5左右给模型增加随机性。另外还有一个常见原因验证集划分不合理。如果训练集和验证集分布差异太大比如按时间切分但数据有很强的时序漂移不管怎么调参都会显示过拟合这时候要检查的就不是参数了。5.2 类别特征处理的两大坑第一个坑类别特征里出现了训练集没见过的类别。模型遇到未知类别时LightGBM通常把它当作缺失值处理不会报错但这部分样本的预测基本靠其他特征扛。解决办法是在做特征工程时就留一个“others”兜底类别或者对出现频次低于某个阈值的类别做合并。第二个坑类别特征的数值本身有大小含义。如果原始特征是“年龄段1,2,3,4”转成category类型没问题但如果原始特征是“商品价格档位199,299,399”这些数值之间有天然的排序关系硬转category反而丢失了这种信息建议保留数值类型喂进去让LightGBM自己找分裂点。5.3 早停失效或提前停止的排查早停失效通常有三个原因。第一early_stopping_rounds太小学习率又很低验证集指标在正常波动被误判为“不提升”提前停了。解决方法是把early_stopping_rounds调到学习率倒数的量级左右学习率0.01对应100轮以上。第二验证集和训练集之间数据泄漏。比如做特征工程时用了全量的均值、最大值去填充缺失值验证集的信息已经被模型“提前看到”早停的参考价值就大打折扣。第三valid_sets传了多个数据集而早停判断用的是第一个导致模型在第一个数据集上早停但第二个数据集上其实还有提升空间。解决办法是确保早停判断的那个验证集排在做决策的位置。5.4 参数组合的负向效果检查清单现象可能原因排查方向训练慢得离谱bin数太大、num_leaves过大、数据未用categorical调小max_bin检查EFB是否生效验证集指标波动剧烈学习率太大、数据量太小、bagging_fraction过低降学习率提高bagging_fraction模型文件巨大num_leaves大且树多或多分类压num_leaves检查是否真的需要那么多树预测结果偏向某一类样本不均衡但没设is_unbalance加is_unbalancetrue或调scale_pos_weight同一组参数跑两次结果不同有随机性参数bagging、feature_fraction固定bagging_seed、feature_fraction_seed5.5 终极技巧用optuna做自动化参数搜索手工调参到一定程度就够了再往上走是低效的。我现在的标准流程是手工跑通基线然后用optuna做一轮贝叶斯搜索把重点参数搜一遍再用搜出来的最优参数做最后的训练。import optuna def objective(trial): params { objective: binary, metric: auc, learning_rate: trial.suggest_float(learning_rate, 0.01, 0.1, logTrue), num_leaves: trial.suggest_int(num_leaves, 15, 255), min_data_in_leaf: trial.suggest_int(min_data_in_leaf, 20, 200), feature_fraction: trial.suggest_float(feature_fraction, 0.5, 0.9), bagging_fraction: trial.suggest_float(bagging_fraction, 0.6, 0.95), bagging_freq: 1, lambda_l2: trial.suggest_float(lambda_l2, 0, 10), verbose: -1 } cv_result lgb.cv(params, train_data, num_boost_round1000, nfold5, early_stopping_rounds50) return cv_result[auc-mean][-1] study optuna.create_study(directionmaximize) study.optimize(objective, n_trials50) best_params study.best_paramsoptuna的搜索空间设计有讲究。learning_rate用对数均匀分布logTrue是因为它在0.01到0.1之间按比例变化才有意义0.01到0.02的差距和0.09到0.10的差距不是一回事。num_leaves建议用整数均匀分布但取值范围不要一刀切到255先根据数据量预估模型复杂度再定区间。我自己实际用下来50轮optuna搜索效果大约等价于手动调参两三天的工作量。但要注意搜索空间设得过大反而容易让结果方差变大最好是先手工调过一轮确定了大概范围再用optuna在范围里精细搜索。6. 从调参到上线模型训练之外的经验之谈参数调优只是建模链路中的一环真正让项目落地的还有几件容易被忽略的事。我在实际项目里吃过亏分享几个心得。第一训练集/验证集/测试集的划分要比模型本身更用心。时间序列数据永远按时间切不能随机切有用户维度的数据要按用户分组切防止同一个人同时出现在训练集和验证集里造成“记忆式过拟合”。第二保存模型不只是存个文件。model.save_model(model.txt)存的是模型文本但加载的时候要记得model lgb.Booster(model_filemodel.txt)并且保存当时的特征名列表。如果上线前的特征工程代码有变动特征顺序对不上预测结果就会全错。第三LightGBM在CPU上的表现其实已经很好了不用一上来就上GPU。devicegpu在小数据量上反而因为数据搬运的开销变慢只有在数据量大到一定程度百万级以上才值得开GPU。而且GPU版本安装麻烦不少如果不是速度瓶颈CPU版本完全够用。第四、关于num_threads参数我建议不要盲目设成CPU核心数。LightGBM的多线程加速在树分裂时效果明显但在直方图构建阶段有锁竞争线程数过多反而会变慢。我试过在32核机器上设num_threads16比设32还要快。一般来说设成物理核心数不是逻辑线程数是比较稳的选择。第五、模型调好之后回头检查一下特征的重要性排序把Top特征和业务直觉对照一遍。如果模型给出的最强特征完全不符合认知大概率是有数据泄漏而不是模型发现了什么新规律。比如我曾经碰到过“用户是否点击”这个未来信息被错当成特征放进去导致模型AUC高达0.99排查后发现是这个字段在预测时根本拿不到这类问题藏在数据里不是调参能解决的。LightGBM调参这条路我自己的体会是单参数怎么理解、参数间如何联动比追求某个参数的最佳取值更重要。你理解了leaf-wise为什么容易过拟合就自然会用min_data_in_leaf和num_leaves组合去控制理解了直方图和GOSS的原理就明白什么时候该信默认参数什么时候必须手动干预。照着以上步骤把基线跑通、再按优先级去调大多数项目都能在一个可接受的训练时间内拿到远好于默认参数的模型。