Python机器学习信用评估实战:从评分卡到风控模型上线
简介这套资源基于 Python 机器学习实现个人信用评估以阿里天池贷款违约预测比赛数据集为对象该数据集包含超过 120 万条贷款记录和 47 列特征变量其中 15 列为匿名脱敏字段资源面向机器学习初学者、数据挖掘课程设计与竞赛选手帮助解决从数据清洗、特征工程、可视化分析到模型训练与评估的完整建模流程。包内共 82 个文件包含 13 个 Python 脚本、62 张特征分布与相关性热力图、2 个 CSV 数据文件、1 份 Word 设计报告及配置辅助文件压缩包仅 5.16MB按 scripts、src、imgs 等模块组织目录层级清晰便于按功能查阅和复用。目前已有 742 人学习。读者可获得可直接运行的源码、预处理与特征工程脚本、全套可视化图表及配套设计报告具体涵盖缺失值填充、类别编码、PCA 降维、网格搜索调参、多模型训练评估与预测提交等关键环节。既适合作为高校课程设计的完整交付物也适合作为贷款违约预测竞赛的入门参考资料。1. 个人信用评估为什么银行那套评分卡现在被Python机器学习换掉了做过信贷风控的人都有体会传统评分卡用逻辑回归加十几个WOE变量一用就是好几年。但今天个人信贷的场景变了借呗、白条、消费金融审批请求是秒级的客户的行为数据维度是几百上千的再用人工调权重、手工切分箱根本排不过来。Python机器学习这波之所以能接手个人信用评估不是因为它比评分卡“高级”而是它能用同样的数据做更细的切分、更快的迭代——把逾期预测从“看征信报告”推进到“看全量行为轨迹”。这篇文章不是讲理论是讲一个能落地的评估模型怎么从一个zip包里搭起来数据怎么清洗、特征怎么做、模型怎么选、上线前怎么验证以及最容易让项目翻车的那几个坑。2. 信用评估问题的定义与数据准备先搞清楚你在预测什么2.1 二分类还是回归逾期标签怎么定才算合理个人信用评估在机器学习里通常被建模成一个二分类问题借款人未来一段时间内会不会逾期。这个“会不会”不是拍脑袋而是有行业惯用口径的。常见做法是定义“坏客户”为逾期90天以上或者“M3”即连续逾期三期宽松一点用30天严格一点用120天。标签定义直接决定模型学到的语义——如果你把30天逾期也算坏那模型会去抓那些“忘了还”的人而不是真正没能力还的人如果只用90天样本量可能太少训练不出来。我一般会先看业务上催收和坏账的拐点在哪里。比如某家小贷公司发现逾期超过45天后的回收率急剧下降那45天就是天然的标签边界。定了标签之后还要定“表现期”给模型一个观察窗口比如用过去6个月的数据做特征预测未来3个月内是否逾期。这个窗口错开很重要否则就会用到未来信息。数据集方面如果刚起步没有自带的业务数据可以用公开数据练手。常见的有UCI的German Credit、Statlog还有一个叫“Give Me Some Credit”的Kaggle数据集约15万条还款记录字段包括年龄、收入、负债率、逾期次数等。这些数据结构简单正好适合跑通贷款预测全流程。要注意的是公开数据没有真正的时间戳做不了严格的时序切分拿到后最好自己加一个模拟的申请日期以便后续做时间外验证。2.2 数据清洗第一步把缺失值和异常值按“拒绝逻辑”处理信用数据里最典型的问题是缺失值大量集中在“收入”“工作年限”这种敏感字段。很多人一看缺失就删行这在信贷场景是大忌那些缺失收入的人很可能是自由职业或者高风险人群直接删掉等于把坏样本从训练集里扔了。正确的做法是先看看缺失率和缺失群体的逾期率。如果缺失组的逾期率明显高于完整组那“是否缺失”本身就是一个特征应该单独编码。异常值也一样。比如年龄字段出现200岁、负债率出现负数这些多半是录入错误直接用上下限截断即可。但像年收入出现99999这种可能是系统默认值而不是真实异常需要和业务确认。我写清洗脚本时会先跑一个describe然后对每个数值型特征画分布图肉眼过一遍再决定截断还是置空不做自动替换。import pandas as pd import numpy as np def clean_credit_df(df): # 先处理明显非法值年龄不在18-100区间的置为NaN df.loc[(df[age] 18) | (df[age] 100), age] np.nan # 负债率超过100%说明录入错误但也可能是极端负债先用封顶处理 df.loc[df[debt_ratio] 0, debt_ratio] np.nan df[debt_ratio] df[debt_ratio].clip(upper1.5) # 为缺失率高的字段生成是否缺失标记 for col in [income, work_years]: df[col _missing] df[col].isna().astype(int) return df这里的逻辑是先修非法值再补缺失的“标记”。注意clip的上限不是拍脑袋要看分布如果负债率的中位数是0.3那1.5已经是很极端的尾部值封在这里不影响大多数样本。等你跑完这步再去填充缺失值我会用中位数填充数值型用众数填充类别型但填充完保留missing标记。这么做的原因是信用卡模型后续要计算WOE而WOE本身要求每个分箱里有足够的坏样本占比缺失单独成箱反而更利于控制风险。2.3 特征工程从原始字段到WOE分箱信用评估里最经典的特征处理是WOEWeight of Evidence编码它本质上是对特征做有监督的分箱然后计算每个分箱的好坏占比。为什么不用one-hot因为信贷数据里有大量稀疏类别比如职业类型有几十种one-hot会让特征维度爆炸而且无法表达“某种职业的风险高低顺序”。WOE的公式是WOE_i ln(好客户占比 / 坏客户占比)这个值越大代表该分箱内的客户风险越低。建模时把原始值替换成WOE逻辑回归的系数解释性就很强——比如某特征的WOE系数为负说明该分箱风险高于平均。分箱方法一般用卡方分箱或决策树分箱两个都能找出和目标变量关联最紧密的切点。def calc_woe(df, feature, target, bins10): # 先用等频分箱保证每个箱里有足够样本 df[bin] pd.qcut(df[feature], qbins, duplicatesdrop) grouped df.groupby(bin, observedTrue).agg( good(target, lambda x: (x 0).sum()), bad(target, lambda x: (x 1).sum()) ) grouped[good_pct] grouped[good] / grouped[good].sum() grouped[bad_pct] grouped[bad] / grouped[bad].sum() # 用0.0001平滑避免除以零 grouped[woe] np.log(grouped[good_pct] / grouped[bad_pct].clip(lower0.0001)) return grouped[woe]这段代码的qcut等频分箱只是起步。实际项目中我通常先用决策树找最优切点再人工合并箱数少于5%的箱。qcut的缺点是对长尾分布不友好比如收入特征可能90%的人集中在低分段等频分箱会把高收入的人切成几十个空箱。稳妥的做法是在分箱前先对收入做对数变换或者直接用百分位加人工干预。3. 从逻辑回归到集成模型选型理由与可复现代码3.1 逻辑回归为什么还是信用评分的基线即使现在XGBoost、LightGBM满地走信用评估领域给监管报备的时候逻辑回归依然是默认选择。原因不是精度而是可解释性——模型要回答“为什么拒绝这个用户”逻辑回归的系数天然能给出答案。逻辑回归对特征单调性要求严格所以上面的WOE转换恰好能保证这一点WOE值本身就是单调变化的回归系数正负对应风险方向。你要是直接用原始收入做变量逻辑回归会认为“收入越高违约越低”但实际数据里可能存在高收入但高负债的群体单调性假设就崩了。所以我的习惯是先跑一个逻辑回归作为基线算AUC和KS再去试LightGBM看提升幅度是否大到值得放弃可解释性。如果提升不超过3%我会留在逻辑回归如果提升超过8%那就上集成模型然后用SHAP做解释。3.2 用LightGBM训练违约预测模型核心代码与参数import lightgbm as lgb from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score # 假设X是WOE编码后的特征y是0/1标签 X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, random_state42, stratifyy ) # 为不平衡样本设置权重坏样本权重 好样本数 / (坏样本数 * 2) weight len(y_train) / (2 * np.bincount(y_train)[1]) model lgb.LGBMClassifier( objectivebinary, learning_rate0.05, n_estimators300, num_leaves31, max_depth5, min_child_samples20, subsample0.8, colsample_bytree0.8, class_weight{0: 1, 1: weight}, random_state42 ) model.fit( X_train, y_train, eval_set[(X_val, y_val)], eval_metricauc, callbacks[lgb.early_stopping(stopping_rounds50)] ) val_auc roc_auc_score(y_val, model.predict_proba(X_val)[:, 1]) print(fval_auc{val_auc:.4f})这里几个参数值得说。learning_rate设0.05而不是默认0.1是为了配合early_stopping防止过拟合num_leaves31是LightGBM作者的推荐值对应max_depth5的树结构再大容易过拟合小样本。min_child_samples20是防止叶子节点样本过少导致统计不稳定——信贷数据里坏样本本来就少叶子里的坏样本数可能只有个位数低于20算出来的置信区间太宽。class_weight里给坏样本加权只是处理不平衡的一种方式。更常用的是设置scale_pos_weight但我直接用class_weight因为LightGBM接口里这两者不能同时优雅共存。你可以对比下两者在验证集上的KS值差别通常不大。3.3 样本不均衡的三个处理方向从数据到目标函数信贷场景坏样本比例一般5%以下直接训练会使模型把所有样本预测为好客户AUC看着也还凑合因为真正量大的好样本都被猜中了。处理不均衡有三个方向按效果排序是调整损失函数权重、欠采样/过采样、使用AUC或KS作为早停指标而不是logloss。调整损失函数权重最简单直接给坏样本更高的惩罚系数即可。欠采样是随机删除好样本达到1:5甚至1:10的比例。欠采样的缺点很明显删掉大量好样本会让模型对好样本的覆盖度下降。过采样用SMOTE则容易生成和真实分布不一致的样本在信用数据里可能伪造出“高收入且高逾期”这种矛盾样本我很少用。更稳的是直接用LightGBM自带的is_unbalance参数它等价于让模型自动调整正负样本权重适合快速跑通但解释性稍差。我实际项目里最喜欢的做法是用全量数据训练不欠采样把class_weight设成坏样本比例的倒数然后调高decision_threshold。也就是模型输出概率后不按0.5切而是取坏样本率的百分位数作为阈值这样能同时保住好样本的召回和坏样本的识别率。4. 模型评估与评分卡转换不只是看准确率4.1 KS、AUC、PSI信贷风控里该盯哪三个指标不要迷信准确率因为正负样本1:20的数据集全预测为好准确率也有95%。信贷领域主流看三个指标AUC描述模型区分好坏客户的能力0.7以上算可用0.8以上算优秀。KS是AUC的另一种表达它测量的是好坏样本累计分布的最大距离通常风控要求KS大于0.3才敢上线。PSI则是模型稳定性指标用来监测模型上线后每月评分分布和开发时的差异PSI超过0.2就说明样本漂移严重需要重新训练。from scipy.stats import ks_2samp def compute_ks(y_true, y_pred_proba): # 把预测概率分成好/坏两组算两组累计分布的最大差 good y_pred_proba[y_true 0] bad y_pred_proba[y_true 1] ks_value ks_2samp(good, bad).statistic return ks_value这里的ks_2samp直接给出两组分布的KS统计量注意取值在0到1之间与风控常说的KS值一致。但实际项目中我们往往还要按概率分箱计算每一箱的累计好样本占比和累计坏样本占比然后画两条曲线看最大垂直距离落在哪个概率区间。如果最大距离出现在高风险区间说明模型能在最危险的客户上拉开差距。4.2 把模型概率映射成信用分从600分到900分怎么做模型输出的是概率但业务方要的是“信用分”。最常见的转换是用逻辑回归的log(odds)做线性映射公式是score base factor * (log(odds))其中odds p/(1-p)。根据业务偏好可以设定“每增加20分odds翻倍”这样factor20/ln(2)。个人信用评估里更简单的做法是直接对LightGBM输出的概率做分位映射但这种方法解释性差不容易向监管说明。我见过的生产方案还是转化回log(odds)。def prob_to_score(prob, base_score600, pdo20, base_odds50): base_odds为基准概率对应的odds比如0.02 - odds0.0204 odds prob / (1 - prob 1e-9) factor pdo / np.log(2) score base_score factor * np.log(odds / base_odds) return int(round(score))这段代码的精髓在于初始分数的设定。base_odds对应基准客户的违约概率假设整体坏样本率是2%那odds就是0.0204。base_score设600分是行业惯例表示“基准风险分”。之后每增加Pdo分odds翻倍分数越高风险越低。注意概率如果过于极端比如0.999log(odds)会很大得分可能上万所以上线时要对score做截断比如封顶900分、保底300分。5. 个人信用评估模型的五个常见坑现象、原因、解决5.1 时间穿越用未来数据预测过去现象模型在训练集上AUC高达0.95但验证集只有0.6甚至线下测试完全跑不通。原因特征里包含了表现期之后才发生的数据。比如你用“过去6个月的还款记录”做特征但样本标签是“未来3个月是否逾期”如果还款记录里包含了未来3个月的数据那模型实际上是在偷看答案。解决严格按时间切分。把申请日期排序前70%的客户做训练后30%做验证并且保证特征窗口和表现期不重叠。例如申请日是2023年6月1日特征只能取2022年12月1日到2023年6月1日之间的数据表现期是2023年6月1日到9月1日。5.2 特征泄漏借款人填表时泄漏的“秘密”字段现象模型里某个特征系数非常大比如“最近一个月被催收次数”是强变量但实际审批时根本拿不到这个数据。原因特征工程时可能不小心把“结果变量”的派生字段也放进去了。比如你把“是否已有贷款”作为特征但如果是通过征信报告查到的贷款状态那这个状态本身就和逾期高度相关因为逾期记录会同步标记在贷款状态里。解决特征回看流程。每新增一个特征先问一句话在未来预测时点这个字段是已知的吗如果不是剔除。另外用特征重要性排序把物理含义可疑的高权重特征拉出来人工审通常能揪出问题。5.3 坏样本太少欠拟合还是过拟合现象模型在训练集上KS0.5验证集KS0.2而且随着树深度增加验证集表现持续下降。原因坏样本只有几百条模型每次分裂都在疯狂记忆这几十个坏样本的细节树越深越糟糕。解决如果坏样本低于1000条不要用LightGBM的强默认参数。我一般把num_leaves降到15以下max_depth降到3min_child_samples提高到50并且用早停。这相当于限制模型容量让它在小样本下学主要规律而不是噪声。另外可以尝试用小批量逻辑回归替代树模型效果更稳定。5.4 拒绝推断被拒绝的人怎么办现象开发模型用的都是“已通过审批”的客户你实际部署上线后要评分的是“未通过审批”的新客户这批人的表现你没见过。原因训练集存在样本选择偏差——能成为训练样本的都是历史通过审批的人被拒绝的人永远没有标签模型对这批人的预测完全靠外推。解决常见做法是“拒绝推断”用已有的模型对被拒绝人群打伪标签然后并入训练集。操作上可以把被拒绝的样本按预测逾期概率分段加权比如预测概率在0.5-0.7之间的人随机标记一部分为坏样本。这不是完美的解法但能让模型边界更平滑至少比完全不处理要好。5.5 线上与线下的数据分布漂移现象模型上线后第一个月PSI就飙到0.3AUC直线下滑。原因开发时的训练数据是历史某段时间的但线上信贷客群会随市场政策变化。比如平台在某个季度放开了学生群体他们本来就是高风险但你训练集里没有这类人。解决上线后必须做两个监控一是每日评分分布对比算PSI二是每日特征缺失率和均值漂移监控。一旦PSI连续5天超过0.15就触发预警人工检查是不是客群结构变了如果是就要考虑重新训练。另外可以在训练时加入时间维度的样本加权近期样本权重更高老样本权重衰减让模型有适度的“记忆衰减”。6. 验证模型能不能用回溯测试与分群对比模型开发完不是看AUC高就上线我们至少要做三个维度的验证时间外验证、分群验证、收益验证。时间外验证用最后3个月数据做测试集看KS掉多少分群验证把客户按年龄、职业、渠道分组分别算KS避免模型只对某个群体有效收益验证是最关键的——用模型预测前10%的高风险客户如果这群人的实际逾期率是不加模型筛选时的2倍以上说明模型真的有区分度。我保留的一个习惯是每次模型迭代后都会做一次“歇一歇”测试把概率分箱输出人工看每个分箱的好坏比是否单调。如果训练时某个分箱好坏比突然反转那多半是特征泄漏或分箱过拟合。这个步骤没人要求做但救过我很多次——因为模型在训练数据上永远能拟合出一个漂亮曲线只有分箱图才是诚实的。还有一个技巧是给模型预测概率加一个“拒绝阈值”的敏感性分析。不要默认0.5而是设计多个阈值0.1、0.15、0.2分别看通过率、逾期率、总收益。通常逾期率和通过率存在一个交差点那个点才是业务上最赚的点。这个工作需要和运营的人一起拍板但你能给出曲线就比拍脑袋强。用Python做个人信用评估说实话门槛不在机器学习算法本身而在于你对数据生成过程的理解——标签怎么来的缺失掩盖了什么时间窗口怎么切。我第一次做这个项目时就因为漏了时间穿越模型线下模拟多赚了30%的收益上报给业务方差点要投产直到我用分箱图检查才发现其中的一个特征引用了未来3个月的还款数据。那种感觉就是机器学习给了你一把锋利的刀但握刀的手必须是审慎的否则切到的不是坏账是自己的饭碗。之后我每次建模都会强制自己在代码里加一个“截止日期”参数所有特征工程只能基于这个日期之前的数据源头上杜绝穿越。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取