1. 为什么单模型总差一口气:从一次调参翻车说起
去年帮一个做设备故障预测的团队看模型,他们用梯度提升树把准确率刷到了92%,但召回率死活上不去,尤其是少数类故障样本,漏报率高得离谱。我让他们把同一个数据集分别喂给逻辑回归、随机森林和XGBoost,结果三个模型在验证集上的错误样本重合度只有40%左右——也就是说,每个模型都在犯不同的错。这件事让我再次确认了一个事实:单个模型再强,也有它看不见的盲区,而集成学习的核心价值,就是让多个“偏科生”互相补位。
Ensemble,中文叫集成学习,不是什么新概念,但它在实际项目中的落地效果,往往比换更复杂的网络结构来得更直接。它的基本逻辑可以用一句话概括:把多个弱学习器或强学习器按照某种策略组合起来,得到一个比任何单一模型都更稳、更准的预测器。这里的“稳”和“准”是两个维度——稳指的是方差降低,准指的是偏差降低。不同的集成方法,本质上就是在偏差和方差之间做不同的取舍。
这篇文章适合谁看?如果你已经跑过几个sklearn模型,知道什么是训练集和验证集,但一遇到“模型效果卡住了”就只会调参或者换算法,那这篇内容就是写给你的。我会把Bagging、Boosting、Stacking这三条主线的原理、实操细节、参数计算和踩坑经验全部拆开讲,代码可以直接抄,参数可以照着调。全文基于我在实际项目中的使用经验,不堆公式,只说人话。
2. 集成学习的三大流派:Bagging、Boosting与Stacking的选型逻辑
2.1 并行 vs 串行:两种完全不同的纠错思路
Bagging和Boosting最根本的区别在于训练方式。Bagging是并行训练——从原始数据集中有放回地采样出多个子集,每个子集训练一个模型,最后投票或平均。Boosting是串行训练——第一个模型先跑,看它错在哪里,然后第二个模型重点纠正这些错误,依次递进。
这个区别决定了它们的适用场景。Bagging的核心作用是降低方差,所以它特别适合那些本身容易过拟合、对训练数据敏感的模型,比如决策树。随机森林就是Bagging最成功的应用——它不仅在样本上做随机采样,还在特征维度上做随机选择,进一步增强了模型的多样性。Boosting的核心作用是降低偏差,它能把一堆弱学习器(比如深度只有3的决策树)逐步提升成一个强学习器。AdaBoost、GBDT、XGBoost、LightGBM都属于这个家族。
Stacking则是另一条路——它不改变基模型的训练方式,而是训练一个元模型来学习如何组合基模型的输出。你可以把它理解成“让模型自己学会怎么投票”。基模型的预测结果作为新特征,喂给一个次级模型(通常是逻辑回归或线性回归),由它来决定每个基模型的权重。
2.2 偏差-方差分解:用生活例子理解集成为什么有效
假设你要预测一套房子的价格。你找了三个中介:A对市中心房源很熟,但对郊区一窍不通;B对郊区了如指掌,但市中心经常估高;C对学区房特别敏感,其他类型一般。如果你只信A,那遇到郊区房源就完蛋;如果你让三个人分别估价然后取平均,结果往往比任何单个人都接近真实价格。
这就是集成学习的直观解释。用偏差-方差的语言来说:A、B、C各自的偏差可能都不大,但方差很大(换个房源就估不准)。取平均之后,方差被拉低了,因为三个人的错误方向不完全一致,正负抵消了一部分。Boosting则是另一种思路——先让A估,发现它在郊区房源上总是低估,然后专门训练B去纠正这个低估,再让C去纠正B的残余错误。最终组合出来的模型,偏差被逐步压下去了。
2.3 选型决策表:什么数据用什么集成方法
| 场景特征 | 推荐方法 | 理由 |
|---|---|---|
| 数据量中等,特征维度高,单棵树过拟合严重 | 随机森林 | 双重随机性有效降方差,对噪声鲁棒 |
| 数据量较大,追求极致精度,允许较长训练时间 | XGBoost / LightGBM | 串行纠错能力强,正则化机制完善 |
| 基模型差异大(如SVM+树模型+神经网络) | Stacking | 元模型能学到不同基模型的互补关系 |
| 类别极度不平衡,少数类识别是关键 | BalancedBagging + Boosting | 采样策略与纠错机制结合 |
| 需要快速上线,模型可解释性要求高 | 随机森林 + 特征重要性 | 训练快,解释性强,调参少 |
这张表不是绝对的,但能帮你快速缩小选择范围。我个人的经验是:先跑随机森林看baseline,再用LightGBM冲精度,最后如果还差一口气,上Stacking。不要一上来就搞复杂集成,很多时候随机森林调好参数就能解决80%的问题。
3. Bagging与随机森林实操:从采样到投票的完整细节
3.1 有放回采样的数学直觉与实现陷阱
Bagging的第一步是Bootstrap采样——从N个样本中有放回地抽取N个。这个“有放回”很关键,它意味着某些样本会被重复抽到,某些样本一次都抽不到。根据概率论,当N足够大时,大约36.8%的样本不会被抽中。这些“袋外样本”(Out-of-Bag)天然可以作为验证集使用,不需要额外划分。
import numpy as np from sklearn.ensemble import BaggingClassifier from sklearn.tree import DecisionTreeClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score # 假设 X, y 是你的特征和标签 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) bagging = BaggingClassifier( estimator=DecisionTreeClassifier(max_depth=8), n_estimators=100, max_samples=0.8, # 每个基模型用80%的样本 max_features=0.7, # 每个基模型用70%的特征 bootstrap=True, bootstrap_features=False, oob_score=True, # 开启袋外评估 random_state=42, n_jobs=-1 ) bagging.fit(X_train, y_train) print(f"OOB Score: {bagging.oob_score_:.4f}") print(f"Test Score: {accuracy_score(y_test, bagging.predict(X_test)):.4f}")这里有几个参数需要解释。max_samples=0.8表示每个基模型只使用80%的样本,剩下的20%作为袋外样本。max_features=0.7表示每个基模型只使用70%的特征。这两个参数是控制模型多样性的关键——采样比例越低,基模型之间的差异越大,集成的降方差效果越明显,但单个基模型的性能会下降。经验值:max_samples在0.6到0.8之间,max_features在0.5到0.8之间。
注意:
bootstrap_features=False表示不对特征进行有放回采样,只是随机选择子集。如果你的特征维度非常高(比如上万维),可以把它设为True,进一步增加多样性。
3.2 随机森林的特征重要性怎么读才不踩坑
随机森林训练完之后,feature_importances_属性会给出每个特征的重要性分数。但很多人直接拿这个分数做特征选择,结果踩了大坑。基于不纯度的重要性有一个致命缺陷:它对高基数特征(取值多的特征)有偏好。比如一个用户ID特征,每个样本取值都不同,树在分裂时会优先选它,因为能最大程度降低不纯度,但这个特征根本没有泛化能力。
更可靠的做法是使用排列重要性(Permutation Importance):
from sklearn.inspection import permutation_importance rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1) rf.fit(X_train, y_train) perm_imp = permutation_importance( rf, X_test, y_test, n_repeats=10, random_state=42, n_jobs=-1 ) # 按重要性排序输出 for i in perm_imp.importances_mean.argsort()[::-1][:10]: print(f"{feature_names[i]}: {perm_imp.importances_mean[i]:.4f} +/- {perm_imp.importances_std[i]:.4f}")排列重要性的逻辑是:把某个特征的值随机打乱,看模型性能下降多少。下降越多,说明这个特征越重要。它的好处是不依赖于模型内部结构,对任何模型都适用,而且不会偏袒高基数特征。我现在的习惯是:先用基于不纯度的重要性快速筛一遍,再用排列重要性做最终确认,两者差异大的特征重点排查。
3.3 袋外样本的妙用与常见误用
袋外样本最大的价值是在不额外划分验证集的情况下评估模型泛化能力。这对于小数据集特别有用——你不需要再切出20%的数据做验证,所有数据都能参与训练。
但袋外评估有一个常见误用:用袋外分数来调参。袋外分数本质上是在训练过程中计算的,如果你根据袋外分数反复调整参数,它就会过拟合。正确的做法是:袋外分数只用来做粗略的模型筛选,最终调参还是要用独立的验证集或交叉验证。
另外,袋外评估在类别不平衡数据上会失真。因为袋外样本的类别分布可能与原始数据不同,某些少数类样本可能根本没出现在袋外集合中。这种情况下,建议还是用分层交叉验证。
4. Boosting家族实战:从AdaBoost到LightGBM的演进与调参
4.1 AdaBoost的权重更新机制与学习率选择
AdaBoost是最早的Boosting算法,它的核心思想是给错分样本更高的权重。第一轮训练后,被分错的样本权重增加,被分对的样本权重降低。第二轮训练时,模型会更关注那些之前分错的样本。
from sklearn.ensemble import AdaBoostClassifier from sklearn.tree import DecisionTreeClassifier adaboost = AdaBoostClassifier( estimator=DecisionTreeClassifier(max_depth=1), # 决策 stump n_estimators=200, learning_rate=0.5, algorithm='SAMME.R', # 支持概率输出的版本 random_state=42 ) adaboost.fit(X_train, y_train)这里max_depth=1表示使用决策树桩——只有一次分裂的树。这是AdaBoost的经典配置,因为它的设计初衷就是组合弱学习器。learning_rate控制每轮模型的贡献权重,值越小,需要越多的n_estimators来达到同样的效果。经验值:learning_rate在0.1到1.0之间,n_estimators在100到500之间。两者需要联合调参——学习率减半,树的数量大约要翻倍。
AdaBoost对噪声和离群点非常敏感,因为那些被反复分错的样本会获得越来越高的权重,最终把模型带偏。如果你的数据清洗不够干净,AdaBoost的表现可能还不如随机森林。
4.2 梯度提升树的残差拟合原理与XGBoost参数计算
梯度提升树(GBDT)的核心是拟合残差。第一棵树预测目标值,第二棵树拟合第一棵树的残差,第三棵树拟合前两棵树的残差……以此类推。XGBoost在GBDT基础上加入了两大改进:二阶泰勒展开和正则化项。
二阶展开意味着XGBoost不仅用一阶导数(梯度)信息,还用二阶导数(海森矩阵)信息来决定叶子的分裂点和权重。这让它收敛更快、精度更高。正则化项则控制了模型复杂度,防止过拟合。
import xgboost as xgb xgb_model = xgb.XGBClassifier( n_estimators=500, max_depth=6, learning_rate=0.05, subsample=0.8, colsample_bytree=0.8, reg_alpha=0.1, # L1正则化 reg_lambda=1.0, # L2正则化 min_child_weight=3, # 叶子节点最小样本权重和 gamma=0.1, # 分裂所需的最小损失下降 objective='binary:logistic', eval_metric='auc', random_state=42, n_jobs=-1 ) xgb_model.fit( X_train, y_train, eval_set=[(X_test, y_test)], verbose=50 )关键参数的计算逻辑:
max_depth=6:控制树的最大深度。深度越大,模型越复杂,越容易过拟合。对于大多数结构化数据,6到10是比较合理的范围。可以用交叉验证来选。min_child_weight=3:叶子节点中样本权重和的最小值。值越大,模型越保守。对于不平衡数据,可以适当降低这个值,让少数类样本也能形成叶子。gamma=0.1:分裂所需的最小损失下降。值越大,模型越保守。可以从0开始调,逐步增加。subsample=0.8和colsample_bytree=0.8:行采样和列采样比例。这两个参数是XGBoost借鉴随机森林的地方,能有效防止过拟合。
4.3 LightGBM的直方图算法与大数据集调参策略
LightGBM在XGBoost基础上做了两个关键优化:直方图算法和Leaf-wise生长策略。直方图算法把连续特征离散化成桶,大大减少了分裂点搜索的计算量。Leaf-wise生长策略每次选择当前损失下降最大的叶子进行分裂,而不是像XGBoost那样按层生长。这让LightGBM在相同叶子数量下能达到更低的损失,但也更容易过拟合。
import lightgbm as lgb lgb_model = lgb.LGBMClassifier( n_estimators=1000, num_leaves=31, # 叶子数量,控制模型复杂度 max_depth=-1, # 不限制深度,由num_leaves控制 learning_rate=0.05, subsample=0.8, colsample_bytree=0.8, min_child_samples=20, # 叶子最小样本数 reg_alpha=0.1, reg_lambda=1.0, random_state=42, n_jobs=-1 ) lgb_model.fit( X_train, y_train, eval_set=[(X_test, y_test)], eval_metric='auc', callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)] )num_leaves是LightGBM最重要的参数。它和max_depth的关系是:num_leaves <= 2^max_depth。对于Leaf-wise生长,建议num_leaves不要超过2^max_depth,否则容易过拟合。经验值:num_leaves在31到255之间,数据量越大可以设得越高。
min_child_samples=20是防止过拟合的关键参数。它要求每个叶子至少包含20个样本,否则不分裂。对于小数据集,这个值可以设小一些(比如5到10);对于大数据集,可以设大一些(比如50到100)。
提示:LightGBM的
early_stopping回调非常实用。它会在验证集指标不再提升时自动停止训练,避免过拟合和浪费时间。建议始终开启。
5. Stacking与Blending:用元模型学习如何组合基模型
5.1 基模型选择与多样性度量
Stacking的第一步是选择基模型。基模型的多样性比单个模型的精度更重要。如果所有基模型都是树模型,它们的错误高度相关,组合效果有限。理想的基模型组合应该覆盖不同的归纳偏置——比如一个线性模型、一个树模型、一个核方法、一个神经网络。
我常用的基模型组合:
- 逻辑回归(线性边界)
- 随机森林(非线性,对噪声鲁棒)
- LightGBM(非线性,精度高)
- SVM(核方法,适合小样本高维)
- KNN(基于距离,完全不同的视角)
多样性可以用预测结果的相关性来度量。如果两个基模型的预测结果相关系数超过0.95,说明它们太像了,可以考虑去掉一个。
import numpy as np from scipy.stats import pearsonr # 假设 base_preds 是每个基模型在验证集上的预测结果矩阵 # shape: (n_samples, n_base_models) corr_matrix = np.corrcoef(base_preds.T) print("基模型预测相关性矩阵:") print(corr_matrix)5.2 元模型训练数据的构造与交叉验证防泄漏
Stacking最容易踩的坑是数据泄漏。如果你直接用基模型在训练集上的预测结果作为元模型的特征,元模型会严重过拟合——因为基模型在训练集上的预测是“见过答案”的,准确率虚高。
正确的做法是使用K折交叉验证来构造元特征:
from sklearn.model_selection import StratifiedKFold from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier import lightgbm as lgb def get_stacking_features(X_train, y_train, X_test, base_models, n_folds=5): """用K折交叉验证构造Stacking特征""" n_models = len(base_models) n_train = X_train.shape[0] n_test = X_test.shape[0] # 训练集上的元特征 train_meta = np.zeros((n_train, n_models)) # 测试集上的元特征 test_meta = np.zeros((n_test, n_models)) skf = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=42) for i, (train_idx, val_idx) in enumerate(skf.split(X_train, y_train)): X_tr, X_val = X_train[train_idx], X_train[val_idx] y_tr = y_train[train_idx] for j, model in enumerate(base_models): model_clone = clone(model) model_clone.fit(X_tr, y_tr) train_meta[val_idx, j] = model_clone.predict_proba(X_val)[:, 1] test_meta[:, j] += model_clone.predict_proba(X_test)[:, 1] / n_folds return train_meta, test_meta # 定义基模型 base_models = [ LogisticRegression(max_iter=1000, random_state=42), RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1), lgb.LGBMClassifier(n_estimators=300, random_state=42, n_jobs=-1) ] train_meta, test_meta = get_stacking_features(X_train, y_train, X_test, base_models) # 训练元模型 meta_model = LogisticRegression(max_iter=1000, random_state=42) meta_model.fit(train_meta, y_train) # 最终预测 final_pred = meta_model.predict(test_meta)这段代码的关键在于:每个基模型在验证折上的预测结果,才是元模型看到的特征。测试集的元特征则是所有折模型预测的平均值。这样既避免了数据泄漏,又充分利用了所有训练数据。
5.3 Blending与Stacking的取舍:简单但更易过拟合
Blending是Stacking的简化版——它不搞K折交叉验证,而是直接把训练集切成两部分:一部分训练基模型,另一部分生成元特征。这样做的好处是简单、训练快;坏处是元模型只看到了一部分数据,更容易过拟合,而且浪费了训练数据。
我的建议是:数据量足够(>10万样本)时用Blending,否则用Stacking。数据量小的时候,每一份数据都很宝贵,K折交叉验证能最大化利用数据。数据量大的时候,Blending的简单性优势就体现出来了,而且过拟合风险也相对可控。
6. 集成学习常见问题与排查技巧实录
6.1 基模型越多越好吗:边际收益递减与计算成本
很多人以为基模型越多越好,实际上集成规模存在明显的边际收益递减。从1个模型增加到10个,性能提升最明显;从10个增加到50个,提升幅度明显减小;超过100个之后,基本就是浪费计算资源了。
我做过一个实验:在同一个数据集上,分别用10、50、100、200棵树的随机森林跑10次交叉验证。结果10棵树的平均AUC是0.852,50棵是0.867,100棵是0.871,200棵是0.872。从100到200,AUC只提升了0.001,但训练时间翻了一倍。
经验法则:先用50个基模型跑baseline,如果比单模型提升明显,再逐步增加到100或200。如果50个和100个的验证分数差异小于0.002,就停在50个。
6.2 集成后效果反而下降:排查清单与解决方案
集成之后效果反而下降,通常有以下几个原因:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 验证分数低于单模型 | 基模型太相似,错误高度相关 | 增加基模型多样性,换不同算法 |
| 训练集分数高,验证集分数低 | 元模型过拟合 | 简化元模型,增加正则化 |
| 某些类别预测极差 | 基模型对少数类不敏感 | 使用类别权重或采样策略 |
| 整体AUC提升但召回率下降 | 集成偏向多数类 | 调整分类阈值或使用代价敏感学习 |
| 训练时间过长 | 基模型太多或太复杂 | 减少基模型数量,使用LightGBM替代XGBoost |
我遇到最多的情况是基模型太相似。比如用随机森林、ExtraTrees和GBDT做Stacking,这三个都是树模型,错误模式高度重合,集成效果自然有限。后来加入逻辑回归和KNN之后,AUC直接涨了1.5个百分点。
6.3 类别不平衡下的集成策略:采样与权重的组合拳
类别不平衡是实际项目中最常见的问题之一。假设正负样本比例是1:100,直接用集成学习,基模型会倾向于全部预测为负类,因为这样准确率也有99%。
解决方案有三条路:
第一,在基模型层面处理。给少数类更高的样本权重,或者使用SMOTE等过采样方法。但要注意,过采样必须在交叉验证的每一折内部进行,不能在划分之前做,否则会造成数据泄漏。
第二,在集成层面处理。使用BalancedBaggingClassifier,它在每次Bootstrap采样时对少数类进行额外采样,让每个基模型看到的类别分布更均衡。
第三,在决策层面处理。不改变模型训练,而是调整分类阈值。默认阈值是0.5,但对于不平衡数据,最优阈值往往在0.1到0.3之间。可以用验证集上的F1分数或AUC来选阈值。
from imblearn.ensemble import BalancedBaggingClassifier from sklearn.tree import DecisionTreeClassifier balanced_bagging = BalancedBaggingClassifier( estimator=DecisionTreeClassifier(max_depth=8), n_estimators=100, sampling_strategy='auto', # 自动平衡类别 replacement=False, random_state=42, n_jobs=-1 ) balanced_bagging.fit(X_train, y_train)注意:使用imbalanced-learn库时,务必确保在交叉验证的每一折内部进行采样。
BalancedBaggingClassifier内部已经处理了这个问题,但如果你手动做SMOTE,一定要用imblearn.pipeline.Pipeline而不是sklearn.pipeline.Pipeline。
7. 集成学习在实际项目中的落地建议
7.1 从单模型到集成的渐进式路线
不要一上来就搞复杂集成。我的建议是分三步走:
第一步,建立单模型baseline。用逻辑回归或单棵决策树跑一个baseline,记录准确率、召回率、AUC。这一步的目的是确认数据本身是否有信号,以及评估后续模型的提升空间。
第二步,上随机森林或LightGBM。这两个模型调参相对简单,效果稳定,能解决大部分结构化数据的预测问题。如果这一步的验证分数已经满足业务需求,就不用再折腾了。
第三步,如果还差一口气,上Stacking。用2到3个差异较大的基模型,加上一个简单的元模型(逻辑回归或线性回归)。不要搞太复杂,元模型越简单越不容易过拟合。
7.2 模型持久化与线上推理的性能考量
集成模型的一个现实问题是推理速度。单个XGBoost模型可能只需要几毫秒,但100棵树的随机森林可能需要几十毫秒,Stacking更是要跑多个基模型再加元模型。在高并发场景下,这个延迟可能无法接受。
优化方案:
- 模型剪枝:训练完之后,去掉那些在验证集上表现差或与其他模型高度相关的基模型。
- 模型蒸馏:用集成模型的预测结果作为标签,训练一个轻量级的单模型(比如小决策树),用单模型上线。
- 硬件加速:使用ONNX Runtime或Treelite来加速树模型的推理。
- 缓存策略:对于重复出现的输入,缓存预测结果。
import onnxruntime as rt from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType # 将sklearn模型转换为ONNX格式 initial_type = [('float_input', FloatTensorType([None, n_features]))] onnx_model = convert_sklearn(rf_model, initial_types=initial_type) with open("rf_model.onnx", "wb") as f: f.write(onnx_model.SerializeToString()) # 推理 sess = rt.InferenceSession("rf_model.onnx") input_name = sess.get_inputs()[0].name pred = sess.run(None, {input_name: X_test.astype(np.float32)})[0]ONNX Runtime的推理速度通常比sklearn的predict快2到5倍,对于树模型尤其明显。
7.3 集成模型的解释性:SHAP值与特征贡献
集成模型最大的缺点之一是可解释性差。随机森林有几百棵树,XGBoost有几百个 boosting 轮次,你很难直接说清楚“为什么这个样本被预测为正类”。
SHAP(SHapley Additive exPlanations)是目前最可靠的解释工具。它基于博弈论中的Shapley值,为每个特征分配一个贡献值,表示该特征对预测结果的贡献大小。
import shap # 对于树模型,使用TreeExplainer explainer = shap.TreeExplainer(lgb_model) shap_values = explainer.shap_values(X_test) # 全局特征重要性 shap.summary_plot(shap_values, X_test, feature_names=feature_names) # 单个样本的解释 shap.force_plot( explainer.expected_value, shap_values[0, :], X_test[0, :], feature_names=feature_names )SHAP值的计算成本比排列重要性高,但它能给出每个样本的个性化解释,这在需要向业务方解释预测结果的场景中非常有用。我通常会在模型上线前跑一次全局SHAP分析,确认模型没有依赖那些不应该依赖的特征(比如ID类特征、时间泄漏特征)。
8. 我踩过的坑与最后分享的几个小技巧
第一个坑是在Stacking中用了太多基模型。有一次我用了8个基模型做Stacking,结果元模型严重过拟合,验证集AUC比单模型还低。后来砍到3个,效果反而好了。基模型不是越多越好,3到5个差异明显的基模型通常是最优区间。
第二个坑是忽略了基模型的校准。逻辑回归和SVM的输出概率往往没有经过良好校准,直接拿来做Stacking的元特征,元模型学到的权重会有偏差。解决方案是在基模型后面加一个CalibratedClassifierCV,或者直接用predict_proba之前先做Platt Scaling。
第三个坑是在时间序列数据上用了随机划分。集成学习本身不解决数据泄漏问题。如果你的数据有时间维度,必须用时间序列交叉验证,否则Bagging的Bootstrap采样会把未来信息泄漏到训练集中。
最后分享一个小技巧:用OOF(Out-of-Fold)预测做模型选择。在Stacking的K折交叉验证过程中,你会得到每个基模型的OOF预测。这些OOF预测的AUC可以作为基模型筛选的依据——去掉那些OOF AUC明显低于其他的基模型,能有效提升最终集成效果。这个技巧在Kaggle比赛中非常常用,在实际项目中也同样有效。
另外,如果你用的是LightGBM,记得开启feature_fraction和bagging_fraction,这两个参数是LightGBM版的列采样和行采样,能显著提升模型的泛化能力。我通常设feature_fraction=0.8、bagging_fraction=0.8、bagging_freq=5,效果很稳。