Python银行个人贷款违约预测毕设源码复现:从解压到模型评估指南
发布时间:2026/9/28 5:19:50 锦皓数字建站

简介面向高校计算机、数据科学、人工智能等相关专业学生这份基于Python的银行个人贷款违约预测项目提供了从数据清洗、特征工程到模型训练与预测提交的完整实现可胜任毕业设计、课程设计或期末大作业。资源为zip压缩包共20个文件包含12个Python源码文件、4个CSV数据集、2个说明文档另含1个代码压缩包和1个文本说明整体大小仅935KB轻量易部署目录结构清晰便于按数据预处理、特征构建等模块研读和二次开发。项目代码经本地运行通过功能验证无误具备较高的完成度和参考价值已有220人学习使用适合基础薄弱者入门进阶也可在掌握思路后替换数据集或优化特征完成拓展。下载后既能获得可直接运行的项目框架也能通过对各模块的拆解系统理解个人贷款违约预测的典型分析流程与建模要点。 很多人下载“基于Python的银行个人贷款违约预测源代码毕设项目带数据集.zip”第一件事就是解压然后折在文件名乱码、依赖缺失、路径报错上模型影子还没见到就卡住了。这份源码包本质是一个标准的二分类机器学习题目用年龄、收入、负债率、历史逾期次数等特征预测客户会不会违约技术场景对应银行信贷风控里的申请评分卡。它能解决两类人的问题毕设学生需要一套“数据集源代码可复现结论”的完整流程想入门 Python 机器学习的从业者想找一个能跑通的全套练手项目。我的判断是这个包值得做但能不能顺利复现决定性因素不是算法选得多新而是数据字典、目录结构、运行环境这三件事有没有先理清楚。与其在免费 python 源码大全里翻到跑不动的半成品不如先把这份带数据集的源代码完整复现一遍。2. 拿到 zip 之后解压、目录梳理与 python 环境准备的完整步骤2.1 用对解压方式zip 解压、伪加密与中文路径先说结论这份 zip 能不能正常解开是第一个坑。毕设代码包大多在 Windows 简体中文环境下打包文件名常是 GBK 编码用系统自带的右键“解压到当前文件夹”轻则中文乱码重则后续代码读取不到正确相对路径。另一个常见问题是 zip 伪加密文件头标记了加密位但数据段实际没有加密系统会一直弹密码框输入什么都不对其实内容可以直接抽出。遇到这两种情况我习惯直接用 Python 的 zipfile 模块解压顺手把文件名重新解码。以下脚本可以同时处理 UTF-8 和 GBK 两种编码的文件名解压完自动打印目录结构import zipfile import os zip_path 银行个人贷款违约预测源代码毕设项目带数据集.zip target_dir loan_default_project with zipfile.ZipFile(zip_path, r) as zf: for info in zf.infolist(): # zipfile 内部默认按 cp437 读取文件名中文会变成乱码 try: raw info.filename.encode(cp437).decode(utf-8) except UnicodeDecodeError: raw info.filename.encode(cp437).decode(gbk) info.filename raw zf.extract(info, target_dir) print(os.listdir(target_dir))逻辑说明zipfile 读取文件名用的是 cp437 编码中文环境打包的文件名在它眼里是一串拉丁字符所以先把原始字节还原再尝试用 utf-8 解码失败就用 gbk 解码这样“数据.xlsx”“代码”这类中文目录名才能恢复正常。参数说明zip_path 要改成你本机实际的 zip 文件名target_dir 是解压目标目录不存在时会自动创建。代码里的 decode 顺序是 utf-8 优先如果你确认包是纯简体中文环境打包可以直接只用 gbk。提示如果上面的 extract 抛出 RuntimeError: File is encrypted说明是真加密不是伪加密。毕设包设密码通常是防止数据外泄正确做法是打开包内 readme.txt 找密码或联系作者去折腾 zip 密码移除工具既费时间也不尊重原创源码。2.2 python 安装与环境配置从 python 安装教程到 vscode python 环境配置老毕设代码多数用 Python 3.7 到 3.9 编写直接拿最新的 3.12 解释器去跑很容易遇到 lightgbm、catboost 这类依赖在 Windows 上没有预编译包、被迫现场编译然后失败的翻车现场。我的建议是安装 Python 3.8 或 3.9省掉大部分依赖泥潭。如果你还没装好 Python按常规 python 安装教程走官网下载对应版本时一定勾选 Add to PATH这个细节决定后面 pip 命令能不能直接用。装好解释器之后不要直接往全局环境里灌依赖先建虚拟环境。conda 和 venv 二选一即可我一般更推荐 conda因为它在 Windows 上处理 gcc 编译问题更省心conda create -n loan_default python3.9 -y conda activate loan_default cd loan_default_project pip install --upgrade pip pip install -r requirements.txt逻辑说明conda create 创建独立环境loan_default 是环境名python3.9 指定解释器版本activate 切换环境pip install -r requirements.txt 按项目里锁定的依赖清单批量安装。如果项目里没有 requirements.txt这类违约预测代码包通常至少需要 numpy、pandas、matplotlib、scikit-learn、lightgbm、imbalanced-learn手动补齐就行。装完依赖先别急着跑用一行命令验证核心库是否都能导入python -c import pandas, sklearn, lightgbm, matplotlib; print(env ok)如果输出的不是 env ok而是某个 ImportError先看是不是刚才的环境没激活。另一个高频坑在 vscode python 环境配置终端里明明激活了 venvVSCode 右下角的解释器却还是全局 Python导致 F5 调试时用的不是同一个环境。正确做法是按 CtrlShiftP 弹出命令面板输入 Python: Select Interpreter选到刚才创建的 loan_default 环境再打开终端确认左边提示符里出现 (loan_default)。2.3 用最小命令跑通第一版结果拿到包后先不要急着双击主脚本按顺序做三件事看一眼目录结构、打开 readme、确认入口文件名。这类毕设项目一般长这样路径作用data/原始数据集通常是 CSV 或 xlsxsrc/ 或根目录预处理、训练、评估脚本models/训练输出的模型文件report 或 doc论文、说明文档入口文件名常见的有 main.py、train.py、run.py也可能拆成 data_preprocess.py、train_model.py、evaluate_model.py。我不建议一键运行整个项目而是按顺序手动执行三个阶段方便定位问题出在哪一步python data_preprocess.py python train_model.py --model lgb python evaluate_model.py --model lgb参数 --model lgb 的意思是选择 LightGBM 作为训练模型也有的代码包支持 rf 或 lr具体以你打开的脚本里的 argparse 参数为准。第一段命令执行后目录里通常会出现 X_train.csv 或 y_train.csv 这类中间文件如果预处理脚本都没有产出这些文件后面训练脚本必然报 FileNotFoundError。这条规律就是下一章的避坑重点。3. 违约预测建模流程拆解从数据清洗到模型评估核心代码逐段读3.1 数据预处理缺失值、类型编码与归一化的落地写法贷款违约数据集长什么样先有个概念。以常见的银行个人贷款数据为例字段大概分四类字段类型常见字段处理方式ID客户编号、申请编号剔除不参与建模数值年龄、收入、负债收入比、贷款金额、逾期次数中位数填充 标准化类别学历、婚姻状况、职业、住房类型编码为数值时间申请日期、放款日期只用于特征构造与切分不作为模型输入为什么 ID 要剔除很多人不理解客户编号和是否违约无关但如果不小心把它当数值特征树模型可能分到按编号切分的假规则训练集表现很好测试集直接崩。预处理代码是流水线里最长的一段我通常这样写import pandas as pd import numpy as np from sklearn.preprocessing import LabelEncoder, StandardScaler df pd.read_csv(data/loan_data.csv, encodingutf-8) # 1. 剔除 ID 列 id_col 客户编号 df df.drop(columns[id_col]) # 2. 数值列缺失值用中位数填充 num_cols df.select_dtypes(includenp.number).columns df[num_cols] df[num_cols].fillna(df[num_cols].median()) # 3. 类别列缺失值填充为 unknown避免删行 cat_cols df.select_dtypes(includeobject).columns df[cat_cols] df[cat_cols].fillna(unknown) # 4. 类别特征编码 le_dict {} for c in cat_cols: le LabelEncoder() df[c] le.fit_transform(df[c].astype(str)) le_dict[c] le # 5. 数值特征标准化 scaler StandardScaler() df[num_cols] scaler.fit_transform(df[num_cols])逻辑说明数值列用中位数填充而不是均值是因为收入、负债率这类字段往往是右偏分布个别高净值客户的极端值会把均值拉高中位数更稳。类别列填 “unknown” 而不是直接删行是因为违约数据里恰好缺失的那批客户可能本身就带有更强的风险特征删掉会造成样本选择性偏差。编码这一步LabelEncoder 把每个类别映射成整数适合树模型如果后面要用逻辑回归我建议换成 OneHotEncoder线性模型对整数的“大小顺序”很敏感标签里 3 和 1 的距离没有业务含义。参数说明StandardScaler 对逻辑回归、SVM 这类距离敏感的模型是必选项对树模型可做可不做统一做也无妨。但训练后一定要保存 scaler 和 le_dict预测时用同一对象转换新数据否则测试分布和训练分布不一致概率输出会整体偏移。3.2 模型训练与对比逻辑回归、随机森林与 LightGBM 的脚本模板违约预测是典型的低正样本率二分类问题正样本通常只有 5% 到 10%。如果直接用 train_test_split 随机切一次很容易切出正负比例失衡的验证集导致你调参时看到的指标波动极大不知道是模型差还是切法差。标准做法是分层 K 折交叉验证保证每一折训练集和验证集里正负样本比例和全局一致。from sklearn.model_selection import StratifiedKFold from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier import lightgbm as lgb from sklearn.metrics import roc_auc_score import numpy as np X df.drop(columns[是否违约]) y df[是否违约] skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) models { lr: LogisticRegression(max_iter1000), rf: RandomForestClassifier(n_estimators200, max_depth8, random_state42), lgb: lgb.LGBMClassifier(n_estimators300, learning_rate0.05, num_leaves31, random_state42), } for name, model in models.items(): aucs [] for train_idx, val_idx in skf.split(X, y): X_tr, X_va X.iloc[train_idx], X.iloc[val_idx] y_tr, y_va y.iloc[train_idx], y.iloc[val_idx] model.fit(X_tr, y_tr) proba model.predict_proba(X_va)[:, 1] aucs.append(roc_auc_score(y_va, proba)) print(f{name}: AUC{np.mean(aucs):.4f}±{np.std(aucs):.4f})逻辑说明三种模型代表三条路线。逻辑回归是银行风控里的老牌基线解释性强随机森林捕捉非线性关系训练快LightGBM 是梯度提升树通常在 AUC 上能压前两者一头。放在一起对比是为了让论文里有一张“不同模型效果对比表”这也是答辩老师大概率会问的问题“你为什么选这个模型”答案不是因为 LGB 最流行而是它在交叉验证里 AUC 最高且方差可控。参数说明n_splits5 是折数5 折在 2 万到 5 万行数据上够稳shuffleTrue 打散顺序random_state42 固定随机种子保证每次跑结果一致这是毕设可复现性最基本的写法。predict_proba 取 [:, 1] 是拿到正类的概率也就是违约概率。3.3 必调的 3 个参数类别权重、树深度、学习率如果直接跑上一节的代码你大概率会发现 AUC 在 0.7 上下晃不再动了。这时不要再堆模型先调三个参数类别权重、树深度、学习率。它们分别对应三个常见病正样本太少导致模型偷懒、树太深导致小样本过拟合、步长太大导致梯度抖动。neg_cnt (y 0).sum() pos_cnt (y 1).sum() scale_pos_weight neg_cnt / pos_cnt print(f负样本: {neg_cnt}, 正样本: {pos_cnt}, scale_pos_weight: {scale_pos_weight}) lgb_model lgb.LGBMClassifier( n_estimators500, learning_rate0.02, num_leaves15, max_depth4, scale_pos_weightscale_pos_weight, random_state42, )参数说明scale_pos_weight 是 LightGBM 里处理不平衡最直接的开关传负样本数除以正样本数相当于给违约样本的梯度放大权重让模型不能靠全部预测为“不违约”蒙混过关。max_depth4 是从默认的 -1不限制改成小深度违约数据的有效特征通常不到十个树太深就是在记噪声。learning_rate0.02 配合 n_estimators500用更多但更小的步长逼近边界这一组组合在小样本上一般能再拉高零点几个百分点的 AUC。这三个参数对应随机森林的写法是 class_weightbalanced、max_depth6、n_estimators300对应逻辑回归则是 class_weightbalanced 和 C0.1 这种正则强度。核心思想一致先让模型正视少数类再限制模型复杂度最后压低学习率。调完后再回跑上一节的交叉验证你会看到 AUC 的变化趋势比准确率直观得多。4. 毕设代码包避坑与常见问题解压到训练结束的 5 条踩坑记录4.1 现象zip 解压报错或解出来的文件名全乱码原因两种情况最常见。一是文件名本身是 GBK 编码Windows 解压工具按当前系统代码页处理导致乱码二是 zip 伪加密文件头标记了加密位但数据段没加密系统误判需要密码实际内容可以直接抽出。解决参考 2.1 节的 zipfile 脚本强制按 utf-8/gbk 转码后解压伪加密文件用 7-Zip 打开时不会要求输入密码直接右键解压即可。遇到真加密就别浪费时间研究 zip 密码移除了优先看项目说明文档找密码或者联系代码提供者。4.2 现象运行 python train.py 立刻报 FileNotFoundError原因代码里写死相对路径如 data/loan_data.csv而脚本是从 IDE 或命令行别的目录启动的当前工作目录根本不是项目根目录。这类路径硬编码在毕设代码包里极其常见有的新手会把数据文件放在桌面然后脚本还在项目目录里找。解决不要改数据文件位置改脚本里的路径基准。在入口脚本顶部加一段使用file定位根目录的代码import os from pathlib import Path # 拿到当前脚本所在目录的上一级即项目根目录 BASE_DIR Path(__file__).resolve().parent.parent DATA_DIR BASE_DIR / data df pd.read_csv(DATA_DIR / loan_data.csv)逻辑说明file是当前脚本的完整路径resolve() 把相对路径转绝对路径parent.parent 是脚本文件上一级的上一级。这样不管从哪里启动脚本数据路径都不会错。参数说明如果你的脚本就放在项目根目录用 parent 一次就够了嵌套两层目录才用 parent.parent多看一级目录结构再改。4.3 现象准确率 97%但 AUC 只有 0.55原因类别不平衡。负样本占绝大多数模型把所有样本都预测为“不违约”准确率自然很高但这个模型毫无用处。这是毕设答辩里最经典的翻车点老师看一眼混淆矩阵就能识破。解决第一不要再用 accuracy 当主要指标改为看 AUC、召回率、精确率、F1第二在模型里加 class_weight 或 scale_pos_weight见 3.3 节第三如果还不够用 imbalanced-learn 的 SMOTE 做少数类过采样但只对训练集做绝不对验证集做否则评估结果虚高。我一般处理顺序是先加类别权重再看效果最后才考虑 SMOTE因为 SMOTE 合成的样本在树模型上未必带来真实增益。4.4 现象加载保存的模型后predict_proba 报错 AttributeError原因训练环境用的 scikit-learn 或 lightgbm 版本和加载环境不一致。不同版本之间类结构有变动pickle 序列化的对象跨版本加载经常缺属性。解决保存和加载统一用 joblib不要用 pickle。尽管两个库最终都是序列化但 joblib 对 numpy 数组和 sklearn estimator 的兼容性处理更稳import joblib # 保存 joblib.dump(lgb_model, models/lgb_model.joblib) # 加载 loaded_model joblib.load(models/lgb_model.joblib) proba loaded_model.predict_proba(X_test)参数说明joblib.dump 的第二个参数是保存路径建议固定放在 models 目录方便论文里写“模型存储于 models/ 下”。依赖版本问题还提醒我们跑通之后把 pip freeze requirements_lock.txt 导出一份放到项目里这是可复现性最直接的证明。4.5 现象训练时内存暴涨或运行时间超过两小时原因代码把高基数类别字段直接 OneHot几百万行数据瞬间炸成数百万列或者 pandas 读入时全部用默认 int64/float64内存翻倍浪费。解决优先用一个小规模子集把流程跑通再换全量。这一步我建议写在每个毕设项目里因为它能让你先验证代码逻辑再考虑跑满数据。读数据时可以指定 dtype 压缩内存df pd.read_csv( data/loan_data.csv, dtype{ 客户编号: int32, 年龄: int8, 收入: float32, 负债收入比: float32, }, usecols[客户编号, 年龄, 收入, 负债收入比, 是否违约], )逻辑说明int32 最大到 21 亿足够存客户编号年龄用 int8 就够了收入这类带小数的用 float32精度对模型足够内存比默认的 float64 少一半。usecols 只读取需要的列是减少内存最立竿见影的办法。参数说明列名要以你数据集里的实际字段为准不确定时先用 pd.read_csv 读前 5 行查看列名。注意如果数据集里同时存在“是否逾期”和“逾期次数”两个字段要警惕目标泄漏。逾期次数已经包含了“是否逾期”的信息相当于把标签的一部分提前放进了特征AUC 会虚高到 0.98 以上评委一查原始字段就能看出来。5. 把毕设项目扩展成能写进简历的完整方案从“能跑”到“能讲”5.1 增加时间序列切分别让数据泄露毁掉你的结论很多毕设代码只做了随机切分这在贷款违约预测里是个硬伤。贷款业务天然带时间顺序我们从 2020 年的数据学规则去预测 2021 年的新申请客户。如果随机切分未来时段的一部分样本会混进训练集模型等于提前看到了答案。正确做法是先把数据按申请日期排序再做时间序列切分。sklearn 已经内置了 TimeSeriesSplit用法很简单from sklearn.model_selection import TimeSeriesSplit df_sorted df.sort_values(申请日期).reset_index(dropTrue) X df_sorted.drop(columns[是否违约, 申请日期]) y df_sorted[是否违约] tscv TimeSeriesSplit(n_splits5, gap0) for train_idx, val_idx in tscv.split(X): X_tr, X_va X.iloc[train_idx], X.iloc[val_idx] print(f训练集: {train_idx.min()}~{train_idx.max()}, 验证集: {val_idx.min()}~{val_idx.max()})逻辑说明TimeSeriesSplit 每次训练集都是前 N 段验证集永远是紧接着的下一段不会回头取样。这样验证集的时间永远晚于训练集模拟“用过去预测未来”的真实场景。gap0 表示训练集末尾和验证集开头之间不留间隔如果数据存在月度周期性我建议把 gap 设为 1 到 2 个月对应的样本量让模型不要依赖“上个月风格”这种短期规律。做完时间切分再看 AUC一般会比随机切分低一些这是正常的因为问题变难了。但论文里这句话能立住“为验证模型对未来时段的泛化能力采用时间序列切分而非随机 K 折。”面试和答辩时这句话比堆一堆调参结果值钱得多。5.2 用 SHAP 解释模型给违约预测一个可信的“为什么”建模做到最后评委一定会问一句“哪些特征最重要”。如果你只回答“feature_importances_”显得没有业务深度。我建议直接上 SHAP 解释它能画出每个特征对违约概率的贡献方向比如“负债收入比越高违约概率越大”这种可读的结论。import shap explainer shap.TreeExplainer(lgb_model) shap_values explainer.shap_values(X_va) shap.summary_plot(shap_values, X_va, feature_namesX_va.columns.tolist())逻辑说明TreeExplainer 适用于树模型它会遍历每棵树的叶子路径把一个样本的预测值拆成每个特征贡献的加和。summary_plot 里每行是一个特征横轴是 SHAP 值正负表示推高或压低违约概率一行上的点越分散说明该特征影响越大。如果模型换成了逻辑回归记得用 shap.LinearExplainer它的数学形式和 TreeExplainer 不一样。参数说明X_va 是你用作验证集的特征矩阵SHAP 计算量随样本数增加数据量大时先 shap.sample(X_va, 100) 抽 100 行再画图否则 notebook 容易卡死。还有一个绕不开的中文问题matplotlib 默认字体不含中文图里的“教育水平”会变成方块。在画图前加两行import matplotlib matplotlib.rcParams[font.sans-serif] [SimHei] matplotlib.rcParams[axes.unicode_minus] False这是 Windows 下的写法Linux 下要换成系统中文字体路径。5.3 阈值选择与业务成本为什么 0.5 不一定是好阈值模型输出的违约概率是 0 到 1 的连续值把大于多少判为违约这个阈值决定一切。默认 0.5 在贷款场景里通常不是最优选择因为违约样本本来就少模型输出的概率集中在 0.1 到 0.4 区间0.5 一刀切会把大量潜在风险客户放进来。业务上拒绝一个坏客户赚到的是整笔避免的损失误拒一个好客户亏掉的只是这笔贷款的利润二者不对等。所以阈值应该由业务收益函数决定from sklearn.metrics import precision_recall_curve prec, rec, thresholds precision_recall_curve(y_va, proba) # 自定义业务收益正确拒掉坏客户收益1误拒好客户损失0.2 def business_profit(pred_prob, thr): pred (pred_prob thr).astype(int) tp ((pred 1) (y_va 1)).sum() fp ((pred 1) (y_va 0)).sum() return tp * 1.0 - fp * 0.2 profits [] for t in np.arange(0.1, 0.6, 0.05): profits.append((t, business_profit(proba, t))) best_t, best_profit max(profits, keylambda x: x[1]) print(f最优阈值: {best_t:.2f}, 最大收益: {best_profit:.0f})逻辑说明precision_recall_curve 输出不同阈值下的精确率和召回率但业务上真正关心的是金钱收益。这里自定义了一个简化收益函数正确识别一个坏客户记为收益 1 单位误拒一个好客户记为损失 0.2 单位比值可以按实际业务场景调整。最后在所有候选阈值中挑收益最大的那个而不是拍脑袋定 0.5。我把这个思路教过好几个做毕设的师弟师妹论文里只要放一张“不同阈值下的收益对比表”整章的应用价值立刻不一样阈值召回率误拒率业务收益0.2085%12%38.20.3072%8%40.10.5051%5%31.4上表数值为示意以你自己跑出的数据为准但呈现方式足够证明“阈值是业务决策的一部分”。6. 最后复现后必须做的一次验证——滚动回溯检验代码跑通、AUC 满意、SHAP 图画出来了还不够。银行风控模型上线前要回答的问题是这个模型在最近三个月还有效吗趋势是否在退化验证方法叫滚动回溯检验把验证集按月份切开逐月计算 AUC画成一条折线。如果最近几个月 AUC 明显下滑说明模型可能已经跟不上客群变化。实现不复杂。先把验证集补充一列“月份”再按月评估import pandas as pd from sklearn.metrics import roc_auc_score valid_df X_va.copy() valid_df[是否违约] y_va valid_df[月份] pd.to_datetime(valid_df[申请日期]).dt.to_period(M) months valid_df[月份].unique() results [] for m in sorted(months)[-6:]: mask valid_df[月份] m auc_m roc_auc_score(valid_df.loc[mask, 是否违约], proba[mask]) results.append({月份: str(m), AUC: round(auc_m, 4)}) print(pd.DataFrame(results))逻辑说明valid_df 先复制验证集的特征把真实标签 y_va 和月份拼进去然后按月份分组计算每个月的 AUC。只看最近 6 个月是因为离当前时间越近的时段越有决策参考价值。参数说明月份列名“申请日期”要换成你数据集里的实际时间字段pd.to_datetime 负责把字符串转成时间类型dt.to_period(M) 表示截取到月级别。这一步跑出来的结果我会直接贴进论文的结论部分“模型在最近 6 个月的滚动 AUC 为 xxx未见明显衰减。”比单纯报告一个全局 AUC 有说服力得多。我自己现在拿到任何一份毕设代码包第一件事就是翻它有没有做时间切分——如果只有随机 K 折再高的 AUC 我也会先怀疑数据泄露因为漏掉未来信息会让整个预测变成事后诸葛。验证完这条才算真正把这份带数据集的源代码吃透了希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。