银行客户产品认购预测:机器学习实战中的特征工程与模型调参要点

发布时间:2026/10/12 10:16:17

银行客户产品认购预测:机器学习实战中的特征工程与模型调参要点 简介一套完整的银行客户产品认购预测系统源码与资料基于Python机器学习构建分类模型用于预测客户对金融产品的认购意愿适合数据挖掘学习者、银行营销分析人员参考。项目涵盖数据预处理、特征工程、模型训练与评估等环节并通过多种算法对比实验选出高准确率模型为精准营销提供数据支持。资源压缩包共79个文件大小约10.47MB包含5个Python脚本、3个Jupyter Notebook、5个CSV数据文件、2个pkl模型文件、43个PNG图表及字段说明Excel等。图表直观展示各特征的分布与正样本对比便于理解数据洞察脚本和Notebook呈现从数据清洗到建模的完整代码流程。目前已有43人学习下载。通过这份资料可掌握银行营销数据集的处理方法、特征工程思路及模型调优技巧还能直接查看训练好的模型与预测结果适合作为机器学习入门到实战的参考项目。1. 银行客户产品认购预测一个比「猜不买」难在哪里的机器学习实战项目「银行客户产品认购预测」这类项目用大白话讲就是营销部门拿到一批客户名单里面有年龄、收入、通话时长、贷款情况等字段要预测「这次电话打过去客户会不会认购某款产品」。听起来是个标准二分类任务真正上手才发现它是一场与「准确率陷阱」的较量——正样本占比往往只有 5% 到 15%模型只要躺平全猜「不认购」准确率就能冲到九成。这个项目的价值在于特征贴近真实业务、成本定义明确、评估方式有讲究非常适合作为机器学习入门到实战的跳板毕业设计、求职作品集、团队预研都能直接参考。拿到源码和资料只是第一步能独立跑通、能调出可解释的结果才算真正握住这套方案。2. 数据与特征拆解看清这 20 来个字段后再谈建模2.1 三类字段与目标变量客户档案、营销触达、外部环境常见银行营销数据一般分成三类特征。第一类是客户静态档案年龄、职业、婚姻状况、教育程度、是否有违约记录、是否有房贷、是否有个人贷款第二类是本次营销动作通话时长、本活动联系次数、距上次联系的间隔天数、上月是否有过接触第三类是外部经济环境就业率变动、消费者信心指数、基准利率等宏观指标。这三类特征对「认购」的解释力完全不同针对银行客户产品认购预测系统设计特征工程时要先把它们分开处理。类别典型字段取值示例建模用途客户静态age、job、marital、education、default42、technician、married、secondary、no刻画客户资产水平与还款稳定性营销动作duration、campaign、pdays、contact180秒、2次、999、cellular本次触达强度与过往营销响应外部环境emp.var.rate、cons.price.idx-1.8、93.9补充经济热度与季节性信息目标变量subscribedyes / no是否完成产品认购动作pdays 这个字段要格外留神很多数据里用 999 表示「从来没有被联系过」这是一个特殊语义不能当普通数值填充。还有 duration 字段即本次通话时长它与认购的关联极强但业务上有一个致命问题预测发生在拨号之前通话还没发生哪来的通话时长这类「未来信息」如果不处理模型在离线验证集上分数很好看上线就翻车。这一条后面展开。2.2 评估指标先定下来为什么 AUC 与召回率比准确率可信在数据层面银行客户认购预测的典型正样本占比很低如果拿准确率当唯一指标训练出来的模型几乎没有实用价值。可以自己看一组数字测试集里 1000 人只有 60 人真正认购一个什么都不学的模型只要全输出「不认购」准确率就是 94%。但业务方要的不是这个他们想知道「这批人里哪些值得打电话」。所以第一步就要把评估指标定为 ROC-AUC 和召回率。AUC 衡量的是排序能力反映「把真正认购的人排到前面去」的概率它不受类别不平衡影响作为模型对比指标很稳而召回率衡量「真正认购的人里抓到了多少」直接关系到营销名单的覆盖度。后续建模和调参都以这两个指标为准准确率只作为辅助参考。如果拿到的源码里还停留在 accuracy建议先改评估逻辑再接数据。3. 特征工程落地从 CSV 原始数据到模型可用的向量3.1 数据读取与缺失值处理分号分隔与 unknown 陷阱原始数据多数以 CSV 格式存放一个样本一行注意分隔符不一定是逗号。我一般先写这段脚本把数据读进来顺便看缺失和类型分布import pandas as pd import numpy as np df pd.read_csv(bank_marketing.csv, sep;) print(df.shape) print(df.isnull().sum()) print(df.dtypes)sep; 是要点这类数据集经常用分号做分隔符用默认逗号读会得到一整列。isnull().sum() 只能看到真正的 NaN而这份数据里很多缺失是以字符串 unknown 存在的比如职业、教育程度里都会有。如果直接df.replace(unknown, np.nan)再统计会看到缺失量比想象大得多。针对缺失值不同字段我一般给不同策略职业和教育程度这类类别字段用众数填充后编码即可pdays 这类带有特殊值 999 的字段不要当缺失处理而是保留 999 并在后续特征衍生时单独生成一个「是否从未接触过」的布尔特征。数值型宏观指标如果缺拉一条序列做线性插值更稳直接填列均值会削弱字段区分度。3.2 类别特征编码One-Hot、有序映射与 target encoding 的边界类别特征里education 有内在顺序适合用有序编码job、contact 这类无序类别需要用 One-Hot 或者目标编码。先看有序编码的做法edu_order { illiterate: 0, basic.4y: 1, basic.6y: 2, basic.9y: 3, high.school: 4, professional.course: 5, university.degree: 6, unknown: 1 } df[education_code] df[education].map(edu_order)map 比 LabelEncoder 更可控因为教育程度是单调有意义的LabelEncoder 按字母序赋值反而丢顺序语义。这里把 unknown 映射到了 1跟最低学历档同值逻辑是「未知学历的营销价值接近最低档」实际业务里可以依此调整。job 字段类别多且取值频率差异大我建议用 target encoding目标编码。它的核心是用该类别下样本的认购率替代原始字符串能压缩维度且保留信息。但关键约束是必须在交叉验证的每一折内、只用训练折数据计算编码否则会引入目标泄漏。常见做法是写一个自定义编码类from sklearn.base import BaseEstimator, TransformerMixin class TargetEncoder(BaseEstimator, TransformerMixin): def fit(self, X, y): self.mapping_ y.groupby(X).mean() self.global_mean_ y.mean() return self def transform(self, X): return X.map(self.mapping_).fillna(self.global_mean_).values这段代码在 fit 时记录每个类别的均值transform 时替换未知类别用全局均值兜底。为什么这么谨慎很多人直接在全部数据上算均值编码模型在验证集上会「偷看」到目标信息那时 AUC 高得漂亮换成新数据立刻打回原形。这个坑后面还会展开。3.3 数值分箱与时间特征衍生让模型看到「聊了多久」的真实含义年龄、通话次数、距上次联系天数这几个数值字段对线性模型要做分箱或标准化。分箱的意义在于年龄和认购并非线性关系——26 岁和 36 岁差别不大但 26 岁和 60 岁的理财偏好差异显著。用 pd.cut 生成年龄段df[age_bin] pd.cut( df[age], bins[0, 25, 35, 45, 55, 100], labels[25, 25-35, 35-45, 45-55, 55] ) age_encoded pd.get_dummies(df[age_bin], prefixage)分箱后要不要再 One-Hot取决于下游模型逻辑回归用 One-Hot树模型直接用数值更方便。还有两个时间字段值得衍生通话时长分钟数、是否工作时段联系。这类字段我更建议直接保留给树模型再额外造两个布尔特征is_contacted_before和is_long_call通话超过 180 秒。最后把特征拼装起来准备进模型feature_cols [age_bin, job_encoded, marital, education_code, default, housing, loan, contact, month, campaign, is_contacted_before, is_long_call] X pd.get_dummies(df[feature_cols], drop_firstTrue) y df[subscribed].map({yes: 1, no: 0})month 也要谨慎处理它是「上个月是否联系过」的营销纪律特征但如果原始字段含义是「本次电话发生的月份」则属于环境信息放进模型没问题。我在做这类项目时习惯把原始 month 删掉只保留一个「营销旺季」布尔字段这样模型更稳。4. 建模训练与调参逻辑回归基线 LightGBM 提精度4.1 逻辑回归基线先跑通全流程再谈复杂模型无论打算用什么高级模型我一般先跑一个逻辑回归作为基线。它的好处是训练快、可解释性强而且能验证特征工程有没有明显泄漏。代码很简单from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_auc_score from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) clf LogisticRegression( C0.1, solverliblinear, class_weightbalanced, max_iter1000 ) clf.fit(X_train, y_train) y_prob clf.predict_proba(X_test)[:, 1] print(AUC:, roc_auc_score(y_test, y_prob))三个参数值得说明。C0.1 是正则化强度的倒数C 越小正则越强防止高维 One-Hot 特征过拟合solver 用 liblinear 是因为它处理小数据集二分类稳定而且对 L1/L2 惩罚都支持class_weightbalanced 告诉模型按类别频率自动调整权重这是对付类别不平衡的第一道防线。跑完先记下 AUC比如 0.78 左右。如果这里 AUC 就蹿到 0.9 以上先不要高兴回头检查特征里有没有混入 duration 之类泄漏字段。基线模型的另一个作用是用来算概率分布画出预测分数直方图能直观看到模型输出是否过于集中。4.2 类别不平衡class_weight 与采样策略对比class_weight 能解决一部分不平衡但不能包治百病。当正样本占比低于 10% 时我更推荐在逻辑回归基础上叠加阈值移动而不是盲目换采样方法。有时候用 SMOTE 过采样反而会让模型过度关注合成样本尤其树模型上效果不稳定。先把 class_weight 打开对比一下不同权重下的结果for w in [None, balanced, {0: 1, 1: 10}, {0: 1, 1: 20}]: clf LogisticRegression(C0.1, solverliblinear, class_weightw, max_iter1000) clf.fit(X_train, y_train) auc roc_auc_score(y_test, clf.predict_proba(X_test)[:, 1]) print(fclass_weight{w}, AUC{auc:.4f})注意AUC 变化通常不大因为 AUC 只看排序真正变化大的是精确率与召回率 trade-off。如果业务目标是「宁可多打十个电话也别漏掉一个潜在认购客户」那就要在保证召回率不低于某个下限的前提下调权重。跑到这个阶段已经能回答「这个项目值不值得做」的第一层疑问特征区分度够不够模型能不能学到信号。4.3 LightGBM 调参用 GridSearchCV 锁定 3 个关键参数当逻辑回归 AUC 稳定在 0.75 以上时我会换 LightGBM 提升上限。树模型擅长捕捉特征交互——例如「年轻人 电话营销」和「中年人 贷款」可能触发完全不同的认购模式。先用一个保守参数集跑通import lightgbm as lgb model lgb.LGBMClassifier( objectivebinary, learning_rate0.05, max_depth4, n_estimators500, subsample0.8, colsample_bytree0.8, verbose-1, random_state42 ) model.fit( X_train, y_train, eval_set[(X_test, y_test)], eval_metricauc )初跑看两件事训练 AUC 和验证 AUC 的距离。如果两者差距大于 0.08说明过拟合优先调小 max_depth、增大 min_child_samples如果两者都很低说明模型欠拟合调大 n_estimators 或调高 learning_rate。不要一上来就跑网格搜索先手动粗调一轮再进 GridSearchCVfrom sklearn.model_selection import GridSearchCV param_grid { max_depth: [3, 4, 6], learning_rate: [0.02, 0.05, 0.1], n_estimators: [300, 500, 800], } gs GridSearchCV( lgb.LGBMClassifier(subsample0.8, colsample_bytree0.8, verbose-1, random_state42), param_grid, scoringroc_auc, cv5, n_jobs-1 ) gs.fit(X_train, y_train) print(best params:, gs.best_params_) print(best AUC:, gs.best_score_)GridSearchCV 会做五折交叉验证每折内部自动完成训练与验证能比较稳地选出组合参数。这里要提醒一个细节全部特征用稀疏矩阵表示时LightGBM 需要categorical_feature参数指定类别列如果忽略这一点类别字段会被当成数值排序精度一般会掉 0.02 左右。5. 模型评估的 5 个常见坑数据泄漏、时间穿越与虚高指标排查5.1 现象验证集 AUC 0.97换一批数据直接崩验证集上 AUC 极高新数据上完全不行几乎可以断定是数据泄漏。原因大多出在 duration 字段。我在一次实战里把通话时长原封不动放进特征AUC 直接从 0.78 升到 0.96但业务上预测发生在拨号之前这个特征根本不存在。解决训练与评估阶段就把 duration 从特征列中剔除而不是等部署时才想起来。这个字段只适合用来做事后分析比如「成交客户的通话时长分布」不能进训练矩阵。5.2 现象模型只输出「不认购」准确率却有 89%这是典型的类别不平衡幻觉。原因不是模型坏了而是默认阈值 0.5 对正样本占比 10% 的数据完全不适用模型算出的认购概率普遍低于 0.5。解决不要用predict()用predict_proba拿概率再按业务成本调阈值。银行客户产品认购预测系统上线时我通常把阈值从 0.5 降到 0.15 或 0.2召回率能明显回升。赔本还是赚钱取决于一次电话营销成本与一笔认购收益的比例。5.3 现象target encoding 后过拟合特征重要性排名乱跳target encoding 如果在整个数据集上计算会让类别特征携带大量目标信息树模型会优先分裂这些「天然泄漏」特征特征重要性排名反复横跳。解决回到 3.2 节的编码实现务必在交叉验证折内 fit。在 GridSearchCV 里要额外留意必须把编码器放进 Pipeline 而不是预先编码否则网格搜索的每一折仍然会看到全局均值。5.4 现象随机划分把未来时间放进了训练集银行营销数据天然带有时间顺序直接用train_test_split(random_state42)随机划分等于把后几个月的样本混进了训练集。当外部环境字段利率、消费指数存在时模型会「记住」特定月份的模式而不是学业务逻辑。解决按时间切分训练集取前 70% 的时间段测试集取后 30%这个动作坚决不用随机种子。5.5 现象调参后 AUC 没变但投放收益反而下滑AUC 是排序指标它只告诉「好坏客户被分开的程度」不告诉「分界点放在哪里」。调参让 AUC 上升 0.005但如果阈值还停在 0.5营销名单里真正被捞出来的认购客户数量可能没涨。解决每次调参后同时记录recallthreshold落在哪个位置。我在实际交付时还会让业务方确认1000 通电话里有 30 通有用和 500 通电话里有 200 通有用哪个更符合当前投放节奏。模型调参永远服务于运营成本不服务于指标数字。6. 从模型到可用概率阈值调整与关键特征验证6.1 用概率阈值切成本一次电话的成本与一次认购的收益模型输出的概率必须换算成营销动作。假定一次有效电话外呼成本约 5 元一笔产品认购带来的利润约 200 元那么只要命中率高于 2.5% 就是赚的。这时在验证集上把预测概率按阈值切分画一条召回率-精确率曲线from sklearn.metrics import precision_recall_curve probas model.predict_proba(X_test)[:, 1] precisions, recalls, thresholds precision_recall_curve(y_test, probas) # 找阈值使得 precision 5% for p, r, t in zip(precisions, recalls, thresholds): if p 0.05: print(fthreshold{t:.3f}, precision{p:.3f}, recall{r:.3f}) break上面这段逻辑是营收要覆盖成本命中率至少 5%找到能让命中率达标的最低阈值。如果这个阈值下召回率太低就需要把业务预算调高或放宽命中要求。这类项目最终交付的往往不是模型本身而是一套「阈值决策表」。6.2 用 SHAP 反验数据质量警惕 duration 特征排第一模型调好之后我习惯再跑一次 SHAP 特征重要性验证结果是否符合业务常识。如果重要性排名第一的是某个宏观指标或某个与认购逻辑无关的字段多半是特征工程或数据切分出了问题import shap explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_test) shap.summary_plot(shap_values, X_test)银行客户产品认购预测这个场景里合理的头部特征排序一般围绕「上次营销响应、联系次数、年龄、贷款状态」展开。如果看到通话时长类特征排到第一直接回到 5.1 节去查泄漏如果看到月份特征异常高回到 5.4 节去查时间切分。这一步是我做每个机器学习项目交付前的固定动作相当于给模型做一次体检。最后说一个我的习惯这类带源码资料的项目复现时一定要自己重新写一遍数据加载和特征编码不要直接跑全部脚本。自己写的过程中会发现数据里哪些字段是黑匣子、哪些策略是作者试过的经验。我最早做类似项目时就是直接跑通源码评估结果漂亮得不像话直到部署前做特征审查才发现 duration 泄漏差点闹笑话。从那以后我每一次都先跑 baseline、再做特征审查、最后才动高级模型。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/12 10:11:09

Matlab 2019b Audio Toolbox设备测试踩坑与实战指南

联调踩坑记:Matlab 2019b Audio Toolbox 设备测试到底怎么搞搞音频处理、语音算法、声学仿真的人,应该都绕不开 Matlab。尤其是 2019b 的 Audio Toolbox,这个版本我从装完一直用到现在,期间做过不少音频设备的接入测试&#xff0c…

2026/10/12 10:11:09

AutoJS实战:安卓短视频App自动化脚本完整开发指南

1. 项目背景与应用场景刷宝短视频是前几年挺火的一款看视频赚积分类应用,核心玩法就是刷视频、做任务、攒积分换现金。虽然现在热度下去了,但把它当作AutoJS的学习练手对象仍然非常合适——App结构不复杂、操作路径固定、界面元素容易定位,新…

2026/10/12 16:42:43

Codex智能体全场景自动化:从代码补全到工程闭环执行

最初接触到Codex智能体,是在一次工期紧张到想掀桌的任务里:要把某个老项目的工具函数拆到独立模块,顺带修掉一堆历史遗留的边界问题。我当时只是抱着试一试的心态,把需求丢给智能体,结果它自己列计划、逐个文件修改、跑…

2026/10/12 16:42:43

信道这章别死记:恒参/随参、多径衰落和香农公式一次讲透

通信原理学到信道这一章,很多人的第一反应是:信道不就是一根线或者一段空气吗,有什么好研究的?等你看完樊昌信、曹丽娜第七版教材的第四章,就知道事情远没有那么简单。信道是整个通信系统模型里最不受我们控制的一环&a…

2026/10/12 16:42:43

编译原理实验全解析:词法分析、NFA转DFA、递归下降与LL(1)语法分析

简介:编译原理是计算机科学的核心课程,编译器前端由词法分析和语法分析构成。词法分析基于有限自动机将源代码转换为记号流,语法分析则依赖上下文无关文法验证程序结构。针对非确定性自动机,子集构造法利用ε闭包与move函数实现NF…

2026/10/12 16:42:43

富士施乐M118w完整版驱动安装指南:解决打印扫描问题

简介:富士施乐 DocuPrint M118w 打印机驱动完整版,面向使用该型号打印机的家庭与办公用户,用于解决设备在电脑上无法识别、打印中断、连接异常或驱动不兼容等常见问题。驱动兼容 32 位与 64 位 Windows 7、Windows 10 及 Windows 11&#xff…

2026/10/12 16:37:43

PyCharm手写BP神经网络求解Iris分类实战

简介:本资源是一份面向机器学习初学者与高校课程实践者的BP神经网络入门级实战项目,聚焦Iris经典数据集的三分类任务,完整呈现从理论理解到PyCharm环境编码实现的全流程。压缩包共4个文件(2个Python源码、1份Word实验报告、1个CSV…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/12 12:01:26

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/12 0:04:22

绝缘子缺陷检测数据集清洗与工业级训练实战指南

简介:本资源是面向电力AI研发人员、工业视觉工程师及智能巡检系统开发者的绝缘子缺陷检测专用YOLO格式数据集,解决无人机航拍场景下绝缘子破损、污闪、积雪等9类典型缺陷的精准识别与定位难题。数据集共2139张真实巡检图像(含训练/验证/测试集…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑