资讯动态

贷款违约预测实战:从特征工程到模型评估的完整流程

发布时间:2026/10/22 10:23:10 来源:尧图企业网站定制
1. 从能不能贷到会不会还违约预测到底在解决什么问题做过信贷业务的人都有一个共识审批通过只是开始真正决定这笔生意赚不赚钱的是放款之后借款人还不还钱。一个客户批了10万额度正常还款带来利息收入逾期不还则可能连本金都收不回来。所以风控体系里最核心的一环就是在放款前尽可能准确地判断这个人的违约概率。这就是贷款违约预测要干的事。我接触过不少刚入门数据挖掘的朋友一上来就问用XGBoost还是LightGBM但问他标签怎么定义、样本怎么选就答不上来了。这其实是本末倒置。违约预测本质上是一个二分类问题给定一个借款人的各项特征收入、负债、历史还款记录、征信查询次数等预测他未来是否会违约。听起来简单但金融场景有它自己的特殊性——样本极度不平衡、特征含义专业、对模型可解释性要求高、还要考虑业务成本。这篇内容适合三类人看一是刚转行做风控建模、想系统了解违约预测全流程的二是已经会调包跑模型、但不知道为什么这么做的三是想拿一个完整项目练手、充实自己作品集的。我会从数据理解一路讲到模型评估和业务落地中间穿插大量实操细节和踩坑经验。代码和数据集我会给出关键片段和构造思路你照着复现完全没问题。需要先说明一点金融风控模型和普通Kaggle比赛最大的区别在于比赛看AUC业务看的是钱。一个把坏人错判成好人的模型漏放和一个把好人错判成坏人的模型误拒在业务上的代价完全不同。这个观念要贯穿整个建模过程后面我会反复提到。2. 拿到数据集先别急着建模字段含义与业务逻辑的对应2.1 先搞清楚每个字段在业务里代表什么很多人拿到一份贷款数据集第一反应是df.info()看数据类型然后直接扔进模型。这是大忌。金融数据的每个字段背后都有业务含义不理解含义就没法做合理的特征工程。以常见的贷款违约数据集为例字段大致分几类。第一类是借款人基本信息比如年龄、职业、婚姻状况、教育程度。第二类是财务信息比如年收入、现有负债、贷款金额、贷款利率。第三类是信用历史比如历史逾期次数、信用账户数量、信用历史长度。第四类是本次申请信息比如贷款用途、申请渠道。我一般会做一张字段对照表把每个字段的中文含义、取值范围、业务解释都列清楚。这个习惯看起来笨但能帮你后面少走很多弯路。比如信用历史长度这个字段直觉上越长越好因为说明这个人有长期借贷记录且能维持但如果一个信用历史很短的人突然申请大额贷款反而要警惕。字段类型示例字段业务解读建模注意点基本信息年龄、职业反映稳定性职业需做类别编码财务信息年收入、负债比反映还款能力注意异常值和缺失信用历史逾期次数反映还款意愿强预测特征重点处理申请信息贷款用途反映资金去向部分用途风险更高2.2 标签定义决定了整个项目的成败违约预测里标签label怎么定义是最关键的一步没有之一。常见做法是借款人在放款后某个观察窗口内比如12个月出现连续逾期超过N天比如90天就标记为违约1否则为正常0。这里有几个坑。第一观察窗口的选择。窗口太短很多违约还没暴露出来正样本偏少窗口太长数据时效性差而且会引入提前还款这类干扰。行业里常用的是12个月或24个月。第二违约的界定标准。是逾期30天算违约还是90天这直接影响正负样本比例。第三表现期和观察期的划分。你不能用放款后才知道的信息去预测放款时的行为否则就是数据穿越模型在线上会彻底失效。我见过一个新手做的模型AUC高达0.95结果一上线就崩了。排查半天发现他把历史最大逾期天数这个字段用错了——这个字段是放款后更新的等于用未来信息预测过去。这种错误在金融建模里非常致命一定要在特征工程阶段就严格检查每个字段的时间属性。2.3 样本不平衡不是简单调个参数就完事违约样本通常只占全部样本的5%到20%极端情况甚至低于1%。这种不平衡会带来两个问题一是模型倾向于把所有样本都预测为正常因为这样准确率也有95%二是评估指标会失真。处理不平衡有几种思路。重采样过采样少数类或欠采样多数类最直接但过采样容易过拟合欠采样会丢信息。SMOTE这类合成少数类的方法在金融场景要慎用因为它生成的虚拟违约客户在业务上可能根本不存在会引入噪声。调整类别权重class_weight是比较稳妥的做法让模型在训练时对少数类更敏感。调整决策阈值则是从业务角度出发不追求0.5的默认阈值而是根据通过率和坏账率的平衡来定。我的经验是先不做任何平衡处理跑一个baseline看AUC和KS然后试class_weight对比效果最后再考虑采样。很多时候特征质量比采样方法重要得多。与其花时间调采样不如多花时间做特征。3. 特征工程才是拉开差距的地方3.1 缺失值处理金融数据里缺失本身就是信息金融数据的缺失值特别多而且缺失往往不是随机的。比如月收入缺失的人可能是自由职业者或不愿透露这类人群的违约风险本身就偏高。所以直接均值填充是最糟糕的做法等于把信息抹掉了。我的处理策略分三步。第一步先统计每个字段的缺失率缺失率超过80%的字段直接考虑丢弃。第二步对缺失率中等的字段做缺失指示变量即新增一列该字段是否缺失把缺失这个信息保留下来。第三步再对原字段做填充数值型用中位数比均值抗异常值类别型用未知单独作为一类。提示填充值的选择要结合业务。比如负债收入比缺失用中位数填充可能不合理因为不同收入段的人负债比差异很大可以考虑分组填充。3.2 数值型特征的离散化与分箱金融风控里连续变量分箱是常规操作原因有三一是业务上更可解释比如年龄25-35岁比年龄27.3岁好理解二是能处理非线性关系违约风险和年龄往往不是线性的三是能提升模型的稳定性。分箱方法有等距、等频、卡方分箱、决策树分箱等。我一般用卡方分箱因为它会考虑标签信息让每个箱内的违约率有显著差异。分箱后要做WOEWeight of Evidence编码把每个箱映射成一个数值。WOE的计算公式是WOE ln( (该箱内正常样本占比) / (该箱内违约样本占比) )WOE编码的好处是单调性好、对异常值不敏感而且天然处理了缺失值缺失单独一箱。IVInformation Value则用来衡量一个特征的预测能力IV大于0.3通常认为预测力强小于0.02基本可以丢弃。import pandas as pd import numpy as np def calc_woe_iv(df, feature, target): 计算单个特征的WOE和IV grouped df.groupby(feature)[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[good_rate] / grouped[bad_rate]) grouped[iv] (grouped[good_rate] - grouped[bad_rate]) * grouped[woe] iv grouped[iv].sum() return grouped, iv3.3 特征交叉让模型看到组合信息单看收入或负债都不够真正有区分度的是组合特征。比如负债收入比总负债/年收入比单独看两个字段强得多。再比如信用额度使用率已用额度/总额度这个指标高说明这个人对信贷依赖度高风险往往更大。我常用的交叉方式有几种。比值型A/B比如负债收入比。差值型A-B比如收入减月供。分箱组合把两个分箱后的特征做笛卡尔积看组合后的违约率。聚合型如果数据里有同一客户的多条记录可以做计数、求和、均值等聚合。这里要提醒一句特征交叉不是越多越好。交叉特征太多会导致维度爆炸而且很多组合在业务上没有意义。我一般会先做业务上说得通的交叉再用IV筛选保留IV高的。3.4 时间窗口特征风控模型的灵魂金融风控里最有价值的特征往往是时间窗口特征。比如过去3个月查询次数过去6个月最大逾期天数过去12个月平均还款金额。这些特征刻画的是借款人的行为轨迹比静态信息预测力强得多。构造这类特征需要原始的交易流水或还款记录。思路是以当前申请时间为基准往前推N个时间窗口对每个窗口做聚合统计。这里要注意时间对齐确保每个特征都只用申请时点之前的信息。# 示例构造过去3/6/12个月的查询次数特征 def build_time_window_features(df, base_date_col, event_date_col, windows[3,6,12]): result pd.DataFrame(indexdf.index) for w in windows: mask (df[base_date_col] - df[event_date_col]).dt.days w*30 result[fquery_cnt_{w}m] df[mask].groupby(df.index).size() return result实际项目中时间窗口特征往往能贡献模型一半以上的预测力。但它的构造也最耗时需要你对业务的时间逻辑非常清楚。4. 模型选型为什么风控偏爱逻辑回归和树模型4.1 逻辑回归可解释性的底线在金融风控领域逻辑回归LR至今仍是主流模型之一原因不是它准而是它可解释。监管要求金融机构能说清楚为什么拒绝这个客户LR的系数直接告诉你每个特征的贡献方向和大小这是树模型和神经网络做不到的。LR的另一个优势是稳定性好、不容易过拟合配合WOE编码后模型非常稳健。缺点是它只能捕捉线性关系对复杂交互无能为力。所以实践中常见做法是LR做baseline和可解释性兜底树模型做主力预测。用LR时要注意几点。第一特征要做WOE编码或标准化否则量纲差异会影响收敛。第二要检查多重共线性VIF大于10的特征要考虑剔除。第三正则化很重要L1可以做特征选择L2防止过拟合。4.2 树模型XGBoost、LightGBM、CatBoost怎么选树模型是当前违约预测的主力。XGBoost成熟稳定参数多、调优空间大LightGBM训练快、内存占用低适合大数据量CatBoost对类别特征处理友好不用手动编码。我的选型经验是数据量在百万级以下、特征以数值为主用XGBoost数据量大、追求训练速度用LightGBM类别特征特别多比如职业、地区用CatBoost。三者效果通常差距不大特征工程的质量远比模型选择重要。调参方面我一般按这个顺序先定learning_rate0.05到0.1和n_estimators配合早停再调max_depth3到8风控场景不宜太深和min_child_weight然后调subsample和colsample_bytree0.7到0.9最后调正则项lambda和alpha。风控场景要特别注意控制模型复杂度树太深容易过拟合而且可解释性差。import lightgbm as lgb from sklearn.model_selection import StratifiedKFold params { objective: binary, metric: auc, learning_rate: 0.05, num_leaves: 31, max_depth: 6, min_child_samples: 50, subsample: 0.8, colsample_bytree: 0.8, reg_alpha: 0.1, reg_lambda: 0.1, scale_pos_weight: 5, # 处理不平衡 verbose: -1 } skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) for train_idx, val_idx in skf.split(X, y): dtrain lgb.Dataset(X.iloc[train_idx], y.iloc[train_idx]) dval lgb.Dataset(X.iloc[val_idx], y.iloc[val_idx]) model lgb.train(params, dtrain, num_boost_round1000, valid_sets[dval], callbacks[lgb.early_stopping(50)])4.3 模型融合锦上添花而非雪中送炭模型融合能提升效果但提升幅度通常有限AUC提升0.005到0.01。常见方法有加权平均、Stacking、Blending。风控场景我更推荐简单的加权平均因为复杂融合会牺牲可解释性而且线上部署麻烦。我的做法是LR和LightGBM各跑一个用验证集上的KS或AUC确定权重做加权平均。如果两个模型相关性很高比如都是树模型融合意义不大如果一个是线性、一个是非线性融合往往有惊喜。5. 评估指标AUC只是起点KS和业务指标才是重点5.1 AUC、KS、PSI分别在看什么AUC衡量的是模型把正负样本分开的能力取值0.5到1越大越好。风控模型AUC能到0.75以上就算不错0.8以上算优秀。但AUC有个问题它对所有阈值一视同仁而业务上我们只关心某个特定通过率下的表现。KSKolmogorov-Smirnov是风控最常用的指标它衡量的是好坏样本累计分布的最大差值。KS大于0.3算可用大于0.4算良好。KS和AUC有一定相关性但KS更直观地反映了模型在最优阈值下的区分能力。PSIPopulation Stability Index衡量的是模型稳定性即训练集和测试集或不同时间段的样本的分布差异。PSI小于0.1说明稳定0.1到0.25需要关注大于0.25说明模型可能失效。风控模型上线后要定期监控PSI一旦超标就要重新训练。指标含义参考标准关注点AUC排序能力0.75可用整体区分度KS最大区分度0.3可用最优阈值表现PSI分布稳定性0.1稳定模型时效性坏账率业务结果越低越好实际盈亏5.2 混淆矩阵背后的业务成本AUC和KS是技术指标但业务关心的是通过率和坏账率的平衡。这就涉及混淆矩阵。把违约客户预测为正常FN漏放代价是本金损失把正常客户预测为违约FP误拒代价是损失利息收入。这两类错误的成本通常不对等。假设一笔贷款本金10万利息收入1万那么漏放一个坏客户的损失可能是10万误拒一个好客户的损失是1万代价比是10:1。这意味着模型应该更倾向于拒绝宁可误拒也不能漏放。所以决策阈值不能简单用0.5而要根据成本比来调整。我一般会画一条通过率-坏账率曲线横轴是通过率纵轴是对应通过率下的坏账率。业务方根据自己能承受的坏账率反推出应该设多少通过率再对应到模型的分数阈值。这个过程叫阈值切分是模型和业务衔接的关键一步。5.3 模型可解释性SHAP值怎么用树模型虽然准但黑箱属性让业务方不放心。SHAP值是目前最好的可解释性工具它能告诉你每个特征对每个样本预测结果的贡献。import shap explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_val) # 全局特征重要性 shap.summary_plot(shap_values, X_val) # 单个样本解释 shap.force_plot(explainer.expected_value, shap_values[0], X_val.iloc[0])SHAP的价值在于一是能验证模型学到的逻辑是否符合业务常识比如逾期次数越多违约概率越高二是能给业务方解释单个客户的拒绝原因三是能发现数据问题比如某个特征贡献异常大可能是数据泄漏。6. 上线之后才是真正的考验监控与迭代6.1 模型监控看什么模型上线不是终点。风控模型会随着时间推移而衰减因为客群在变、市场环境在变、竞争对手的策略也在变。监控主要看三个层面。数据层面监控入模特征的分布变化计算PSI。如果某个特征的PSI超过0.25说明这个特征的分布发生了显著变化模型可能不再适用。模型层面监控模型分数的分布看是否和训练时一致。如果分数整体偏移说明客群变了。业务层面监控实际坏账率、通过率、审批时效等。如果实际坏账率明显高于模型预测说明模型高估了客户质量。6.2 什么时候该重新训练模型不是PSI一超标就要重训。我的经验是先排查原因再决定动作。如果是个别特征分布变化可能只是数据采集问题如果是整体偏移才考虑重训。重训的频率取决于业务变化速度。消费信贷通常3到6个月重训一次房贷这类长周期产品可以一年一次。重训时要注意样本的时间窗口用最近的数据但也要保证有足够的坏样本表现期完整。6.3 一个容易被忽略的点模型和策略的配合模型输出的是违约概率但最终决策是策略做的。策略会综合模型分数、硬规则比如年龄不符直接拒、额度策略、定价策略等。模型好不代表业务好如果策略设计不合理再好的模型也白搭。举个例子模型给某个客户打了高分低风险但硬规则发现他最近一个月查询了20次征信这种多头借贷信号是强风险策略应该直接拒绝而不是听模型的。所以模型和策略是互补关系模型负责精细区分策略负责兜底和拦截明显风险。7. 几个我踩过的坑和实操建议第一个坑是数据泄漏。前面提过但值得再强调。任何在放款后产生的字段都不能用作特征。我一般会维护一个可用特征白名单只有明确在申请时点就能获取的字段才允许入模。第二个坑是过拟合验证集。调参调太多次模型在验证集上表现很好但测试集一塌糊涂。解决办法是留一个完全独立的测试集只在最后评估时用一次中间调参只用训练集和验证集。第三个坑是忽略业务周期。金融数据有很强的周期性比如年底资金紧张违约率会上升。如果训练数据只覆盖了某几个月份模型可能学不到周期性规律。构造特征时可以考虑加入月份、季度等时间特征。第四个坑是特征重要性误导。树模型的特征重要性是使用次数或增益不代表因果。一个特征重要性高可能只是因为它和标签有相关性但业务上不可干预。真正有价值的是可干预的特征比如额度、利率这些能通过策略调整来改变结果。最后一个建议多做baseline少追求花哨。我见过太多人一上来就上深度学习、上复杂融合结果不如一个调好参的LightGBM。风控建模的核心竞争力在特征工程和业务理解模型只是工具。把数据理解透、把特征做扎实、把评估做严谨比什么都重要。代码和数据集方面建议你找一份公开的贷款违约数据很多数据平台都有按照本文的流程走一遍先做EDA理解字段再定义标签然后做特征工程重点做WOE编码和时间窗口特征接着跑LR和LightGBM对比最后用KS和PSI评估。走完这一遍你对违约预测的理解会完全不一样。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑