贷中风险预测模型实战:Python机器学习与LightGBM实现
2026/9/11 13:45:10 网站建设 项目流程

简介:面向毕业设计或期末大作业需要完成贷中风险预测课题的计算机相关专业学生,这份基于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实现贷中风险预测:从滚动特征到LightGBM

3.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_periods=1) .mean() .reset_index(level=0, drop=True) ) return df samples = create_rolling_features( df=trans_data, group_key='customer_id', value_col='paid_ratio', windows=[3, 6] )
  • sort_values很关键,滚动统计要求组内数据按照月份由早到晚排列,否则窗口里混入未来数据。
  • min_periods=1表示不足3个月时也计算均值,避免因为冷启动产生大量空值。
  • reset_index(level=0, drop=True)去掉组索引,让新特征按原行顺序拼接到表里。

除了均值,还可以增加近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, label=y_train) test_data = lgb.Dataset(X_test, label=y_test, reference=train_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_round=500, valid_sets=[test_data], callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)] )
  • metric: auc用于快速判断模型排序能力,进入业务验证阶段再换成KS或自定义损失。
  • min_data_in_leaf=200能有效控制过拟合,尤其当样本量只有几万条时,叶子节点样本太少容易让坏客户细节被无限放大。
  • 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', ascending=False).reset_index(drop=True) 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(f'Test KS: {ks:.4f}')
  • KS值描述了模型对好坏客户的排序分离程度。训练集KS一般0.3以上才有业务使用价值,测试集KS如果掉到训练集的一半以下,基本可以判断过拟合。
  • 这版代码为了演示,使用sort_values后直接累计。实际项目中还会在得出KS的同时画出分箱对比图,放进文档说明的验证章节。

4. 贷中风险预测模型的调参与业务对齐

4.1 样本不平衡的解决顺序

贷中数据集里的坏样本占比通常不到5%。直接拿原始比例训练,模型会把所有人都判成好人,因为这样做损失最小。处理不平衡的顺序很重要,常见做法是:

  1. 先延长观察期,多积累几个月数据,让坏样本量增加。
  2. 再调整样本权重,让坏样本在损失函数里贡献更高。
  3. 然后调决策阈值,用验证集找到召回率落在目标区间的划分点。
  4. 最后才考虑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, label=y_train, weight=sample_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, label=y_train), ...) imp = lgb_model.feature_importance('gain') print(sorted(zip(X_train_copy.columns, imp), key=lambda 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(f'cutoff={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, buckets=10): edges = np.percentile(expected, np.linspace(0, 100, buckets + 1)) edges[-1] += 1e-6 expected_pct = np.histogram(expected, bins=edges)[0] / len(expected) actual_pct = np.histogram(actual, bins=edges)[0] / len(actual) return np.sum((expected_pct - actual_pct) * np.log(expected_pct / actual_pct)) psi = calculate_psi(train_proba, latest_proba) print(f'PSI: {psi:.4f}')
  • np.percentile(expected, np.linspace(0, 100, 11))得到的是期望分布的分位数边界,实际分布也会按这些边界分箱,保证两者比例可以比较。
  • PSI小于0.1代表稳定性很好,0.1到0.2需要观察,大于0.2就意味着模型区分度可能已经失效,需要重训或重新选特征。
  • 把PSI计算逻辑写进源码,并设置每月跑批任务,输出变化趋势图。这样答辩时展示的不只是模型训练的过程,还有持续监控的思路,正好对应贷中模型区别于贷前模型的长期运营属性。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询