ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于随机森林的贷款违约预测实战:调参、评估与避坑

基于随机森林的贷款违约预测实战:调参、评估与避坑 简介基于随机森林算法的贷款违约预测模型研究项目面向毕业设计、期末大作业与课程设计等场景提供完整可运行的Python源码与配套实验数据集。项目覆盖数据探索、特征分析、缺失值处理随机森林、决策树、逻辑回归、提升树等多模型训练以及贷款违约概率预测的完整流程同时附带特征重要性、学习曲线、抽样叶节点等可视化图表便于从特征筛选、模型调参与分类效果对比等多个角度理解信贷风控建模过程。压缩包共22个文件其中7个py脚本为模型训练与预测主体11个png为分析图表2个csv为建模数据1个xls为数据字典另有README说明文档整体仅6.16MB部署轻量便捷。目前已有143人学习/下载代码经严格调试且注释详细可直接复现借助数据字典和可视化图表也可快速修改特征或尝试不同模型作为课程设计、毕业设计或期末大作业的参考实现。1. 贷款违约预测随机森林为什么比单棵决策树稳得多随机森林在贷款违约预测里几乎成了默认首选不是因为它“听起来高级”而是它在表格数据上真的比单棵决策树稳、比逻辑回归更能吃非线性关系。这个项目就是一套基于随机森林的完整贷款违约预测源码训练数据用的是经典的 cs-training.csv 和 cs-test.csv跑通之后你能同时拿到决策树、随机森林、逻辑回归、Boosting 四套模型的结果和 AUC 对比图写毕业设计或者期末大作业的模型对比部分时素材是现成的。适合的人群很明确准备毕设但还没定题的学生、课程设计需要“能跑能出图”项目的开发者以及想搞清楚随机森林和决策树到底差在哪的初学者。代码注释写得很细新手按 README 走一遍就能复现老手可以直接替换特征工程部分做自己的风控数据实验。2. 数据先说话读懂 cs-training.csv 里的违约样本与字段分布2.1 先看 Data Dictionary.xls再谈建模打开项目里的 Data Dictionary.xls你会发现它定义的字段比一般毕设项目完整得多。目标列是 SeriousDlqin2yrs含义是“两年内是否出现严重逾期”这是典型的二分类违约标签。其余特征包括 RevolvingUtilizationOfUnsecuredLines无担保循环额度使用率、age、DebtRatio负债率、MonthlyIncome月收入、NumberOfOpenCreditLinesAndLoans开放信贷笔数以及几组逾期次数字段。import pandas as pd train_df pd.read_csv(cs-training.csv) print(train_df.info()) print(train_df.head())这段代码是任何一次建模前我必跑的“体检”先用 info() 看每列的缺失量和数据类型再用 head() 确认列名是否和 Data Dictionary.xls 一致。这个项目里 MonthlyIncome 缺失率大约 20%NumberOfDependents 也有零星缺失两列都建议单独处理。注意这里有个训练集和测试集的差别cs-training.csv 里带目标列 SeriousDlqin2yrscs-test.csv 没有后面 predict.py 的逻辑就是围绕这个差异设计的。正负样本比例是另一个关键点。数据里违约样本占比大概在 7% 左右属于典型的不均衡分类场景。如果你拿 accuracy 当核心指标模型全预测“不违约”也能拿到 93% 的“高分”但这个模型在风控场景里毫无价值。所以项目里画了 percentage.png 来直观展示占比这就是为什么要用 AUC、K-S、recall 而不是准确率来评估。2.2 跑一遍 data_analysis.py可视化占比与特征依赖项目根目录下的 data_analysis.py 就是干这件事的。建议在虚拟环境里执行先装依赖再跑脚本pip install pandas numpy matplotlib seaborn scikit-learn python data_analysis.py脚本运行后会在 imgs 目录下生成 percentage.png 和 dependency.png前者是违约与正常样本的占比柱状图后者是特征相关性热力图。dependency.png 值得多看两眼它帮你快速定位哪些特征之间高度相关——比如 NumberOfTime30-59DaysPastDueNotWorse 和 NumberOfTimes90DaysLate 都是逾期次数字段天然存在相关性进随机森林时会影响特征重要性排序的解读。这个脚本本身也是学习材料。它从读取 CSV、检查缺失、统计分布到输出图片每一步都是标准 EDA 流程的骨架。如果你要把这份项目改成自己的毕设数据探索部分直接在这个脚本上扩展就行比如增加特征分位数统计、对月收入做对数变换、把逾期次数做分箱处理。EDA 做完心里有底了再进建模环节。2.3 缺失值填充MonthlyIncome 的补法决定了模型上限MonthlyIncome 是连续型变量分布右偏极端高收入样本会把均值拉得很高。常见做法是取训练集中该列的中位数填充而不是用均值或者直接用 0 填。用 0 填充的危害很直接DebtRatio 是负债率分母是月收入收入填成 0 会让这部分样本的负债率变成无穷大或在数据预处理阶段被异常值规则清洗掉等于白白丢了一批样本。income_median train_df[MonthlyIncome].median() train_df[MonthlyIncome] train_df[MonthlyIncome].fillna(income_median) train_df[NumberOfDependents] train_df[NumberOfDependents].fillna( train_df[NumberOfDependents].median() ) print(train_df.isnull().sum())这里我用 fillna() 配合 median()因为中位数对异常值不敏感比 mean() 稳健。NumberOfDependents 的缺失量很小同样用中位数补不影响大局。补完之后再用 isnull().sum() 检查一遍确保没有残留缺失值再进模型。如果你用了 GridSearchCV 或交叉验证记住填充操作必须放在交叉验证流程内部用每一折的训练数据去算中位数再用这个中位数去填该折的验证集。直接在全量数据上填充再切分会让验证集“看见”训练集的信息这在后面第 5 章会展开讲属于最隐蔽的翻车点之一。3. 四套模型逐一跑通决策树、随机森林、逻辑回归、Boosting3.1 为什么项目把随机森林作为主线逻辑回归在信贷评分卡里是传统标配因为它可解释性强能输出每个特征的系数方便生成打分卡。但它的表达能力有限特征与目标之间如果存在非线性关系比如年龄与违约风险呈现 U 型分布逻辑回归需要手动构造交叉项才能拟合而随机森林不需要你手动做这些。随机森林的原理是 Bagging 加特征随机选择对训练集做有放回抽样每棵树只从全部特征里随机选一部分做分裂。这样做的好处是每棵树之间的相关性被刻意压低把单棵高方差、容易过拟合的决策树通过投票平均变成低方差模型。这也是随机森林和决策树区别最核心的一条决策树深了必然过拟合随机森林通过多树平均把这个缺陷平滑掉了。项目里同时保留了 train_use_dtc.py、train_use_rfc.py、train_use_lr.py、train_use_boosting.py 四个训练脚本正好凑成论文里的模型对比表。rfc.png 和 dtc.png 分别是随机森林和单棵决策树的学习曲线放在论文里能直接说明“单棵树在高深度下训练集 AUC 很高、测试集 AUC 掉得厉害随机森林则更平缓”。3.2 train_use_rfc.py随机森林训练与关键参数打开 train_use_rfc.py核心结构大致是这个思路from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split X train_df.drop(columns[SeriousDlqin2yrs]) y train_df[SeriousDlqin2yrs] X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, random_state42, stratifyy ) clf RandomForestClassifier( n_estimators100, max_depth10, min_samples_leaf5, class_weightbalanced, random_state42, n_jobs-1, ) clf.fit(X_train, y_train) importance pd.Series(clf.feature_importances_, indexX.columns).sort_values(ascendingFalse) print(importance)几个参数我展开说一下。n_estimators100 是该项目给出的起步值再往上加到 300 收益递减但训练时间线性增加。max_depth10 限制每棵树的最大深度防止单棵树把训练集背下来。min_samples_leaf5 强制每个叶子节点至少要有 5 个样本也能起到剪枝作用。class_weightbalanced 是按类别频率自动加权违约样本权重约为正常样本的 15 倍相当于在损失函数里放大了少数类的信号比人工调权重省事。n_jobs-1 表示用全部 CPU 核并行训练对 n_estimators100 这种中等规模任务能把时间压到十几秒。训练完打印特征重要性你会看到逾期次数字段通常排在前列。这时候结合业务想一下NumberOfTimes90DaysLate 这类字段与“两年内违约”在业务上高度相关模型优先用它分裂是合理的但也可能是目标泄漏的隐患。怎么判断第 5 章会给具体信号。3.3 决策树与逻辑回归脚本的用途不只是“凑数”很多做毕设的人把决策树脚本当配角训完不管了。实际上 dtc_learn_curve.py 里画出的学习曲线正是回答“随机森林为什么比单棵树好”的实证素材。你可以用 train_use_dtc.py 训一棵深度为 20 的决策树观察它在训练集上几乎完美拟合在验证集上表现远不如随机森林这就是论文里讨论过拟合最好的截图证据。train_use_lr.py 这一路也有价值逻辑回归在特征标准化后跑能得到每个特征的系数和显著性这在信贷风控里叫评分卡转换。很多导师会追问“随机森林可解释性差怎么办”项目里同时含逻辑回归模型的对比正好给了你一个回应的抓手——解释性部分交逻辑回归预测精度部分交随机森林两者对照着写模型对比章节就有血有肉了。记住在论文里对比时统一用一个评估指标比如都在同一验证集上算 AUC别一手用 accuracy、一手用 F1那样对比没有说服力。4. 调参实战从 max_depth 到 sample_leaf读图比看日志快4.1 max_depth.png 告诉你的过拟合信号项目 imgs 目录里的 max_depth.png 是一张横轴为深度、纵轴为 AUC 的折线图一般会同时画出训练集和验证集两条线。当你看到训练集 AUC 一路走高验证集 AUC 先升后降那个“后降”的拐点就是过拟合的开端。我一般拿到这张图先找拐点。比如 max_depth8 时训练集和验证集都涨到 max_depth12 时训练集继续涨、验证集开始平或下行那就把深度定在 10 左右。不要贪心往 15、20 去试随机森林里单棵树越深整体模型的方差越大最后平均出来的结果也稳不到哪去。注意 max_depthNone 会让树长到叶子纯为止训练集 AUC 能接近 1但验证集通常很难看。你在 max_depth.png 里看到训练验证差距拉开的形状就是它对“没过拟合的树不该长多深”给出的直接答案。4.2 sample_leaf.png叶子节点样本数的下限含义sample_leaf.png 对应的是 min_samples_leaf 这个参数的扫描结果。它的作用原理是叶子节点样本数设得太小比如 1每棵树都在捕捉极个别样本的特点方差变大设得太大比如 50每棵树都长不动模型整体偏向欠拟合。在这个数据集上通常 5 到 20 之间是比较稳的区间。看图时同样盯验证集曲线选择保持验证集 AUC 高且训练验证差距小的那个点。min_samples_leaf 配合 max_depth 一起调比单独调一个效果好因为深度限制和叶子下限本质上是两种不同的剪枝手段同时约束能让树在纵向和横向同时收缩。4.3 用 GridSearchCV 自动搜参别手动一个个试手动遍历几百组参数组合会把人试麻。我一般把候选范围压缩后交给 GridSearchCV像这样from sklearn.model_selection import GridSearchCV from sklearn.ensemble import RandomForestClassifier base_clf RandomForestClassifier( n_estimators200, class_weightbalanced, n_jobs-1, random_state42, ) param_grid { max_depth: [8, 10, 12, None], min_samples_leaf: [3, 5, 10], max_features: [sqrt, log2, 0.6], } gs GridSearchCV( base_clf, param_grid, scoringroc_auc, cv5, verbose1, n_jobs-1, ) gs.fit(X_train, y_train) print(gs.best_params_) print(gs.best_score_)为什么要用 roc_auc 做 scoring 而不是 accuracy前面讲过这数据正负样本 1:15accuracy 对“全预测不违约”的方案给 93 分这个分数没有风控意义。roc_auc 对类别不均衡不敏感直接优化它是在往正确方向上使劲。max_features 这个参数容易被忽略它对随机森林的“随机性”贡献很大。取值 “sqrt” 表示每次分裂只看特征总数的平方根个“log2” 是取对数你还可以传 0.6 表示按 60% 比例随机抽取。项目默认用 sqrt效果通常够。这一组网格大概要跑几十个组合。如果你机器核数不多布尔参数和大量 n_estimators 组合别一块丢进去否则一次网格搜索能把午饭时间都搭进去。稳妥做法是先用默认参数把特征工程和数据集确定下来最后再开一次网格搜索定终版参数。5. 避坑笔记贷款违约预测里的四个翻车现场5.1 训练集 AUC 0.98验证集掉到 0.72先查数据泄漏现象随机森林在训练集上表现特别好一换验证集立刻崩。原因大概率是数据预处理阶段在全量数据上做了缺失值填充、标准化或异常值剔除再切分训练验证集验证集的信息提前流进了训练过程。解决把填充和标准化全部放进 Pipeline或者至少先 train_test_split 再分别对训练部分和验证部分执行同样的操作。这个项目里如果自己加特征工程务必注意 csv-training 和 cs-test 都不能混着算统计量。5.2 accuracy 0.93 但召回率几乎为 0被不均衡坑了现象模型报告写 accuracy0.93导师一看这个数字就觉得稳了但细看混淆矩阵发现违约样本被全部预测成了正常。原因这是正负样本 1:15 下不做任何处理、直接优化准确率的必然结果。模型发现“全部猜正常”就能拿到高分于是偷懒。解决评估指标切换到 roc_auc、K-S 和混淆矩阵并在随机森林里开启 class_weightbalanced必要时对少数类做 SMOTE 过采样。项目里给出 percentage.png 就是提醒你先看到比例再定策略。5.3 特征重要性里逾期次数字段排第一是线索还是泄漏现象NumberOfTimes90DaysLate 特征重要性得分遥遥领先高达 0.3 以上其他特征都在 0.1 以下。原因这个字段描述的是“过去 90 天逾期次数”而目标 SeriousDlqin2yrs 是“两年内是否严重违约”两者业务相关性极强。模型用这个字段获得高精度但它更像是对过去行为的重复描述而不是预测未来风险的新信息。解决不能直接删掉但论文里要单独讨论它的业务含义说明这个特征在实际风控中的可用性高低。也可以用这个字段做分箱后和目标的交叉分析看趋势是否符合业务直觉再决定保留还是降权。5.4 predict.py 报 ValueError: columns mismatch测试集列顺序不一致现象predict.py 跑起来直接报错说输入特征列和模型训练时的列对不上。原因cs-test.csv 比 cs-training.csv 多了一列 Id且原始列顺序不同。暴力读取直接预测sklearn 不会自动做列对齐。解决预测前手动选取特征列并 reindex保证列顺序与训练时一致。feature_cols list(X_train.columns) test_df_reindex test_df[feature_cols].copy() prob clf.predict_proba(test_df_reindex)[:, 1]这个脚本的思路就是把 Id 列单独提出来用于提交结果其余列一律按 feature_cols 顺序取。另外确认 test_df 里不要包含目标列因为测试集本来就没有目标如果手工拼接时多了一列同样会触发这个报错。5.5 树可视化失败graphviz 装不到位现象运行可视化代码报“graphviz not found”或“cannot find dot”。原因pip install graphviz 只是装了 Python 接口系统级的 graphviz 软件本体没装或者装了但没把路径加进 PATH。解决Windows 装 graphviz 的 Windows 安装包Linux 用 apt install graphviz装完在代码里设置 os.environ[PATH]把它加进系统环境变量然后再调用 export_graphviz 生成树图。这个坑几乎每个跑决策树项目的人都会踩一次遇上了别怀疑代码写错。6. 从评估到交付用 predict.py 输出提交文件并按 AUC 曲线选阈值项目调到这步随机森林参数基本定了。最后一段路是把模型用到 cs-test.csv 上生成可提交的预测文件同时选一个比 0.5 更合理的违约判定阈值。predict.py 的核心逻辑是先加载训练好的模型再对测试集做同样的预处理和列对齐最后输出两列Id 和违约概率。把概率输出而不是直接输出 0/1 类别是因为后续选阈值时可以做不同尝试不用每次重新跑模型。from sklearn.metrics import roc_curve import numpy as np fpr, tpr, thresholds roc_curve(y_val, clf.predict_proba(X_val)[:, 1]) j_score tpr - fpr best_idx np.argmax(j_score) best_threshold thresholds[best_idx] print(best threshold:, best_threshold)约登指数是 TPR 减去 FPR 的最大值点它不偏向多数类也不偏向少数类在这个不均衡场景下比默认 0.5 更靠谱。选好阈值后对测试集概率做二分类就能得到一份带业务语义的违约名单。我还习惯把模型持久化保存joblib.dump(clf, rfc_model.pkl)并通过 joblib.load 在 predict.py 里加载。这样训练和预测解耦毕设答辩演示时不用每次现场重新训练。这份项目从数据字典到学习曲线图再到四个训练脚本整个链路是闭环的。以前我做类似课题时图省事直接拿全量数据填充完再切分结果验证集 AUC 虚高答辩时被导师一个追问就露馅了。从那以后我每次做这类预测都强制走一遍数据体检、模型对比、AUC 选阈值这套流程才敢出结果。希望这份项目源码和这些踩坑笔记帮到你。本文还有配套的精品资源点击获取
返回列表