资讯详情 贷款违约预测Python源码拆解:从数据血缘到深度学习模型
📅 2026/10/12 19:01:56
简介这份资源是基于深度学习的个人贷款违约预测系统完整Python源码定位为高校计算机、人工智能、金融科技等相关专业的毕设或课程设计参考项目适合希望从数据处理到模型搭建完整体验预测流程的学习者。交付包共28个文件其中csv格式的8个原始数据集覆盖账户、贷款、交易等核心字段Python脚本用于数据预处理、独热编码与金额清洗ipynb则提供分步实验记录配套运行教程和数据集说明文档总大小16.37MB结构清晰便于按模块研读。项目自带详细注释和可视化分析脚本能够直观展示性别、年龄、账户余额等特征对违约评估的影响帮助理解贷款风控场景下的特征工程与建模思路。已有673人学习下载适合作为课程设计拓展或毕业设计二次开发的实用蓝本。1. 贷款违约预测不是玄学这份 95 分毕设源码包怎么一步步拆着用一份答辩拿到 95 分的深度学习个人贷款违约预测 Python 源码注释几乎拉满还带详细运行教程。项目用捷克某银行的九张真实业务表从账户、客户、贷款、卡片到交易流水串联起来目标是预判一笔贷款会不会变成坏账。压缩包把功能拆成数据读取、金额清洗、特征构建和模型主入口单文件能独立跑也能串成完整流程。适合正在做课程设计、毕业设计的学生也适合想补全“商业取数”这一课的在职新人。下面按实际复现顺序先理数据血缘再把关键脚本的执行逻辑和参数讲透最后给一组直接能用的评估与避坑技巧。2. 先看数据血缘九张表怎么拼出一张能训练的宽表在动手跑任何模型之前得先把 data 目录里的文件关系摸清楚。这份数据不是那种一行一个样本的现成训练集它更像银行底层数据库的逻辑导出九张表分散在不同文件里靠主外键关联。如果第一步的关联逻辑就搞错后面所有特征工程都白搭。2.1 accounts、clients、disp、loans主键关系与贷款状态定义核心四张表是整个项目的骨架。accounts.csv 以 account_id 为主键记录账户开户日期、开户地区编号和扣款频率clients.csv 以 client_id 为主键记录客户性别、出生日期和所在地区disp.csv 是账户与客户的关联表同时存放 account_id 和 client_id还用 type 字段区分所有者和使用者loans.csv 挂在 account_id 上一笔贷款一行包含金额、期限、每月还款额和状态字段。accounts: account_id, frequency, district_id, date clients: client_id, birth_number, district_id disp: disp_id, client_id, account_id, type loans: loan_id, account_id, date, amount, duration, payments, status我拿到这类多表数据时习惯先把主外键链路画出来clients 通过 disp 找到 accountaccount 再关联 loans 和 cardstrans 和 order 则通过 account_id 直接挂在账户下。这里有一个很容易被忽略的细节disp 是一张多对多关系表一个账户可能对应多个客户一个客户也可能操作多个账户。如果直接拿 disp 去做连接样本行数会被放大要是没做去重或聚合同一笔贷款会被重复计算若干次模型看到的就是一份虚胖的数据。正确做法是先确定建模粒度。这个项目的预测对象是“每笔贷款是否违约”所以应该以 loans 为主表向 accounts 和 clients 方向做多对一关联遇到多对多路径时先按业务语义聚合比如一个账户对应多个 disp 记录就只取 type 为所有者那条或者单独统计账户下客户的数量。这样每一行样本才严格对应一笔贷款的唯一记录。接着看 loans 表的 status 字段这是全项目唯一的标签来源。原始状态分四种0 代表贷款已结清且无违约1 代表已结清但出现过还款问题2 代表还在正常还款3 代表正在还款但已经出现违约。训练二分类模型时必须自己做映射而映射方式直接影响模型学到的语义。我采用的映射是把 status 为 1 和 3 的样本视为违约正例0 和 2 视为正常负例。这样既覆盖了已经坏掉的贷款也覆盖了正在坏掉、尚未到期的贷款模型能学到“中途变坏”的模式。如果只拿 0 和 1 训练等于把所有在还的客户都丢了样本量折损一半且完全无法预测存续期贷款。这里推荐打开项目根目录的“商业取数逻辑.xlsx”对照着看。它的价值不在数据本身而在于逐字段解释了取数口径哪些表做主表、哪些字段做关联键、统计窗口截止到哪一天。金融风控项目最忌讳的就是时间口径混乱。常见错误是统计交易特征时把贷款发放之后的流水也算进去这在建模里叫泄漏会在训练阶段制造虚高的分数模型一到真实场景就崩后面避坑章节会专门展开。2.2 card、trans、order、district行为特征从哪来除了核心四张表剩下四张表补全了账户的行为画像。card.csv 记录账户下信用卡的发放情况包含卡类型和发卡时间通过 disp_id 关联到具体账户trans.csv 是交易流水字段涵盖交易类型、发生金额、交易后余额是判断客户资金活跃度最重要的来源order.csv 是账户发起的定期付款订单反映周期性支出习惯district.csv 是地区宏观统计包括人口、平均工资、失业率等。这些文件不能直接进模型因为每个账户的交易次数不一样深度学习需要的是定长向量。常见做法是按 account_id 分组做聚合把流水压成统计量。我会在特征构建阶段对贷款发放日之前三到六个月的交易记录计算交易总笔数、入账金额均值、最大出账金额、期末账户余额、入账次数占比这几个字段再把 loans 本身的金额、期限、月还款额拼接进来最后 join 上 district 里的失业率和平均工资。这里要提醒一句第一版特征不要贪多。先用贷款金额、期限、月还款额、账户平均余额、交易笔数、地区平均工资这十个以内的字段跑通基线。十来列特征配两三层全连接网络在几千条样本上正好一上来就把所有行为特征塞进去特征维度上百模型很容易在验证集上表现不稳定。加特征要一个一个加每加一组就看一次验证集指标确定有提升再保留这是控制过拟合最朴素也最有效的办法。这章的最后想强调一点很多同学会把精力全放在模型结构上却忽略数据血缘这一步。实际上课程设计和毕业设计的评分点经常落在“数据怎么来的、口径怎么定的”这一层的答辩阐述上。能把九张表的关系讲清楚、把 status 的映射逻辑讲明白哪怕模型只是两层全连接评委也会认为你有完整的工程意识。3. 把脚本跑起来读取、清洗与训练集构建的前后顺序压缩包里的脚本可以拆成三段流水线read_file.py 负责把 CSV 读进内存fix_money.py 负责清洗金额字段里的特殊字符贷款违约预测的数据构建.py 负责把分散的表拼成宽表。运行教程文档里建议按 exercise_20190910.py、fix_money.py、read_file.py 的顺序跑我复现后认为更合理的顺序是从依赖关系最底层的函数读起。3.1 fix_money.py金额字段到底藏了什么雷fix_money.py 定义了一个非常短小的清洗函数作用是把类似“$4,100”这种带货币符号和千分位逗号的字符串转成数值。初看似乎不值得单独写一个脚本但金融数据里这种格式处处都是下单金额、余额、转账金额几乎每个数值字段都可能混入符号。如果直接 pd.to_numeric遇到非数字字符会整列报错或变成 NaN。def fix_money(money_str): if isinstance(money_str, str): cleaned money_str.replace($, ).replace(,, ) return float(cleaned) return money_str print(fix_money($4,100)) # 输出 4100.0这个函数的逻辑不复杂先判断输入是否为字符串因为数据里可能存在已经转成 float 的列是字符串就依次去掉美元符号和千分位逗号再转成浮点数不是字符串就原样返回。这里有个容易被忽略的分支把非字符串原样返回保证了重复调用时不会把已清洗过的数值再误处理一次。实际运行时我建议做个扩展把函数改成能批量处理一个 Series同时用 try-except 兜底无法解析的脏值。教学版脚本只覆盖了标准格式真实数据集里还可能遇到“4,100.50”“$ -4,100”这类带负号或小数点的写法。把 replace 换成正则表达式 re.sub(r[$,], , s) 会通用得多。这一步虽然基础但我见过不少人因为没清洗干净模型训练时某列特征全是 NaN最后整列被 pandas 丢弃特征数量不一致导致模型训练直接报错。3.2 read_file.py读取函数与 one_hot 编码的适用边界read_file.py 定义了两个函数read_file 负责读取指定目录下所有 CSVone_hot 负责对分类字段做独热编码。单独运行这个脚本会读取 data 文件夹里的全部 CSV并打印 accounts 数据集的形状和前几行目的是让你确认数据真实可用而不是一路报错。import pandas as pd import os def read_file(path): return pd.read_csv(path, sep,, encodingutf-8) def one_hot(df, column): return pd.get_dummies(df, columns[column], prefixcolumn)read_file 的封装值得学习的一点是路径参数化函数接收完整路径而不是写死在代码里。很多项目把路径写死在脚本顶部换台电脑就找不到文件。我一般会在主脚本里用 os.path.join 拼路径这样不管压缩包解压到哪个目录只要保持 data 文件夹的相对位置不变脚本就不会因为绝对路径失效而翻车。one_hot 在这个项目里主要作用于 card 的卡类型、disp 的关系类型等低基数分类列。独热编码的适用边界很清楚分类取值只有几个或十几个时编码后维度可控模型能感知类别差异当分类取值到几十上百时比如 district 的地区编号用独热编码就会产生大量稀疏列。几千样本配上几百维稀疏特征神经网络要花更多迭代才能收敛。对于高基数分类我一般会退一步改用频数编码或目标编码比如把地区替换成该地区历史贷款违约率既保留信息又控制维度。这个取舍在特征构建脚本里也会遇到。3.3 贷款违约预测的数据构建.py从多表 join 到宽表生成最关键的脚本是“贷款违约预测的数据构建.py”。它把前面读到的表按业务逻辑拼成模型输入。整体流程是先读 loans 作为主表join accounts 拿到账户基础信息再关联 district 拿到地区宏观数据按 account_id 聚合 trans 的交易统计量最后裁掉贷款发放日之后的记录。import pandas as pd loans pd.read_csv(data/loans.csv, parse_dates[date]) trans pd.read_csv(data/trans.csv, parse_dates[date]) trans_features trans[trans[date] loans[date].max()].copy() trans_features trans_features.groupby(account_id).agg( trans_count(trans_id, count), avg_balance(balance, mean), max_withdraw(amount, max) ).reset_index() train_df loans.merge(trans_features, onaccount_id, howleft) train_df[label] train_df[status].apply(lambda x: 1 if x in (1, 3) else 0)这段代码里最关键的不是 merge 本身而是先做了时间截断trans 只保留贷款发放日之前的记录。这是防止泄漏的第一道闸门。如果忽略这一步把整张 trans 表聚合进来账户在贷款发放后的还款行为也会被统计进特征。模型等于提前看到了答案训练指标会异常漂亮但拿到新数据预测时对应统计量根本不存在效果自然崩塌。特征聚合的参数值得逐项说明。trans_count 是贷款发放前的交易总笔数反映账户活跃度avg_balance 是平均余额反映资金充裕程度max_withdraw 是最大出账金额反映资金压力峰值。这三个参数分别从频率、水位、压力三个维度刻画账户行为是风控特征中最经典的组合。label 的映射用 lambda 一行完成把 status 的 1 和 3 转成违约正例其余转成负例。到这里一张能直接喂给神经网络的宽表就构建完成了。4. 深度学习模型怎么搭main.ipynb 的训练主流程与评估陷阱数据层处理完正式进入模型部分。项目的主训练逻辑集中在 main.ipynb 里前导的 exercise_20190910.py 承担了数据探索和可视化职责先把数据分布看清楚再进建模环节。4.1 从可视化到建模网络结构怎么选才不翻车exercise_20190910.py 运行后会输出多张图表包括不同卡类型持卡人的性别分布、年龄分布、账户余额对比。这一步看起来只是探索性分析实际价值是帮你在建模前确认特征分布是否合理。比如发现某类卡片的持卡人余额严重右偏就需要考虑做 log 变换否则神经网络对这种高偏态特征的学习效率很低。进入 main.ipynb 后模型结构的选择不必追求复杂。对于宽表只有几十个特征、样本量几千行的任务两层到三层的全连接网络足够。我第一次做这类项目时也曾想过直接上 LSTM 或 Transformer理由是“深度学习嘛结构越复杂越高级”。结果训练时间长、参数多验证集指标反而不如简单 MLP。原因很简单交易流水已经被聚合成统计量时序细节早就不存在了序列模型没有用武之地。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout model Sequential([ Dense(32, activationrelu, input_dimX_train.shape[1]), Dropout(0.3), Dense(16, activationrelu), Dense(1, activationsigmoid) ]) model.compile(optimizeradam, lossbinary_crossentropy, metrics[AUC])这段结构是课程设计里最常见的配置输入层 32 个神经元承接特征向量中间加 Dropout 防止过拟合第二层压缩到 16输出层用 sigmoid 输出违约概率。编译时的 metrics 选 AUC 而不是 accuracy这是针对不平衡数据的一个重要决定。这个项目里违约样本占比通常只有一成左右即使全部预测为正常准确率也能到九成accuracy 完全失去参考意义AUC 才能反映模型是否真的分得开好坏样本。训练时的参数要多说一句batch_size 不要调太小样本只有几千条时batch_size32 或 64 可以让梯度更新更稳定epochs 可以从 50 起步配合 EarlyStopping 监控验证集 AUC。如果没有早停机制训练到后期模型会慢慢过拟合训练集 AUC 一路走高验证集却开始掉头向下。4.2 不平衡数据下的评估别被准确率骗了项目里违约样本占比低这是金融风控数据的常态。评估时如果只看 accuracy哪怕模型什么都不学只要把全部样本预测为正常准确率也可能高达九成。这个数字在答辩时很唬人但毫无实际意义。正确做法是同时看 AUC、召回率和混淆矩阵。from sklearn.metrics import roc_auc_score, confusion_matrix, recall_score y_pred_prob model.predict(X_valid).ravel() auc roc_auc_score(y_valid, y_pred_prob) recall recall_score(y_valid, (y_pred_prob 0.5).astype(int)) cm confusion_matrix(y_valid, (y_pred_prob 0.5).astype(int)) print(AUC:, auc, Recall:, recall) print(cm)AUC 衡量的是模型把违约样本排在正常样本前面的能力与阈值无关是答辩时最该拿出来的指标。recall 衡量的是所有真实违约样本里模型找回了多少在风控场景中漏掉一个违约客户比误伤一个正常客户代价更大所以 recall 要优先保证。混淆矩阵则能直观看到阈值 0.5 下模型的漏报和误报数量。我建议在答辩展示时把这三样同时放出来先摆 AUC 证明模型排序能力强再摆 recall 证明业务价值最后用混淆矩阵回应评委可能提出的“阈值为什么定 0.5”的问题。如果递归率太低后面第 6 章的阈值扫描方法可以直接用来调参。5. 避坑指南特征泄漏、编码膨胀与环境兼容的排查实录这个项目整体代码质量在毕设里算高的但拆包过程中依然有几个高频问题值得单独记录。这些问题我自己第一次复现时也踩过每一条都是实打实的翻车经验。5.1 训练集 AUC 高达 0.98验证集却只有 0.65疑似特征泄漏现象模型在训练集上指标接近满分一换到验证集或测试集分数立刻腰斩。训练曲线看起来一切正常但最终模型完全不可用。原因这是典型的数据泄漏。最常见的位置在聚合交易特征时没有做时间截断把贷款发放后的交易流水也统计进了特征。账户后续的还款行为已经包含了结果信息模型自然“未卜先知”。另一个隐蔽来源是归一化时用了全量数据的统计量而不是只用训练集部分计算均值和方差。解决回到“贷款违约预测的数据构建.py”确认 trans 聚合前已经过滤了日期大于贷款发放日的记录。归一化或标准化时用 sklearn 的 fit_transform 只在训练集上执行再用训练集的均值和方差去 transform 验证集。修复后 AUC 会回落到合理区间这恰恰说明之前的高分是假的。5.2 独热编码生成几百列稀疏特征训练速度骤降现象把 district 地区编号、card 卡类型全部做 one_hot 后特征维度从 20 涨到 300 多模型训练明显变慢验证集指标却没有明显改善。原因district 的取值数量较多独热编码的维度随类别数线性增长。几千条样本配几百个稀疏特征神经网络很大一部分参数在拟合无关维度训练效率低还容易过拟合。解决对低基数分类字段保留 one_hot对高基数分类改用频数编码或目标编码。district 可以映射为地区平均工资或地区违约率一个字段搞定信息量更大。card 类型这种只有三五个取值的继续用 one_hot 没毛病。5.3 Python 3.7 环境能跑换到新版 Python 后 pandas 读 CSV 报错现象按照运行教程配置环境后执行 read_file.py 报错提示找不到某个 pandas 函数或 dtype 参数不兼容。原因项目的 .pyc 文件显示它是在 Python 3.7 环境下开发的代码里可能使用了旧版 pandas 的默认行为。新版 pandas 某些读文件参数或字符串解析规则有变化导致同样的代码在不同环境下表现不一致。解决优先按教程建立 Python 3.7 虚拟环境不要直接用系统最新版。conda create -n loan_default python3.7 之后再用 pip 安装 requirements 里的依赖。如果是新版环境强行运行可以把报错信息贴进搜索引擎多半是 read_csv 的 encoding 参数或 dtype 参数需要显式指定。这类环境问题不是代码有 bug而是版本漂移不必改业务逻辑。5.4 数据集中存在重复账户或被多张卡关联训练样本量虚高现象训练集构建完成后打印 train_df.shape 发现行数明显多于 loans 表的记录数部分账户在样本里出现多次。原因join 时选择了多对多的关联路径。一个账户可能对应多个 disp 记录或多个 card直接 merge 会让主表的行数成倍放大同一笔贷款被拆成多行特征却基本相同。解决先在关联路径上做去重。disp 只保留每个 account 的第一条记录card 只保留每个 account 的最早一张或者把多卡聚合为持卡数量和卡类型列表再回到主表做一对一连接。判断方法很简单以 loans 的行数为基准每次 merge 后检查行数是否变化一旦变大就说明关联键不唯一。6. 把项目再往前推一步阈值扫描与交叉验证的完整替换方案模型训练完很多同学直接拿默认 0.5 的阈值交差了。这对不平衡数据来说远不是最优解。最后一章分享两个改动成本极低、但能让项目质感和答辩深度明显提升的技巧。第一个技巧是阈值扫描。既然违约样本占比低模型输出的概率普遍偏低0.5 这个阈值并不适合。思路是在验证集上遍历从 0.1 到 0.9 的阈值计算每个阈值下的精确率和召回率找出两者平衡点或符合业务偏好的阈值。代码可以用几行实现得到的最优阈值直接替换预测时的分类边界。import numpy as np from sklearn.metrics import precision_score, recall_score best_th 0.5 best_recall 0 for th in np.arange(0.1, 0.9, 0.05): pred_bin (y_pred_prob th).astype(int) rec recall_score(y_valid, pred_bin) if rec best_recall: best_recall rec best_th th第二个技巧是把单次划分改成 K 折交叉验证。课程设计代码里通常只有一次 train-test-split结果受随机划分影响很大。用 StratifiedKFold 做五折每折单独训练并记录 AUC最终取均值指标稳定性会强很多。评委如果问“模型稳定性怎么样”你能直接给出五折 AUC 的均值和方差比单次结果有说服力得多。从那以后我每次做完成一个建模项目都强制走一遍交叉验证加阈值扫描的流程即使只是课程作业也会跑一遍用五折 AUC 均值替代单次分数。这样做的好处是任何一次训练结果异常时我能立刻区分是划分运气问题还是模型本身的问题。这两个技巧加起来不超过 30 行代码但对项目结论的可信度提升非常明显。希望这篇拆解能帮你少踩几个坑把这份源码真正吃透后再交出自己的版本。本文还有配套的精品资源点击获取