简介面向计算机相关专业毕业设计及机器学习实战学习者这是一套银行客户逾期行为预测的完整Python实现包含全部源码与训练/测试数据可用于毕设、课程设计或期末大作业。压缩包共10个文件以CSV数据、Jupyter Notebook建模分析文档、Excel字段说明及使用说明为主整体约127.3MB其中Notebook覆盖数据探索EDA与XGBoostLR、GBDTLR两类组合模型可对照学习特征工程与模型调优流程。包内另附Python脚本、字段说明与运行指导项目经过严格调试具备完整可复现的实验链路方便直接部署或在此基础上拓展改进。目前已有229人学习下载适合需要快速搭建金融风控预测项目的初中级学习者。1. 机器学习银行客户逾期行为预测这份毕业设计源码到底能跑到什么程度银行客户逾期行为预测本质就是给你一堆客户历史行为和还款记录让你在放款前判断“这个人未来会不会逾期”。听起来像风控专家的黑匣子但这份 python 毕业设计源码把全流程摊开了从原始 train.csv/test.csv 开始先做探索性数据分析再用 XGBoostLR、GBDTLR 两套组合模型做预测最后输出 submission.csv。它不是只有一堆代码文件而是带着字段说明、数据字典和能直接运行的 notebook很适合计算机专业做毕业设计、课程设计或者想练机器学习完整流程的同学。我拆这份资源时的第一感受是它的价值不在模型本身有多高大上而在“数据处理到提交结果”这条链路是完整闭环的训练集、测试集、提交样例、字段说明全齐下载下来改改特征和参数就能跑通。适用人群很具体——需要应付毕设开题、中期检查和最终答辩的学生以及想从“只懂调库”过渡到“能解释特征为什么有效”的初学者。下面我按实际动手顺序讲讲这份源码里最值得关注的地方。2. 读懂 data 目录train.csv、test.csv 与字段说明的配合逻辑2.1 字段说明.xlsx 是一切的起点有些同学拿到项目先看模型代码其实顺序反了。这个资源里字段说明.xlsx才是真正的钥匙。它告诉你 train.csv 里每一列到底代表什么业务含义这直接决定你后续怎么做特征工程。一般来说银行逾期数据集包含客户基本信息年龄、收入、职业、历史借贷记录贷款金额、期限、历史逾期次数、还款行为最近还款情况、账户状态等。我建议拿到文件先做三件事第一打开字段说明把每个字段的“业务含义”和“数据类型”列出来第二对应到 train.csv 的实际列名检查有没有语义不明确的字段第三标记出哪些字段可能是“事后变量”也就是逾期结果已经发生后才产生的数据这类字段在预测时要特别注意否则会造成数据穿越。2.2 训练集与测试集的划分规则train.csv 和 test.csv 的划分逻辑决定了你能不能正确复现提交结果。常见做法是按时间切分——前面的月份做训练后面的月份做测试这样才符合银行风控的实际场景用历史数据训练预测未来客户。但很多初学者拿到数据就直接用 train_test_split 随机切分这会导致模型“偷看”未来信息线上分数会比本地验证好看真实场景却翻车。我在 notebook 的 EDA 部分先确认了数据的行数、列数、数据类型分布然后检查是否有明显的重复样本和全空列。用info()和describe()先做快速体检这个动作不要跳过它能帮你发现字段说明里没提到的脏数据。2.3 EDA notebook三个必看的分析点用户逾期_EDA.ipynb 里有一批值得抄作业的可视化分析我拆的时候重点看了三块第一块是目标变量分布。逾期样本占比多少、是否严重不平衡。这个数字直接决定你要不要用 scale_pos_weight、SMOTE 或者自定义评估指标。银行场景里逾期率通常很低可能只有百分之几这种情况下准确率是无意义的必须看 AUC 或 PRAUC。第二块是数值型特征的分布和缺失率。train.isnull().mean()这个操作虽然基础但能快速列出每个字段的缺失比例我一般会把缺失率超过 30% 的字段单独拎出来判断是要删除还是做特殊填充。第三块是特征与目标的单变量关系。比如年龄、收入这些字段分组后计算逾期率看是否存在明显的单调趋势。这一步能给你后续特征工程提供方向不是随便找几个字段扔进模型就完事。3. 从 EDA 到建模输入数据清洗与特征工程的可复现操作3.1 缺失值处理不同字段区别对待从 EDA 进入建模前缺失值处理是必经之路。不是所有字段都适合用均值填充。以银行逾期数据为例收入字段的缺失可能代表客户无固定收入直接填均值反而掩盖了这个信号。我一般按字段业务含义分组处理。import pandas as pd import numpy as np train pd.read_csv(data/train.csv) # 查看每个字段的缺失率和数据类型 missing_report pd.DataFrame({ dtype: train.dtypes, missing_count: train.isnull().sum(), missing_ratio: train.isnull().mean() }) print(missing_report[missing_report[missing_ratio] 0].sort_values(missing_ratio, ascendingFalse)) # 数值型字段按中位数填充避免极值影响 num_cols train.select_dtypes(include[np.number]).columns for col in num_cols: if train[col].isnull().any(): train[col] train[col].fillna(train[col].median()) # 类别型字段用众数填充同时保留缺失标记 cat_cols train.select_dtypes(include[object]).columns for col in cat_cols: if train[col].isnull().any(): train[col] train[col].fillna(missing)这段代码的意图很清楚先输出缺失报告让你知道每个字段脏到什么程度再分别用中位数和众数填充。为什么数值型用中位数而不是均值因为收入、贷款金额这类字段容易有长尾分布均值会被极值拉偏中位数更稳健。类别型填missing而不是直接填空字符串是为了让模型能识别“这一列原本缺失”这个信息LightGBM 和 XGBoost 都能直接利用这个模式。3.2 特征构造的常见思路这个项目里最值得学的不是调参而是特征构造的思路。我从源码里梳理出几条适用性很强的方向第一是时间类特征。如果原始数据里有申请日期、还款日期可以构造“距今天数”“历史逾期距上次逾期的间隔”这类特征。逾期行为往往有短期重复性间隔越短风险越高。第二是聚合统计特征。按客户 ID 分组计算历史借款次数、平均借款金额、最大逾期天数等。银行客户的数据结构通常是“一个客户多条借款记录”如果不做聚合模型学不到客户维度的整体画像。第三是交叉特征。把“收入区间和贷款金额的比值”“年龄和贷款期限的组合”这类特征显式构造出来。虽然树模型天然能做特征分裂但显式的比值特征往往能让分裂更有业务解释性。# 构造客户维度的聚合特征 customer_group train.groupby(customer_id).agg({ loan_amount: [count, sum, mean, max], overdue_days: [max, mean, sum] }) customer_group.columns [loan_count, loan_total, loan_mean, loan_max, overdue_max, overdue_mean, overdue_sum] customer_group customer_group.reset_index() # 合并回训练集 train pd.merge(train, customer_group, oncustomer_id, howleft)这里说明一下groupby().agg()的用法是同时算多个统计量减少重复代码。loan_count是客户借款次数overdue_max是历史最大逾期天数这些特征捕获了客户的整体风险偏好。合并时用howleft保证保留训练集的所有行不会因为某些客户在聚合表里不存在而丢失。3.3 类别特征编码不踩 LabelEncoder 的坑很多教程直接用 LabelEncoder 处理所有类别特征这在树模型里风险不大但如果是类别取值特别多的字段容易出问题。我更习惯先看字段的类别数再做决定。类别数少10 个以内用 one-hot类别数多几十上百用频率编码或 target encoding。项目里如果只是树模型LabelEncoder 也可以用但热编码更稳。# 检查类别特征的唯一值数量 for col in cat_cols: n_unique train[col].nunique() print(f{col}: {n_unique} unique values) # 低频类别合并防止类别数过多导致过拟合 value_counts train[employment_type].value_counts() rare_categories value_counts[value_counts 100].index train[employment_type] train[employment_type].replace(rare_categories, rare)低频类别合并这一步很实用有些类别只出现几次模型根本学不到稳定规律还会在测试集上遇到根本没见过的类别时直接报错或随机分裂。设置阈值 100 的意思是样本少于 100 的类别统一归为rare测试集里出现新类别时也按这个逻辑处理。4. XGBoostLR 与 GBDTLR 的实现细节两套组合模型怎么选、怎么调4.1 为什么要做模型融合单模型上限在这里银行逾期数据通常是非线性关系复杂、特征维度高、样本不平衡的场景。单棵决策树太弱RandomForest 能缓解方差但偏差不一定够。XGBoostLR 和 GBDTLR 的核心思路是用树模型自动做特征交叉和筛选把原始特征转换成稀疏的“叶子节点指示向量”再喂给 LR 做最终分类。这样既利用了树的非线性拟合能力又继承了 LR 的稳定性和可解释性。一句话概括树模型负责找到特征之间的复杂互动规律LR 负责在树模型输出的高阶特征上做最后的决策边界。这个套路在 Kaggle 金融风控类比赛中是久经考验的方案。4.2 XGBoost 部分的关键参数解读源码的 XGBoostLR.ipynb 里关键参数集中在树的复杂度控制和防止过拟合上。我挑几个实际起作用的说max_depth控制单棵树深度太深容易记住噪声浅了又学不到复杂规律。常见范围 4~8风控数据通常不会太深。learning_rate每棵树对最终结果的贡献权重调小后需要更多弱学习器但泛化更稳。常用 0.01~0.1。subsample和colsample_bytree控制样本采样和特征采样的比例相当于给每棵树提供不同的视角降低过拟合。scale_pos_weight处理正负样本不平衡的关键参数一般设为负样本数除以正样本数的比例。import xgboost as xgb from sklearn.linear_model import LogisticRegression # 将训练数据转为 DMatrix 格式 dtrain xgb.DMatrix(X_train, labely_train) dtest xgb.DMatrix(X_test, labely_test) # 树模型参数 xgb_params { max_depth: 6, learning_rate: 0.05, subsample: 0.8, colsample_bytree: 0.8, scale_pos_weight: (y_train 0).sum() / (y_train 1).sum(), eval_metric: auc, objective: binary:logistic, nthread: 4, seed: 42 } # 训练并输出最佳迭代次数 bst xgb.train(xgb_params, dtrain, num_boost_round500, evals[(dtrain, train), (dtest, test)], early_stopping_rounds50, verbose_evalFalse)逻辑说明scale_pos_weight设置正负样本比例让模型在训练时加大对少数类的惩罚力度。eval_metric用auc而不是logloss因为逾期预测关心的不是概率绝对值而是排序能力。early_stopping_rounds50的意思是连续 50 轮验证集 AUC 不提升就停止训练防止白白迭代浪费算力。4.3 从 XGBoost 叶子节点到 LR 输入树模型训练完成后把原始特征矩阵转成叶子节点指示向量。这一步是 XGBoostLR 融合的关键桥梁。它的原理是每个样本经过一棵树会落在某个叶子节点上我们用 0/1 表示这个样本是否落在每个叶子节点上。多棵树组合起来就得到一个稀疏的高维特征向量。# 获取每棵树叶子节点的位置 leaf_preds_train bst.predict(dtrain, pred_leafTrue) leaf_preds_test bst.predict(dtest, pred_leafTrue) # 对叶子位置做 one-hot 编码 from sklearn.preprocessing import OneHotEncoder enc OneHotEncoder(handle_unknownignore) X_leaf_train enc.fit_transform(leaf_preds_train) X_leaf_test enc.transform(leaf_preds_test) # 用 LR 在叶子特征上做最终分类 lr LogisticRegression(C0.1, max_iter500, solverliblinear) lr.fit(X_leaf_train, y_train) y_pred_lr lr.predict_proba(X_leaf_test)[:, 1]代码里注意两点一是handle_unknownignore测试集可能出现训练集中没见过的叶子组合忽略而不是报错二是 LR 的正则化系数C0.1这里设小一点是让正则更强因为叶子特征维度比较高过拟合风险也随之增大。4.4 GBDTLR 与 XGBoostLR 的差异GBDTLR 用的树模型是lightgbm的LGBMClassifier我在对比两个 notebook 后认为主要区别在工作效率LightGBM 用直方图算法训练速度快内存占用小。如果数据量几万行差异不大如果几十万行LightGBM 能省下大量时间。import lightgbm as lgb lgb_model lgb.LGBMClassifier( n_estimators300, max_depth5, learning_rate0.05, subsample0.8, colsample_bytree0.8, scale_pos_weight(y_train 0).sum() / (y_train 1).sum(), random_state42 ) lgb_model.fit(X_train, y_train) leaf_train lgb_model.predict(X_train, pred_leafTrue) leaf_test lgb_model.predict(X_test, pred_leafTrue) # 后续 LR 训练逻辑与 XGBoost 版本一致注意predict(X, pred_leafTrue)返回的是二维数组形状为(样本数, 树数量)而 XGBoost 的pred_leafTrue返回的是(样本数, 树的叶子索引)。两者输出维度含义略有不同合并进 OneHotEncoder 前要先确认数组形状。5. 避坑指南这份源码运行中常见的四个翻车点5.1 缺失值填充后模型效果反而变差现象不填充缺失值还能跑到 0.7 的 AUC用中位数填充后掉到 0.68而且特征重要性排序变得很奇怪。原因某些字段的缺失本身是业务信号比如“收入缺失”很可能代表无稳定收入来源这类客户恰恰是高风险群体。强制填充抹掉了这个强信号。解决先按缺失率分桶缺失率高的字段单独新增一列is_missing标记列原字段填充中位数。然后在模型里确认is_missing列是否进入高重要性区间。如果进入说明缺失信息确实有效必须保留。5.2 pred_leaf 编码后 LR 维度爆炸现象训练 300 棵树每棵树 30 个叶子OneHotEncoder 后特征维度直接到 9000 多LR 训练时间暴涨且结果不稳定。原因每棵树贡献一个字段的 one-hot字段数量随着树的数量线性增长而不是随样本量自适应。解决缩减树的数量到 100~200 棵或者限制max_depth到 4~5 减少叶节点数。更实用的做法是只取前 N 棵树比如重要性排序前 80 棵的特征做融合其余树丢弃。这样能在保持信息的前提下控制维度。5.3 训练集与测试集特征不一现象训练集有 35 列测试集只有 33 列或者训练集测试集列名相同但顺序不同合并时报 ValueError。原因特征工程阶段对训练集做了单独的列拼接测试集没执行同样的代码。最常见的是pd.merge时键字段在测试集里被误删。解决把特征工程封装成函数训练集和测试集走同一套流程。代码里明确列出必须保留的键字段合并完成后用assert list(X_train.columns) list(X_test.columns)强制校验。def build_features(df): # 所有特征构造步骤 ... return feature_df X_train build_features(train) X_test build_features(test) assert list(X_train.columns) list(X_test.columns), 特征列不一致检查特征工程5.4 提交格式不匹配导致评分报错现象submission.csv 的行数和测试集不一致或者 ID 列存在重复和缺失提交到评测平台后显示格式错误。原因测试集在数据清洗时删除过重复行而提交模板仍然是原始测试集的行数或者在reset_index后没有把索引列正确输出为 ID。解决提交前严格对照原始 submission.csv 的结构只改预测概率列ID 列完全复用。并且保证预测数组的行顺序与测试集一致导出行前按 ID 升序排列。6. 验证模型质量的三个技巧从本地 AUC 到提交前检查本地验证的逻辑和线上评分逻辑必须同步。这个项目里训练集带标签、测试集无标签本地验证只能用切分训练集模拟。我习惯先固定随机种子再按时间或按客户 ID 做分层切分避免同一个人同时出现在训练和验证里造成信息泄漏。第一切分方式直接影响验证可信度。随机切分看到的 AUC 偏高因为模型“记住了”一批相近样本的分布时间切分更贴近真实场景但样本量不够时方差大。我一般两种都跑记录下差异幅度。如果差异超过 0.03说明模型过拟合或者特征里有时间漂移需要回头看特征。第二计算 PRAUC 比单纯看 AUC 更稳。逾期样本占比通常低于 5%AUC 可能很好看但实际正样本召回率很低。PRAUC 对正样本占比变化更敏感能反映少数类的分辨能力。from sklearn.metrics import roc_auc_score, average_precision_score # 假设 y_val 是标签y_pred 是 LR 输出的概率 auc roc_auc_score(y_val, y_pred) pr_auc average_precision_score(y_val, y_pred) print(fAUC: {auc:.4f}, PRAUC: {pr_auc:.4f})第三提交前做一次完整的预测结果检查。对比提交模板的 ID 顺序确认概率值范围在 0 到 1 之间确认没有 NaN。最简单的方法是把 submission.csv 读回来用isnull().sum()检查残差再跑一次len(submission) len(test)。这套项目我拆过不少类似的真正拉开分数的往往不是模型选型而是特征工程的一致性和验证的严谨度。从那以后我每次做这类数据的整理都强制走一遍“字段说明 → EDA → 特征工程封装 → 验证一致性检查”的流程漏掉任何一步都会在提交阶段补课。希望这一套思路帮到你把脱了线的代码跑成自己的东西。本文还有配套的精品资源点击获取