简介面向毕业设计或期末大作业需要完成贷中风险预测课题的计算机相关专业学生这份基于Python机器学习的项目覆盖了数据处理、特征工程、模型构建与评估的完整流程可作为金融风控方向实战练习的入门参考。压缩包仅10.83MB共50个文件包含19个Python脚本、5个Jupyter交互式分析笔记、11个CSV数据文件、4个Word文档说明、2个答辩PPT以及Excel结果表等源码均经过调试可直接运行。目前已有62人学习下载内容经导师和助教评审认可难度适中特别适合作为课程大作业或本科毕业设计的参考蓝本。项目源自2020“江苏银行杯”金融大数据建模挑战赛内含赛题方案文档、数据字段说明、特征工程与多种模型对比如LightGBM、XGBoost、随机森林等并附有最终版风险预测模型和答辩PPT便于学习者对照复现、查漏补缺也能从中获取建模与答辩汇报的实用经验。1. 贷中风险预测模型先把“贷中”两个字想清楚在信贷风控里贷中风险预测模型负责放款之后到结清之前这一段的风险识别。Python生态下的机器学习库让“用行为数据预判客户变坏”这件事可以在一套简洁的代码架构里完成。标题里带上“源码文档说明答辩PPT”通常说明它不是一个孤立的训练脚本而是能展示完整项目逻辑的交付物。贷中场景的核心问题很明确借款人已经拿到钱模型要利用他后续的还款行为判断他会不会在未来30天或60天内变成逾期客户。这类模型直接服务额度调整、早期预警、催收优先级排序是消费金融、信用卡和小微贷场景里最常被问到的建模题目之一。如果你是数据建模工程师或者正拿机器学习项目做毕业设计这条路径能同时解决“模型怎么做”和“项目怎么讲清楚”两件事。2. 贷中风险预测的理论框架观察点、标签与模型选择2.1 行为数据里的风险信号贷中模型和贷前模型的最大差异是数据形态。贷前拿到的是申请时点的静态信息学历、收入、职业这些字段决定了初始额度贷中手里多了一张还款行为的时间序列表真正有区分度的信号变成了还款行为在一段连续时间内的变化趋势。比如近三个月还款率是否下降、账单日之后第几天才还款、额度使用率是不是持续走高、有没有出现过连三累六的历史逾期痕迹。把这些行为窗口转成结构化特征是贷中建模的第一步。如果一个项目直接把“逾期天数”“还款金额”当作原始特征丢进机器学习模型往往学不到时间维度上的风险信号。我一般会以客户为主体按自然月对齐账单和还款记录再在时间轴上滑动生成统计量。统计窗口太短容易波动太长会稀释近期风险变化常见组合是3个月、6个月和12个月。实际建模里趋势和波动比单月数值更重要。客户6月还款率0.9、7月还款率0.4比连续两个月都是0.6更值得警惕因为前者说明还款能力正在快速恶化。所以贷中模型的特征工程不会只取当期值还要计算时间窗口内的均值、最大值、标准差和变化斜率这些变量才能真正捕捉风险加速度。2.2 标签定义与表现期选择贷中模型的样本单位不是“一笔贷款一行”而是“一个客户在一个观察点上的状态”。假设当前要预测2024年6月观察点之后的风险特征只能用2024年6月及之前的数据标签只能来自2024年7月以后的表现期。表现期是指观察点结束后用来判断“是否变坏”的那段时间。常见的标签方案有三种标签方案观察期表现期坏样本定义适用场景A6个月30天逾期≥30天早期预警B6个月60天逾期≥60天额度管理C12个月90天逾期≥90天呆账预测方案A样本最多坏样本定义更敏感适合快速捕捉风险信号方案C更接近最终损失但标签成熟需要更长时间模型迭代节奏会被拖慢。如果说“逾期≥30天”对应M1那么“逾期≥60天”对应M2。实际项目中我会同时构造多套标签先看不同方案下的好坏样本比例和迁移一致性再确定主模型标签。提示打标签时表现期结束时间必须在数据集可用的时间范围内。尚未过完表现期的样本不能直接标记为“好客户”宁缺毋滥。2.3 模型选型业务约束决定算法上限贷中风险预测的模型选择要在精度和可解释性之间找平衡。业务方需要对额度调整、止付动作给出理由监管也要求关键决策具备可解释性。下面把常用机器学习模型放在贷中场景里对比模型优点局限在贷中的定位逻辑回归稳定、计算快、系数可解释非线性关系需要手工变换基准模型、合规版本随机森林自动处理非线性概率校准偏差较大特征筛选、对照实验XGBoost / LightGBM精度高、可处理大量特征调参成本高、过拟合风险大主力模型深度学习时序模型能自动提取时序模式数据量要求高、解释性差有海量行为数据的团队这套选型里我一般把逻辑回归作为基线验证特征工程是否有效LightGBM作为主力模型重点调稀疏数据和样本权重最后用SHAP值给业务方讲原因。模型评估指标上AUC只能代表排序能力贷中场景更看重召回率固定在某档位时的精准率因为催收资源有限不能把所有风险苗头全都处理一遍。如果团队没有机器学习平台也没关系逻辑回归加特征分箱也能达到可用的KS。但树模型能自动处理缺失值对行为数据的非线性关系更友好更适合需要快速迭代的贷中场景。关键是统一评估口径不能每换一个模型就换一套指标否则源码和文档对不上答辩时一问就露馅。3. 用Python实现贷中风险预测从滚动特征到LightGBM3.1 建模样本的构造方法贷中建模的输入数据通常是账单流水。假设原始表有客户编号customer_id、订单编号order_id、月份month、账单金额bill_amount、应还金额due_amount、实还金额paid_amount、当前逾期天数overdue_days、授信额度limit_amount。每个客户每月产生一行流水我们要把它转换成“客户-观察点-特征-标签”的样本表。常见做法是截取一个固定观察点比如2024年6月往前看6个月作为特征窗口往后看30天作为表现期。样本样例看起来像这样sample pd.DataFrame({ customer_id: 1001, month: 2024-06-30, paid_ratio_3m: 0.86, max_overdue_3m: 2, limit_usage_ratio: 0.55, is_bad: 0 })这样每一行都对应一个客户在特定月份的观测特征里不包含任何2024年7月以后的数据。后续所有特征生成逻辑都必须遵守这个时间边界。把数据加工成这种形状是贷中项目能继续往后走的前提。3.2 滚动窗口特征生成的Python代码下面的函数用rolling计算近3个月和近6个月的还款率均值还款率定义为实还金额除以应还金额import pandas as pd def create_rolling_features(df, group_key, value_col, windows): 按客户分组生成滚动窗口统计特征 df df.sort_values([customer_id, month]).copy() for w in windows: df[f{value_col}_mean_{w}m] ( df.groupby(group_key)[value_col] .rolling(w, min_periods1) .mean() .reset_index(level0, dropTrue) ) return df samples create_rolling_features( dftrans_data, group_keycustomer_id, value_colpaid_ratio, windows[3, 6] )sort_values很关键滚动统计要求组内数据按照月份由早到晚排列否则窗口里混入未来数据。min_periods1表示不足3个月时也计算均值避免因为冷启动产生大量空值。reset_index(level0, dropTrue)去掉组索引让新特征按原行顺序拼接到表里。除了均值还可以增加近3个月最大逾期天数、近6个月逾期次数、还款间隔标准差、额度使用率变化斜率。每增加一个特征都要回答“它在观察点时刻能不能被真实计算出来”。如果特征计算需要未来两个月的还款记录就会造成信息穿越模型指标再好看也上不了线。3.3 按时间切分训练集和测试集贷中模型不能用随机切分原因是同一客户可能在不同月份重复出现在样本里随机切分会把客户的一部分月份放进训练集另一部分放进测试集让测试结果虚高。正确做法是按观察点月份排序切出时间上的前段和后段。train samples[samples[month] 2024-01-01] test samples[samples[month] 2024-01-01] features [paid_ratio_3m, paid_ratio_6m, max_overdue_3m, limit_usage_ratio] X_train, y_train train[features], train[is_bad] X_test, y_test test[features], test[is_bad]这里以月份作为切分边界保证测试集在时间上总是晚于训练集。想要更严格还可以按“客户最近观察点是否在边界之前”来做分组切分避免同一个客户的信息同时出现在两侧。时间切分会让测试集指标比随机切分低一些但更接近模型真实上线后的表现。3.4 训练LightGBM模型项目核心模型使用LightGBM训练代码可以保持最小可运行import lightgbm as lgb train_data lgb.Dataset(X_train, labely_train) test_data lgb.Dataset(X_test, labely_test, referencetrain_data) params { objective: binary, metric: auc, learning_rate: 0.05, num_leaves: 31, min_data_in_leaf: 200, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 1, verbose: -1 } model lgb.train( params, train_data, num_boost_round500, valid_sets[test_data], callbacks[lgb.early_stopping(50), lgb.log_evaluation(100)] )metric: auc用于快速判断模型排序能力进入业务验证阶段再换成KS或自定义损失。min_data_in_leaf200能有效控制过拟合尤其当样本量只有几万条时叶子节点样本太少容易让坏客户细节被无限放大。early_stopping(50)在验证集上连续50轮无改善就停止节省调参时间。需要注意早停用到的验证集不能和最终测试集混用否则指标会偏乐观。3.5 用KS统计量做模型验证贷中模型效果最常用的输出是KS和AUC。AUC已经可以从lightgbm的训练日志里看到KS则需要额外计算import pandas as pd def calc_ks(y_true, y_pred): df pd.DataFrame({y: y_true, pred: y_pred}) df df.sort_values(pred, ascendingFalse).reset_index(dropTrue) total_bad df[y].sum() total_good len(df) - total_bad df[cum_bad] (df[y] 1).cumsum() / total_bad df[cum_good] (df[y] 0).cumsum() / total_good return max(abs(df[cum_bad] - df[cum_good])) ks calc_ks(y_test, model.predict(X_test)) print(fTest KS: {ks:.4f})KS值描述了模型对好坏客户的排序分离程度。训练集KS一般0.3以上才有业务使用价值测试集KS如果掉到训练集的一半以下基本可以判断过拟合。这版代码为了演示使用sort_values后直接累计。实际项目中还会在得出KS的同时画出分箱对比图放进文档说明的验证章节。4. 贷中风险预测模型的调参与业务对齐4.1 样本不平衡的解决顺序贷中数据集里的坏样本占比通常不到5%。直接拿原始比例训练模型会把所有人都判成好人因为这样做损失最小。处理不平衡的顺序很重要常见做法是先延长观察期多积累几个月数据让坏样本量增加。再调整样本权重让坏样本在损失函数里贡献更高。然后调决策阈值用验证集找到召回率落在目标区间的划分点。最后才考虑SMOTE等采样方法。合成样本可能破坏还款行为的时间顺序导致模型学到不存在的模式。示例如下bad_weight len(y_train) / (2 * y_train.sum()) sample_weight np.where(y_train 1, bad_weight, 1.0) train_data lgb.Dataset(X_train, labely_train, weightsample_weight)bad_weight让正负样本在总损失中大致均衡但并不是越均衡越好。坏样本权重太低模型只会给违约概率在0.1附近的样本排序权重过高又会把大量好客户误伤。权重系数需要配合验证集一起选观察不同权重下的召回率变化而不是只盯训练集AUC。4.2 用未来噪声检查时间穿越信息穿越是贷中模型里最容易出现的隐性错误。一个可行的自查方法是在特征集里加入一个随机噪声如果模型给了这个随机噪声很高的特征重要性说明样本构造出了问题。import numpy as np np.random.seed(0) X_train_copy X_train.copy() X_train_copy[future_noise] np.random.randn(len(X_train)) lgb_model lgb.train(params, lgb.Dataset(X_train_copy, labely_train), ...) imp lgb_model.feature_importance(gain) print(sorted(zip(X_train_copy.columns, imp), keylambda x: -x[1])[:5])feature_importance(gain)表示特征在节点分裂时带来的总增益增益越高对模型贡献越大。如果future_noise排名进入前20或者排名靠前的特征存在“未来计算”嫌疑就要逐列检查特征构造代码。这个技巧可以写进源码文档的“模型验证”章节。4.3 LightGBM在贷中场景的关键参数下面这组参数是我在贷中数据集上的默认起点参数推荐值作用learning_rate0.03 ~ 0.1控制每轮更新步长num_leaves31 ~ 127模型容量越大越容易过拟合min_data_in_leaf200 ~ 500限制叶子节点最少样本量feature_fraction0.7 ~ 0.9每棵树随机抽样特征比例bagging_fraction0.7 ~ 0.9每轮行采样比例调参策略上先用默认参数跑一遍观察验证集AUC和KS再把num_leaves从31增加到127如果验证集指标不升反降说明数据量撑不起更大的模型容量。把min_data_in_leaf从200提高到500能显著减少由个别客户异常还款引起的噪声。最后再微调feature_fraction让每棵树的特征组合更丰富。4.4 把概率转换为风险分数模型输出的是违约概率业务系统更习惯用整数分数。评分卡风格的分数的换算方式是score 1000 - 50 * np.log((1 - prob) / prob)这里采用负向映射关系分数越低风险越高。接下来用测试集验证不同分数阈值下的业务效果results pd.DataFrame({prob: prob_test, y: y_test}) results[score] 1000 - 50 * np.log((1 - results[prob]) / results[prob]) for cutoff in [650, 680, 700, 720]: pred (results[score] cutoff).astype(int) tp ((pred 1) (results[y] 1)).sum() recall tp / results[y].sum() precision tp / pred.sum() print(fcutoff{cutoff}, recall{recall:.3f}, precision{precision:.3f})np.log((1-prob)/prob)是log-odds乘以50加1000只是把分数拉伸到业务友好的区间。遍历cutoff后可以根据业务要求比如“最少覆盖30%坏客户”选择一个精确率与召回率均衡的阈值。这组输出可以直接做成表格放进答辩PPT比贴一堆AUC更直观。5. 贷中风险模型项目打包源码目录、文档要点与PSI验证5.1 目录结构决定答辩时的讲解效率标题里同时出现源码、文档说明和答辩PPT意味着交付物需要有一目了然的组织方式。我建议使用下面的目录project/ ├── data/ │ ├── raw/ │ └── processed/ ├── src/ │ ├── features.py │ ├── train.py │ └── evaluate.py ├── docs/ │ ├── model_document.md │ ├── variable_list.csv │ └── test_report.md └── slides/ └── defense.pptx这个结构的价值在于“每条说明都能找到对应的代码”。答辩时先说目录再说“变量字典在docs/variable_list.csv训练入口在src/train.py”评委如果有技术问题可以直接打开文件核对。没有目录结构的项目哪怕指标很高也容易被判定为“不知道如何落地”。5.2 文档说明里必备的三个模块文档说明不是把代码注释复制一遍而是要回答建模过程的三组问题。第一组是样本口径观察点、表现期、坏样本定义、排除规则第二组是变量字典每个特征的计算窗口、数据来源、缺失率第三组是效果报告训练集和测试集的AUC、KS、分箱图、阈值选择表。如果文档里还能附带requirements.txt并把Python版本和关键依赖库写清楚就可以看作一个能被别人复现的高分项目。5.3 用PSI验证模型上线后的稳定性贷中模型上线一段时间后客户群体和还款行为都会变化。PSI是衡量预测分数分布稳定性的常用指标代码逻辑是把期望分布按照十分位数分箱再计算实际分布在每个箱内的占比差异。def calculate_psi(expected, actual, buckets10): edges np.percentile(expected, np.linspace(0, 100, buckets 1)) edges[-1] 1e-6 expected_pct np.histogram(expected, binsedges)[0] / len(expected) actual_pct np.histogram(actual, binsedges)[0] / len(actual) return np.sum((expected_pct - actual_pct) * np.log(expected_pct / actual_pct)) psi calculate_psi(train_proba, latest_proba) print(fPSI: {psi:.4f})np.percentile(expected, np.linspace(0, 100, 11))得到的是期望分布的分位数边界实际分布也会按这些边界分箱保证两者比例可以比较。PSI小于0.1代表稳定性很好0.1到0.2需要观察大于0.2就意味着模型区分度可能已经失效需要重训或重新选特征。把PSI计算逻辑写进源码并设置每月跑批任务输出变化趋势图。这样答辩时展示的不只是模型训练的过程还有持续监控的思路正好对应贷中模型区别于贷前模型的长期运营属性。本文还有配套的精品资源点击获取