ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

信贷风控实战:贷款违约预测Python源码与特征工程全解析

信贷风控实战:贷款违约预测Python源码与特征工程全解析 简介这是一套面向毕业设计、课程设计与期末大作业场景的银行个人贷款违约预测Python项目适合数据科学、大数据及相关专业学生用以完整掌握从数据清洗、特征工程到模型构建的贷款风控建模流程。压缩包共20个文件以12个Python源码为核心另有4个CSV数据集、说明文档及演示数据整体仅935KB轻量而结构清晰源码按数据预处理、特征构建含标签编码、类别与时间特征提取、主程序等模块划分便于按需研读和二次开发。目前已有220人学习下载其预测建模思路与工程组织形式对入门进阶、比赛立项均有较强参考价值。项目经过本地成功运行并配有项目说明与提交示例既能支撑完整毕设呈现也可作为课程项目汇报的基线方案适合在此基础上扩展调优。1. 拿到贷款违约预测源码先别急着跑这份压缩包能帮你省下两周调参时间第一次做银行个人贷款违约预测的毕设或课设最容易卡住的不是模型选型而是“数据怎么清洗、特征怎么造、submit文件怎么对齐”。这套基于Python的个人贷款违约预测源代码压缩包里带着完整的训练/测试样本和特征构造脚本DataClean.py、AddFeature.py、LabelEncoderFeature.py、ClassFeature.py、TimeFeature.py、main.py一条链路串到底懂一点pandas就能改出属于自己的版本。它解决的是信贷风控里最典型的二分类问题给定用户历史借贷行为与基础属性预测其未来是否违约。适合正在做毕业设计、课程设计、期末大作业或者想让简历上多一个“金融数据挖掘”真实案例的在校学生。2. 跑通项目主流程代码结构与运行链路2.1 压缩包内目录结构与各模块职责拿到压缩包后先解压看目录。这套项目的组织方式非常规矩数据、清洗、特征、训练、提交分得清清楚楚。我拆过的项目里这种结构对毕设答辩特别友好——评委问“你这几个模块怎么协作”你直接对着文件讲就行。主要目录和文件分三层路径职责对应阶段data/test_public.csv测试集无Label列用于生成预测数据输入preprocess/DataClean.py去重、缺失值填充、异常值处理数据清洗FeatureBuilder/AddFeature.py业务派生特征如收入负债比、查询次数聚合特征工程FeatureBuilder/LabelEncoderFeature.py类别特征编码特征工程FeatureBuilder/ClassFeature.py分组统计特征特征工程FeatureBuilder/TimeFeature.py日期字段的时序特征与周期特征特征工程main.py主流程串起全部步骤并输出submit模型训练与预测这里有一个值得学习的点特征工程被拆成四个文件而不是堆在一个脚本里。AddFeature管业务理解LabelEncoderFeature管类别编码ClassFeature管聚合统计TimeFeature管时间解析。实际做金融类比赛或项目时特征文件越拆越细因为特征迭代太快全部塞在一起后期根本不敢动。这套代码的拆分方式是能直接沿用的工程习惯。2.2 从原始CSV到提交文件的完整运行顺序先看运行链路再看细节。常见的做法是DataClean处理得一份干净表 → FeatureBuilder生成训练特征与测试特征 → main.py读入特征训练模型 → 预测test_public.csv → 按submit_example.csv的格式写结果。我一般会先单独跑一遍每个模块验证一下而不是直接执行main.py。原因很现实main.py一旦报错你分不清是数据问题还是特征问题。分开跑每步的输入输出都看得到排查成本低很多。# 执行顺序示例在项目根目录下 # 第一步清洗数据生成 clean_train.csv 与 clean_test.csv python preprocess/DataClean.py # 第二步分别构造训练集和测试集特征 python FeatureBuilder/AddFeature.py python FeatureBuilder/LabelEncoderFeature.py python FeatureBuilder/ClassFeature.py python FeatureBuilder/TimeFeature.py # 第三步训练并生成提交文件 python main.py这个顺序里最容易忽略的是第二步很多新手把特征脚本只跑了一遍以为它同时处理了train和test。实际上这四个特征脚本通常各自定义了一个build函数函数内部分别读入clean_train和clean_test生成两组特征再合并返回。所以每次改完特征脚本train和test都要重新生成否则会特征错位。main.py里一般也保留了重新读取原始数据再全走一遍的完整流程拿来当总入口用。2.3 main.py训练主流程速读先别急着逐行读main.py先看它做了哪几件事。常见的流程是读入清洗后的训练集与测试集 → 做特征对齐 → 划分训练验证集 → 训练分类模型 → 输出预测概率 → 写成submit_example.csv同格式的结果。# main.py 核心流程简化示意 import pandas as pd from sklearn.model_selection import train_test_split from sklearn.ensemble import GradientBoostingClassifier # 1. 读入已经完成特征工程的数据 train pd.read_csv(data/clean_train_with_features.csv) test pd.read_csv(data/clean_test_with_features.csv) # 2. 特征列与标签列分离 feature_cols [c for c in train.columns if c not in [Id, Label]] X train[feature_cols] y train[Label] # 3. 切分训练集与验证集 X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, random_state42, stratifyy ) # 4. 训练模型 model GradientBoostingClassifier(n_estimators300, max_depth4, learning_rate0.05) model.fit(X_train, y_train) # 5. 输出验证集上的评估 print(val accuracy:, model.score(X_val, y_val)) # 6. 对测试集预测并保存 test[pred] model.predict_proba(test[feature_cols])[:, 1] test[[Id, pred]].to_csv(submit_example.csv, indexFalse)解释几个关键参数。stratifyy很重要贷款违约场景正负样本比例通常接近1:9甚至更低如果不按类别比例切分验证集里可能一个正样本都分不到模型效果评估直接失真。random_state42固定随机种子保证每次跑出来的结果可复现——这一点答辩时老师一定会问不设种子的话“这次AUC 0.72下次AUC 0.69”根本没法解释。GradientBoostingClassifier是这类表格型风险预测的稳妥起点LR、RF、XGB都有人用但这套代码里GBDT默认参数就能达到不错的效果后续想提分再换成XGBoost或LightGBM。3. 数据清洗DataClean.py里的三个关键处理3.1 Id重复与同一借款人多笔贷款的取舍很多人拿到贷款数据第一反应是“直接删重复行”但信贷数据里Id重复不一定是脏数据可能是同一个用户在平台上借了多笔钱。DataClean.py里一般会先按Id统计出现次数再结合业务字段判断。# DataClean.py 处理重复Id的常见策略 import pandas as pd df pd.read_csv(data/test_public.csv) # 先看重复情况 dup_count df[Id].duplicated().sum() print(duplicated Ids:, dup_count) # 如果Id重复但LoanDate等业务字段不同视为多笔借款保留 # 如果全部字段完全一致视为录入重复只保留第一条 df_clean df.drop_duplicates(subset[Id], keepfirst)这里的逻辑有个取舍keepfirst适合“完全重复”的脏数据但如果同Id下不同借款记录都是有效样本直接保留第一条会丢失信息。更稳妥的常用做法是先按所有字段做一次去重drop_duplicates()不带subset再去看Id维度是否仍然重复。如果仍重复说明同一个人有多笔贷款这时候应该保留而不是删后续特征工程里可以按Id聚合出“人均借款次数”“累计金额”这类强特征。DataClean.py帮你把“去重”这个动作做成可配置的你可以根据实际数据分布决定哪一层去重要生效。3.2 缺失值填充数字列与类别列分开处理测试集和训练集通常都有缺失字段。无脑fillna(0)会在答辩时被追问“为什么填0依据是什么”。更稳的做法是数字列按业务含义填充收入缺失填中位数利率缺失填众数类别列单独构造“未知”级别。# 缺失值处理示意 num_cols df_clean.select_dtypes(include[int64, float64]).columns cat_cols df_clean.select_dtypes(include[object]).columns # 数字特征优先中位数填充保留是否缺失的标记 for col in num_cols: if df_clean[col].isnull().sum() 0: df_clean[col _missing_flag] df_clean[col].isnull().astype(int) df_clean[col] df_clean[col].fillna(df_clean[col].median()) # 类别特征新增unknown级别 for col in cat_cols: df_clean[col] df_clean[col].fillna(unknown)注意_missing_flag这个细节它把“缺失这件事本身”变成了特征。实际信贷数据里收入字段缺失往往意味着人群分布特殊模型可以学到“这个字段缺失的人违约率偏高”。如果直接静默填充这个信号就丢了。DataClean.py里是否有这一步取决于原始版本但我是强烈建议保留这个习惯。fillna(df_clean[col].median())里用中位数而不是均值是因为收入、负债这类字段长尾严重均值会被极值拉偏中位数更稳健。3.3 异常值截断收入、负债字段的1%与99%分位贷款数据里最不缺的就是极端值有人年收入填了个八位数有人负债率写成300%。这类值不一定是录入错误但会让GBDT的分裂点被拉到离谱的位置。常见做法是分位数截断。import numpy as np def clip_outliers(df, cols, lower0.01, upper0.99): for col in cols: q_low df[col].quantile(lower) q_high df[col].quantile(upper) df[col] df[col].clip(lowerq_low, upperq_high) return df df_clean clip_outliers(df_clean, num_cols)clip而不是“删除”这样能保留样本量。信贷场景正样本本来就少删一行极端值可能就删掉了一个关键违约案例。截断阈值1%/99%不是拍脑袋先看分位数再决定如果99%分位和最大值差了几十个数量级说明尾部几乎全是噪声可以大胆截到99%。反过来说如果某个字段99%分位和最大值接近那这个字段的极端值本身就是有效信号不该截。我一般会先df[col].describe()看一眼分布再决定对哪些列启用截断。4. 特征工程四个文件到底在构造什么4.1 AddFeature从业务角度加派生变量数据清洗做完原始字段就是那么十几个光靠这些特征跑模型分数通常很平庸。AddFeature.py的核心是用业务逻辑组合原始字段造出模型一眼看不懂但我们知道有预测力的新变量。信贷场景下最典型的是“收入负债比”和“近段时间的查询次数”。# 常见AddFeature派生逻辑 # 假设原始字段中有MonthlyIncome, MonthlyDebt, CreditCardUsage df[debt_to_income] df[MonthlyDebt] / (df[MonthlyIncome] 1e-6) # 加1e-6防止除零 # 额度使用率信用卡已用额度 / 总授信额度 df[credit_utilization] df[CreditCardUsage] / (df[CreditLimit] 1e-6)debt_to_income是信贷风控里教科书级的比率特征直接反映一个人的还款压力。1e-6这种细节值得学它不是魔法是防除零报错的标准手段。答辩时老师问到“为什么加1e-6”能回答“防止除数为零导致Inf”会很加分。窗口类聚合也常在这一步做比如“过去半年查询次数”“过去三个月逾期次数”这类特征把时间维度上的行为密度压缩成一个数值对违约预测的提升非常明显。4.2 LabelEncoderFeature类别编码的踩坑点类别特征不能直接喂给线性模型和GBDT。LabelEncoderFeature.py的作用是把字符串类别转成整数编码但这里有个常见的坑用同一个LabelEncoder对训练集fit、再对测试集transform测试集一旦出现训练集没见过的类别直接报错。# 正确的做法fit训练集transform测试集 from sklearn.preprocessing import LabelEncoder le_dict {} for col in cat_cols: le LabelEncoder() # 只对训练集fit可以自动处理未知类别 combined list(set(train[col].astype(str).unique()) | {unknown}) le.fit(combined) train[col] le.transform(train[col].astype(str)) # 测试集里没见过的类别映射为unknown test[col] test[col].astype(str).map(lambda s: le.transform([s])[0] if s in le.classes_ else le.transform([unknown])[0])这里我额外把unknown也纳入了fit的类别集合再配合前面的缺失值填充就能保证训练和测试的特征空间完全一致。另一种更稳的方案是直接用pd.factorize或者category类型但LabelEncoder的可控性更好而且能保存编码映射表换数据集重训时能沿用同一张表。这个文件里保存的.pkl模型在答辩演示时很有用老师问“你这些编码规则是什么”你直接展示映射表比空口解释强得多。4.3 ClassFeature与TimeFeature分组统计与时间周期ClassFeature.py做的事是“分组后算统计量”——按某个主键聚合数值列生成组内均值、方差、计数再映射回每个样本。比如按Id分组计算“该用户历史借款次数”然后每条记录带上这个聚合值。# 分组统计特征示意 grp df.groupby(Id)[LoanAmount] df[user_loan_count] grp.transform(count) df[user_loan_amount_mean] grp.transform(mean) df[user_loan_amount_std] grp.transform(std)transform和agg的区别在于transform返回和原DataFrame等长的Series可以直接赋值新列不需要再做merge。这些“用户级”特征比单笔维度特征更能刻画整体还款习惯但要注意测试集里新出现的Id无法从训练集学到可信聚合值需要做平滑或回退到全局均值否则预测时会出现大量缺失。项目里一般会预留一个默认填充值这一步在答辩的“特征工程”部分常被问及值得说不透。TimeFeature.py负责从日期列中榨取信息借款日到今天的间隔天数、借款周几、月份周期、是否月初/月末等等。# 时间特征构造示意 df[loan_date] pd.to_datetime(df[LoanDate]) today pd.Timestamp(2024-01-01) # 以数据采集截止日为基准 df[loan_to_today] (today - df[loan_date]).dt.days df[loan_month] df[loan_date].dt.month df[loan_weekday] df[loan_date].dt.weekday df[is_month_start] df[loan_date].dt.is_month_start.astype(int)loan_to_today这类“距今天数”特征的价值在于捕捉时间衰减效应半年内的借贷行为对违约预测的影响远大于两年前的记录。这里有个坑是基准日期的选取直接用pd.Timestamp.now()会在重新跑数据时随时间变化导致特征分布漂移。项目里通常固定一个截止日让特征可复现。答辩或面试时主动说一句“我把基准时间固定了保证训练测试特征一致”比默默写死代码要加分不少。5. 避坑指南跑贷款违约预测最容易翻车的五个位置5.1 中文路径与编码问题导致数据读不进现象直接运行DataClean.pypd.read_csv(data/test_public.csv)报UnicodeDecodeError或者路径是中文文件夹名时找不到文件。原因大部分公开数据集CSV是UTF-8编码但Windows下Excel改过的文件可能存成GBK而Python默认读UTF-8中文路径在部分环境需要额外配置才识别。解决读文件统一显式指定编码路径建议保持英文。如果数据源本身是GBK改用pd.read_csv(path, encodinggbk)。不确定编码时用chardet检测一下再读。这个排查用不了两分钟别在这上面浪费时间。5.2 训练集与测试集特征列顺序不一致现象训练时模型正常预测测试集时报ValueError: feature shape mismatch或者预测概率全部变成同一个值。原因清洗或特征构造过程中训练集和测试集走了不同的分支比如测试集某列全为缺失被直接drop但训练集保留着pandas的concat默认按列名对齐顺序一致时没问题顺序不一致就翻车。解决在进入模型之前做一次强制对齐。常见做法是test test[X_train.columns]把测试集的列顺序直接对齐训练集。另外每次跑完特征脚本后打印一下train.shape和test.shape多一个少一个都能当场发现。5.3 LabelEncoder在测试集上遇到新类别直接报错现象训练集fit的LabelEncodertransform测试集时ValueError: y contains previously unseen labels。原因测试集中出现了训练集里没见过的类别值。这是信贷数据里最常见的分布偏移新用户、新渠道、新职业类型都会引入新类别。解决前面已经写了具体做法——fit时合并训练集类别与unknowntransform时对没见过的类别映射到unknown。核心原则是“两个数据集共享同一个类别空间”这比任何单一编码方式都重要。5.4 样本不均衡被忽略准确率虚高现象模型在验证集上准确率0.94但预测结果几乎全是“不违约”查看AUC只有0.55。答辩现场一比划就露馅。原因贷款违约率通常在3%10%之间模型只需要把每条样本都判为“不违约”准确率就有90%以上。没有AUC或召回率兜底准确率就是自欺欺人。解决至少要report准确率、AUC、召回率三项。代码里加from sklearn.metrics import classification_report, roc_auc_score在验证集上打印这几个指标。如果确认类别极不平衡再加class_weightbalanced或对多数类下采样。这个项目里自带的数据集正负比没到极端但答辩扩展时经常会往这个方向提问要提前有数。5.5 提交文件行数与Id顺序和test_public不一致现象提交后评分系统报“提交文件行数不符”或“Id顺序错误”直接0分。原因清洗时dropna不加条件地删掉了几行测试样本或者输出时不按原Id顺序排列。解决在DataClean.py和main.py各留一份Id列表输出submit之前先用pd.merge按Id对齐。用样例文件当模板保证列名、列顺序、Id顺序完全一致。# 提交前强制对齐 submit submit_example[[Id]].merge(pred_df, onId, howleft) assert len(submit) len(test_public) submit.to_csv(submit_final.csv, indexFalse) print(submit.shape)6. 本地验证与交付跑分前先把数据切对再谈调参6.1 时间切分替代随机切分防数据泄露想要拿这套代码去参加比赛或写进简历项目光靠train_test_split不够。贷款数据本质是时间序列用随机切分训练模型会学到“未来信息”导致线上分数比本地验证低一大截。更稳的验证方式是按时间排序切分用前一段时间的样本做训练后一段时间的样本做验证。# 按时间切分而不是随机切分 df df.sort_values(LoanDate) split_idx int(len(df) * 0.8) train_df df.iloc[:split_idx] val_df df.iloc[split_idx:] model.fit(train_df[feature_cols], train_df[Label]) val_pred model.predict_proba(val_df[feature_cols])[:, 1] auc roc_auc_score(val_df[Label], val_pred) print(time-based val AUC:, auc)如果还嫌不够稳可以做K折时间序列交叉验证每次用前k折训练预测第k1折。这套逻辑封装成函数后可以直接复用到后续所有表格型比赛项目。数据泄露是金融预测“玄学退分”的头号凶手时间切分能从机制上规避一大半。6.2 提交前的一分钟自检除了Id和行数对齐提交前我习惯再走一遍这三步。第一步看预测分布打印test_public[pred].describe()确认概率在合理区间不能全是0.5也不能全是0.0001。第二步抽样比对随机挑三行测试数据手工推一遍特征构造逻辑确认特征列不是全零。第三步看列名submit里的列名必须是Id和pred评分系统通常按列名匹配写错一个单词就整份无效。这三步花不到一分钟但能避免90%的“跑通了结果分没了”的尴尬。很多同学把注意力全放在调AUC上忽略了交付的最后一公里血泪经验告诉我这步比调参更值钱。6.3 一个能撑起答辩节奏的进阶习惯从那以后我每次拿到这类带特征工程的源码都强制自己先跑一遍时间切分、跑一遍完整提交自检再动参数。这个顺序形成一个习惯之后任何类似项目都能在两小时内稳定交付而不是靠运气刷分。如果你还打算把项目写进简历建议再补一个LightGBM版本的main_lgb.py做对比实验把两个模型的AUC、训练时间、特征重要性排表放进答辩PPT——这一页往往比整篇论文更让评委记住你。这套源码的价值不在于“开箱即用”而在于你能顺着清洗、特征、评估的完整链路理解金融风控里数据怎么玩。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进