ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

R语言Stacking集成学习:银行客户认购预测与营销响应率提升

2026/10/7 3:51:18 拓冰建站 浏览量
R语言Stacking集成学习:银行客户认购预测与营销响应率提升 银行客户产品认购预测我用Stacking把响应率翻了一倍R语言完整实现朋友在网点做理财经理总行推了一款定期存款产品分给他一条几万人的存量客户名单让他挨个打电话。他打了三天嗓子哑了响应率不到5%。这其实是零售银行营销里最让人头疼的事产品是好产品名单也是合规引入的名单但人力就这么多先给谁打、不先给谁打直接决定了这场营销的ROI。我是做数据分析的当时就跟他聊与其让客户经理凭经验和运气打电话不如让模型先给客户排个序模型认为最有可能认购的人排在最前面。这个项目后来我落地为一次完整的建模实践——用银行公开的营销数据集预测客户是否会认购定期存款产品模型层面没有用最简单的逻辑回归而是选择了集成学习里的Stacking方法全程在R语言里实现。这篇文章就是这次实践的完整复盘包含数据预处理的细节、Stacking的原理拆解、元特征生成层面对抗数据泄漏的完整链路以及几段可以直接改用的R代码。适合正在接触集成学习、或者想用机器学习给业务方输出营销名单的读者参考。1. 业务问题与建模目标为什么银行需要给客户排序的能力1.1 营销困境的本质是资源错配银行的存量客户成百上千万但客户经理的人数是固定的。一次产品营销尤其是电话营销拨打量有上限因为一个电话平均要聊三到五分钟能打的电话数量天然受限。传统做法是什么按客户资产规模排序钱多的先打。这个逻辑不能说错但存在明显盲区有钱不代表有意愿。一位资产千万的客户可能早被各种电话骚扰到反感而一位存款只有几万块的年轻白领可能正攒着首付对定存产品兴趣很高。所以营销名单的排序本质上是回答一个问题在相同资产条件下哪些客户当前对这个产品的潜在响应概率更高这就是客户产品认购预测要做的事——给每个客户算一个认购倾向分再按分数降序分配营销资源。1.2 建模目标不是追求准确率而是提升排序质量这里要特别强调一个视角问题。很多入门者拿到这种二分类任务第一反应是追求准确率Accuracy但在这个业务场景里准确率是个陷阱。原因很简单正样本占比太低。这类电话营销数据里实际认购的客户通常只占约10%上下。如果模型把所有客户都预测成不认购准确率能做到90%但这对业务毫无价值因为你等于一个电话都不用打。所以在营销响应这种正负样本极不平衡的任务里我们更关注的是模型能不能把真正会认购的那批人排到名单前面去。业内衡量这个能力的标准一般是两个AUCROC曲线下面积衡量模型把正样本排在负样本前面的能力不受阈值影响。KS值衡量模型区分正负样本的最大差距银行风控和营销模型里非常常见的指标。我这次建模过程中给业务方汇报时也从不说模型准确率94%而是说如果只给前20%的客户打电话我们能抓到全量响应客户中大约40%~50%的人。这句话业务方一听就懂因为直接对应着人力投入和营销产出。1.3 数据集说明与关键字段项目用的是UCI上的经典银行营销数据集对应的是一个葡萄牙银行机构的电话营销活动记录一共45211条样本17个输入变量目标变量是客户是否认购了定期存款。样本里正样本5289个占比约11.7%。这个数据在学术和工业实践里都很常见拿来做Stacking练习和复现都比较方便不用自己费劲清洗脏数据。关键字段大概分成几类字段类型字段业务含义客户基本信息age, job, marital, education年龄、职业、婚姻、教育程度财务状态default, balance, housing, loan违约记录、余额、房贷、个人贷款本次营销信息contact, month, day_of_week, duration联系渠道、月份、星期、通话时长历史营销信息campaign, pdays, previous, outcome本次营销拨打次数、上次联系距今天数、历史拨打次数、历史结果目标变量y是否认购定期存款这里每个字段都有讲究后文我会重点讲几个容易出坑的字段比如duration通话时长和pdays上次联系天数。2. Stacking方法的核心原理从意见征询到两级模型协作2.1 单模型的瓶颈在哪里说实话如果只追求一个能用的模型逻辑回归或者随机森林已经能应付这个场景。但问题是银行客户对产品的认购行为不是简单的线性关系。举一个具体例子年龄和认购定期存款的关系。太年轻的客户积蓄少对流动性要求高不太可能买定存年纪太大的客户可能更倾向保守但消费和医疗支出也要考虑同样不一定买。也就是说年龄和认购倾向大概率是倒U型关系甚至更复杂。逻辑回归这种线性模型学不到这种弯弯绕绕的东西单棵决策树又特别容易过拟合对数据里的噪声非常敏感。这就是我选择集成方法的出发点。集成学习的核心逻辑一句话多个有差异的模型犯错的位置往往不完全重叠把它们组合起来整体表现比任何一个单独模型都稳定。2.2 集成学习的三个流派和Stacking的定位业内主流的集成方法分三派Bagging并行训练多个模型各自独立预测最后投票或取平均。典型代表是随机森林。Boosting串行训练每一轮模型都在修正上一轮的预测残差。典型代表是XGBoost、LightGBM。Stacking堆叠不走投票而是把多个模型的预测结果当作新的特征再交给一个元模型去学习如何组合这些预测。前两种大家接触得比较多Stacking相对少一些但理解难度并不高。我常用一个比喻一个老板要做重大决策他不会只听一个顾问的意见也不会简单地让几个顾问投票而是会请几个风格不同的顾问比如销售总监、技术总监、财务总监分别说出他们的看法然后老板结合当前公司的实际情况自己再做一个综合判断。在Stacking里那几个风格不同的顾问就是第一层基学习器老板就是第二层元学习器。2.3 为什么第二层不是简单平均有人会问既然要综合多个模型的意见为什么不直接对几个模型的预测概率取算术平均非得再训练一个模型这个问题问得很好。平均权重的问题是一刀切。这个逻辑回归模型整体AUC低一些不代表它在某个客户群里不擅长那个XGBoost整体很强但不代表它在每个客户个体上都有把握。算术平均等于强行给所有顾问一样的发言权无法根据每个客户的具体情况动态调整谁的判断更可信。元学习器学习的是每个基学习器的可信度模式。比如逻辑回归更懂低收入客户群XGBoost更擅长捕捉复杂交互元模型会在不同区间给不同模型不同的权重。这就是Stacking相对于简单平均的本质优势从固定投票升级为看人下菜碟。3. R环境下数据清洗与特征工程这一环比模型选择更重要3.1 脏活累活数据读入与基础清洗项目里我用的R实现第一步就是把数据读进来统一处理好类型再往下走。这一步代码不复杂但几个细节值得讲清楚library(caret) library(pROC) library(xgboost) library(randomForest) library(glmnet) bank - read.csv(bank_marketing.csv, stringsAsFactors TRUE) str(bank) summary(bank)stringsAsFactors TRUE在这里是故意这么设的因为后续建模希望字符型变量以因子factor形式存在尤其对树类模型来说因子处理比手动做一堆哑变量更方便。这组数据的缺失值情况比较好没有大面积的空值。但在做真实银行数据时这一步千万不能跳过。我的习惯是先跑一遍summary观察每个变量的取值范围和缺失比例出现明显的异常值再单独处理。比如age出现200岁这种数据肯定要清洗掉或者修正。3.2 最容易让人误入歧途的duration字段这个字段我在多个项目里见过有人踩坑必须单独拿出来说。duration是最后一次通话的时长在模型里如果直接用AUC会瞬间飙到0.9以上看起来效果惊人。但你冷静想一下业务的时序关系通话时长是在营销人员已经给客户打完电话之后才知道的未打电话之前根本没有这个值。你拿着它去预测客户会不会买等于拿着答案去猜答案这是典型的数据泄漏。实际建模时要处理这个字段两种思路一是直接去掉因为预测阶段没有对应的真实值二是如果业务上确实需要保留通话信息作为线索也得分场景讨论比如在客户接起电话的前10秒能不能预判他会不会买这种实时场景里才可能用到部分通话信息。我在这个项目里选择直接剔除。删掉这列之后模型AUC回落到0.75~0.85的正常区间这才是真实可落地的水平。3.3 pdays的999编码处理pdays表示客户上次被联系过距今多少天但这个字段存在一个特殊值999业务含义是该客户之前从未被联系过。如果把999当成单纯的数值保留进模型做逻辑回归时它会被当成一个巨大的连续数值系数解释完全错乱做树模型时它又会被当成一个纯粹的分叉点丢失掉从未联系过这个业务含义。我的处理方式新增一个哑变量pnever ifelse(pdays 999, 1, 0)同时把pdays在999处的值替换成0或缺失标志让模型同时看到是否联系过和距上次联系天数两个维度的信息。这种业务语义拆解是特征工程里最基础也最有价值的一步比调模型参数的影响大得多。3.4 month和day_of_week这样的周期变量怎么处理模型里还有月份和星期这样的变量。直接按数字编码是不对的因为12月12和1月1其实是相邻的数字编码会让模型把12和1的距离当成11去理解。直接做one-hot也有问题因为月份是12个类别展开后维度增加且丢失了周期性。实操中有两种主流方案如果用的是树模型直接把month变成因子让树自己去切分比如发现3月9月10月这一类客户响应率高这是树模型的自由切分能力完全OK。如果准备喂给逻辑回归这种线性模型可以考虑用正弦余弦变换比如cos_month cos(2 * pi * month / 12)把12月到1月的周期性保留下来。我这里主要用树模型加逻辑回归混合的Stacking结构所以month直接作为因子保留逻辑回归那边依赖one-hot展开。这里有一个经验不要怕变量多但要怕变量含义混乱含义理清楚了展开成哑变量只是时间问题。3.5 训练集与测试集的划分数据划分我固定用分层抽样确保训练集和测试集里正样本比例接近。R里直接set.seed(2024) train_idx - createDataPartition(bank$y, p 0.7, list FALSE) train_data - bank[train_idx, ] test_data - bank[-train_idx, ]createDataPartition会按y这个因变量的类别比例进行分层抽样这样训练集和测试集的响应率就不会出现训练集6%、测试集20%这种严重漂移。注意set.seed必须设否则每次跑出来的结果随机波动太大后面评估模型根本没法判断提升是来自算法还是来自运气。4. 基学习器选型多样性决定了Stacking的上限4.1 为什么说基学习器越像Stacking越没用Stacking的核心收益来自基学习器之间的差异性。如果两个模型本质上是同一类算法比如随机森林和梯度提升树都是树模型它们的预测结果高度相关犯错模式也高度重合那么第二层模型没有任何分歧可以利用跟只用一个模型没区别。所以我在选基学习器时刻意挑选了不同算法族的代表而不是清一色堆树模型。最终确定的组合是随机森林、XGBoost、逻辑回归三件套。偶尔我也会加一个KNN进去但经验表明核心的三个已经够用。4.2 三件套各自的定位基学习器所属流派擅长处理的问题在Stacking里的角色逻辑回归线性模型线性关系、全局趋势、可解释性强提供全局线性视角补充树模型看不到的平滑判断随机森林Bagging高维特征、类别特征、抗过拟合提供稳定的基线预测抵抗单模型噪声XGBoostBoosting非线性交互、复杂模式捕捉特征间的复杂交互关系挖掘深层规律逻辑回归的作用经常被忽略但在Stacking里它是非常关键的一环。它虽然简单但学习到的是全局线性的规律。树模型擅长把一个空间切分成很多块而逻辑回归擅长给整个空间一个平滑的整体判断。两种思路互补性很强在线性部分很强、非线性部分弱的数据上加入逻辑回归之后Stacking的提升往往最明显。随机森林的价值是稳。它对异常值和噪声的容忍度高不容易被个别极端样本带偏而且对类别型变量非常友好不需要做标准化。XGBoost是主要非线性主力能发现变量之间的交互作用。但注意XGBoost调参敏感树太多容易过拟合eta太大又学不到细节稍后会给参数思路。4.3 为什么不把所有模型都塞进去有读者可能会问既然Stacking鼓励多样性那多放几个模型会不会更好比如LightGBM、CatBoost、SVM全都放进去。从原理上讲基学习器确实越多越好但前提是它们都有独立价值。实际做下来你会发现几个问题训练成本成倍增加每个模型都要做交叉验证生成元特征基学习器放5个以上跑一轮实验的时间就是一个下午。强相关模型塞多了第二层元模型容易出现过拟合因为它要拟合的不再是不同意见的集合而是一堆高度共线的变量。我的个人标准是先看两两模型的预测相关性。如果两个模型的相关系数超过0.85只留其中一个就够了如果相关性在0.7以下说明互补性好加进去有戏。这个原则比盲目堆模型可靠得多。4.4 基学习器的参数设置思路逻辑回归用glmnet做L2正则alpha0这样能防止特征共线性把系数放大。正则强度用交叉验证选择lambda.min防止逻辑回归在稀疏哑变量矩阵上过拟合。随机森林里树的数量设500mtry取默认sqrt(特征数)在实际数据上表现已经足够稳定。树太多收益递减树太少方差偏大。XGBoost这一块需要一点耐心。我初始设eta0.05max_depth5然后用内部交叉验证确定nrounds。这样配置的核心思路是小学习率加合理深度让模型学得稳但不死记。rf_model - randomForest(x x_train, y factor(y_train), ntree 500, importance TRUE) xgb_params - list(eta 0.05, max_depth 5, objective binary:logistic, eval_metric auc) xgb_model - xgboost(data xgb.DMatrix(data x_train, label y_train), params xgb_params, nrounds 200, verbose 0) lr_model - cv.glmnet(x_train_dummy, y_train, family binomial, alpha 0, type.measure auc)5. 第一层训练与元特征生成防止数据泄漏的关键细节5.1 最容易被忽略的坑元特征来自见过样本的模型Stacking如果只写一个先训练几个模型把预测结果拼起来再训练元模型看似简单但这里藏着一个足以毁掉整个模型的大坑。假如我先在全部训练数据上训练随机森林再去预测同一批训练数据得到的预测结果是什么是模型对见过面的老朋友的判断而不是对新客户的判断。树模型在训练数据上的预测概率通常两极分化接近0或者接近1极其自信。用这种结果去训练元模型元模型会以为基学习器很强大但真正到了测试集、面对从没见过的客户时基学习器的预测就会收敛得多没这么极端元模型的判断基础就崩塌了。结果就是Stacking在训练集上的表现漂亮得惊人但验证集和测试集上立刻原形毕露。这是我见过最多人踩的坑也是Stacking被说成容易过拟合的根本原因。5.2 正确解法K折交叉验证生成元特征正确的做法是对于训练集中的每个样本它的元特征必须来自一个从未见过该样本的模型。具体方式就是K折交叉验证把训练集切成K份我常用5份。每次取其中K-1份训练基学习器用训练好的模型去预测剩下1份。重复K次让每个样本都恰好被一个没见过它的模型预测过一次。把这些预测结果拼起来才是可以喂给元模型的干净元特征。而测试集的元特征则是用K个模型中任一个通常是全量训练集重训的模型去预测测试集得到的。这样生成的训练元特征和测试元特征在分布上是一致的第二层模型学到的规律才真实可信。5.3 R语言完整实现下面这段代码是项目里核心的元特征生成过程用的是5折交叉验证每一折训练三个基学习器分别对验证折叠生成预测最后合并成完整的元特征训练集。set.seed(2024) K - 5 folds - createFolds(factor(y_train), k K, list TRUE) meta_rf - numeric(length(y_train)) meta_xgb - numeric(length(y_train)) meta_lr - numeric(length(y_train)) for (i in 1:K) { train_idx - unlist(folds[-i]) valid_idx - unlist(folds[i]) # 基学习器1随机森林 rf_fold - randomForest(x x_train[train_idx, ], y factor(y_train[train_idx]), ntree 500) meta_rf[valid_idx] - predict(rf_fold, x_train[valid_idx, ], type prob)[, 1] # 基学习器2XGBoost xgb_fold - xgboost( data xgb.DMatrix(data x_train[train_idx, ], label y_train[train_idx]), params list(eta 0.05, max_depth 5, objective binary:logistic, eval_metric auc), nrounds 200, verbose 0 ) meta_xgb[valid_idx] - predict(xgb_fold, xgb.DMatrix(data x_train[valid_idx, ])) # 基学习器3逻辑回归L2正则 lr_fold - cv.glmnet(x_train_dummy[train_idx, ], y_train[train_idx], family binomial, alpha 0, type.measure auc) meta_lr[valid_idx] - predict(lr_fold, newx x_train_dummy[valid_idx, ], s lambda.min, type response)[, 1] } meta_train - data.frame(rf meta_rf, xgb meta_xgb, lr meta_lr, y y_train)需要注意的是这里逻辑回归的x_train_dummy是做了one-hot展开后的矩阵而树模型用的是原始因子矩阵。Stacking的基学习器可以各自使用适合自己算法的预处理方式这没关系因为到元特征层面所有模型的输出都已经统一成了概率值。这段代码跑完检查一下meta_train这三列的相关性。我这次项目里随机森林和XGBoost的元特征相关性大概在0.6~0.7之间逻辑回归和两个树模型的相关性在0.5左右。这个水平比较理想说明三个模型既没有完全重合也没有弱到完全没有共识。5.4 测试集的元特征生成测试集的元特征不需要交叉验证直接用在全量训练集上重新训练的基学习器预测测试集即可。但注意必须用全量训练集重训而不是交叉验证里某个单折模型这样才能用上全部信息。# 用全量训练集重训三个基学习器 rf_full - randomForest(x x_train, y factor(y_train), ntree 500) xgb_full - xgboost(data xgb.DMatrix(data x_train, label y_train), params xgb_params, nrounds 200, verbose 0) lr_full - cv.glmnet(x_train_dummy, y_train, family binomial, alpha 0, type.measure auc) meta_test - data.frame( rf predict(rf_full, x_test, type prob)[, 1], xgb predict(xgb_full, xgb.DMatrix(data x_test)), lr predict(lr_full, newx x_test_dummy, s lambda.min, type response)[, 1] )6. 元模型与两层模型的协作从得分到营销名单6.1 元模型选什么逻辑回归是最优解第一层基学习器训练好了元特征的训练数据也准备好了接下来就是第二层元模型的训练。这里我的选择依然是逻辑回归而且是带正则的逻辑回归。可能有人觉得不够刺激会想再叠一层XGBoost。但从原理来讲元模型的工作不是重新发现复杂规律而是学习第一层模型的权重组合。它的输入只有几个概率值维度很低完全不需要复杂的非线性映射。逻辑回归既简单、又稳定而且它还能告诉我们每个基学习器的权重直接反映谁在这个数据集上更可信。如果此时再用复杂的模型做元模型第一层模型的预测误差会在第二层被进一步放大过拟合风险比逻辑回归高得多。真正做好Stacking的诀窍是第一层强而各异第二层弱而稳健。set.seed(123) meta_model - glm(y ~ rf xgb lr, data meta_train, family binomial(link logit)) # 查看三个基学习器的系数 summary(meta_model)看模型系数通常会得到一个信息XGBoost的系数最大、最显著说明在这个数据集上XGBoost的信息量最高随机森林次之逻辑回归贡献也不错。这三个系数合在一起就是元模型学到的信任分配。6.2 效果评估不能只看单点指标模型训练结束后常规操作是画ROC曲线、算AUC。我用pROC包在测试集上评估结果大致是这个量级模型测试集AUC逻辑回归单独0.78随机森林单独0.80XGBoost单独0.82Stacking三模型集成0.85最基础的逻辑回归做基线大概在0.78左右XGBoost单模型能到0.82Stacking之后在0.85附近。这个提升幅度不是特别夸张大概3个点的AUC。但请注意在银行营销这种大规模存量名单场景里AUC每提升一点对营销名单头部人群的响应率提升都可能非常可观这是量变到质变的过程。再算KS值。把测试集预测概率按10个分箱计算每个分箱里好样本认购和坏样本不认购累计占比差的绝对最大值。Stacking的KS大约在0.55~0.6之间说明认购和不认购两个群体在得分分布上分离得比较明显。不过要说明一下具体数字和数据集有关不同运行环境下可能浮动。我这篇文章里的核心价值在于完整的建模方法和验证思路不必纠结数字本身。6.3 从模型得分到营销名单阈值怎么定模型只是产出概率真正对业务有价值的是那张营销名单。我通常按预测概率从高到低排序让业务方按人力上限去切前多少比例。假设全量名单有10000个客户整体实际响应率约11.7%随机拨打10000人预计能约到1170个响应客户。如果把Stacking模型的预测概率降序排列只打前20%也就是2000人这2000人的实际响应率按模型头部提纯效果来看大概率能做到30%左右也就是约600个响应客户。同样的电话量从1170降到600看起来是减少了但注意这里只打了2000个电话就抓到600个而原先要打10000个电话才抓到1170个。如果把这2000人的额度换成功力相当于用原来五分之一的电话量抓到了一半的响应客户。这种对比口径财务人员一眼就能看懂模型的价值也就落地了。6.4 阈值不是拍脑袋定的具体的阈值设定要结合成本和收益来算每通电话的营销成本人力通讯时间单笔产品签约带来的平均利润模型在每个阈值下的捕获率曲线我的习惯是先算预期收益 响应率 × 平均单户利润然后算营销成本 拨打量 × 单通成本找到投入产出比最高的切分点而不是机械地固定在top20%。有时候top30%的名单ROI反而比top20%更高因为头部太强势的客户也许已经被其他渠道转化过了。7. 实战过程中踩过的坑从AUC虚高到特征语义错乱7.1 虚拟变量和树模型的因素冲突有一次我图省事把所有分类变量一次性做了one-hot展开然后把展开后的矩阵同时喂给随机森林和逻辑回归。结果树模型的表现反而下降了。原因在于随机森林处理原始因子时可以利用按类别切分的优势做多路切分而展开成大量0/1哑变量后树的每一次切分只能选一个哑变量逻辑上等价于只能选某个类别忽略了类别之间的组合关系。解决办法树模型用原始因子矩阵线性模型用one-hot后的哑变量矩阵。基学习器可以有不同的输入形态这在Stacking框架里完全成立因为每个模型内部的处理逻辑本来就是独立的。7.2 类别变量强行转数值逻辑回归系数彻底失控另一个类别变量处理上的坑是图省事把职业、教育水平转成1、2、3、4这样的数字编码然后直接喂给逻辑回归。逻辑回归会把它当成连续变量比如教育程度从1变到4模型会理解为每升一级认购概率变化一个固定量但实际上从小学到大学的语义距离根本不是等距的。解决方法是除非变量天然有序且间隔确实近似相等否则一律转成因子线性模型侧做one-hot树模型侧转为因子直接使用。7.3 特征相关性和同质化的误判还有一次我只用了两个树模型做Stacking随机森林和GBDT。跑出来的结果和单模型XGBoost几乎一样毫无提升。后来做了预测相关性分析才发现这两个模型的相关系数在0.9以上——它们犯了几乎一样的错误元模型学不到任何新东西。从那次之后我给自己定了个流程每加一个基学习器之前先算一遍它与现有模型在验证集预测上的Spearman相关系数。如果相关系数高就果断放弃不因为多一个模型听上去更高级而堆砌无意义的结构。7.4 R语言环境依赖的坑xgboost安装编译问题R语言实现里还有一类跟模型无关但很折磨人的坑就是依赖包的安装。xgboost包在部分R版本下需要编译而这又牵扯到本地的C编译器和相关系统库。我在Windows机器上装过几次都要折腾半天。经验上两个办法最稳妥尽量用install.packages(xgboost)走CRAN预编译版本不要轻易去GitHub装开发版。如果公司电脑不方便装编译器直接在anaconda环境里用conda install -c conda-forge r-xgboostconda会帮忙处理动态库依赖比裸R折腾少得多。另外一个常见问题是rJava。如果你要用某些包比如部分建模辅助工具依赖Java环境容易被环境变量问题卡住。解决办法也很简单不用就坚决不装R语言生态里同一个功能通常能找到无Java依赖的替代方案。7.5 不要迷信模型复杂度最后一个坑不是技术上的而是心态上的。遇到过几次同行为了追求Stacking的全都要效果把LightGBM、CatBoost、SVM、神经网络全部拽进来训练花了几个小时最后AUC也就比三模型版本高了0.003连统计显著性都过不了。我的经验是Stacking不是模型越复杂越好而是信息互补性越强越好。三个不同算法族的模型已经能覆盖线性、Bagging、Boosting三大主流思想性价比最高。在此基础上再加模型边际收益递减而且引入共线风险。真正要投入精力的地方是特征工程和样本质量那才是压杠杆的地方。写到最后的一点经验按我个人习惯每次跑完一个模型都会把三件事记录下来基学习器的预测相关性矩阵、元模型的权重系数、各模型在测试集上的AUC。这三样东西是下一次建模时最有价值的输入。尤其是相关性矩阵直接决定要不要替换某个基学习器比单纯看单个AUC更有指导意义。如果未来想把这套流程做得更系统可以往两个方向扩展一是把基学习器换成带业务约束的模型比如把产品风险等级作为先验特征二是在元特征里加入非模型信息比如客户历史交互次数、渠道偏好等业务侧变量让第二层的判断依据更丰满——这也是Stacking这个框架最有想象力的一点它的输入不必只来自模型任何你觉得有判别力的信息都可以作为特征参与第二层学习。