ARTICLE DETAIL

资讯详情

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

贷中风险预测模型实战:从样本定义到LightGBM调参的Python风控项目

贷中风险预测模型实战:从样本定义到LightGBM调参的Python风控项目 简介这份资源是面向计算机相关专业学生与项目实战学习者的贷中风险预测完整项目包以Python机器学习为核心围绕金融大数据建模赛题展开适合用作毕业设计、期末大作业或技能练习难度适中。压缩包共50个文件约10.83MB包含19个py脚本、11个csv数据集、5个ipynb笔记本、4个docx文档、3个xlsx表格、2个pptx演示文稿及若干txt说明覆盖数据处理、特征工程、模型训练与结果输出全流程。项目源自金融大数据建模挑战赛初赛方案内含赛题说明、数据字段解释、特征提取思路与多模型对比代码涉及决策树、LightGBM、XGBoost、随机森林、AdaBoost、高斯朴素贝叶斯等算法实现并附答辩PPT与最终版风险预测演示便于理解建模逻辑与汇报要点。已有62人学习源码经本地编译调试可运行文档与代码配套能帮助读者快速复现实验、掌握贷中风险预测的完整实现路径。1. 贷中风险预测模型为什么AUC 0.85的模型上线后还是被业务方骂很多做风控算法的同学都有过这种经历用 Python 和机器学习在历史数据上把贷中风险预测模型调到 AUC 0.85KS 0.4兴冲冲交给业务方结果上线一个月被骂得狗血淋头。问题不在模型本身而在于贷中这个场景和贷前完全是两码事。贷前是「这个人会不会坏」贷中是「这个已经放了款的人未来几个月会不会坏」样本定义、观察窗口、表现期、变量可用性全都不一样。这个项目标题里的「贷中风险预测模型」核心就是解决存量客户在还款周期内的违约预警问题典型场景包括额度调整、催收策略分级、提前结清判断。适合谁看有 Python 基础、懂一点机器学习、但没完整做过信贷风控落地的人也适合正在准备答辩、需要一套能讲清楚业务逻辑和代码细节的项目的同学。源码和文档只是载体真正值钱的是这套从数据到模型到评估的完整链路以及那些只有踩过才知道的坑。2. 贷中风险预测的数据底座样本定义和特征工程怎么做才不翻车2.1 贷中样本的观察期与表现期怎么切贷中模型和贷前最大的区别在于样本定义。贷前通常用「申请时点」作为观察起点贷中则要用「某个存量时点」作为观察起点。常见做法是取每个客户每个账期的还款日作为观察点往前推 6 个月作为特征观察期往后推 3 个月作为表现期。如果客户在表现期内出现逾期超过 30 天标记为坏样本否则为好样本。这里有个血泪经验表现期不能太短否则会把「暂时忘记还款但后来还了」的人误判为坏客户也不能太长否则模型上线时坏样本还没成熟导致训练集和线上分布不一致。我一般会做 vintage 分析按放款月份分组看不同表现期的坏账率是否收敛。如果 3 个月和 6 个月的坏账率差异小于 5%说明 3 个月表现期够用。import pandas as pd import numpy as np # 假设原始还款流水表 loan_repay 包含cust_id, loan_id, due_date, repay_date, overdue_days # 构造贷中样本以每个账单日为观察点 def build_mid_loan_samples(repay_df, obs_months6, perf_months3): repay_df: 还款流水含 cust_id, due_date, repay_date, overdue_days obs_months: 特征观察期长度月 perf_months: 表现期长度月 df repay_df.copy() df[due_date] pd.to_datetime(df[due_date]) df[repay_date] pd.to_datetime(df[repay_date]) # 观察点 每个账单日 df[obs_date] df[due_date] # 特征窗口起点 df[feat_start] df[obs_date] - pd.DateOffset(monthsobs_months) # 表现窗口终点 df[perf_end] df[obs_date] pd.DateOffset(monthsperf_months) # 标记坏样本表现期内最大逾期天数 30 df[is_bad] (df[overdue_days] 30).astype(int) # 去重同一客户同一观察点只保留一条 df df.sort_values([cust_id, obs_date, overdue_days], ascending[True, True, False]) df df.drop_duplicates(subset[cust_id, obs_date], keepfirst) return df[[cust_id, obs_date, feat_start, perf_end, is_bad]]这段代码的关键参数是obs_months和perf_months。观察期太短特征不够稳定太长会引入太久远的行为反而稀释近期风险信号。我一般设 6 和 3但具体要看产品账期。如果是等额本息 12 期表现期可以缩到 2 个月如果是先息后本表现期要拉到 6 个月。另外注意overdue_days要取表现期内的最大值不是观察点当天的值否则会漏掉中间逾期又还上的情况。2.2 贷中特征工程从还款行为里挖出风险信号贷中模型的特征和贷前差异很大。贷前看的是申请信息、征信查询、多头借贷贷中看的是还款行为、额度使用、账龄变化。我一般把特征分成四类还款行为类、额度使用类、账龄类、外部变量类。还款行为类包括过去 6 个月平均逾期天数、最大逾期天数、逾期次数、提前还款次数、还款日当天还款比例。额度使用类包括当前额度使用率、过去 6 个月平均使用率、使用率变化趋势。账龄类包括开户时长、已还期数、剩余期数。外部变量类包括近 3 个月征信查询次数、其他机构贷款笔数。def build_repay_features(repay_df, sample_df): 基于还款流水构造贷中特征 df repay_df.merge(sample_df[[cust_id, obs_date, feat_start]], oncust_id) # 只保留观察期内的还款记录 df df[(df[due_date] df[feat_start]) (df[due_date] df[obs_date])] feats df.groupby([cust_id, obs_date]).agg( avg_overdue_days(overdue_days, mean), max_overdue_days(overdue_days, max), overdue_cnt(overdue_days, lambda x: (x 0).sum()), early_repay_cnt(repay_date, lambda x: (x df.loc[x.index, due_date]).sum()), on_time_rate(overdue_days, lambda x: (x 0).mean()), total_periods(due_date, count) ).reset_index() # 额度使用率需要额外从额度表关联这里用占位 feats[credit_util] np.nan # 实际项目从额度表计算 return feats这里有个容易翻车的地方early_repay_cnt的计算用了 lambda 里嵌套 df 索引实际跑的时候如果数据量大性能会很差。我一般会先把repay_date due_date标记成一列布尔值再 groupby 求和。另外on_time_rate在样本里如果全是 1方差为 0树模型会直接忽略但逻辑回归会报错需要做方差过滤。提示贷中特征一定要做时间切片验证。用 2023 年 1 月到 6 月的数据训练用 7 月到 9 月的数据测试如果 KS 下降超过 30%说明特征不稳定大概率是引入了未来信息或者外部变量口径变了。3. 用 Python 把贷中风险预测模型跑起来从逻辑回归到 LightGBM 的完整代码3.1 基线模型逻辑回归 WOE 分箱为什么还是首选虽然现在 LightGBM 很火但贷中风险预测模型我仍然建议先做逻辑回归 WOE 分箱作为基线。原因有三个一是可解释性强业务方和风控策略同学能看懂每个变量的方向二是稳定性好逻辑回归对异常值和缺失值不敏感三是监管友好很多金融机构要求模型必须能给出评分卡形式的解释。WOE 分箱的核心是把连续变量离散化然后计算每个箱的 WOE 值。WOE ln(坏样本占比 / 好样本占比)IV sum((坏样本占比 - 好样本占比) * WOE)。一般选 IV 0.02 的变量入模IV 0.5 的要警惕可能是变量泄露。import pandas as pd import numpy as np from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_auc_score, roc_curve def calc_woe_iv(df, feature, target, bins5): 计算单个变量的 WOE 和 IV df df[[feature, target]].copy() df[bin] pd.qcut(df[feature], qbins, duplicatesdrop) grouped df.groupby(bin)[target].agg([count, sum]) grouped.columns [total, bad] grouped[good] grouped[total] - grouped[bad] grouped[bad_rate] grouped[bad] / grouped[bad].sum() grouped[good_rate] grouped[good] / grouped[good].sum() grouped[woe] np.log(grouped[bad_rate] / grouped[good_rate]) grouped[iv] (grouped[bad_rate] - grouped[good_rate]) * grouped[woe] iv grouped[iv].sum() return grouped, iv # 示例对 avg_overdue_days 做分箱 # grouped, iv calc_woe_iv(train_df, avg_overdue_days, is_bad, bins5) # print(fIV {iv:.4f})参数bins控制分箱数量一般 5 到 10 箱。太少会损失信息太多会导致每个箱样本不足WOE 波动大。我一般先试 5 箱如果 IV 不显著再增加到 8 箱。另外pd.qcut是等频分箱如果变量有大量重复值duplicatesdrop会自动合并但要注意合并后的箱是否有业务含义。3.2 LightGBM 调参贷中场景下这几个参数最影响 KSLightGBM 在贷中风险预测里表现通常比逻辑回归好尤其是特征交互复杂的时候。但调参不能瞎调贷中场景下最影响 KS 的参数是num_leaves、learning_rate、min_child_samples和subsample。num_leaves控制树的复杂度贷中样本通常几万到几十万我一般设 31 到 63。设太大容易过拟合设太小欠拟合。learning_rate我一般设 0.05配合n_estimators500到 1000用早停防止过拟合。min_child_samples设 50 到 100保证每个叶子节点有足够样本避免学到噪声。subsample设 0.8行采样增加泛化能力。import lightgbm as lgb from sklearn.model_selection import train_test_split # 假设 X 是特征矩阵y 是标签 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.3, random_state42) params { objective: binary, metric: auc, boosting_type: gbdt, num_leaves: 31, learning_rate: 0.05, min_child_samples: 50, subsample: 0.8, subsample_freq: 1, colsample_bytree: 0.8, reg_alpha: 0.1, reg_lambda: 0.1, random_state: 42, n_jobs: -1 } dtrain lgb.Dataset(X_train, labely_train) dvalid lgb.Dataset(X_test, labely_test, referencedtrain) model lgb.train( params, dtrain, num_boost_round1000, valid_sets[dvalid], callbacks[lgb.early_stopping(50), lgb.log_evaluation(100)] ) # 评估 y_pred model.predict(X_test) auc roc_auc_score(y_test, y_pred) print(fTest AUC: {auc:.4f}) # 特征重要性 importance pd.DataFrame({ feature: model.feature_name(), importance: model.feature_importance(importance_typegain) }).sort_values(importance, ascendingFalse) print(importance.head(10))这段代码里early_stopping(50)表示如果验证集 AUC 连续 50 轮不提升就停止训练防止过拟合。importance_typegain表示用信息增益衡量特征重要性比split更可靠。实际项目中我还会做 5 折交叉验证看 AUC 的均值和方差方差太大说明模型不稳定。注意LightGBM 对缺失值自动处理但贷中数据里缺失值往往有业务含义。比如「近 3 个月征信查询次数」缺失可能是客户没有征信记录这本身就是风险信号。我一般会额外加一列缺失标记让模型自己学。4. 模型评估与上线前的避坑清单AUC 高不代表能用4.1 KS、PSI、Lift 这三个指标怎么看AUC 只反映排序能力贷中模型还要看 KS、PSI 和 Lift。KS 衡量的是模型区分好坏样本的最大能力贷中模型 KS 一般要求 0.3 以上。PSI 衡量的是训练集和测试集或线上的分布差异PSI 0.1 表示稳定0.1 到 0.25 需要关注大于 0.25 说明模型可能失效。Lift 看的是 top 10% 分数段的坏账率是整体的多少倍一般要求 3 倍以上。def calc_ks(y_true, y_pred): fpr, tpr, thresholds roc_curve(y_true, y_pred) ks max(tpr - fpr) return ks def calc_psi(expected, actual, bins10): 计算 PSIexpected 是训练集分数actual 是测试集分数 breakpoints np.percentile(expected, np.arange(0, 100, 100/bins)) expected_perc np.histogram(expected, breakpoints)[0] / len(expected) actual_perc np.histogram(actual, breakpoints)[0] / len(actual) psi np.sum((expected_perc - actual_perc) * np.log(expected_perc / actual_perc)) return psi ks calc_ks(y_test, y_pred) psi calc_psi(model.predict(X_train), y_pred) print(fKS: {ks:.4f}, PSI: {psi:.4f})calc_psi里的bins一般设 10但要注意如果分数分布很集中分箱后某些箱样本为 0会导致 log 计算报错。我一般会加一个极小值1e-6防止除零。另外 PSI 计算要用训练集的分数分布作为基准不是用标签。4.2 上线前必须检查的 5 个坑第一个坑特征穿越。比如用了「未来 3 个月是否逾期」来构造特征模型 AUC 能到 0.99但上线就废。检查方法很简单看特征的时间戳是否都在观察点之前。第二个坑样本不均衡。贷中坏样本通常只有 1% 到 5%直接训练模型会偏向好样本。我一般用scale_pos_weight或者欠采样但要注意欠采样后概率校准会偏需要做 Platt Scaling。第三个坑分数分布漂移。训练集分数集中在 0.1 到 0.3线上分数集中在 0.4 到 0.6说明特征口径变了。上线前一定要用最近 3 个月的数据做一次 PSI 检查。第四个坑缺失值处理不一致。训练时用中位数填充上线时用 0 填充模型直接翻车。我一般会把填充逻辑封装成函数训练和推理共用。第五个坑模型版本管理混乱。改了特征没改版本号线上跑的还是旧模型。我一般用model_YYYYMMDD.pkl命名并在文件里存特征列表和参数。提示答辩 PPT 里不要只放 AUC 和 KS要把 PSI 和 Lift 也放上去再放一张分数分布对比图。评委老师一看就知道你是真做过落地不是只跑了个 demo。5. 从源码到答辩怎么把贷中风险预测模型讲成一个高分项目5.1 源码目录怎么组织才像真实项目很多同学把代码全堆在一个main.py里答辩时老师一看就觉得是临时凑的。我一般会按真实项目结构组织credit_risk_model/ ├── config/ │ └── config.yaml # 数据库连接、模型参数 ├── data/ │ ├── raw/ # 原始数据 │ └── processed/ # 清洗后数据 ├── features/ │ ├── build_features.py # 特征工程 │ └── feature_config.py # 特征列表和分箱配置 ├── models/ │ ├── train.py # 训练脚本 │ ├── predict.py # 推理脚本 │ └── evaluate.py # 评估脚本 ├── utils/ │ ├── db.py # 数据库工具 │ └── logger.py # 日志 ├── notebooks/ │ └── eda.ipynb # 探索性分析 ├── requirements.txt └── README.mdconfig.yaml里放数据库连接和模型超参不要硬编码在代码里。feature_config.py里放每个变量的分箱边界和 WOE 映射这样上线时直接读配置就行。train.py和predict.py分开训练用历史数据推理用线上数据但特征工程逻辑要复用同一个模块。5.2 答辩 PPT 的 10 页结构答辩 PPT 不要超过 12 页我一般用 10 页第 1 页封面第 2 页业务背景和问题定义第 3 页数据说明和样本定义第 4 页特征工程放 IV 排名前 10 的变量第 5 页模型选型对比逻辑回归 vs LightGBM第 6 页评估指标AUC、KS、PSI、Lift第 7 页分数分布和阈值选择第 8 页上线策略建议第 9 页项目难点和解决方案第 10 页总结和展望。第 7 页的阈值选择很关键。贷中模型一般会设两个阈值高分位比如 top 10%触发人工审核低分位比如 bottom 30%自动通过。阈值不是拍脑袋定的要根据业务成本和收益算。我一般会画一张阈值 - 通过率 - 坏账率曲线让业务方自己选。5.3 一个容易被忽略的技巧用 SHAP 解释单笔预测答辩时老师经常会问「这个客户为什么被判定为高风险」。如果你只回答「模型算出来的」分数直接砍半。我一般会用 SHAP 做单笔解释import shap explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_test) # 单笔解释 shap.force_plot(explainer.expected_value, shap_values[0], X_test.iloc[0])SHAP 会告诉你每个特征对这笔预测的贡献。比如「近 3 个月逾期次数 2」贡献了 0.15 的风险分「额度使用率 0.3」贡献了 -0.05 的风险分。这样业务方就能理解模型不是黑匣子也愿意配合落地。我做了这么多年贷中模型最大的教训就是不要只盯着 AUC要多花时间在样本定义和特征稳定性上。一个 AUC 0.75 但 PSI 稳定的模型比 AUC 0.85 但三个月就失效的模型有价值得多。源码和 PPT 只是敲门砖真正让你在答辩和工作中站稳的是这套从业务到数据到模型的完整思考方式。希望帮到你。本文还有配套的精品资源点击获取
返回列表