
简介本资源为基于机器学习的个人信贷违约预测识别项目完整源码与训练测试数据集面向计算机相关专业学生及从业者可用于毕业设计、期末课程设计或课程大作业帮助解决信贷风控场景下违约行为建模与预测的实践需求。压缩包共59个文件约142.33MB涵盖16个csv数据文件、10个py源码脚本、6个go文件以及pth模型权重、docx与pdf报告、pptx答辩幻灯片、png结果图、tex论文源文件等从数据处理、模型训练到结果验证形成完整链路。项目内含决策树、MLP、距离转概率等多种模型实现并附有训练与验证脚本、模型字典及结果输出文件目录结构清晰便于按模块阅读与复现。目前已有179人学习下载评审分达97分代码经过严格调试可运行适合作为机器学习入门到进阶的实战参考也能为信贷违约预测类课题提供可复用的方案与排错思路。1. 信贷违约预测项目从一份 zip 到能跑通的评分卡模型拿到「基于机器学习的个人信贷违约预测识别项目源码训练测试数据集」这个 zip多数人第一反应是解压、找train.py、python train.py然后被一堆路径报错和字段对不上的问题劝退。这个标题背后其实是一条完整的信贷风控建模链路原始申请数据清洗、类别不平衡处理、特征工程、模型训练、阈值调优、违约概率输出。它解决的是「给定一个借款人的申请信息判断他未来会不会违约」这个二分类问题适合想入门金融风控的算法同学、需要做课程设计或毕业设计的学生以及想从通用机器学习转向业务落地的工程师。这篇笔记不依赖某个具体仓库而是按这类项目最常见的结构把从数据到模型的每一步讲清楚让你拿到任何一份类似的信贷数据集都能复现。2. 先看懂数据信贷违约预测的字段结构和标签定义2.1 一份典型信贷数据集长什么样个人信贷违约数据集通常来自 Lending Club、Give Me Some Credit 或国内某银行的脱敏样本字段可以分成几类。第一类是身份与申请信息比如年龄、职业、婚姻状况、教育程度第二类是额度与期限比如申请金额、利率、分期期数第三类是信用历史比如过去逾期次数、信用账户数、负债收入比第四类是标签列通常叫default、loan_status或is_bad取值 0/1。以 Give Me Some Credit 为例核心字段包括SeriousDlqin2yrs两年内是否严重逾期即标签、RevolvingUtilizationOfUnsecuredLines无担保额度使用率、age、DebtRatio、MonthlyIncome、NumberOfOpenCreditLinesAndLoans、NumberOfTimes90DaysLate等。这些字段名看着长但每一个都对应明确的业务含义理解它们比急着调参重要得多。提示不同来源的数据集字段名差异很大先做一次字段映射把业务含义和列名对应起来后面所有处理都基于映射表避免改一处漏一处。2.2 标签不平衡违约样本为什么总是少数信贷违约是典型的不平衡二分类问题。真实业务里违约率通常在 3% 到 10% 之间也就是说 1000 个样本里可能只有几十个正例。如果直接训练模型会倾向于全部预测为「不违约」准确率看着有 90% 以上但召回率接近零这种模型上线就是灾难。处理不平衡常见有三种做法。一是重采样对正例过采样SMOTE或对负例欠采样二是调整类别权重在模型里设置class_weightbalanced三是换评估指标不看准确率看 AUC、KS、召回率。我一般会先算一下正负样本比例如果低于 1:10优先用类别权重加阈值调优而不是一上来就 SMOTE因为合成样本在信贷场景里容易引入不真实的组合。import pandas as pd df pd.read_csv(cs-training.csv) # 假设标签列名为 SeriousDlqin2yrs pos df[SeriousDlqin2yrs].sum() neg len(df) - pos print(f正例: {pos}, 负例: {neg}, 比例: 1:{neg/pos:.1f})这段代码做的是最基础的正负样本统计。pos是违约样本数neg是正常样本数输出比例用来判断后续要不要做不平衡处理。如果比例超过 1:20建议在训练时加类别权重如果低于 1:5可以先不做特殊处理靠阈值调优解决。2.3 缺失值和异常值信贷数据里最常见的两类脏数据信贷数据的缺失往往不是随机的。比如MonthlyIncome缺失的人可能恰恰是收入不稳定或不愿披露的人群这类缺失本身携带信息。常见做法有三种直接删除缺失行、用中位数或均值填充、把「是否缺失」作为一个新特征。我一般对缺失率低于 5% 的字段直接填充高于 30% 的字段考虑丢弃或单独建模中间地带加一个缺失指示列。异常值方面age出现 0 或 200、DebtRatio出现极大值都很常见。处理方式不是无脑删而是先看分布再用分位数截断。比如把超过 99 分位数的值替换为 99 分位数的值这样既保留了样本又不会被极端值带偏。# 缺失值填充与异常值截断 df[MonthlyIncome] df[MonthlyIncome].fillna(df[MonthlyIncome].median()) df[NumberOfDependents] df[NumberOfDependents].fillna(0) # 对 DebtRatio 做 99 分位截断 q99 df[DebtRatio].quantile(0.99) df[DebtRatio] df[DebtRatio].clip(upperq99) # 年龄异常值处理 df df[(df[age] 18) (df[age] 100)]这里fillna用中位数是因为收入分布偏态严重均值容易被高收入拉偏。clip做的是上截断把极端值压到 99 分位。年龄过滤直接去掉不合理样本因为 18 岁以下和 100 岁以上在信贷场景里基本是数据错误。3. 特征工程把原始字段变成模型能用的信号3.1 分箱与 WOE信贷评分卡的核心操作信贷风控里最经典的特征处理是分箱加 WOEWeight of Evidence。分箱是把连续变量切成若干区间比如年龄切成 18-25、26-35、36-45 等WOE 衡量的是每个区间里违约样本和正常样本的比例差异。WOE 越大说明这个区间违约风险越高。WOE 的计算公式是WOE ln(该区间违约数/总违约数 ÷ 该区间正常数/总正常数)。IVInformation Value是 WOE 的加权和用来衡量单个变量的预测能力。一般 IV 小于 0.02 认为无预测力0.02 到 0.1 弱0.1 到 0.3 中等大于 0.3 强但要警惕过拟合。import numpy as np def calc_woe_iv(df, feature, target): lst [] for val in df[feature].unique(): sub df[df[feature] val] good ((sub[target] 0)).sum() bad ((sub[target] 1)).sum() lst.append([val, good, bad]) tmp pd.DataFrame(lst, columns[value, good, bad]) tmp[good_pct] tmp[good] / tmp[good].sum() tmp[bad_pct] tmp[bad] / tmp[bad].sum() tmp[woe] np.log(tmp[bad_pct] / tmp[good_pct]) tmp[iv] (tmp[bad_pct] - tmp[good_pct]) * tmp[woe] return tmp, tmp[iv].sum()这段代码先按特征取值分组统计每组的好样本和坏样本数再算占比和 WOE最后累加得到 IV。实际使用时先对连续变量做分箱再把分箱结果作为feature传入。注意np.log在占比为零时会报错实际代码里要加一个极小值平滑。3.2 特征筛选别把几十个字段一股脑塞进模型拿到二三十个字段不是全都用。筛选逻辑分三步先去掉缺失率过高、单一值占比过高的字段再算 IV保留 IV 大于 0.02 的最后看变量之间的相关性相关系数超过 0.7 的保留 IV 更高的那个。# 假设已经算好每个特征的 IV iv_dict {age: 0.05, DebtRatio: 0.12, MonthlyIncome: 0.08, RevolvingUtilization: 0.35} selected [k for k, v in iv_dict.items() if v 0.02] print(保留特征:, selected) # 相关性检查 corr_matrix df[selected].corr().abs() upper corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k1).astype(bool)) to_drop [col for col in upper.columns if any(upper[col] 0.7)] print(建议剔除:, to_drop)IV 筛选直接过滤掉预测力弱的变量相关性检查用上三角矩阵避免重复计算。to_drop里的是高度相关的特征保留其中一个即可。这一步能显著减少模型复杂度也降低过拟合风险。3.3 训练集测试集划分时间维度比随机划分更重要很多教程直接用train_test_split随机划分但在信贷场景里随机划分会导致未来信息泄露。正确做法是按时间划分用过去 12 个月的数据训练用最近 3 个月的数据测试。如果数据里没有时间字段至少要做分层抽样保证训练集和测试集的违约率一致。from sklearn.model_selection import train_test_split X df[selected] y df[SeriousDlqin2yrs] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) print(训练集违约率:, y_train.mean()) print(测试集违约率:, y_test.mean())stratifyy保证划分后正负比例和原始数据一致random_state固定随机种子方便复现。输出两个违约率是为了确认分层生效如果差异超过 1 个百分点说明数据量太小或分层没起作用。4. 模型训练与调优从逻辑回归到梯度提升树4.1 逻辑回归信贷评分卡的基线模型逻辑回归是信贷风控的基线原因是可解释性强每个特征的系数可以直接换算成评分。训练时用class_weightbalanced处理不平衡用 L2 正则防止过拟合。from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from sklearn.metrics import roc_auc_score, classification_report pipe_lr Pipeline([ (scaler, StandardScaler()), (clf, LogisticRegression(class_weightbalanced, C1.0, max_iter1000)) ]) pipe_lr.fit(X_train, y_train) y_prob_lr pipe_lr.predict_proba(X_test)[:, 1] print(LR AUC:, roc_auc_score(y_test, y_prob_lr))StandardScaler把特征缩放到同一量纲逻辑回归对尺度敏感这一步不能省。C是正则强度的倒数越小正则越强。predict_proba取第二列是违约概率。AUC 在 0.75 以上算可用0.8 以上算不错。4.2 梯度提升树处理非线性关系的利器XGBoost 和 LightGBM 在信贷数据上通常比逻辑回归高 2 到 5 个百分点的 AUC。它们能自动处理特征交互和非线性但可解释性差一些。训练时重点调三个参数n_estimators、max_depth、learning_rate。import lightgbm as lgb params { objective: binary, metric: auc, learning_rate: 0.05, max_depth: 5, num_leaves: 31, scale_pos_weight: neg / pos, verbose: -1 } dtrain lgb.Dataset(X_train, labely_train) dtest lgb.Dataset(X_test, labely_test, referencedtrain) model_lgb lgb.train(params, dtrain, num_boost_round500, valid_sets[dtest], callbacks[lgb.early_stopping(50)]) y_prob_lgb model_lgb.predict(X_test, num_iterationmodel_lgb.best_iteration) print(LGB AUC:, roc_auc_score(y_test, y_prob_lgb))scale_pos_weight设为负例数除以正例数等价于给正例更高权重。early_stopping(50)表示验证集 AUC 50 轮不提升就停止防止过拟合。max_depth5是信贷场景的常用值太深容易记住噪声。4.3 阈值调优0.5 不是万能分割线模型输出的是概率要变成 0/1 决策需要一个阈值。默认 0.5 在平衡数据上合理但在不平衡数据上往往偏高。调阈值的方法是画 KS 曲线或看不同阈值下的召回率和精确率。from sklearn.metrics import roc_curve fpr, tpr, thresholds roc_curve(y_test, y_prob_lgb) ks tpr - fpr best_threshold thresholds[ks.argmax()] print(最佳阈值:, best_threshold, KS:, ks.max())KS 值衡量的是模型区分好坏样本的能力KS 大于 0.3 算可用大于 0.4 算优秀。ks.argmax()找到 KS 最大时的阈值这个阈值通常比 0.5 低能提高召回率。实际业务里还要结合通过率和坏账率来定不是纯数学最优。5. 避坑与排查信贷违约预测里最容易翻车的五件事5.1 用准确率评估模型上线后坏账率没降现象是训练报告准确率 95%但业务方反馈模型没筛出几个坏人。原因是数据不平衡模型全预测为正常也能有高准确率。解决方法是换 AUC、KS、召回率作为主指标并在训练时加类别权重或调整阈值。5.2 特征里混入了标签泄露字段现象是 AUC 高到 0.99交叉验证也很漂亮但换一批数据就崩。原因往往是某个字段在违约发生后才能获取比如「催收次数」「逾期后还款金额」。解决方法是逐个字段确认获取时间只保留申请时点就能拿到的信息。5.3 缺失值填充用了全量数据统计量现象是线下测试很好线上效果差一截。原因是用全量数据算的中位数填充包含了测试集信息造成泄露。解决方法是只在训练集上算填充统计量再应用到测试集。# 正确做法训练集算中位数测试集用训练集的中位数 train_median X_train[MonthlyIncome].median() X_train[MonthlyIncome] X_train[MonthlyIncome].fillna(train_median) X_test[MonthlyIncome] X_test[MonthlyIncome].fillna(train_median)5.4 分箱在训练集和测试集上不一致现象是训练时 IV 很高测试时某个分箱的样本数为零。原因是分箱边界用了全量数据的分位数。解决方法是分箱边界只在训练集上确定测试集按同样的边界切分出现空箱就合并到相邻箱。5.5 模型上线后没有监控效果衰减发现太晚现象是模型刚上线 AUC 0.8半年后降到 0.65。原因是客群变化、政策调整、经济环境变化导致数据分布漂移。解决方法是每月算一次 PSIPopulation Stability IndexPSI 超过 0.2 就要考虑重新训练。6. 进阶技巧用 SHAP 解释模型和做拒绝原因输出模型跑通只是第一步业务方更关心「为什么拒绝这个人」。逻辑回归可以直接看系数树模型就需要 SHAP 值。SHAP 能给出每个样本每个特征的贡献度正贡献推高违约概率负贡献拉低。import shap explainer shap.TreeExplainer(model_lgb) shap_values explainer.shap_values(X_test) # 单个样本解释 sample_idx 0 shap.force_plot(explainer.expected_value, shap_values[sample_idx], X_test.iloc[sample_idx], matplotlibTrue)TreeExplainer对树模型计算精确的 SHAP 值force_plot画出单个样本的推拉力量。实际输出拒绝原因时取 SHAP 绝对值最大的三个特征翻译成业务语言比如「近两年逾期次数过多」「负债收入比过高」。另一个技巧是概率校准。树模型输出的概率往往偏极端可以用 Platt Scaling 或 Isotonic Regression 校准让预测概率更接近真实违约率。from sklearn.calibration import CalibratedClassifierCV calibrated CalibratedClassifierCV(model_lgb, methodisotonic, cv5) calibrated.fit(X_train, y_train) y_prob_cal calibrated.predict_proba(X_test)[:, 1] print(校准后 AUC:, roc_auc_score(y_test, y_prob_cal))校准不改变排序所以 AUC 基本不变但概率更可靠。业务上如果要用概率算预期损失校准这一步不能省。我自己做这类项目最大的教训是别急着上复杂模型先把数据清洗和特征工程做扎实逻辑回归的 AUC 往往已经够用。很多翻车不是因为模型不够强而是因为标签定义不清、字段泄露、评估指标选错。每次拿到新数据先花半天做字段核对和分布检查比后面调参三天都值。希望帮到你。本文还有配套的精品资源点击获取