
简介面向计算机相关专业的毕业设计、课程大作业及金融风控实战学习者这是一份基于机器学习的贷中风险预测模型完整项目。代码均经本地编译调试且可运行曾获导师指导认可并取得98分高分配套文档说明与答辩PPT难度适中适合快速上手参考。压缩包共50个文件以19个Python源码脚本、11个CSV数据文件为主另含Jupyter Notebook案例演示、Word文档、Excel结果及答辩演示文稿整体仅10.83MB目录结构清晰便于按功能模块调用。内容源自2020年“江苏银行杯”金融大数据建模挑战赛初赛方案覆盖数据字段说明、特征工程、多模型对比随机森林、AdaBoost、XGBoost等及结果合并脚本可帮助理解贷中风险预测从预处理到建模评估的完整流程。目前已有62人浏览学习适合需要快速搭建高完成度项目或学习机器学习应用细节的学生与开发者。1. 贷中风险预测不是建模题是业务题先想清楚要预测什么贷中风险预测模型指的是客户已经拿到贷款、还在还款周期内时判断他未来会不会断供、坏账或额度滥用。很多准备毕业设计或求职项目的读者容易把它当成普通二分类练习拿一堆特征跑个随机森林就完事。但在真实信贷场景里难点从来不在算法而在标签定义、观察点设置、特征口径和上线验证。这篇笔记按我做过信贷风控项目的习惯把从建模框架、源码落地、文档答辩到避坑的完整路径讲一遍让你拿到“源码文档答辩PPT”这类项目包时不只看得懂代码还能讲清楚每个选择背后的理由。2. 贷中风险预测的建模框架样本、标签、特征与评估指标怎么定2.1 先分清楚贷前、贷中、贷后的边界为什么贷中模型不能照搬贷前评分卡信贷风控按资金流转周期分三段贷前是审批环节决定“给不给额度”贷中是放款后到结清前的存量管理决定“这个客户要不要降额、预警、催收”贷后是逾期后的催收处置决定“怎么把损失要回来”。标题里的“贷中风险预测模型”关注的就是第二段。贷前评分卡常用的申请资料特征年龄、收入、房产、征信查询次数在贷中阶段只能算是背景信息真正的强变量是客户拿到钱之后的行为。比如每月还款是否准时、额度使用率是否突然拉满、是否频繁更换绑定手机号、是否在其他平台借新还旧。这些行为特征在贷前根本不存在所以贷中模型需要的是“行为数据 时序窗口”而不是单纯拿申请资料堆评分卡。另一个关键差异是目标定义。贷前预测的是“这个人未来会不会变成坏客户”贷中预测则是“在某个观察点之后的一段时间内这个人会不会从当前状态滑向逾期”。所以样本是动态的同一个客户每个月都可能生成一条新样本模型需要定期重训。我在项目里一般会把样本按自然月切分每个客户每月取一条记录这样既能扩充样本量又贴合业务上月度风险重估的节奏。2.2 标签定义用滚动率而不是“是否逾期”一刀切很多新手一上来就把标签定义成“未来 30 天内是否逾期”这个做法在答辩时很容易被追问。因为贷中场景里短期逾期不一定等于坏账有人只是忘了还隔天就补上而有人表面上不逾期却同时从七八个平台上借贷资金链随时可能断裂。更稳的做法是用滚动率来定义坏客户。滚动率的意思是“处于某一逾期状态的人未来进入更严重逾期状态的比例”。常见做法是定义坏客户为观察点之后的 90 天或 120 天滚动窗口内出现过 M3逾期 90 天以上或进入核销。这样做的好处是标签有业务含义坏账预测才跟利润挂钩而不是机械地抓住一个“逾期 30 天”的短期状态。我一般会先把客群按观察点时点的逾期天数分成 C、M1、M2、M3 几档然后统计每个档位的滚动率。如果 M2 到 M3 的滚动率明显跃升比如从 20% 跳到 70%就把坏标签锚定在 M3。实际操作里一个简便标签是D 日在观察点观察点后 120 天内最大逾期天数 90 则标记为 1否则为 0。这个定义既简单又能过评审关键是你得能解释为什么选 90 天而不是 30 天。2.3 观察点与表现期时间窗口是贷中建模最容易翻车的地方观察点是你做预测的那个时点表现期是观察点之后用来判断好坏的时间长度。这两个概念直接决定样本怎么切也是最容易被人问住的地方。很多人拿历史数据一顿乱切把 2019 年到 2022 年的数据混在一起训练集和测试集之间都有未来信息模型的 AUC 高得离谱一上线就崩。正确做法是选一个固定的历史日期作为观察点比如 2022 年 6 月 30 日。在这个观察点上所有存续期客户生成一条样本特征只允许使用这个日期之前的数据。然后留出 120 天表现期到 2022 年 10 月 31 日去看每个客户的真实表现。这样整批样本只有一种观察点不存在时间重叠。如果样本量不够可以滚动切多个观察点。比如从 2022 年 1 月到 2022 年 6 月每个月末各设一个观察点每个观察点都用自己的表现期。这时必须保证训练集和验证集的观察点在时间上是前后关系比如用 2022 年前 4 个月做训练、5 月做验证、6 月做测试不能随机按客户 ID 分割。这是贷中模型和普通分类任务最大的区别乱分割等于在和时间对赌。2.4 特征工程还款行为、额度使用、多头借贷与外部数据贷中模型的特征可以分成五组我列个表方便你写文档时照着搭框架。特征组代表特征说明还款行为近 6 个月按期还款次数、逾期次数、提前还款次数反映还款意愿和能力额度使用近 1 个月额度使用率、近 3 个月平均使用率、峰值使用率使用率突然拉满往往是资金链紧张的信号账户行为绑卡次数、改绑手机次数、登录设备变化数异常变更往往意味着人已经不在原地址工作多头借贷近 6 个月征信查询次数、在其他平台借款数借新还旧是贷中的典型危险信号存量信息剩余本金、剩余期数、放款金额、利率用于计算 EAD 和 PD 的辅助变量特征构造要特别注意窗口长度。我一般跑三个窗口近 1 个月、近 3 个月、近 6 个月这样能捕捉短期异常和长期趋势。比如“近 1 个月使用率 95% 近 6 个月平均使用率 40%”就比单纯一个“使用率 70%”更能识别突然恶化。连续特征最好做缺失值填充和分位数裁剪。像额度使用率、查询次数这类长尾变量有极端值时树模型也能用但 LightGBM 在缺失值处理上虽然自带机制我仍然习惯先填 -1 或中位数避免线上特征缺失时行为不一致。还有一类是时序派生最近一次还款距今的天数、距离下一次还款日还剩几天这类“时间差”特征在贷中模型里比绝对值更稳定。2.5 评估指标AUC、KS 与 PSI 怎么配合使用答辩 PPT 里只放一个 AUC 是不够的。风控模型更看重 KS因为 KS 衡量的是模型把好人和坏人分到两个极端分布的能力。AUC 是全局排序能力KS 是最大分箱差异两个都看一般同类场景里 AUC 0.75 对应 KS 大约 0.35 到 0.45 算不错。PSI 不是模型训练指标而是变量稳定性指标。贷中模型上线后每个月都要算一次 PSI如果某个特征分布和训练时偏离太远模型就失效了。我习惯在建模时就计算每个特征的 PSI 作为稳定性筛选通常保留 PSI 0.1 的特征0.1 到 0.25 的需要观察大于 0.25 的直接剔除。这个习惯能让你少踩很多线上波动的大坑。另外贷中场景往往样本不平衡坏客户占比可能只有 2% 到 5%不能只看 Accuracy。重点看坏样本召回率Recall和坏样本精确率Precision的权衡以及提升度Lift。如果一个模型在坏样本占比 3% 的情况下能把最危险的 10% 客群中的坏客户浓度提高到 15% 以上这个模型就是有价值的。3. 从源码到可运行项目目录、数据流与核心训练脚本3.1 常见项目目录与数据流源码不是单文件拿到“源码文档说明答辩PPT”这种项目包第一件事不是读模型代码而是先看目录结构和数据流。常见做法是把项目拆成数据预处理、特征工程、模型训练、评估预测四个模块单独一个 train.py 从头跑到尾的项目只适合课程作业不适合答辩和实际落地。我会这样组织目录project/ ├── data/ # 原始数据与中间数据 │ ├── raw/ # 放贷、还款、征信等原始表 │ ├── features/ # 特征工程产出的中间表 │ └── samples/ # 最终建模样本与标签 ├── src/ │ ├── data_prepare.py # 数据清洗与样本切分 │ ├── feature_build.py # 特征构造 │ ├── train_model.py # 训练与调参 │ └── predict_score.py # 预测与分数输出 ├── scripts/ │ ├── run_all.sh # 一键执行脚本 │ └── eval_report.py # 生成评估报告 ├── docs/ │ ├── 项目文档.md │ └── 答辩PPT.pptx └── requirements.txt这种结构的好处是每个文件职责单一评审问“特征在哪里做的”时你能直接指到 feature_build.py。数据流一般是原始表 - 特征表 - 样本表 - 模型文件 - 预测结果。每一步都落盘中间结果可以复现这是给后期调参留的后悔药。3.2 核心训练脚本用 LightGBM 跑一个可复现的基线下面我给出一个去掉业务字段、只保留核心逻辑的训练脚本写法用的是 LightGBM 二分类。数据我模拟出三个特征使用率、还款天数差、多头查询次数。你实际替换成自己的特征主键和日期字段即可。# train_model.py import pandas as pd import lightgbm as lgb from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score, precision_recall_curve # 读取建模样本假设已经按观察点和表现期切好 df pd.read_csv(data/samples/model_samples.csv) # 特征列列表排除 ID、观察点日期和标签 feature_cols [ credit_usage_1m, # 近1个月额度使用率 repay_days_diff_mean, # 还款日与实际还款日平均间隔 inquiry_cnt_6m, # 近6个月征信查询次数 multi_loan_cnt, # 当前存量其他平台贷款笔数 ] X df[feature_cols] y df[bad_label] # 0/1 标签1 表示表现期内进入M3 # 按观察点切分假设 obs_date 越大越靠近未来 dates df[obs_date] train_mask dates 2022-04-30 val_mask (dates 2022-04-30) (dates 2022-05-31) test_mask dates 2022-05-31 dtrain lgb.Dataset(X[train_mask], labely[train_mask]) dval lgb.Dataset(X[val_mask], labely[val_mask]) params { objective: binary, metric: auc, learning_rate: 0.05, num_leaves: 31, max_depth: -1, min_data_in_leaf: 100, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 1, lambda_l2: 1.0, verbose: -1, } model lgb.train( params, dtrain, num_boost_round500, valid_sets[dval], callbacks[lgb.early_stopping(stopping_rounds50), lgb.log_evaluation(50)], ) # 测试集评估 y_prob model.predict(X[test_mask]) auc roc_auc_score(y[test_mask], y_prob) print(fTest AUC: {auc:.4f}) # 保存模型 model.save_model(models/lgb_baseline.txt) feature_imp pd.DataFrame( {feature: feature_cols, importance: model.feature_importance(gain)} ).sort_values(importance, ascendingFalse) feature_imp.to_csv(models/feature_importance.csv, indexFalse)逻辑说明脚本先按观察点切分而不是随机切分这是贷中项目与普通机器学习作业最大的不同。early_stopping 用验证集决定最佳迭代轮数防止过拟合。feature_importance 输出用 gain 类型的特征重要性它衡量的是特征在树分裂时带来的增益总和比默认的 split 次数更稳定尤其适合不平衡样本。参数说明learning_rate 0.05 配合 500 轮加早停是常规组合学习率越低模型越稳但训练越慢。num_leaves 控制在 31 附近防止树太复杂。min_data_in_leaf 设 100 是为了在大样本下避免叶节点样本太少贷中样本通常几十万起步这个值还可以提到 500。feature_fraction 和 bagging_fraction 都是随机性参数能抗过拟合线上应用时建议固定随机种子保证每次重训结果可复现。3.3 参数说明从树模型到概率输出的关键参数初学者习惯把参数照抄但贷中模型和 Kaggle 比赛场景不完全一样。比赛追求极限准确率风控追求稳定和可解释。我一般把参数分成两档基线档和调优档。基线档就是上面那组直接能跑到不错的水平。调优档才去动 num_leaves、min_data_in_leaf 和 lambda_l2。有一个容易被忽视的参数是 max_binLightGBM 默认 255。如果线上特征分布漂移不大保持默认就好。但如果特征里有大量长尾值把 max_bin 调小到 127 可以让模型对噪声更鲁棒代价是精度略降。反过来如果特征是经过等频分箱后的分数max_bin 设大一点能保留更多信息。预测输出时model.predict() 给出的是样本为正类的概率范围在 0 到 1 之间。但这个概率不能直接拿去当分数用因为它会随坏样本占比变化。实际项目里通常要做分数映射把概率映射到 300 到 900 分的区间这个我在第 6 章会讲。3.4 预测与落库线上使用时的接口写法模型训练完要能用起来答辩时最好展示这段代码能证明你不只会 train 还会 deploy。预测脚本按批次读入待评估客户的当月特征输出每个客户的风险概率和分数写回风险表。# predict_score.py import pandas as pd import lightgbm as lgb model lgb.Booster(model_filemodels/lgb_baseline.txt) # 读取本月待评估客群的特征表 features pd.read_csv(data/features/feature_current_month.csv) feature_cols [ credit_usage_1m, repay_days_diff_mean, inquiry_cnt_6m, multi_loan_cnt, ] # 预测并输出概率加分数 features[prob] model.predict(features[feature_cols]) features[score] (650 - 50 * (features[prob].rank() / len(features))).round(0) # 只输出需要关注的字段避免把客户敏感信息到处放 result features[[customer_id, obs_date, prob, score]] result.to_csv(data/predict/risk_result.csv, indexFalse)这段代码里有两个小心思分数用的是 rank 分位映射保证整体分数分布近似均匀而不是直接用概率线性缩放这样分数阈值更容易解释。另外输出字段刻意没有带原始特征避免中间层泄漏客户隐私。在实际生产里预测接口通常封装成函数输入当月特征 DataFrame输出风险分数然后由策略引擎决定是降额、调价还是进入催收队列。4. 文档说明与答辩PPT把黑匣子讲成评审听得懂的方案4.1 文档说明写什么从“做了什么”到“为什么这么做”项目包里附带文档说明目的是让导师或面试官在没跑代码之前就能看懂整个项目。很多人的文档写成了代码注释合集直接往里面贴代码块这是不对的。一份能过评审的贷中风险预测文档应该按“背景—数据—方法—实验—结论”的逻辑展开。我会建议文档按下面这个 Markdown 结构写# 贷中风险预测模型项目说明 ## 1. 项目背景与业务目标 - 存量客群风险上升需要提前识别高风险客户 - 目标在观察点后120天内识别进入M3的客户 ## 2. 数据说明 - 数据来源、时间范围、样本量 - 观察点与表现期定义 - 标签定义滚动率法 ## 3. 特征工程 - 特征列表与业务含义 - 缺失值处理与异常值截断 - 特征稳定性筛选PSI ## 4. 模型选型与训练 - 为什么选LightGBM表格型数据、训练快、自带缺失值处理 - 训练/验证/测试切分逻辑 - 模型参数表与调参记录 ## 5. 模型评估 - AUC / KS / 召回率 / 提升度 - 特征重要性分析 - 分箱稳定性分析 ## 6. 结论与后续 - 模型效果总结 - 上线监控方案重点是每节里都要有一句“为什么”。比如选 LightGBM你写“因为数据结构以数值型为主存在缺失值且需要快速迭代重训LightGBM 的经验风险下表现优于逻辑回归和随机森林”比写“业界常用”更有说服力。4.2 答辩PPT的页级结构十分钟讲完一个高分项目答辩 PPT 不需要 30 页10 到 12 页最合适。多了讲不完少了显得工作不够。我习惯按下面的页级结构做页码内容关键落脚点1封面项目名称、答辩人、指导老师标题要具体写“基于机器学习的贷中风险预测模型设计”2业务背景与痛点用一句话说清贷中风险为什么比贷前更棘手3数据概览与样本切分放数据量和观察点切分图证明你懂时间窗口4标签定义展示滚动率分析的表格这是专业度的分水岭5特征工程列特征分组表突出还款行为和多头借贷特征6模型结构图不用复杂画一个数据流即可7训练与调参放参数表和验证曲线8评估结果放 ROC、KS 曲线和提升度图9特征重要性放 gain importance 排序图10风险应对策略说明高分客户如何触发降额、预警11总结与展望讲模型局限性和后续迭代方向每一页只讲一个核心点PPT 是提示词不是文稿。页面上放图和大数字解释放在你嘴里。比如评估页只放 0.73 AUC、0.39 KS、10% 客群捕获 35% 坏客户这三个数然后展开讲每个数的业务含义。4.3 答辩现场高频问题与应答口径答辩时老师或面试官最爱追问的不是代码而是几个致命细节为什么坏客户定义是 M3观察点为什么选那个日期训练集和测试集怎么切的特征里有没有未来数据。你可以不把答案写进 PPT但文档里必须写清楚。我习惯用“业务技术”双层应答。比如问“为什么选 LightGBM”先答技术层面表格数据上梯度提升树效果好自带类别特征和缺失值处理再补业务层面信贷模型要追求稳定性和可解释性LightGBM 的特征重要性可以输出给策略同事评估。这样回答比只背参数强得多。还有一类陷阱问题如果线上负样本比例只有 3%你模型预测概率阈值怎么定。别直接说 0.5应该按精度的计算找召回率和精确率的平衡点比如取坏样本召回率前 10% 的分数作为预警阈值。这个应答能直接拉开和一般课程设计的差距。5. 常见问题与避坑样本不平衡、时间穿越和特征泄漏5.1 样本不平衡换评估指标比换采样方法更重要现象训练集坏样本只占 2%模型把所有人都预测成好客户AUC 看着还行但坏客户召回率几乎为 0。原因默认阈值 0.5 在不平衡数据下没有意义因为模型输出的概率整体偏低没人能超过 0.5。而很多人只看 Accuracy被 98% 的表面正确率骗了。解决先用 class_weight 给坏样本加权重或者用 scale_pos_weight 把正样本权重设为负样本量的比值。但这只是缓兵之计最关键是改用 AUC、KS、召回率评估并采用分数阈值调整而不是概率阈值直接切。我在项目里会在训练时对坏样本做 SMOTE 过采样但只在特征维度比较低时用特征太多时 SMOTE 容易制造噪声。5.2 时间穿越用未来信息训练出来的高性能是假象现象验证集和测试集 AUC 高达 0.9但模型上线后效果直线下降。原因最常见的问题是切分时没有按时间切随机切分导致同一个人在不同时期的表现同时出现在训练集和测试集里。或者特征里用了表现期之后才发生的还款记录。解决严格按观察点切分这是我在第 2 章强调过的事。特征表中所有字段必须加“截止到某个日期”的限定比如“近 1 个月额度使用率”必须用观察点当天的近 1 个月不能用整张表的近 1 个月。一个检查技巧是跑一个基线逻辑回归如果变量系数符号和业务常识相反比如查询次数越多风险越低那基本就是特征把未来信息带进去了。5.3 特征泄漏标签里的“已经逾期”漏进特征现象某几个特征的贡献度极高模型几乎靠一到两个特征就能区分好坏客户但一看特征内容发现它们实际上是标签本身。原因构造“是否逾期”类特征时把表现期内的逾期状态也算了进去。比如特征里头有个“当前逾期天数”客户在观察点已经逾期 30 天而标签定义是未来 120 天逾期 90 天以上这个特征和标签高度相关属于目标泄漏。解决把特征的取值窗口严格限定在观察点之前。动手写代码前先列一张表每个特征的起始日期、截止日期、计算窗口。观察点当天算不算我习惯观察点当天的数据可以用因为放款后的策略执行天然有时间差。表现期内的任何行为数据一律禁用。5.4 线上与线下的特征一致性现象线下测试效果不错线上预测时概率分布整体偏低或偏高导致阈值失效。原因最主要的是离线训练用的特征字段和线上实时计算的特征口径不一致。比如离线特征库里“还款距离今天数”用的是自然日线上实时算的时候用了工作日数值差一两天模型输出就变了。另一个常见原因是缺失值填充方式不一致离线用中位数填充线上没做填充直接传空值。解决把特征工程代码封装成一个函数训练时调用它处理历史数据预测时调用它处理当月数据保证逻辑完全一致。每次模型上线前跑一遍线上采集的特征样本和离线特征样本的 PSI超过 0.1 就要回头查口径差异。5.5 逾期标签的观察点没对齐现象建模时有的样本是 2022 年 6 月观察点有的是 2021 年 6 月混合在一起训练KS 波动非常大。原因不同观察点下的宏观环境不同。2021 年客群资质好2022 年整体风险上升混合在一起会引入时间异质性模型学到的是宏观趋势而不是个体风险差异。解决要么用单一观察点要么把观察点月份作为特征加进去或者干脆分群建模。我在做贷中项目时通常会把新老客户分开建模因为放款满 3 个月的客户才有足够的行为数据这种分群比硬混合更合理也能减少观察点不一致带来的偏差。6. 让模型真正能用概率转分数、模型监控与上线验证技巧6.1 概率到芝麻分数的映射单调性和可解释性优先模型输出的概率不能直接对外展示业务同事和监管都不接受“风险概率 0.37”这种说法。常见做法是映射到 300 到 900 分的标准分制。我常用的线性映射公式是score 650 - 50 * log(p / (1 - p)) / log(2)这个公式以 650 分为基准当概率为 50% 时分数 650概率每翻一倍分数减 50 分。映射后分数是单调递减的分数越高风险越低。要验证分数是否满足单调性可以按分数每 50 分一档统计实际坏账率画一条折线理论上折线必须严格下降。如果哪一档上升了说明特征分箱或模型拟合出了问题这就是我最后要强调的验证技巧。6.2 上线后的监控PSI 和数据漂移是贷中的救命草模型不是训练完就结束。上线后第一个月、第三个月、第六个月要分别计算分数分布 PSI 和关键特征 PSI。如果某个月的 PSI 超过 0.25必须检查当月客群是否发生结构性变化比如获客渠道变了或者外部征信政策调整导致查询次数整体下降。我自己的习惯是每月生成一份监控报告包含当月预测分数分布图、实际坏账率与预测值的对比、特征稳定性表。这里给一个最小可用的监控脚本片段import pandas as pd def calc_psi(expected, actual, bins10): expected_prob expected.value_counts(normalizeTrue).sort_index() actual_prob actual.value_counts(normalizeTrue).sort_index() # 按分数等频分箱后逐一计算 psi 0.0 for i, e in enumerate(expected_prob): a actual_prob.get(i, 0.0) 1e-6 psi (a - e) * np.log(a / e) return psi注意实际和期望必须是同一分箱规则不能各自自适应分箱否则 PSI 算出负数那是分箱没对齐不是分布稳定。6.3 我的收尾提醒经历过几次上线翻车后我最大的教训是贷中模型七分在数据切分三分在算法代码写得再花哨观察点和标签定义错一个后面全部白做。项目包里那份文档说明和答辩 PPT不是给你拿去背的而是用来倒逼你把每个决策想清楚。答辩前拿纸把观察点、标签、评估指标、线上监控这四件事各写一遍写得出来这个项目就是你的。希望帮到你。本文还有配套的精品资源点击获取