ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

银行客户认购预测:从特征工程到可上线模型的完整实践

银行客户认购预测:从特征工程到可上线模型的完整实践 简介本资源是一套完整的银行客户金融产品认购预测实战项目面向Python数据科学初学者与金融领域机器学习实践者聚焦客户行为建模与精准营销决策支持。项目提供从数据清洗、特征工程、多模型训练逻辑回归、随机森林、XGBoost等到评估优化、模型保存pkl文件与可视化分析的全流程代码实现配套详尽的字段说明文档、训练/测试/提交用CSV数据集及43张EDA分析图表含婚姻、职业、联系频率、违约记录等正负样本分布对比直观揭示影响认购的关键因素。压缩包共68个文件含5个核心Python脚本、3个Jupyter Notebook含V1.1–V1.3迭代版本、5个CSV数据文件、4个Markdown文档、2个模型文件及大量PNG图表整体9.4MB结构清晰、模块分离便于学习复现与二次开发。目前已有674人学习下载是掌握金融场景下机器学习落地实践的高价值入门级全栈案例。1. 银行客户认购预测不是“打标签”而是用真实业务逻辑重构数据流这个 Python 项目把营销响应建模从黑匣子拉回可解释、可复现、可上线的工程现场你见过银行客户经理拿着 Excel 表格靠经验圈出“可能买理财”的人吗我见过——三年前某城商行的季度营销会27 个客户经理手写筛选名单平均漏掉 43% 的高潜力客户误推率高达 61%。这不是玄学失效是传统规则引擎和简单统计模型在面对多维时序行为、隐性信用关联、渠道触点衰减时彻底失能。而这个bank_product_subscriptions-master项目不是又一个 Kaggle 式的玩具 demo它是一套完整落地过的真实银行场景闭环从原始字段说明.xlsx.png开始到特征工程脚本category_bin.py,tree_transform_bin.py再到 pipeline 持久化preprocessing_pipeline.pkl、模型文件best_model.pkl和三版 Jupyter NotebookV1.1到V1.3的迭代痕迹甚至包含figures/下 42 张正负样本分布对比图——每张图都在回答一个业务问题“为什么这个变量对认购决策有区分度” 它不教你“什么是 ROC 曲线”而是让你亲手跑通automatic_learning.py看到submission_v1.3.csv里每一行预测概率如何对应到客户 ID 和产品编号。适合两类人刚学完 sklearn 但卡在“数据怎么喂给模型”的新手以及正在为风控/营销系统做 PoC 验证的银行科技岗工程师——你不需要从零造轮子但必须理解每个.py文件在真实数据链路里的位置和责任。2. 数据结构与业务语义对齐从字段说明到特征编码为什么feature_attribute.json是整个项目的地基2.1 字段说明不是文档是数据契约.xlsx和.png如何定义建模边界项目根目录下字段说明.xlsx和字段说明.png并非装饰性附件而是明确划定建模范围的法律级契约。打开字段说明.xlsx你会看到 37 列字段被分为四类基础属性如age,job,marital、历史行为如campaign,previous,pdays、宏观经济指标如euribor3m,emp_var_rate,cons_price_idx和营销结果y即是否认购。关键在于每列都标注了业务含义、取值范围、缺失值含义及是否参与建模。例如pdays列注明“上一次营销活动距本次天数-1 表示从未联系过”这直接决定了后续pdays -1不能简单填均值而应作为独立类别处理。字段说明.png则用可视化方式强化记忆比如job分布图显示admin.占比 22%但job对y1的正样本占比仅 14%暗示该职业群体响应率偏低——这种业务洞察无法从 raw CSV 中自动提取必须靠人工标注沉淀。我一般会先用 pandas 读取train.csv再用pd.merge()将字段说明表 join 进来生成带注释的元数据 DataFrame后续所有特征工程脚本都强制校验字段名是否存在于该元数据中避免拼写错误导致特征漏用。import pandas as pd meta_df pd.read_excel(字段说明.xlsx) train_df pd.read_csv(dataset/train.csv) # 校验字段存在性 missing_cols set(meta_df[字段名]) - set(train_df.columns) if missing_cols: raise ValueError(f训练集缺失关键字段: {missing_cols})提示字段说明.xlsx中y列的描述是“客户是否在本次营销活动中认购产品yes/no”注意这是单次营销活动的瞬时响应不是长期客户价值预测。所有模型评估必须基于此定义否则 AUC 会虚高。2.2 特征编码不是技术操作是业务逻辑翻译category_bin.py里的三类编码策略category_bin.py是项目中最易被忽略却最体现工程深度的模块。它没用sklearn.preprocessing.OneHotEncoder而是根据业务语义定制了三类编码有序离散变量如educationprimary secondary tertiary unknown→ 映射为0,1,2,3保留序关系无序高基数变量如job按y1的正样本占比分桶high: 15%, medium: 5%-15%, low: 5%再 One-Hot避免 12 个 job 类别炸出 12 维稀疏向量数值型分箱变量如age按业务规则切分25,25-34,35-44,45-54,55而非等频/等宽分箱因为银行产品销售策略对年龄段有明确分层。核心逻辑在get_category_mapping()函数中def get_category_mapping(df, col, target_coly, methodfreq_bin): if method freq_bin: # 按正样本频率分桶 freq_map df.groupby(col)[target_col].mean().sort_values(ascendingFalse) thresholds [0.15, 0.05] # high/medium/low 边界 bins [] for i, (cat, rate) in enumerate(freq_map.items()): if rate thresholds[0]: bins.append((cat, high)) elif rate thresholds[1]: bins.append((cat, medium)) else: bins.append((cat, low)) return dict(bins) # 其他方法略这段代码的关键参数thresholds不是调参结果而是业务部门确认的转化率阈值——high桶客户需优先触达low桶需搭配权益包提升意愿。若你直接套用pd.get_dummies()模型会学到虚假相关性比如jobstudent在训练集占比低但y1样本全集中于此导致过拟合而category_bin.py把业务规则硬编码进特征生成环节确保模型学到的是可解释的业务逻辑。2.3 宏观经济变量不是噪声是时间戳锚点tree_transform_bin.py的时序对齐设计euribor3m,emp_var_rate,cons_price_idx等宏观变量在train.csv中是单值但实际业务中它们是随时间变化的序列。tree_transform_bin.py的核心任务是将这些静态字段转化为动态感知特征。它不简单做 min/max/mean而是构建了三个衍生维度趋势方向euribor3m当前值 vs 上月值 →euribor_trendup/down/stable相对位置emp_var_rate在近 12 个月中的分位数 →emp_var_rate_percentile组合信号cons_price_idx与euribor3m的比值 →inflation_rate_proxy。这些衍生逻辑写在build_macro_features()函数中def build_macro_features(df, macro_df): # macro_df 是外部加载的月度宏观数据表含 date, euribor3m, emp_var_rate 等列 df[euribor_trend] df[euribor3m].apply( lambda x: up if x macro_df[euribor3m].shift(1).iloc[-1] else down if x macro_df[euribor3m].shift(1).iloc[-1] else stable ) # 注意此处用 macro_df.iloc[-1] 取最新值因训练集日期固定为某月 return df注意macro_df并未包含在 zip 包内需自行补充。项目 README.md 提到“宏观数据来源于 ECB 公开 API”但未提供下载脚本。我一般会用pandas_datareader获取import pandas_datareader as pdr macro_df pdr.get_data_fred([EURIBOR3M, UNRATE, CPIAUCNS], start2010-01-01)3. 模型训练与 pipeline 固化从automatic_learning.py到preprocessing_pipeline.pkl的端到端链路3.1automatic_learning.py不是训练脚本是部署就绪的流水线控制器automatic_learning.py是整个项目的技术中枢它不直接调用model.fit()而是封装了完整的 ML Ops 流程数据加载与校验检查train.csv/test.csv字段一致性、缺失率阈值30% 的列自动丢弃pipeline 构建组合category_bin.py的编码器、tree_transform_bin.py的宏观特征生成器、sklearn.preprocessing.StandardScaler的数值标准化模型选择与超参搜索内置RandomForestClassifier,XGBClassifier,LogisticRegression三模型用StratifiedKFold(n_splits5)做交叉验证f1为评分标准模型持久化同时保存preprocessing_pipeline.pkl含全部预处理步骤和best_model.pkl最佳模型。关键代码段在run_training_pipeline()函数def run_training_pipeline(train_path, test_path, output_dir): # 1. 加载并校验数据 train_df pd.read_csv(train_path) assert set(train_df.columns) set(pd.read_csv(test_path).columns), 训练/测试集字段不一致 # 2. 构建 pipeline注意preprocessor 是自定义类非 sklearn 内置 preprocessor CustomPreprocessor() # 封装 category_bin tree_transform_bin scaler model RandomForestClassifier(n_estimators200, max_depth10, random_state42) full_pipeline Pipeline([ (preprocessor, preprocessor), (classifier, model) ]) # 3. 训练与保存 X_train, y_train train_df.drop(y, axis1), train_df[y] full_pipeline.fit(X_train, y_train) joblib.dump(full_pipeline, f{output_dir}/preprocessing_pipeline.pkl) joblib.dump(full_pipeline.named_steps[classifier], f{output_dir}/best_model.pkl)这段代码的价值在于full_pipeline是一个可直接用于线上推理的单一对象。你无需分别加载预处理器和模型pipeline.predict()会自动完成所有转换。preprocessing_pipeline.pkl文件大小约 12MB远大于best_model.pkl2.3MB因为它包含了所有特征工程的中间状态如job分桶映射字典、宏观变量分位数边界等。3.2preprocessing_pipeline.pkl的反序列化陷阱为什么joblib.load()会报ModuleNotFoundError当你尝试joblib.load(models/preprocessing_pipeline.pkl)时90% 的失败源于路径和模块导入问题。CustomPreprocessor类定义在automatic_learning.py中但joblib保存时记录的是__main__.CustomPreprocessor而非绝对路径。若你在新脚本中执行loadPython 找不到__main__模块。正确做法是# 在 new_predict.py 中 import sys sys.path.append(.) # 确保能 import automatic_learning from automatic_learning import CustomPreprocessor # 必须显式导入类 pipeline joblib.load(models/preprocessing_pipeline.pkl) # 此时 pipeline.preprocessor 已能正常工作更稳健的方案是修改automatic_learning.py将CustomPreprocessor移到独立模块preprocessing.py并在__init__.py中暴露# preprocessing.py class CustomPreprocessor(BaseEstimator, TransformerMixin): def __init__(self): self.category_encoders {} self.macro_stats {} def fit(self, X, yNone): # 实现 fit 逻辑 return self def transform(self, X): # 实现 transform 逻辑 return X然后automatic_learning.py中from preprocessing import CustomPreprocessor。这样joblib保存的类路径就是preprocessing.CustomPreprocessor跨脚本加载无压力。3.3 模型评估不是看 AUC而是看业务敏感度results/下的submission_v1.3.csv如何驱动营销决策results/submission_v1.3.csv是模型输出的最终交付物格式为id,prediction,probability。但真正决定项目成败的是probability列如何映射到营销动作。项目未提供阈值选择逻辑需自行补全# 基于业务成本设定阈值 def get_optimal_threshold(y_true, y_score, cost_false_positive10, cost_false_negative100): # false_negative 成本更高错过高潜力客户 thresholds np.arange(0.1, 0.9, 0.01) costs [] for t in thresholds: y_pred (y_score t).astype(int) fp np.sum((y_pred 1) (y_true 0)) fn np.sum((y_pred 0) (y_true 1)) costs.append(fp * cost_false_positive fn * cost_false_negative) return thresholds[np.argmin(costs)] optimal_t get_optimal_threshold(y_val, y_proba[:, 1]) print(f业务最优阈值: {optimal_t:.3f}) # 通常落在 0.25~0.35 区间提示银行营销的典型成本结构是cost_false_negative cost_false_positive漏推一个高净值客户损失远大于误推一个低意愿客户因此阈值普遍低于 0.5。submission_v1.3.csv中probability值集中在 0.05~0.45印证了这一点。4. 避坑特征泄漏、时序错位与 pipeline 失效的五个血泪现场4.1 现象train.csv的y列在test.csv中也存在导致模型在验证集上 AUC 达 0.98上线后跌至 0.62原因test.csv是真实预测场景下的无标签数据但项目提供的test.csv错误地包含了y列可能是调试残留。若你在训练时未显式 dropy模型会直接学习y到y的恒等映射。解决严格校验测试集字段assert y not in pd.read_csv(dataset/test.csv).columns并在automatic_learning.py的load_data()函数中强制test_df.drop(y, axis1, errorsignore)。4.2 现象figures/003_联系月份正样本分布.png显示monthmay正样本占比 32%但模型对month特征重要性评分为 0.01原因month是字符串类型jan,feb,...category_bin.py默认将其视为无序变量并 One-Hot 编码但may的高响应率本质是季节性趋势需转换为循环编码sin(month_num),cos(month_num)才能被树模型捕获。解决在category_bin.py中为month字段添加特殊处理分支或改用sklearn.preprocessing.CyclicEncoder。4.3 现象preprocessing_pipeline.pkl加载后pipeline.transform()报KeyError: euribor3m原因train.csv和test.csv的字段顺序不一致test.csv中euribor3m在第 18 列train.csv中在第 15 列而CustomPreprocessor的transform方法默认按列索引取值未按列名对齐。解决在CustomPreprocessor.transform()开头添加X X.reindex(columnsself.feature_names_)其中self.feature_names_在fit()中保存为list(X.columns)。4.4 现象notebook/认购产品_V1.3.ipynb运行到model.predict()时内存溢出OOM原因Jupyter 默认使用全部 CPU 核心运行RandomForestClassifier而n_estimators200在 37 维特征下需 16GB 内存。Notebook 未设置n_jobs1。解决在 notebook 中显式指定RandomForestClassifier(n_jobs1)或改用XGBClassifier(n_jobs1)内存效率更高。4.5 现象backup/dotתpng.txt文件名含 Unicode 字符ת希伯来字母在 Windows 系统解压时报错原因GitHub 仓库创建者使用了非 UTF-8 编码的文件系统导致文件名乱码。dotתpng.txt实际应为dot_to_png.txt用于将决策树 dot 文件转 PNG。解决手动重命名该文件或用7-Zip解压时勾选 “UTF-8 编码” 选项。内容为一行命令dot -Tpng tree.dot -o tree.png可直接复制使用。5. 模型可解释性落地用tree_transform_bin.py的中间产物反推决策路径让客户经理看懂“为什么推这个产品”5.1 从best_model.pkl中提取单棵树并用graphviz可视化关键路径best_model.pkl是RandomForestClassifier但其内部单棵树的决策逻辑才是业务可解释的核心。项目figures/目录下虽有001_婚姻分布.png等统计图但无法回答“为什么这个客户被预测为高概率”。解决方案是提取森林中最重要的树estimators_[0]并导出为 dot 文件import joblib from sklearn.tree import export_graphviz import subprocess rf_model joblib.load(models/best_model.pkl) # 提取第一棵树通常最具代表性 tree rf_model.estimators_[0] # 导出 dot 文件注意feature_names 必须与 pipeline 输出一致 export_graphviz( tree, out_filetree.dot, feature_namesrf_model.feature_names_in_, # 关键需与 pipeline.transform() 输出列名匹配 class_names[no, yes], filledTrue, roundedTrue, special_charactersTrue ) # 转 PNG需提前安装 graphviz subprocess.run([dot, -Tpng, tree.dot, -o, tree.png])注意rf_model.feature_names_in_在RandomForestClassifier中默认为None需在automatic_learning.py的fit()后手动赋值model.feature_names_in_ preprocessor.get_feature_names_out()。否则export_graphviz会报错。5.2 用SHAP解释单个客户预测生成可交付的 PDF 报告tree_transform_bin.py生成的宏观特征如euribor_trend为 SHAP 解释提供了业务锚点。以下代码生成客户 ID 为12345的解释报告import shap import pandas as pd import joblib pipeline joblib.load(models/preprocessing_pipeline.pkl) model joblib.load(models/best_model.pkl) # 获取该客户原始数据从 test.csv test_df pd.read_csv(dataset/test.csv) client_row test_df[test_df[id] 12345].drop(id, axis1) # pipeline.transform() 得到模型输入 X_processed pipeline.transform(client_row) # 创建 explainer用 TreeExplainer 适配 RF explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_processed) # 生成 force plot交互式 HTML和 summary plot静态 PNG shap.force_plot(explainer.expected_value[1], shap_values[1][0], client_row.iloc[0], matplotlibTrue, showFalse) plt.savefig(shap_force_12345.png, bbox_inchestight) # 关键将 SHAP 值映射回业务字段名 feature_names pipeline.named_steps[preprocessor].get_feature_names_out() shap_df pd.DataFrame([shap_values[1][0]], columnsfeature_names) shap_df.to_csv(shap_explanation_12345.csv, indexFalse) # 交付给业务方shap_explanation_12345.csv输出示例featureshap_valuedescriptionjob_high0.28该客户职业属于高响应率分组如 managementeuribor_trend_up0.15欧元同业拆借利率上升预示资金成本提高客户更倾向锁定收益age_35_44-0.0835-44 岁群体整体响应率中等非最强驱动力这份 CSV 可直接粘贴进营销话术模板“推荐理由客户职业属高响应分组0.28且当前利率上行环境利好理财销售0.15”。5.3 将preprocessing_pipeline.pkl与 Flask API 集成实现分钟级上线真正的落地不是跑通 notebook而是让客户经理在 CRM 系统里输入客户 ID3 秒内返回预测概率。以下是精简版 Flask 服务# app.py from flask import Flask, request, jsonify import joblib import pandas as pd app Flask(__name__) pipeline joblib.load(models/preprocessing_pipeline.pkl) model joblib.load(models/best_model.pkl) app.route(/predict, methods[POST]) def predict(): data request.json # {id: 12345, age: 38, job: management, ...} df pd.DataFrame([data]) try: X_proc pipeline.transform(df) proba model.predict_proba(X_proc)[0, 1] return jsonify({id: data[id], probability: float(proba)}) except Exception as e: return jsonify({error: str(e)}), 400 if __name__ __main__: app.run(host0.0.0.0, port5000)部署命令Ubuntupip install flask gunicorn pandas scikit-learn joblib gunicorn -w 4 -b 0.0.0.0:5000 app:app从那以后我每次交付银行模型都强制走一遍gunicorn压测用ab -n 1000 -c 100 http://localhost:5000/predict验证并发能力。如果Failed requests 0 或Time per request 500ms立刻回溯preprocessing_pipeline.pkl的 transform 效率——通常是category_bin.py中的groupby().mean()未加cacheTrue导致重复计算。希望帮到你。本文还有配套的精品资源点击获取
返回列表