资讯详情

资讯详情

随机森林实战:贷款违约预测项目源码全拆解

简介基于随机森林算法的贷款违约预测模型研究项目经导师指导并认可评审分达98分属于高分实战成果主要面向计算机相关专业学生及需要项目实战练习的学习者同样可用于课程设计、期末大作业等场景。压缩包仅5.8MB共12个文件主要包括4个CSV数据文件、2个Python脚本、4个INI配置文件及1个XLS数据表覆盖训练数据、算法实现与参数配置可支撑从数据探索、特征工程到模型评估的完整流程。附带Spyder项目配置方便直接打开调试复现。目前已有74人学习下载是具有较高参考价值的机器学习实战项目可为毕业论文、课设或简历项目提供可复用的算法框架与代码细节。1. 从随机森林到贷款违约预测一份 98 分项目源码的完整拆解贷款风控要做的核心事情是在放款前回答一个问题这个人未来两年还不还得上随机森林不是这个场景里最新的模型却是实战里最扛折腾的那一个——它不需要海量数据和昂贵显卡对缺失值和特征工程也相对宽容数据稍微脏一点、分布偏一点依然能给你一个能用的基准结果。这份项目源码就是用 Kaggle 经典的 Give Me Some Credit 数据集把 EDA、缺失处理、随机森林建模、基准输出和结果解读走了一条完整流程最终在导师评审时拿到了 98 分。适合四类人要交课程设计或期末大作业的计算机专业学生、刚入门机器学习想找一个完整训练流程做参考的开发者、想在金融风控场景里快速验证随机森林效果的从业者以及想复现一个“能跑、能解释、能答辩”的违约预测模型的学习者。核心文件就两个 Python 脚本加一份基准 CSV没有任何重框架依赖读起来不累改起来也不费力。2. 数据与预处理把 15 万条流水转成可直接训练的 11 个特征2.1 字段语义和脏数据来源“Give Me Some Credit”是 Kaggle 上非常经典的竞赛数据特点是样本量大、字段少特别适合拿来当课程设计素材你不需要花大量时间在复杂的多表拼接上。训练集大约有 15 万条记录每条记录是一个借款人的历史快照目标列只有一列SeriousDlqin2yrs表示这个人是否在接下来两年内出现 90 天以上的严重逾期1 代表违约0 代表正常履约正样本占比大约只有 7%。这意味着你面对的不是普通二分类而是一个典型的类别不平衡问题。在动手写模型之前必须先弄清楚每个字段的真正含义。这套数据集里的 11 个特征个个有讲究我拆这份源码时第一感受是data analysis.py里花时间最多的不是在构造新特征而是在清洗异常值。下面这张表是我把项目涉及的字段整理出的语义说明和处理建议字段名含义类型处理建议SeriousDlqin2yrs两年内是否严重逾期目标列0/1无需处理RevolvingUtilizationOfUnsecuredLines循环信用卡额度使用率浮点裁剪极端值age借款人年龄整数0 值置为缺失后填充NumberOfTime30-59DaysPastDueNotWorse逾期 30-59 天次数整数上限裁剪DebtRatio月度债务支出/收入比浮点保留原值MonthlyIncome月收入浮点中位数填充NumberOfOpenCreditLinesAndLoans未偿还信贷笔数整数直接使用NumberOfTimes90DaysLate逾期 90 天以上次数整数上限裁剪NumberRealEstateLoansOrLines房产贷款笔数整数直接使用NumberOfTime60-89DaysPastDueNotWorse逾期 60-89 天次数整数上限裁剪NumberOfDependents家属人数整数众数填充字段语义这部分最容易犯的错是“望文生义”。比如NumberOfTimes90DaysLate字面很简单但原始数据里有人取值是 96 次这在真实信用卡生命周期里基本不可能出现大概率是录入错误或者系统重复计算。再比如RevolvingUtilizationOfUnsecuredLines这个字段表示额度使用率理论上应该在 0-1 之间但原始数据里出现了 1.5、2.5 这种超额使用的情况。这些极端值如果不处理随机森林虽然不像线性模型那样敏感但树的分裂点选取会被这些离群值带偏最终影响的是概率输出的排序能力。2.2 清洗流水线的四步实现data analysis.py扮演的角色是一个标准的预处理管道读入原始 CSV做缺失值统计填充策略异常值裁剪输出一个干净的中间数据集。我按源码思路把它简化为一个可独立运行的清洗脚本代码不长但每一步都有数据依据import pandas as pd import numpy as np df pd.read_csv(Give me some credit/cs-training.csv, index_col0) # 第一步统计缺失比例决定填充策略 missing_ratio df.isnull().mean() print(missing_ratio[missing_ratio 0]) # 第二步MonthlyIncome 右偏明显用中位数填充而不是均值 df[MonthlyIncome] df[MonthlyIncome].fillna(df[MonthlyIncome].median()) # 第三步NumberOfDependents 是计数变量用众数填充 df[NumberOfDependents] df[NumberOfDependents].fillna( df[NumberOfDependents].mode()[0] ) # 第四步age 为 0 属于脏数据置为缺失后统一用中位数 df.loc[df[age] 0, age] np.nan df[age] df[age].fillna(df[age].median()) # 第五步三个逾期次数特征做上限裁剪超过 20 一律压到 20 for col in [NumberOfTime30-59DaysPastDueNotWorse, NumberOfTimes90DaysLate, NumberOfTime60-89DaysPastDueNotWorse]: df[col] df[col].clip(upper20) # 第六步信用卡使用率裁剪到 1.5防止异常值扰动分裂点 df[RevolvingUtilizationOfUnsecuredLines] df[ RevolvingUtilizationOfUnsecuredLines ].clip(upper1.5)逻辑说明MonthlyIncome是典型的右偏连续变量少数超高收入者会把均值拉得很高如果拿均值去填空缺相当于给原本收入偏低的人凭空补了一截收入模型学到的“收入-违约”关系就会失真所以这里必须选中位数。NumberOfDependents是离散计数变量众数最能代表多数家庭的结构直接填 0 或均值都会改变分布形状。age的 0 值不是合法年龄必须先替换成np.nan再进填充逻辑否则模型会学到一个“年龄为 0 的人违约率异常高”的虚假规律。clip(upper20)做的是极值压缩保留异常值作为“有风险信号”这一存在事实但不让它的具体数值参与树分裂。参数说明裁剪上限 20 和 1.5 不是拍脑袋定的。我建议动手前先跑一下df[col].quantile(0.99)和value_counts()看看 99 分位在哪里、极端分布长什么样再结合业务逻辑确定边界。逾期次数超过 20 次在风控上已经没有区分度压平是安全的。你如果把上限改成 10 或者 30AUC 会有小幅波动但不会翻车这一点你在课程设计答辩时可以作为“参数敏感性分析”的内容来讲反而加分。特征构造这一步源码里做得非常克制基本没有添新列。我的个人经验是随机森林对单一特征的非线性关系不敏感你手动构造age乘以NumberOfOpenCreditLinesAndLoans这种交叉项AUC 提升非常有限。但如果你想让报告有亮点不妨加一个简单的收入稳定性特征比如MonthlyIncome / (DebtRatio 1e-6)然后观察它在新一轮特征重要性里的排名。这个动作成本很低却能让答辩时多一个可讲的故事。注意源码包里的.DS_Store是 macOS 自动生成的文件与项目逻辑无关直接忽略.spyproject是 Spyder IDE 的工程配置换到 PyCharm 或 VS Code 时也不需要拷贝。真正要关心的只有data analysis.py、data model.py和rf_benchmark.csv这三个文件。3. 随机森林建模300 棵树、均衡权重与基准输出解读3.1 为什么是随机森林随机森林在贷款违约预测里几乎是默认基准模型它的原理可以用六个字概括袋装、随机、投票。训练时从原始数据里有放回地抽取多份子样本每份子样本独立训练一棵决策树每棵树的每个分裂点只随机选取一部分特征做候选最后把多棵树的预测结果投票汇总。这种“样本随机 特征随机”的双随机结构让它在保持低偏差的同时显著降低方差在 15 万样本这种量级上不太容易过拟合。面对只有 7% 正样本的类别不平衡数据随机森林相比逻辑回归还有一个实际优势逻辑回归要对类别不平衡做专门处理要么过采样、要么欠采样、要么手动调样本权重每一步都会引入新的超参数而随机森林只需要声明class_weightbalanced框架会自动根据类别频率给少数类更高的惩罚权重。这一点对初学者非常友好也是我习惯先拿随机森林做 first baseline 的根本原因你的项目里选它做核心模型方向是对的。3.2 训练脚本与基准结构data model.py做三件事把清洗后的数据切分成训练集和验证集在训练集上拟合随机森林输出预测概率和评估指标并写入rf_benchmark.csv。下面这段代码的流程和源码一致我在注释里把每个关键选择解释清楚import pandas as pd from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import (roc_auc_score, classification_report, confusion_matrix) X df.drop(SeriousDlqin2yrs, axis1) y df[SeriousDlqin2yrs] # 按正负样本比例分层切分固定随机种子保证可复现 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) # 核心模型300 棵树最大深度 10叶子最小样本数 5 rf RandomForestClassifier( n_estimators300, max_depth10, min_samples_leaf5, class_weightbalanced, random_state42, n_jobs-1 ) rf.fit(X_train, y_train) # 概率输出比类别输出更重要 y_prob rf.predict_proba(X_test)[:, 1] y_pred rf.predict(X_test) print(AUC:, roc_auc_score(y_test, y_prob)) print(classification_report(y_test, y_pred)) print(confusion_matrix(y_test, y_pred)) # 保存基准后续所有调参动作都以此为锚点 pd.DataFrame({y_true: y_test.reset_index(dropTrue), y_prob: y_prob}).to_csv(rf_benchmark.csv, indexFalse)逻辑说明stratifyy不是可选项它是必选项。类别不平衡数据如果不做分层切分验证集里的正样本比例可能被切到 3% 甚至更低导致 AUC 被严重低估你需要看到一份符合真实分布的测试集来处理标志。random_state42的意义常被忽略没有它同一份代码在不同时间跑出来的结果对不上报告里的数字没法复核。在金融场景里模型结果必须能在任何一台机器上复现固定随机种子是基本功。n_jobs-1是让随机森林占满所有 CPU 核心300 棵树在 15 万样本上通常几十秒就能训完。参数说明class_weightbalanced的作用是让少数类样本在计算分裂质量时获得更大权重本质是调整损失函数对少数类错分的惩罚力度。max_depth和min_samples_leaf是这组参数里最重要的两个正则化项违约数据本身噪声较多树太深容易把边界刻画到极端样本上叶子样本数太少又会出现“一叶知秋”的过拟合。如果训练速度太慢优先把n_estimators降到 200而不是放宽max_depth。rf_benchmark.csv这份文件是项目的“证据”。它保存的是真实标签和预测概率而不是最终的 0/1 类别。很多初学者会问为什么不直接存y_pred这是整份源码里最值得学习的一个设计因为模型输出的 0.7、0.3 这类概率只有在和具体业务阈值结合时才能变成决策。阈值 0.5 只是默认值不是最优值保存概率就保留了后续调阈值的空间。答辩时如果被问到“为什么存概率”你就回答“阈值属于业务决策不属于模型决策”这是一个能让评分老师认可的回答。3.3 评估指标的选取逻辑在贷款违约预测里AUC 是最核心的指标因为它只看模型对正负样本的排序能力不依赖具体阈值。roc_auc_score在 0.85 以上就代表模型有不错的区分度了。但光看 AUC 不够分类报告里的精确率、召回率、F1 分数要在特定阈值下才有意义这也是源码里把classification_report和confusion_matrix同时打印的原因。如果你在报告里只放一个准确率 93%评委老师一眼就会看出你忽略了 7% 正样本的事实这个坑下面再展开讲。数据做一个补充性指标 KS 值用来衡量模型对好坏客户的区分距离它和 AUC 正相关但在风控领域更直观。4. 避坑指南随机森林在违约预测里最容易翻车的四个场景4.1 忽略类别不平衡模型变“全判负机器”现象模型跑完AUC 0.85 看起来不错但分类报告里精确率很高、召回率只有 0准确率停留在 93% 左右混淆矩阵里正样本一个都没抓出来。原因随机森林默认假设类别分布是均衡的在只有 7% 正样本的数据里最优策略就是全部判负因为全猜错 7% 的违约客户也能拿到 93% 的准确率。模型没有“错”是评估方式和默认参数共同导致的。解决在RandomForestClassifier里设置class_weightbalanced让少数类错分的代价变大。如果还是不理想就手动调阈值把预测概率按 0.2、0.3、0.4 分别尝试计算每种阈值下的精确率和召回率选择一个业务上能接受的平衡点。这个动作放答辩 PPT 里是亮点不是败笔。4.2 缺失值填充一刀切右偏字段被均值害惨现象MonthlyIncome缺失比例大约两成有人图省事直接fillna(df[MonthlyIncome].mean())结果发现 AUC 比直接删掉缺失行的版本还低零点零几。原因月收入是右偏分布少数高收入者把均值拉得很高拿均值填出来的收入对缺失群体来说普遍虚高。最要命的是这种虚高只发生在缺失群体内部模型学到的“收入-违约”关系在这部分样本上是错的。解决对这种右偏连续变量一律用中位数填充源码里就是这么处理的。判断方法很简单写一行df[MonthlyIncome].skew()看到偏度大于 1 就直接走中位数。如果不想手写可以用sklearn.impute.SimpleImputer(strategymedian)。4.3 不固定随机种子结果变成玄学现象昨天跑出 AUC 0.851今天没改一行代码重跑变成 0.843数据集完全没动只重新执行了一次脚本。原因train_test_split默认随机切分随机森林内部的袋装采样也依赖随机数生成器。没有固定random_state每次运行天然不同。这不是 bug但会让课程设计报告里的数字失去可复核性。解决养成本地起一个常量SEED 42在所有可能引入随机性的地方都传进去包括train_test_split、RandomForestClassifier、GridSearchCV。后续调参只改一处别人复现你的代码也能得到相同结果。4.4 极端值不裁剪树的分裂点被毫无意义的 96 次带偏现象NumberOfTimes90DaysLate字段最大值为 96特征重要性排名冲到前三但你单独看这些极端样本它们大多是录入错误不是真实的严重逾期者。原因随机森林对极端值并非完全免疫。树在找分裂阈值时会把 96 这样的极端值当作一个合法候选为了照顾这个奇异点树边界会被拉到一个没有业务意义的位置。解决对这类计数特征做上限裁剪比如clip(upper20)或者直接按 0、1、2、3、4、5 分箱。裁剪前先看value_counts()和 99 分位数不要凭感觉定边界。你已经跑过一版完整模型之后回到这一步改改边界再重新训练对比前后特征重要性排名能很直观地看到极端值的影响。提示以上四个问题在课程设计答辩里几乎是必问内容。把这四件事的处理过程写进 README你的项目实操感会明显强于那些只贴一个训练代码的作业。5. 进阶特征重要性与 GridSearchCV 调参的实践边界5.1 特征重要性从模型内部反推业务逻辑随机森林最容易被低估的能力是feature_importances_这个属性。它能输出每个特征在整片森林分裂过程中的贡献度对课程设计来说这是“模型不是黑匣子”这件事最好的实证材料。提取方法非常简单# 用训练好的 rf 模型直接提取特征重要性 importance pd.DataFrame({ feature: X_train.columns, importance: rf.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance.head(8))逻辑说明这里的importance是基于基尼杂质减少量累计出来的数值越大代表该特征在分裂时贡献的信息越多。在信用风险数据上排序靠前的通常是RevolvingUtilizationOfUnsecuredLines、NumberOfTimes90DaysLate、age之后才是DebtRatio和MonthlyIncome。这个顺序和业务直觉对得上一个额度几乎刷爆的人比一个借了很多笔的人更容易违约历史上有过 90 天以上严重逾期的人比收入低的人风险信号更强烈。你拿这个结果去答辩评委不会觉得你在背稿子。参数说明输出的是归一化后的 0-1 数值所有特征加总为 1。判断标准很简单如果某个特征的重要性低于 0.02可以考虑删掉它再重跑一版看看 AUC 有没有明显下滑。如果性能几乎没变说明它确实是噪声。进阶一点的做法是用sklearn.inspection.permutation_importance做扰动验证它比基尼重要性更稳定但在 15 万样本上会多跑几十秒不是每个项目都需要。5.2 GridSearchCV 搜索策略不要盲目穷举随机森林的超参数空间很大n_estimators、max_depth、min_samples_leaf、max_features每一项都有可调范围直接穷举会让训练时间爆炸。常见做法是先做粗搜锁定合理区间再在小区间内细搜。下面是我在这个项目上常用的一组搜索配置兼顾了时间成本和效果from sklearn.model_selection import GridSearchCV param_grid { n_estimators: [200, 300, 500], max_depth: [8, 10, 12], min_samples_leaf: [3, 5, 8], max_features: [0.6, 0.8, sqrt] } grid GridSearchCV( RandomForestClassifier( class_weightbalanced, random_state42, n_jobs-1 ), param_grid, cv5, scoringroc_auc, n_jobs-1, verbose1 ) grid.fit(X_train, y_train) print(grid.best_params_) print(grid.best_score_)逻辑说明cv5是把训练集再切成五份做交叉验证这样调参结论不依赖单一切分结果稳定性比单次 train_test_split 更强scoringroc_auc是这里的关键默认的 accuracy 在 7% 正样本的场景下会给出一个虚高的分数让你误以为模型很好。max_features同时支持浮点比例和sqrt这种内置策略浮点值适合特征数不多的情况sqrt是随机森林默认选项两者都可以保留在搜索空间里。参数说明GridSearchCV会对param_grid里的组合做笛卡尔积搜索这个组合有 3×3×3×381 种每种跑 5 折交叉验证总耗时在一般笔记本上可能接近半小时批量设置没什么问题。如果时间紧张直接用RandomizedSearchCV替代设n_iter20效果几乎一样时间能省三分之一。调参结果出来后把最优参数重新拟合到完整训练集上再输出一次rf_benchmark.csv和原始基准对比看 AUC 到底提升了多少。我见过太多人调完参不重新落盘最后报告里选用的还是旧模型的数字这类“结果对不上”的问题在答辩现场非常尴尬。调参还有个边界要认清随机森林的max_depth和min_samples_leaf对 AUC 的影响通常是先升后平找到的是一个平缓区域而不是尖锐峰值。所以不要指望调参带来 0.02 以上的提升稳定提升 0.003-0.005 就已经是一个合理结果。如果你发现调参后 AUC 暴涨先怀疑数据泄漏而不是庆幸自己拿到了超参数。6. 让模型真正上线阈值校准与模型保存的最后一公里训练完成不等于模型可以交付。在贷款违约预测场景里随机森林输出的是一堆概率值只有经过阈值校准才能变成实际的审批决策依据。0.5 这个默认阈值在类别不平衡数据上通常不是最优解因为 7% 的正样本意味着你的违约概率整体偏低模型输出的 0.3 可能已经对应了一个风险很高的客户。我习惯在模型跑通后做三件事第一画出 ROC 曲线确认 AUC 不是虚高第二遍历 0.1 到 0.7 之间的阈值找到精确率和召回率的平衡点第三用joblib保存最终模型避免每次重训。下面这段阈值搜索代码就是我在这类项目上的固定操作import numpy as np import pandas as pd from sklearn.metrics import precision_recall_curve, f1_score # 读取基准输出遍历阈值寻找最优决策点 df_bench pd.read_csv(rf_benchmark.csv) precision, recall, thresholds precision_recall_curve( df_bench[y_true], df_bench[y_prob] ) best_threshold 0.5 best_f1 0 for t in thresholds[::20]: y_tmp (df_bench[y_prob] t).astype(int) f1 f1_score(df_bench[y_true], y_tmp) if f1 best_f1: best_f1 f1 best_threshold t print(Best threshold:, best_threshold) print(Best F1:, best_f1)这段代码不修改任何模型参数只调整最终判决的门槛找到 F1 最大的那个阈值。在类似 Give Me Some Credit 这种数据上最优阈值往往落在 0.2-0.3 区间远低于默认的 0.5。这是因为精确率和召回率在低阈值区间更容易达到平衡而风控业务更看重“不要漏掉坏客户”哪怕会多误伤几个好客户。从那以后我每次拿到这类风控项目都强制自己走一遍固定流程先固定SEED、再做类别平衡、输出概率而不是标签、最后调阈值。这四个动作做全项目基本不会翻车报告和代码也经得起追问。这份源码把前面三步都替你做好了阈值校准这一步留给你自己动手把它补上你的项目才算真正闭环。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →