Stacking模型融合实战:银行客户产品认购预测解析

发布时间:2026/10/6 8:46:46
Stacking模型融合实战:银行客户产品认购预测解析 别人在调参炼丹的时候我还在研究怎么把几个模型“组队打团”。今天想聊聊一个非常适合结构化数据竞赛和金融风控场景的进阶玩法基于Stacking方法的银行客户产品认购预测。这个项目本质上是个二分类问题但难点不在算法本身而在于如何把多个模型的预测结果“拧成一股绳”让最终的预测精度比任何单一模型都高。这篇文章会从模型选型、特征工程、交叉验证防止标签泄漏、到元模型设计完整盘一遍我的实操思路和踩坑记录希望能给你一点参考。1. 内容整体设计与思路拆解1.1 我为什么不用单个模型硬扛而是选择Stacking方案银行客户产品认购预测这个场景我最早接触的时候第一反应也是直接上XGBoost或者LightGBM。毕竟GBDT系列对付这种带大量离散特征的表格数据效果一向很稳。但真正跑下来发现两个问题第一个是单模型的上限很快就能看到调参调到后期每提升零点零零几个AUC都要付出巨大代价第二个是客户认购行为本身包含的信号非常多元有的客户偏好高频小额有的偏好低频大额还有相当一部分人属于“观望型”单一模型的假设空间很难同时覆盖住这些不同形态的“用户画像”。Stacking能解决什么问题说白了就是让不同类型的模型各自表达“我看客户应该是这样”然后把它们的判断结果作为新特征再交给一个更聪明的模型去学“我该怎么结合大家的意见”。这个思想类似你做一个项目决策不会只听一个专家的建议而是把营销、运营、数据、财务几个岗位都叫到一个屋子里各说各的观点然后你这个负责人再综合所有人的发言做最终拍板。Stacking就是把这个“专家会诊”的过程用算法实现了。1.2 整体框架的四个模块整个项目我拆成了四个模块特征工程、基学习器训练、元特征生成、元模型融合。特征工程是地基基学习器是“不同视角的专家团队”元特征生成是“Experts 各自提交观点报告”元模型融合是“最终决策委员会”。特征工程方面我构建了三类特征基础客户画像特征年龄、收入、职业状态、行为特征历史交易频次、平均金额、最近一次交易距今天数、衍生组合特征收入与交易金额的比值、产品偏好交叉项。这里不做过的细节展开但有一句话必须强调Stacking对特征的敏感度非常高基学习器如果全是同一个“信息源”训练出来的融合效果会大打折扣。所以我在构造特征时专门让不同基模型使用不同侧重点的特征子集人为制造“认知差异”。基学习器我选了四个XGBoost、LightGBM、CatBoost、RandomForest。这里有一个比较关键的经验类别特征多的选CatBoost有天然优势连续特征和缺失值模式复杂的选XGBoost和LightGBM更强RandomForest可以起到“低方差基线”的作用。四个模型各有偏科融合起来反而比四个“全才”效果更好。2. 基学习器的选型逻辑与参数调优2.1 不同基模型在银行认购场景下的“性格差异”这四个模型在银行客户认购预测这件事上表现出的行为模式差异非常明显我简单说下我在实际数据集上观察到的现象。XGBoost对特征间的交互项捕捉能力很强。比如客户“年龄超过40岁且收入超过20万且已有两笔以上存款产品”这种组合型特征XGBoost能很快挖掘出来。但它的缺点是训练时间长尤其在数据量达到几十万条时需要花较多时间调参数。LightGBM的速度优势在这个场景下很实用。同样跑五折交叉验证LightGBM比XGBoost能快三分之一左右而且它对类别型特征和稀疏特征的处理很友好。银行数据里“职业类型”“婚姻状况”大家都懂会被编码成十几个或几十个哑变量维度LightGBM的直方图算法在这种高维稀疏特征下不会出现太多的性能退化。CatBoost对类别特征的处理是“天生内置”。很多人在用其他GBDT模型前都要自己做LabelEncoder或者OneHotEncoder总担心顺序编码会引入不存在的顺序关系。CatBoost直接在训练过程中处理类别特征会自动计算类别间的目标统计信息并施加平滑处理来防止过拟合。客户认购数据里“地区”“职业”“学历”这些离散特征占比不低CatBoost在这种场景下表现相当稳健。RandomForest属于Bagging流派它的特点是方差低不容易对训练集的噪声产生过拟合。在Stacking框架里它扮演的更像是一个“稳定型选手”其它三个GBDT模型可能在某些局部分区上预测过头RandomForest的输出可以起到很好的平衡作用。2.2 防止标签泄漏的关键操作K折交叉验证的OOF预测这里我不妨细说下这段逻辑设计因为这是我个人认为整个流程最关键的一步也是新手最容易出问题的地方。想象一下如果你用全量训练集训练模型再用这个模型去预测训练集本身然后把预测结果当作元特征送给上层模型学习。看起来没毛病实际上已经把部分答案“泄露”给了上层模型。上层模型看到元特征时会直接把这些特征和标签的映射关系学进去等真正预测测试集时因为测试集的样本上的元特征分布和训练集不一致最终结果往往虚高上线后效果立刻打回原形。标准做法是先把训练集分成K折一般取5折每一折里用其余K-1折的数据训练模型对当前折的数据做预测这个过程循环K次最后得到每个训练样本一个“没见过我这个样本的模型”给出的预测值这个预测值就是干净无泄漏的Out-of-Fold预测简称OOF。测试集也要做同样的K次预测取平均得到测试集的元特征。我最初自己手动实现这个逻辑时很容易为了化简代码而偷懒结果就是元特征越训越漂亮线下AUC能到0.95线上/验证集一测直接打回原形。打过Kaggle或者参加过数据挖掘比赛的朋友应该都懂线下验证指标如果高得离谱先别高兴回头检查是不是泄漏了。2.3 参数调优的经验值参考以LightGBM为例我在这类银行数据集上常用的参数初始值供你参考import lightgbm as lgb params { objective: binary, metric: auc, boosting_type: gbdt, learning_rate: 0.02, num_leaves: 31, max_depth: 7, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 5, min_data_in_leaf: 50, lambda_l1: 0.1, lambda_l2: 1.0, verbose: -1, seed: 42 }这里我特别说明三个参数背后的考虑。learning_rate设置成0.02而不是默认的0.1虽然会让训练轮数变多但每棵树的贡献更小降低单个模型过拟合的风险。Stacking框架内任何一个基学习器如果过拟合它在OOF上产生的元特征就会“虚高”进而拉低元模型的泛化能力。feature_fraction和bagging_fraction两个都在0.8左右这保证了每棵树只能看到80%的特征和80%的样本。银行客户数据往往存在大量相关特征收入与资产、消费水平与信用卡额度等随机抽样特征能让每个基学习器在更丰富的特征组合下学习而不是每次都看到同一对高相关特征。min_data_in_leaf设为50是一个很好的正则化手段。银行数据往往有噪音尤其是一些历史行为特征存在缺失值如果叶子节点样本太少模型容易学进去个别异常客户的特殊模式。XGBoost和CatBoost的参数设置也按类似逻辑核心记住一条在基学习器这一层宁可欠拟合一点也不要过于激进地拟合训练集。基学习器如果过拟合产生的OOF预测值分布本身就趋向极端0和1之间缺少中间地带这样的元特征给到元模型融合的增益几乎为零。3. 实操过程与核心环节实现3.1 数据准备与评估指标的选择银行客户产品认购预测的数据集我以葡萄牙银行机构的营销活动数据集为例这是该领域非常经典的开源数据特征是客户年龄、工作类型、婚姻状况、教育背景、余额、住房贷款、个人贷款、上次联系时长、过往营销接触次数、上次联系距离当前天数、之前的接触次数等加上社会经济背景指标目标变量是客户是否认购了定期存款。样本量大约4万多条一条关键的筛选逻辑要提前想清楚这个数据集只在“有联系记录的客户”里做了标记没被联系过的客户严格来说不构成“未认购”样本。建模前要把这个抽样逻辑讲清楚否则后续评估指标会被干扰。评估指标我选了AUC、LogLoss和KS值三个一起看。AUC衡量整体区分度LogLoss则更侧重概率预测的校准度而银行场景很关心KS值因为涉及人群排序和分层策略。不建议只看AUC因为AUC对类别不平衡不敏感而银行客户认购正样本占比往往只有百分之十几或者更低AUC高不代表“预测为正的概率值”一定准这对后续业务上圈选客户名单非常重要。3.2 特征工程的核心处理细节在真实项目里数据预处理永远比调参重要。这块我吃过亏说几个容易踩坑的细节。缺失值处理要分类型。连续型特征像收入和交易金额用中位数填充并用一个“是否缺失”的0/1标志位记录下来。类别型特征缺失则单独填一个“unknown”类别不要盲目用众数填充——这在银行场景里很危险因为“未知”本身往往意味着客户属性发生了改变或渠道信息断裂包含对认购行为有预测力的信息。日期型特征全部转成“距今多少天”而不是保留年月日。比如“上次联系日期”在原始数据里是日期格式实际建模必须转成“距离数据集截止日期多少天”。这样既能反映时效性又能避免模型学习到跟具体年份有关的虚假关联。特征归一化要不要做分模型。RandomForest就不需要树模型对特征的单调变换不敏感做不做归一化不影响分裂点查找。如果你额外引入逻辑回归、或者元模型选择线性模型归一化就必须要做。所以我在送入各基模型前做了两套特征版本一套原值直接给树模型一套标准化后给线性模型或MLP。这个细节经常被忽略但对融合效果有一定影响。3.3 Stacking训练流程的完整代码逻辑下面这段代码是整个Stacking训练流程的核心骨架我用了完整注释方便你直接参考和复现。import numpy as np import pandas as pd from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score, log_loss from xgboost import XGBClassifier from lightgbm import LGBMClassifier from catboost import CatBoostClassifier from sklearn.ensemble import RandomForestClassifier N_FOLDS 5 SEED 42 def stacking_oof_predict(model, X_train, y_train, X_test): skf StratifiedKFold(n_splitsN_FOLDS, shuffleTrue, random_stateSEED) oof_train np.zeros((X_train.shape[0],)) oof_test np.zeros((X_test.shape[0],)) for fold_idx, (trn_idx, val_idx) in enumerate(skf.split(X_train, y_train)): X_tr, y_tr X_train.iloc[trn_idx], y_train.iloc[trn_idx] X_va, y_va X_train.iloc[val_idx], y_train.iloc[val_idx] model_clone model.__class__(**model.get_params(deepFalse)) model_clone.fit(X_tr, y_tr) oof_train[val_idx] model_clone.predict_proba(X_va)[:, 1] oof_test model_clone.predict_proba(X_test)[:, 1] / N_FOLDS oof_auc roc_auc_score(y_train, oof_train) print(f{model.__class__.__name__} OOF AUC: {oof_auc:.5f}) return oof_train, oof_test # 假设已经完成了特征工程得到以下变量 # X_train: 训练集特征 DataFrame # y_train: 训练集标签 Series # X_test: 测试集特征 DataFrame xgb_model XGBClassifier(n_estimators300, learning_rate0.02, max_depth6, subsample0.8, colsample_bytree0.8, reg_lambda1.0, random_stateSEED, eval_metricauc, use_label_encoderFalse) lgb_model LGBMClassifier(n_estimators1000, learning_rate0.02, num_leaves31, max_depth7, feature_fraction0.8, bagging_fraction0.8, bagging_freq5, min_data_in_leaf50, reg_alpha0.1, reg_lambda1.0, random_stateSEED) cat_model CatBoostClassifier(iterations800, learning_rate0.03, depth6, l2_leaf_reg3.0, random_seedSEED, verbose0) rf_model RandomForestClassifier(n_estimators600, max_depth12, min_samples_leaf20, max_features0.6, random_stateSEED, n_jobs-1) models [xgb_model, lgb_model, cat_model, rf_model] train_meta np.zeros((X_train.shape[0], len(models))) test_meta np.zeros((X_test.shape[0], len(models))) for i, model in enumerate(models): train_meta[:, i], test_meta[:, i] stacking_oof_predict(model, X_train, y_train, X_test) # 保存元特征方便后续调试元模型 np.save(train_meta.npy, train_meta) np.save(test_meta.npy, test_meta) np.save(y_train.npy, y_train.values)3.4 元模型的选择与逻辑回归的意外优势Stacking的最后一层元模型我最终选择了带L1正则化的逻辑回归。这是我在几次对比实验后确定下来的方案。很多做Stacking的人喜欢直接用LightGBM作为元模型理由很简单——效果好。但问题在于这相当于“让一个专家去综合另一个专家的意见”虽然灵活但过度求强往往适得其反。逻辑回归作为元模型有几个明显的好处。第一模型简单不容易学过头。第二输入特征只有四个四个基模型的预测概率维度极低不需要复杂的非线性变换。第三可解释性强通过权重系数能直观看到每个基模型对最终预测的贡献度这在银行场景里做模型风控审计非常有价值。我训练逻辑回归时还额外加了一个操作——对元特征做标准化。虽然在大多数场景下逻辑回归对特征尺度不敏感但加了L1正则化后特征尺度会影响正则化惩罚的量级标准化后更公平。我跑完归一化和不归一化的对照实验归一化后AUC提升了0.003左右幅度不大但属于白捡的收益。from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline meta_model make_pipeline( StandardScaler(), LogisticRegression(penaltyl1, solverliblinear, C1.0, random_stateSEED) ) meta_model.fit(train_meta, y_train) test_pred meta_model.predict_proba(test_meta)[:, 1] # 打印元模型权重观察各基模型的贡献度 lr_step meta_model.named_steps[logisticregression] for idx, weight in enumerate(lr_step.coef_[0]): print(fModel {idx} ({models[idx].__class__.__name__}) weight: {weight:.4f})这里有一个比较有意思的结果四个基模型的OOF AUC相差不大XGBoost大约0.86LightGBM约0.87CatBoost约0.86RandomForest略低约0.84。但逻辑回归学习到的权重并不是严格跟各自的OOF AUC成比例CatBoost的权重反而比LightGBM还要高一点。原因是CatBoost和LightGBM之间的预测相关性更低而XGBoost和LightGBM之间的相关性更高。所以Stacking选基学习器差异性比单个模型精度更重要。这一点挺关键的如果四个模型预测结果高度相关那融合只是对同一个预测分布的微小扰动全部用高分模型反而带来不了多样性的增益。4. 常见问题与排查技巧实录4.1 元特征一旦出现“完美线性可分”就要小心有一次调参时我把基学习器的learning_rate从0.02改成0.1同时把n_estimators调得很高结果OOF AUC一路冲到了0.92。当时我还挺兴奋但紧接着发现测试集预测成绩根本没涨甚至有小幅下跌。复盘时用混淆矩阵分析发现元特征的分布出现了明显的“两边极端中间空心”形态。所有训练样本的预测概率都集中分布在0.1以下或者0.9以上中间0.3到0.7区间几乎没人。这就是典型的基学习器过拟合后的表现——它面对任何样本都“信心爆棚”但其实OOF预测是多个折的平均出现这种分布说明每个单折都严重过拟合了当前训练折的内部噪声。标准解法就是回退基模型的复杂度增加正则强度比如加大min_data_in_leaf和reg_lambda调低learning_rate并同步减少训练轮数。另外还可以对元特征做“截断”——把概率小于0.05的按0.05处理大于0.95的按0.95处理虽然这会损失一点极端信息但对逻辑回归层的稳定性有帮助。4.2 类别不平衡问题要不要用重采样或class_weight银行客户认购数据正样本占比如果只有10%~15%直接用原始比例喂给模型模型的输出概率会偏向多数类。但在这个场景里我实际验证后发现不要在Stacking基学习器层使用过采样或欠采样。原因是这样过采样会增加正样本的重复极易导致基模型对少数类过拟合OOF预测出来的正样本概率虚高欠采样会丢失大量负样本信息而银行客户数据里负样本的信息量并不低——不认购的客户里面也分“完全不来往型”和“高潜力观望型”扔掉负样本等于扔掉了这两类人之间的区别。正确的做法是把类别不平衡问题交给“阈值调整”环节。模型输出的概率本身不需要做校正业务侧的目标是在保证覆盖率的前提下提升响应率。所以我在训练时直接让class_weight保持默认均衡权重训练结束后用验证集找使F1最大化的阈值。这一招比任何采样技巧都直接管用。4.3 重复跑Stacking时OOF结果波动怎么办Stacking训练过程中有个很尴尬的环节——训练一次要跑很久如果中间发现某个基模型参数调得不合适需要重来整个链路都要跟着重新跑。遇到这个问题时我后来是这样处理的把元特征保存成文件就像代码里展示的每一个基模型训练完立刻单独保存一份OOF结果和测试集预测。这样后面调试元模型时不需要重新训练基学习器大大节省验证时间。毕竟基学习器跑一次五折交叉验证大概要十五到二十分钟等待过程中配合这个保存机制整体调试效率能提升不少。不过这招有个前提基学习器的参数一旦确定不要轻易改动。如果你回头改了LightGBM的num_leaves那它产生的OOF元特征变了整条链路都得重跑不可能只更新单列特征。4.4 元模型要不要加入原始特征这是一个曾经争论不休的问题站在实际项目立场我给一个可以直接用的结论如果基学习器数量很少两三个可以尝试把原始特征中最重要的前20个拼接到元特征后面再喂给元模型。这相当于给最后一层的决策委员会补充上下文背景信息而不是只听几个专家的二手结论。但如果你已经有四个甚至更多基学习器我建议不要加原始特征。因为元特征维度够大再塞入原始特征会让元模型过于复杂而且原始特征经过基学习器之后的信息已经高度压缩再重复加入只会增大过拟合风险而不增加新信息。我在自己的实验里也验证过四个基学习器基础训练下加入原始特征后AUC反而下跌了约0.005。5. 效果评估与业务场景的落地接入5.1 线下评估的完整维度不只是AUC最终模型在这个银行数据集上得到的线下评估结果折合下来AUC大约在0.89到0.91区间相比最佳单模型LightGBM提升了约0.02到0.03。但AUC只是其中一个维度还有很多值得关注的指标。KS值从单模型的约0.62提升到了0.66说明正负样本群体的概率分布分离度更高这直接决定了按预测概率排序后在客户名单的“TOP 10%”区间里能圈到多少实际认购客户。实际业务中营销触达成本是固定的模型要帮助决策的是“按照概率排序后应该触达到前百分之多少”KS值高意味着同样触达量下能圈中更多有效客户。LogLoss从单模型的0.42降到了0.38左右。这个指标在银行风控场景里非常实用因为后续如果要做预期损失计算或者利润优化需要的不是排名而是校准良好的概率值。如果单个样本的预测概率从0.7变成0.8对排序影响不大但对于预计收入计算来说绝对是天壤之别。5.2 银行场景下的模型落地从预测到行动模型做完之后与业务侧的衔接是一道非常需要经验处理的工作。我习惯把预测结果分成三档高价值客户预测概率前10%、中价值客户前10%~30%、低潜力客户其余70%。对高价值客户建议客户经理一对一电话沟通对中价值客户推送短信加App弹窗低潜力客户保持常态触达即可。三档策略后模型的营销响应率提升在2到3倍区间这是银行侧业务打标时经常看到的结果。这里有个小教训不要只给业务侧输出一个概率值一定要同时输出预测背后最关键的三四个特征贡献项。比如某个客户预测概率很高模型认为他收入高且持有理财产品超过两年且距离上次交易不超过七天。业务同事拿到这些特征就能针对性设计话术而不是提供一个无法解释的黑盒分数。5.3 上线监控与定期重训机制银行数据有一个特点客户行为随着市场环境和产品供给的变化而漂移。季度性理财产品节奏、利率调整、节假日红包活动等都会导致模型特征分布的变动。所以模型上线后至少每周监控三件事特征的均值漂移PSI群体稳定性指数、每天的预测概率分布、以及每周的实际响应率。另外在重训频率上建议月度重训一次基学习器。这主要是为了让基模型学习最新几个月的客户行为特征同时元模型也可以同步更新。注意不要每天重训否则模型会对短期噪音过度响应容易频繁波动。如果出现系统性事件比如银行结构调整、利率体系变动导致所有客户行为模式短期扭转要人工介入评估判断是否需要提前重训。最后分享一个小技巧Stacking在银行客户产品认购预测这种中等规模表格数据上效果提升相当明确。但整个流程中最容易被人忽视的是验证逻辑的一致性。我在项目中发现不少人只检查了OOF的AUC却没有检查测试集预测的分布是否合理——比如测试集预测均值如果和历史正样本率差距过大往往说明训练测试分布已经发生了较大漂移这时所有线下验证指标都不可靠。另外如果你打算在类似场景下复现这个方案我建议优先保证特征工程质量其次确保交叉验证没有泄漏最后才去调Stacking的层数和模型组合。顺序搞反了的话常常会出现模型怎么调都原地踏步的情况那通常不是融合方法的问题而是上游数据的问题。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询