考试通知

机器学习贷中风险预测模型:从特征工程到上线监控全攻略

机器学习贷中风险预测模型:从特征工程到上线监控全攻略 简介面向金融风控与机器学习建模的贷中风险预测项目源于江苏银行杯金融大数据建模挑战赛基于信贷场景数据用Python完成数据处理、特征工程与多模型对比适合高校学生、科研人员及从业者用于课程设计、毕业设计或竞赛复现。压缩包共49个文件约10.97MB包含19个Python脚本、CSV/TXT数据、5个Notebook、Excel、Word与PPT等覆盖数据清洗、特征构造、模型训练到结果输出全流程。模型脚本涵盖决策树、随机森林、XGBoost、LightGBM等主流算法便于横向对比。项目附初赛作品方案与最终版报告PPT代码可运行修改目前已有69人浏览学习适合具备Python基础、希望在信贷风控中搭建基准模型并理解竞赛完整流程的读者。1. 机器学习贷中风险预测模型贷后阶段才是风控真正的战场一笔信贷业务真正让人睡不着的往往不是放款那一刻的审核而是放款之后漫长的还款周期里借款人还款能力悄然恶化却被系统毫无感知的那几周。机器学习贷中风险预测模型干的正是这件事在贷款存续期内用客户最近的还款行为、账户变动和外部征信信号预测未来 30~60 天内的违约概率把“还没逾期、但很可能逾期”的客户提前找出来。它与贷前信用评分的区别在于贷前看的是“这个人当初是否符合准入”贷中看的是“他拿到钱之后行为有没有变质”。这篇文章写给银行零售信贷、消费金融、助贷机构的风控建模和策略同学也适合打算入行金融风控的 Python 开发者。读完之后你会得到一条从特征工程、模型训练到上线监控的完整路线按步骤就能复现一个可落地的贷中风险预测模型。2. 贷中风险预测的数据准备特征从哪里来、怎么加工2.1 贷中数据的常见来源和关键字段贷中风险预测和贷前信用评估最大的不同是数据的时间维度和动态性。贷前模型看的是“这个人今天长什么样”贷中模型看的是“这个人自从拿到贷款之后行为发生了哪些变化”。一个在放款时资质正常的客户可能在三个月后因为失业、过度消费或者多头借贷变成高风险这种变化只有从动态数据里才能读出来。常见的数据源大概分四块。第一块是还款行为数据这是贷中模型的核心特征。还款是否准时、是否经常走最低还款、有没有展期或借新还旧的行为这些直接体现借款人的还款能力和还款意愿。第二块是账户行为数据指借款人在机构内部账户上的资金流情况包括账户余额、大额转入转出、工资入账规律性等。对绑定了代发工资或银行卡流水的产品这类特征区分度极高。第三块是外部征信与多头借贷数据比如客户在别的机构有没有新增借款、近期申请频次是否突然上升能够反映借款人是否出现了“拆东墙补西墙”的资金紧张信号。第四块是产品维度的静态特征例如贷款金额、期限、利率、剩余本金、剩余期数。字段组示例字段在贷中模型里的业务含义还款行为还款金额、实际还款日与应还日偏差、逾期天数、展期次数还款习惯的变化最直接的风险信号账户资金账户余额、大额交易频率、工资入账规律性当前偿付能力的动态变化外部征信新增借贷记录、近30天申请查询次数、涉诉记录多头借贷和资金链紧张的识别产品信息剩余本金、剩余期数、担保方式、利率控制贷款产品本身的固有能力差异产品维度特征在放款时刻就已确定但贷中模型对它的解读方式不同。比如剩余期数越少部分借款人的违约意愿反而可能上升因为“还完最后一两期就可以结束”的心理会改变行为模式利率高的次级产品其客群本身对经济波动更敏感。这些特性和动态行为特征叠加才能形成完整的风险画像。2.2 行为特征工程的核心思路时间窗口与变化率有了原始字段下一步是把它加工成模型真正能用的特征。这是整条链路里最考验功夫的一环实际操作中会占到贷中建模工作量的六七成。我一般把行为特征分成三大类。第一类是时点特征取最近一个时间点的绝对值比如最近一期是否逾期、本月还款日当天账户余额。这类特征对短期违约预测最直接但有一个缺点它只看某一个瞬间容易被偶然波动带偏。第二类是窗口统计特征对过去一段时间做聚合统计比如过去 3 个月逾期次数、过去 6 个月平均还款天数偏差、过去 90 天最大连续逾期天数。窗口长度的选择有讲究窗口太长特征对近期行为变化不够敏感窗口太短容易被单次异常拉高。我通常先做 30 天、60 天、90 天、180 天四档再用特征重要性去验证哪个窗口的信息增益更稳定。第三类是变化率特征这是拉开模型效果差距的关键。把两个时间窗口的均值做差或做比比如“最近 30 天还款天数偏差均值相比前 90 天均值上升了多少”。借款人的风险恶化往往是渐进过程变化率特征能捕捉到那个“刚开始出现偏差”的早期信号这是单纯时点特征很难做到的。特征加工时有一个必须避开的坑时间穿越。建模时使用的任何特征只能用截至观察时点已经发生的数据绝不能把未来信息混进来。比如用第 12 期的逾期状态去预测第 6 期的风险这种样本在离线回测里分数会非常漂亮但一上线就翻车因为生产环境里第 12 期的数据根本不存在。注意贷中建模必须严格定义“观察点”和“表现期”。观察点之前的数据做特征观察点之后一段时间30~60 天是否逾期作为标签。观察点和表现期的错位一旦做错模型只在回测里“灵验”实际业务里毫无价值。3. 用Python搭建贷中风险预测模型从逻辑回归到LightGBM3.1 一个可以快速跑通的基线逻辑回归逻辑回归不是最先进的模型但它在金融风控里的地位依然稳固。可解释性强、训练快、对特征分布的要求宽容上线后和监管、审计沟通成本低。几乎每个贷中模型项目里都能看到它作为基线存在。它擅长捕捉特征的线性效应缺点是无法自动学习特征之间的交互。假设你已经整理好一张宽表每行是一个“客户—观察点”样本其中 y 是未来 30 天是否逾期的标签x 列是前面加工好的数值特征。下面是一段可以跑通的逻辑回归代码import pandas as pd from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression # 假设 df 已经包含特征列、观察日期列和标签列 feature_cols [col for col in df.columns if col not in [customer_id, obs_date, y]] X df[feature_cols].copy() y df[y].copy() # 按时间切分前80%时间段做训练后20%做验证不随机切 split_date df[obs_date].quantile(0.8) train_idx df[obs_date] split_date test_idx df[obs_date] split_date X_train, X_test X[train_idx], X[test_idx] y_train, y_test y[train_idx], y[test_idx] # 逻辑回归对特征尺度敏感先做标准化 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # class_weightbalanced 处理违约样本偏少的问题 model LogisticRegression(C0.1, max_iter200, class_weightbalanced) model.fit(X_train_scaled, y_train) y_pred_proba model.predict_proba(X_test_scaled)[:, 1]这段代码里有三个细节值得说明。第一切分方式刻意用时间切分而不是随机切分因为贷中模型要预测的未来样本在时间上一定晚于训练样本随机切分会把部分未来信息渗进训练集导致上线后表现打折。第二class_weightbalanced是处理违约样本偏少的朴素但有效的方法它按类别频率自动给少数类更高的惩罚权重。第三C0.1是正则化强度的倒数C 越小正则化越强。样本量只有几万条时默认的 C1.0 往往会过拟合我一般会从 0.01、0.1、1.0 里快速扫一遍再定。max_iter200是给优化迭代留出余量防止特征多、样本大时出现“未收敛”的报错。3.2 用交叉验证跑一个更高精度的 LightGBM逻辑回归只能学到特征的线性组合。贷中风险场景里很多信号是非线性的比如“过去 3 个月逾期 1 次且当前账户余额低于 100 元”组合起来风险远高于两个条件单独作用的叠加。树模型天然适合这种交互LightGBM 是表格数据上性价比最高的一类选择。继续沿用上面的 df但把索引重置避免交叉验证时索引错位import lightgbm as lgb from sklearn.model_selection import StratifiedKFold, cross_val_predict X_lgb df[feature_cols].reset_index(dropTrue) y_lgb df[y].reset_index(dropTrue) model lgb.LGBMClassifier( objectivebinary, learning_rate0.05, num_leaves31, min_child_samples50, feature_fraction0.8, bagging_fraction0.8, bagging_freq1, reg_lambda1.0, n_estimators500, verbosity-1, random_state42, ) # 5折交叉验证得到每个样本“没被自己训练用过的”预测概率 cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) oof_pred cross_val_predict(model, X_lgb, y_lgb, cvcv, methodpredict_proba)[:, 1] # 用全量数据再训练一版用于后续上线 model.fit(X_lgb, y_lgb)交叉验证的核心价值是让每个样本都有一套“没被自己训练过的”预测分数方便后续统一做阈值分析和策略回测避免单次切分带来的运气成分。参数里几个关键的值得展开说num_leaves31控制单棵树的复杂度常规取值范围是 15~127叶子数越多模型越容易记住噪声min_child_samples50强制每个叶子至少有 50 个样本金融数据通常噪声较大这个值我很少调到 20 以下feature_fraction0.8和bagging_fraction0.8分别是列采样和数据采样作用都是降低过拟合。reg_lambda1.0是 L2 正则项和逻辑回归里的 C 是同一个思路。参数取值说明num_leaves31单棵树叶子数越大复杂度越高min_child_samples50叶子最小样本数防噪声feature_fraction0.8每棵树随机选 80% 特征bagging_fraction0.8每棵树随机选 80% 样本reg_lambda1.0L2 正则抑制过拟合learning_rate0.05学习率小一点配合更多轮数LightGBM 相比逻辑回归像一个“黑匣子”你很难直接说出某个特征对单个客户判断的确切贡献。所以业界的通行做法是用逻辑回归做基线和解释性输出用 LightGBM 做分数主力再用 SHAP 值对重点客户做特征归因。两套模型并行既保精度也保解释能力。4. 贷中风险模型的评估与调优KS、AUC与阈值怎么选4.1 风控场景里真正该看的指标AUC、KS与提升度贷中模型好不好不能只盯准确率。违约样本通常只占 2%~5%哪怕全部预测为正常准确率也有 95% 以上但这个模型毫无意义。风控界的通行做法是看区分度也就是模型能不能把“即将违约的人”和“正常还款的人”清楚地分开。区分度的核心指标有三个。AUC 是 ROC 曲线下方的面积衡量模型对“随机一个违约样本”和“随机一个正常样本”排序的能力AUC0.5 等于瞎猜0.7~0.8 在贷中场景算可用0.85 以上算优秀。KS 是风控里最常用的指标本质是累积正常分布和累积违约分布之间的最大差距业界一般要求 KS 大于 0.3 才能上线大于 0.4 已经相当不错。提升度Lift则是把样本按模型预测分数从高到低分成十等份看每一等份里实际违约率是整体违约率的多少倍最高分组的倍数越高说明模型抓头部风险的能力越强。下面是一段计算 AUC 和 KS 的代码import pandas as pd import numpy as np from sklearn.metrics import roc_auc_score def calc_ks(y_true, y_score): df_ks pd.DataFrame({y: y_true, score: y_score}) df_ks df_ks.sort_values(score).reset_index(dropTrue) total_bad df_ks[y].sum() total_good len(df_ks) - total_bad df_ks[cum_bad] df_ks[y].cumsum() / total_bad df_ks[cum_good] (1 - df_ks[y]).cumsum() / total_good return float((df_ks[cum_bad] - df_ks[cum_good]).abs().max()) auc roc_auc_score(y_test, y_pred_proba) ks calc_ks(y_test, y_pred_proba) print(fAUC: {auc:.4f}, KS: {ks:.4f})KS 的计算思路是把样本按预测分数排序逐步计算累积违约率与累积正常率的差值取最大值。这个最大值同时暗示了一个分割点在那个位置切一刀好坏样本的区分最明显可以作为风险分层边界的初始参考。但无论指标算得多漂亮模型想被业务接受还得通过一道业务校验模型分数能不能直接转化成可执行策略。这一步通常比调参更花时间。指标含义贷中模型的常见达标线AUC好坏样本排序能力0.7 以上可用KS好坏样本累积分布最大差距0.3 以上可上线提升度头部分层相对整体风险的倍数头部 10% 至少翻 3 倍召回率对违约样本的捕获率与催收资源产能匹配4.2 类别不平衡与阈值选择把业务损失写进决策贷中风险模型的训练集里违约样本可能只占 2%。如果不做任何处理模型会把几乎所有样本判为正常AUC 依然不低但你在实际业务中根本找不到“高危客户”。前面代码里的class_weightbalanced是起点更精细的做法围绕采样和加权展开。一是下采样把正常样本随机抽到和违约样本相近的数量。优点是训练快、特征分布直观缺点是丢掉大量正常样本的信息预测概率需要重新校准回原始分布。二是上采样用 SMOTE 等手段合成违约样本适合特征维度不高、样本量可控的场景代价是合成样本可能引入噪声。三是样本加权按业务对错误的容忍度给样本分配权重比如“漏掉一个违约”的损失远高于“误伤一个正常”那违约样本的权重就调高。实际项目里我优先推荐加权它比采样更容易解释也不会扭曲样本的原始分布。模型输出的概率本身不代表最终决策阈值选择才是承上启下的关键一步。贷中模型最常见的做法是算“不同阈值下的预期业务成本”把测试集按预测概率排序逐一计算各阈值下模型抓住了多少真实违约客户、误伤了多少正常客户然后和业务成本一起算总账。import numpy as np thresholds np.arange(0.3, 0.8, 0.05) cost_default 5000 # 一个真实违约客户带来的平均损失含本金和催收费用 cost_action 200 # 一次人工催收干预的成本 for th in thresholds: pred_bad y_pred_proba th tp (pred_bad (y_test 1)).sum() fp (pred_bad (y_test 0)).sum() # 粗算被标记为违约的真实违约户坏账照成误伤的正常户浪费催收资源 total_cost tp * cost_default fp * cost_action print(f阈值 {th:.2f}: 标记违约 {pred_bad.sum()} 户, 真实违约 {tp} 户, f误伤 {fp} 户, 业务成本 {total_cost:.0f})这里真正的决策标准不是 AUC 最大化而是总业务成本最小。cost_default5000和cost_action200看起来只是两个数却是整个阈值决策里最关键的输入。每个机构的坏账成本和催收成本不一样算出来的最优阈值点完全不同。这也解释了为什么我经常说模型调参很重要但更重要的是你有没有把自己的业务损失函数想清楚。调参那点“玄学”本质上是在拟合业务目标而不是拟合某个指标。5. 贷中风险模型的金融落地避坑指南预警、催收与策略生效5.1 从模型分数到业务动作风险分层、预警与催收策略模型分数本身不产生价值产生价值的是分数驱动的业务动作。贷中模型最常见的应用方式是做风险分层把预测概率切成若干等级每一档对应一组操作。风险等级预测概率区间示例处理动作极高风险 0.8T0 人工催收介入优先冻结额度、提前收贷高风险0.6 ~ 0.8纳入 24 小时催收队列短信电话提醒临时降额中风险0.4 ~ 0.6延长观察期触发补充资料核验低风险0.2 ~ 0.4常规贷后跟踪保持额度极低风险 0.2正常流转可考虑提额策略的触发点要看机构自己的资源。有的机构催收人力充足可以把干预阈值压到 0.4人力紧张的优先保住 0.6 以上的客户用短信类低成本触达把 0.4~0.6 之间的客户覆盖住。这个节奏建议用上线前的历史样本做一次策略回测再去排生产计划。风险分层最好和额度调整联动对中高风险客户临时下调额度、冻结提现功能比单纯催促还款更容易让客户感受到“行为变化带来的后果”从而改变其还款行为。提示模型输出的概率只是一个中间产物。真正给业务创造价值的是围绕分数建立的预警规则和动作闭环。每个月都要复盘“分数变化 → 策略动作 → 客户行为变化”这条链路的转化率否则模型迟早沦为摆设。5.2 金融落地中常见的4个踩坑记录以下四段都是我在贷中模型落地过程中见过的真实教训按“现象 → 原因 → 解决”来写希望你能绕开。坑一模型回测效果惊艳上线后一落千丈。现象离线回测 KS 有 0.42上线后月度考核 KS 掉到 0.2 以下催收队列里的客户准确率明显下降。原因最常见的是特征泄漏。几乎每个新团队都会踩一次。典型场景是在构造特征时不小心用到了未来信息比如用“当月是否被风控系统预警”做特征但这个字段恰恰是模型上线后要预测的结果。还有一种更隐蔽的标签泄漏用观察点之后 30 天是否逾期做标签但特征里混入了逾期开始后的还款记录等于把答案提前抄进了试卷。解决上线前做一次特征回溯审计把每个特征的定义时间点和观察点逐一比对。人工抽检 20 个特征凡是定义中出现了“当月”“未来”“TN”等字样的全部重新审视。这个审计动作一定要写进建模流程而不是靠临时起意。坑二存量客户分数明显比新客更安全同一个策略在新老客上效果不一致。现象模型对存量客户打分普遍偏高偏向低风险对新客打分偏低催收策略按分数排序后新客队列被过分挤占存量客户的早期风险反而没被充分识别。原因存量客户的数据里带了长期历史还款表现这部分信息天然比新客多更重要的是不同时间段客户的准入政策不同客群本身的分布发生了变化模型输入分布在新老客之间不一致。解决对存量客群和新客分别做特征分布对比群体稳定性指数PSI超过 0.1 就说明分布漂移明显。考虑分群建模或者把新客定义为只有贷前信息的人群单独做一套准入版贷中模型。不为拟合精度而为策略一致性。坑三样本里的正常客户混着“潜在违约者”模型学不到早期风险信号。现象模型训练完成后发现它倾向于把“目前看起来正常”的人全部判为低风险早期预警能力很弱。原因贷中建模时通常把观察点后 30 天未逾期的样本标记为 0。但很多客户在观察点时风险未暴露可能在第 60 天才逾期。这类样本被当成正常样本等于让模型误以为“现在没逾期的人都是安全的”。解决把标签窗口拉长到 60 天或 90 天做敏感性测试或者采用复合标签观察点后 30 天内逾期标记 160 天内逾期也标记 1但要对标签窗口做严格定义避免重叠。更干净的做法是直接删掉那些表现期不足的样本但代价是样本量减少需要权衡。坑四训练和预测的特征不一致导致生产环境分数分布异常。现象模型上线后线上分数严重向 0 或向 1 偏移和测试集分布差异巨大业务部门开始质疑模型是不是坏了。原因训练时用的是一版字段定义生产代码里因为上游字段改名或加工逻辑调整某个特征被填了默认值。最常见的翻车点是“最近 30 天还款次数”这类短期窗口特征生产环境如果没拿到当月数据默认填 0整个特征分布就变形了。解决在模型上线前加一道特征一致性校验对生产环境跑一版样本取其特征和训练集的特征做均值、缺失率、分位数对比。差异超过阈值的特征直接报警。这个校验脚本必须自动化每次上游数据变更都触发才能保证线上模型长期可用。6. 贷中模型上线后的持续监控与迭代PSI、再训练与人工校准模型上线不是终点。贷中场景的客群和外部环境一直在变尤其是经济周期波动时一个原本稳定的模型可能在几个月内快速失效。我养成的习惯是每个月固定做三件事看分数分布稳定性、做再训练评估、抽检人工催收反馈。第一件事是监控分数分布的稳定性核心指标是 PSI。计算方式是把训练集的预测分数作为基准分布把当月线上预测分数作为对比分布按十分位分箱后算两个分布的差异程度。PSI 小于 0.1 说明分布稳定0.1~0.25 需要关注大于 0.25 就要准备模型迭代。PSI 异常时先排查是不是上游特征出问题再考虑客群结构变化。第二件事是再训练评估。我普遍建议贷中模型按月或按季度重训不要迷信“一次训练用到天荒地老”。重训不是简单地把新数据追加进去重新跑一遍而是要比较新旧模型在不同分数段的表现尤其关注头部高风险人群的召回率是否下降。如果旧模型还在用半年前的阈值很可能已经漏掉了大量新增风险客户这就是“后悔药”也没有的损失。第三件事是人工校准。催收人员在实际跟进中经常会对模型打分提出质疑比如“这个客户电话一打就还了怎么被标成高分”这类反馈不能当耳旁风。我每个月会把催收记录里“高风险但一次还款即结清”的样本拉出来看模型是不是对某些职业、某些借款期限的群体系统性误判。如果误判集中在某个特征组合上说明模型还没学到这部分规律下次迭代时要补充相关字段或调整特征工程。一个更务实的技巧是用人工审核产生的标签做半监督迭代。催收过程中确认的真实违约样本是比模型预测更干净的标签来源。把每个月催收反馈里的“新增确认违约”和“明确误杀”作为下一轮训练的额外权重样本放进去模型会越来越少犯同样的错误。这样循环下来贷中模型才真正变成了业务的一部分而不是躺在 PPT 里的一个数字。这也是我这几年来最想分享的一点机器学习模型在金融里的价值不在算法本身而在你和业务方一起维护它的那套机制。希望帮到你。本文还有配套的精品资源点击获取
← 返回资讯列表 预约报考咨询 →
NEXT STEP

看完公告,下一步怎么走?

把报考交给靠谱的人:材料预审、批次抢报、考前辅导、复审提醒,全程有人跟。

进入报考专题