资讯动态

逻辑回归构建贷款违约预测模型:从特征工程到评分卡落地

发布时间:2026/10/17 9:44:53 来源:尧图企业网站定制
前阵子业务侧找到我说某个小额信贷产品的通过率压了好几轮坏账率却始终没降到预期希望我把审批模型重新做一遍。聊到后面就很明确这个场景下第一优先落地的是个人贷款违约预测模型而且大概率要用逻辑回归打底。不是因为它简单恰恰是因为在这个业务里它最符合当下对可解释、可监控、可迭代的要求。这篇文章就以这个项目为主线把逻辑回归做贷款违约预测的整个链路聊一遍从数据怎么取、特征怎么做、模型怎么训到评估指标怎么选、怎么把模型分数翻译成业务能用的审批策略。适合刚入行风控建模的数据分析、想转风控策略方向的同学以及被业务方追问“模型能不能解释一下”的算法工程师。1. 项目背景与建模思路1.1 为什么个人贷款违约预测先选逻辑回归先回答最常被问的一个问题现在有随机森林、XGBoost、神经网络为什么还要先做逻辑回归我的答案很直白——因为风控场景对模型的要求排序第一的不是精度而是可解释性和可控性。业务人员问“为什么拒绝这个人”你不能回答“模型算出来的”。金融机构、审计、合规、贷后催收每一环都需要你讲清楚“哪个特征、往哪个方向、推了多少分”。逻辑回归天然就是一个线性加权模型系数就是变量对违约概率的影响方向标准化之后系数大小还能横向比较特征贡献。另外逻辑回归还有一个被低估的优势训练快、上线轻、稳定性好。特征数量在几十个以内、样本量几万到几十万时逻辑回归跑起来几乎是实时的。相比复杂模型它对特征分布漂移的敏感度更低尤其在样本量不足或者噪音很大的信贷数据里逻辑回归往往比复杂模型更抗造。换句话说逻辑回归不是“老土”而是风控建模的基准线和压舱石。新模型上线前业务方最常问的一句话就是“相比逻辑回归你的方案AUC能高多少、坏账率能降多少”没有这个baseline后面的机器学习模型很难有说服力。1.2 这个项目到底在解决什么问题贷款违约预测本质上是一道二分类题给定借款人的历史信息预测其在未来一段时间内是否会逾期超过一定天数。放在信贷业务的审批环节模型输出的违约概率会直接影响三件事批不批、批多少、利率定多少。更细一点划分风控模型覆盖贷前、贷中、贷后三个阶段。贷前是信用审核也就是这个项目的核心场景贷中做额度管理和预警比如发现某用户多头借贷猛增系统自动降额贷后做催收策略比如把违约概率高的客户排在催收名单前面提高触达频率。这个项目聚焦贷前审批。业务方给的历史数据包括用户基础信息、收入负债情况、历史借贷行为、查询次数、还款表现等目标变量是“放款后6个月内是否发生90天以上逾期”。这个口径不是拍脑袋定的必须在项目一开始就和业务方对齐。有一个容易被忽略的坑逾期定义。M1逾期1-30天M2逾期31-60天M3逾期61-90天。如果你拿M1当违约目标样本里“坏客户”会特别多模型大概率学到的不是“还款恶化”而是“暂时手头紧”。所以我通常建议用M3作为违约口径既代表真实信用恶化又有足够的样本支持建模。1.3 技术选型与整体建模流程这个项目我用的是Python技术栈主要依赖三件套pandas做数据处理、scikit-learn做建模评估、matplotlib和seaborn画分布图。如果你用的是R或者SQL加Excel思路也完全一致核心是特征和口径标签清洗跑不掉。整体流程可以拆成六步。第一步拿数和口径对齐确定样本窗口、表现期窗口、特征截至时间点。第二步数据清洗去重、补缺失、处理异常值。第三步特征工程把原始字段组合成更有业务含义的变量比如收入负债比、近3个月信贷查询次数。第四步特征筛选用相关性、IV值、VIF把无效变量剔掉。第五步建模训练划分训练集验证集标准化后跑逻辑回归。第六步评估与落地看混淆矩阵、AUC、KS输出分数阈值建议。这六步听上去平平无奇但每一步都有细节。后面我把实际项目里踩过的坑和解决方式全部展开讲尤其是特征筛选和阈值选择这两个地方最容易被赶进度忽略。2. 数据准备与特征工程2.1 业务口径与样本定义先看数据。我这次用的是某信贷平台过去一年的放款记录选取表现期已结束的样本也就是说每笔贷款从放款到现在至少过了6个月。清洗后可用的样本大约8万条真实违约率7%左右。这5%到10%的违约率是个人无抵押小额贷款的典型水平。如果抵押贷款违约率往往在1%到3%如果是信用卡客群又可能到15%以上。拿到数据第一步永远是画违约率时间分布确认没有明显的季节性或者业务口径切换否则模型上线一段时间后大概率漂移。样本字段大致分四类身份信息、财务能力、借贷历史、行为特征。我整理了一张表展示核心字段和业务含义实际业务里字段远不止这些但这几类是最常用的骨架。字段名类型业务含义示例值age数值年龄32income数值月收入元12000education分类学历等级本科marriage分类婚姻状态已婚house_status分类居住状态自有住房loan_amount数值申请金额元50000installment数值月还款额元2800debt_ratio数值负债收入比0.42credit_queries_3m数值近3个月征信查询次数4overdue_6m数值近6个月逾期次数1utilization数值信用卡额度使用率0.38target标签6个月内是否M3违约0/12.2 数据清洗与异常处理数据清洗看着琐碎却直接影响模型效果。我按优先级做三件事先查重复样本再处理缺失值最后砍异常值。重复样本容易出现在多表关联时比如同一个客户被授权机构字段拆成多条记录。我用主键去重同时保留最后一次申请的信息。缺失值处理分情况讨论对年龄、收入这类数值型连续变量用中位数填充比用均值更稳因为收入分布右偏均值会被高收入人群拉高对学历、居住状态这类分类变量单独设一个“未知”类别比用众数填充更合理。异常值要当心。收入为负、年龄大于80、月还款额大于月收入好几倍这些要么是录入错误要么是异常申请。我采用两个策略明显的逻辑错误直接剔除数值落在99.9%分位数以上的做截断处理比如收入顶部截断到99.9分位。一个很重要的原则缺失率太高的变量直接放弃。如果某个字段缺失率超过70%用它建出来的特征会引入大量噪声。我这次数据里“工作单位性质”缺失率接近一半本来想用来做特征后来发现填充后反而拉低了模型AUC果断放弃。2.3 特征构造与筛选原始字段可以直接进模型但效果一般。借款人的还款意愿和能力往往藏在组合特征里。我构造了几个核心特征。收入负债比把月还款额除以月收入这个比值越高说明每月可支配收入越紧违约风险越大。信贷查询次数近3个月征信查询次数代表借款人近期资金饥渴度频繁查询说明可能同时在向多个平台借钱也就是多头借贷风险。额度使用率信用卡已使用额度除以总额度使用率长期80%以上的人资金链往往比较紧张。特征筛选我常用三个指标配合相关性和VIF处理共线性、IV值评估单个特征的区分度、业务逻辑判断变量是否可用。IV值的直观含义是“这个特征能把好客户和坏客户分开多少”经验参考区间我列在这里IV值区分能力小于0.02基本无区分能力剔除0.02-0.1弱区分能力谨慎使用0.1-0.3中等区分能力保留0.3以上强区分能力但需检查是否有未来信息我这次筛下来收入负债比、近3个月信贷查询次数、近6个月逾期次数这三个特征IV值都在0.15以上是模型的绝对主力。2.4 样本不平衡问题怎么处理7%的违约率意味着正负样本比例大约1:13。如果不做任何处理逻辑回归会“偷懒”——把所有样本都预测成好客户也能达到93%的准确率。这是新手最容易栽跟头的地方。我的处理方法分三步。第一步给模型加class_weight让少数类在损失函数里占更高权重scikit-learn里直接在LogisticRegression里设置class_weightbalanced即可。第二步是过采样用SMOTE在训练集里合成一些违约样本注意只能在划分训练集之后做否则验证集里会混入合成样本评估结果虚高。第三步也是最关键的一步把决策阈值从默认的0.5往下调比如调到0.3甚至0.2。阈值调整的核心逻辑是宁可多拦截一些边缘客户也不放过可能的坏客户。具体调多少要看业务收益和损失的平衡这个我在第4章详细算给你看。3. 逻辑回归原理与模型训练3.1 为什么线性回归做不了二分类很多人一开始会想既然逻辑回归是线性模型那直接用线性回归预测违约概率行不行答案是不行原因有二。第一线性回归的预测值域是负无穷到正无穷但概率必须在0到1之间。你不小心就会预测出“违约概率1.3”这种毫无意义的结果。第二线性回归对离群点极其敏感。收入10万的人和一个收入1万的人在线性回归里会拉出一条非常极端的拟合线把正常收入群体的预测完全带偏。逻辑回归的做法是套一个Sigmoid函数把线性组合的结果压缩到0到1之间。公式长这样p 1 / (1 e^(-z))其中 z β0 β1x1 β2x2 ... βpxp当z趋向正无穷时p趋近1当z趋向负无穷时p趋近0。这个函数不仅把值域锁住了还天然具备“中间敏感、两端饱和”的特性接近0.5的样本概率变化最明显正好符合风控要精细区分中部人群的需求。3.2 损失函数、正则化与优化逻辑回归用的是交叉熵损失函数。直观理解是如果样本真实标签是1模型给出的概率越接近1损失越小概率越接近0损失呈指数增长。反过来对标签0也一样。L(β) -Σ [yi * log(pi) (1 - yi) * log(1 - pi)]训练过程就是通过梯度下降不断调整β让总损失最小化。为了防止过拟合还要加正则化项。常用的是L2正则化也就是Ridge正则化强度由超参数C控制C越小惩罚越大。项目里我踩过一个坑刚开始max_iter用默认的100结果模型报警说没收敛。原因很简单特征量纲差异大比如收入几万、信贷查询次数只有几次梯度下降要走很久。解决办法是先标准化特征再看收敛。这也是为什么第3.3节的代码里标准化的地位很高。3.3 建模实操从数据划分到模型训练下面这段代码就是本项目里实际建模部分的核心可以直接照着改。import pandas as pd from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression df pd.read_csv(loan_sample.csv) features [ income, age, debt_ratio, credit_queries_3m, overdue_6m, utilization, loan_amount, installment ] X df[features] y df[target] # 按目标变量分层划分确保训练集和验证集里违约比例一致 X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.3, stratifyy, random_state42 ) # 标准化只fit训练集验证集用同一套参数transform scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_val_scaled scaler.transform(X_val) model LogisticRegression( penaltyl2, C1.0, class_weightbalanced, solverliblinear, max_iter1000, random_state42 ) model.fit(X_train_scaled, y_train)这里有三个细节都很关键。分层采样stratifyy保证训练集和验证集都保持7%的违约率否则可能验证集里违约样本特别少评估结果波动大。标准化时先fit训练集再用同一个scaler去transform验证集这是防止数据泄漏的基本功很多人直接在全部数据上做标准化会轻微放大模型效果。solver选liblinear是因为它在中小样本上速度快而且支持L2正则和二分类项目数据量8万条完全够用。训练完成后模型的系数通过model.coef_输出截距通过model.intercept_输出。比如coef_里debt_ratio的标准化系数是0.63意思是收入负债比每提高一个标准差违约的log odds就增加0.63。4. 模型评估与参数调优4.1 混淆矩阵与基础指标模型训练完第一件事不是看准确率而是看混淆矩阵。只有理解这四个格子才能真正看懂后面所有指标。预测为违约预测为正常实际为违约TP真正例FN假负例实际为正常FP假正例TN真负例准确率是(TPTN)/全部样本。但在违约率7%的数据里就算模型把所有样本都判为正常准确率也有93%。所以风控场景重点关注两个指标精确率是预测为违约的里面真正违约的比例衡量“拦得准不准”。召回率是真实违约样本里被成功预测出来的比例衡量“拦得全不全”。这两者天然矛盾阈值往低调召回率上去、精确率下来阈值往高调精确率上去、召回率下来。项目里业务方问得最多的是“你们模型能抓出多少坏客户”这时候我优先看召回率。实际落地时我会把阈值定在召回率70%-80%的位置再根据预算调整精确率。4.2 ROC曲线、AUC与KSROC曲线和AUC是评估排序能力的标准工具。ROC的横轴是假正率FPR纵轴是真正率TPR每个不同阈值都对应曲线上一个点。AUC就是曲线下的面积表示随机抽一个好样本和一个坏样本模型把坏样本判得更坏的概率。AUC等于0.5相当于瞎猜0.7到0.8算可用0.8以上算优秀。我这个项目最后AUC做到0.82在逻辑回归里已经不错了。我还会额外看一个风控专属指标KS值。把样本按模型预测概率从高到低排序分成10等份逐桶计算累计好样本占比和累计坏样本占比两者差值的最大绝对值就是KS。KS大于0.3说明模型区分度可接受大于0.4是很好。我的模型KS做到0.41对应AUC 0.82这是行业里比较典型的对照关系。下面这段代码画出ROC曲线并计算AUCfrom sklearn.metrics import roc_auc_score, roc_curve import matplotlib.pyplot as plt y_prob model.predict_proba(X_val_scaled)[:, 1] auc_score roc_auc_score(y_val, y_prob) fpr, tpr, thresholds roc_curve(y_val, y_prob) plt.plot(fpr, tpr, labelfAUC{auc_score:.3f}) plt.plot([0, 1], [0, 1], linestyle--, colorgray) plt.xlabel(False Positive Rate) plt.ylabel(True Positive Rate) plt.legend() plt.savefig(roc_curve.png)注意别只用AUC一个指标做决策。AUC高代表排序能力强但业务真正关心的是在哪个概率阈值上审批、拒掉多少人、遗漏多少坏账。这就引出下一个问题。4.3 阈值选择不能只看0.5逻辑回归默认用0.5做分类边界但风控业务里这个阈值几乎从不等于0.5。原因是两类错误成本完全不对称把一个好客户误拒损失的是这笔贷款的利息收入把一个坏客户放进来损失的是本金加上催收成本往往几十倍于利息。这里需要引入简单的业务测算。假设贷款金额5万元好客户12个月还清平台收益率8%坏客户违约后平均回收率50%也就是坏账损失约2.5万元。那么每拒绝1万个好客户损失收入约40万元每拦截1万个坏客户节省坏账2.5亿元。差距是六百多倍。在这个假设下把阈值从0.5降到0.3虽然精确率下降了多误拒一些好客户但整体坏账损失大幅下降。我实际测算时发现阈值调到0.25左右平台综合收益最高这个位置对应的召回率约75%精确率约30%。也就是说预测为违约的客户里大约三成是真的坏客户但75%的真实坏客户都能被拦下来。用数学表达当 p / (1-p) 好客户收益 / 坏客户损失 时就应该拒绝。阈值不是模型定的是业务成本和收益的比例定的。5. 结果解释与业务落地5.1 系数怎么解读才真正有用逻辑回归系数可以直接换算成Odds Ratio。Odds是违约概率与不违约概率之比即p/(1-p)。系数β对应的Odds Ratio就是e^β含义是“该特征每增加一个单位违约odds变为原来的e^β倍”。我这轮模型的几个核心特征系数示例特征标准化系数Odds Ratio解读近6个月逾期次数0.852.34逾期次数增加1个标准差违约odds翻倍以上收入负债比0.631.88负债压力越大违约风险明显上升信贷查询3个月次数0.511.67近三月查询次数偏多多头借贷风险增加年龄-0.220.80年龄越大违约风险略有下降信用卡额度使用率0.411.51额度使用率高说明资金链紧张标准化系数可以直接比较特征重要性。这个模型里近6个月逾期次数影响最大信贷查询次数紧随其后。跟业务同事汇报时我会说第一看历史还款表现第二看近期资金饥渴度第三看负债收入压力。这样说业务方马上能懂。5.2 从模型概率到标准评分卡光给业务方一个违约概率还不够大部分机构喜欢用评分卡展示结果分数越高代表风险越低比如600分算及格线。我用的换算公式是Score Offset Factor * ln(odds)其中Factor表示Odds翻倍时分数增加的幅度PDO设为50那么Factor 50 / ln(2) ≈ 72.13。再定一个基准分比如当odds 1时分数为600那么Offset 600。于是Score 600 72.13 * ln(p / (1 - p))直观算一下如果模型输出违约概率p0.07odds约0.075ln(odds)约-2.59分数约413。如果p0.5odds1分数正好600。p0.8odds4ln(4)约1.39分数约700。分数越低风险越高审批策略里就可以设一个拒绝线比如低于380直接拒绝380到450转人工审核。这里有一个经验不要直接用连续概率去映射实际落地时通常先对特征做分箱和WOE编码再在分箱层面上算评分权重。好处是不光模型稳定还能把每个分数段对应的人群画像直接描述给业务方看别小看这步审批策略的负责人最需要的就是“分数段人群解释”。5.3 上线评估与监控指标模型上线不等于工作结束。我每次都会给监控报表里放三个核心指标缺一不可。第一个是PSI用来衡量线上最近一个月评分分布与建模样本评分分布的偏移程度。PSI小于0.1说明分布稳定0.1到0.25说明有明显偏移需要关注超过0.25基本就要考虑重训或复盘。第二个是AUC衰减用线上已表现出结果的样本回算AUC如果从0.82掉到0.75以下说明模型区分度在弱化。第三个是业务指标通过率、坏账率、迁移矩阵也就是同一个客户在新老模型下的审批结果变化比例。人口结构变化、经济周期波动、平台自身客群策略调整都会让模型效果衰减。有一个案例是某平台缩小了低龄客群投放线上评分整体上移看起来模型变准了其实是客群变了。这种虚假繁荣最容易让人忽略监控的必要性。6. 常见问题与坑位实录6.1 特征共线性让系数符号变“怪”我第一版模型训练完发现月收入的系数是负数也就是说收入越高越容易违约业务方看到直接质疑模型是不是反了。排查下来是共线性的问题月收入跟授信额度、负债比这些变量高度相关多个变量抢同一段信息导致系数符号不稳定。处理办法是先把特征相关系数矩阵打出来看哪些变量相关系数大于0.7再计算VIFVIF大于10的变量优先删除或者做合并。最终我把月收入、授信额度两个变量做了融合构造了一个更干净的授信额度收入比特征系数符号立刻正常了。6.2 模型“永远预测不违约”怎么办如果你训练出来的模型预测违约率最高的样本也只有0.1那大概率是样本不平衡没处理好。逻辑回归非常容易被多数类主导默认情况下它找到的规律就是“全判好”。这个问题的标准解是三步组合拳class_weightbalanced 训练集过采样 阈值下调。做完这三个动作之后模型输出的概率分布会整体上移排序能力也会变好。但有一个前提过采样只用到训练集验证集必须保持真实分布否则你看到的AUC是虚的。6.3 特征穿越与数据泄漏建模过程中最容易出致命错误的就是特征穿越也就是用了不该出现在同一时间线上的信息。比如预测目标是“放款后6个月是否违约”特征里却包含了“当前逾期状态”这种已经对未来做出反应的字段等于提前给了模型答案。训练时AUC能到0.9以上上线后就瘫了。排查方法很朴素对每个特征按特征时间截止点重新处理一遍确保建模时只使用截至申请日之前就能获得的信息。我还会做一个独立的时间序列验证用上半年数据训练下半年数据验证如果AUC掉得离谱大概率是时间窗口或者特征穿越出了问题。6.4 上线后效果与迭代节奏模型上线前我会跟策略团队一起做冠军挑战者测试。新模型作为挑战者只切5%流量线上老策略作为冠军继续承接95%跑一个月对比通过率和坏账率。这里要注意一个统计陷阱5%流量里的违约样本量往往很少一个月不够至少要积累两到三个月的表现期数据再下结论。这位挑战者模型最终帮平台在同等通过率下把坏账率压低了约12%。幅度看着不大但放到几千万的贷后余额里一年避免的损失非常可观。这也是为什么即使逻辑回归看起来“不够智能”在风控领域依然有不可替代的位置。项目做完后我最大的感受是模型能不能拿高分七成工作在前面特征和口径占五成评估和落地占两成真正写代码只占三成不到。尤其是逻辑回归它的上限完全取决于你喂给它的特征质量。先静下心跟业务聊明白“什么叫坏客户”比急着调参重要得多。最后分享一个每次建模我都会做的小动作留出最近两个月的样本不参与训练单独当成一个隐藏验证集。这个隐藏集上的AUC和KS如果跟正式验证集差太多说明模型有时间过拟合风险。多花这半小时能省掉上线后跟业务方解释的无数精力。

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

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

免费获取报价 →
↑