news 2026/10/11 0:36:31

Python机器学习信用评估实战:从评分卡到风控模型上线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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(upper=1.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分箱

信用评估里最经典的特征处理是WOE(Weight of Evidence)编码,它本质上是对特征做有监督的分箱,然后计算每个分箱的好坏占比。为什么不用one-hot?因为信贷数据里有大量稀疏类别,比如职业类型有几十种,one-hot会让特征维度爆炸,而且无法表达“某种职业的风险高低顺序”。WOE的公式是:

WOE_i = ln(好客户占比 / 坏客户占比)

这个值越大,代表该分箱内的客户风险越低。建模时把原始值替换成WOE,逻辑回归的系数解释性就很强——比如某特征的WOE系数为负,说明该分箱风险高于平均。分箱方法一般用卡方分箱或决策树分箱,两个都能找出和目标变量关联最紧密的切点。

def calc_woe(df, feature, target, bins=10): # 先用等频分箱,保证每个箱里有足够样本 df['bin'] = pd.qcut(df[feature], q=bins, duplicates='drop') grouped = df.groupby('bin', observed=True).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(lower=0.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_size=0.2, random_state=42, stratify=y ) # 为不平衡样本设置权重:坏样本权重 = 好样本数 / (坏样本数 * 2) weight = len(y_train) / (2 * np.bincount(y_train)[1]) model = lgb.LGBMClassifier( objective='binary', learning_rate=0.05, n_estimators=300, num_leaves=31, max_depth=5, min_child_samples=20, subsample=0.8, colsample_bytree=0.8, class_weight={0: 1, 1: weight}, random_state=42 ) model.fit( X_train, y_train, eval_set=[(X_val, y_val)], eval_metric='auc', callbacks=[lgb.early_stopping(stopping_rounds=50)] ) val_auc = roc_auc_score(y_val, model.predict_proba(X_val)[:, 1]) print(f'val_auc={val_auc:.4f}')

这里几个参数值得说。learning_rate设0.05而不是默认0.1,是为了配合early_stopping防止过拟合;num_leaves=31是LightGBM作者的推荐值,对应max_depth=5的树结构,再大容易过拟合小样本。min_child_samples=20是防止叶子节点样本过少导致统计不稳定——信贷数据里坏样本本来就少,叶子里的坏样本数可能只有个位数,低于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翻倍”,这样factor=20/ln(2)。个人信用评估里,更简单的做法是直接对LightGBM输出的概率做分位映射,但这种方法解释性差,不容易向监管说明。我见过的生产方案还是转化回log(odds)。

def prob_to_score(prob, base_score=600, pdo=20, base_odds=50): """ base_odds为基准概率对应的odds,比如0.02 -> odds=0.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.999),log(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降到3,min_child_samples提高到50,并且用早停。这相当于限制模型容量,让它在小样本下学主要规律而不是噪声。另外可以尝试用小批量逻辑回归替代树模型,效果更稳定。

5.4 拒绝推断:被拒绝的人怎么办

现象:开发模型用的都是“已通过审批”的客户,你实际部署上线后要评分的是“未通过审批”的新客户,这批人的表现你没见过。

原因:训练集存在样本选择偏差——能成为训练样本的都是历史通过审批的人,被拒绝的人永远没有标签,模型对这批人的预测完全靠外推。

解决:常见做法是“拒绝推断”,用已有的模型对被拒绝人群打伪标签,然后并入训练集。操作上可以把被拒绝的样本按预测逾期概率分段加权,比如预测概率在0.5-0.7之间的人,随机标记一部分为坏样本。这不是完美的解法,但能让模型边界更平滑,至少比完全不处理要好。

5.5 线上与线下的数据分布漂移

现象:模型上线后第一个月PSI就飙到0.3,AUC直线下滑。

原因:开发时的训练数据是历史某段时间的,但线上信贷客群会随市场政策变化。比如平台在某个季度放开了学生群体,他们本来就是高风险,但你训练集里没有这类人。

解决:上线后必须做两个监控:一是每日评分分布对比,算PSI;二是每日特征缺失率和均值漂移监控。一旦PSI连续5天超过0.15,就触发预警,人工检查是不是客群结构变了,如果是,就要考虑重新训练。另外可以在训练时加入时间维度的样本加权,近期样本权重更高,老样本权重衰减,让模型有适度的“记忆衰减”。

6. 验证模型能不能用:回溯测试与分群对比

模型开发完不是看AUC高就上线,我们至少要做三个维度的验证:时间外验证、分群验证、收益验证。时间外验证用最后3个月数据做测试集,看KS掉多少;分群验证把客户按年龄、职业、渠道分组,分别算KS,避免模型只对某个群体有效;收益验证是最关键的——用模型预测前10%的高风险客户,如果这群人的实际逾期率是不加模型筛选时的2倍以上,说明模型真的有区分度。

我保留的一个习惯是,每次模型迭代后都会做一次“歇一歇”测试:把概率分箱输出,人工看每个分箱的好坏比是否单调。如果训练时某个分箱好坏比突然反转,那多半是特征泄漏或分箱过拟合。这个步骤没人要求做,但救过我很多次——因为模型在训练数据上永远能拟合出一个漂亮曲线,只有分箱图才是诚实的。

还有一个技巧是给模型预测概率加一个“拒绝阈值”的敏感性分析。不要默认0.5,而是设计多个阈值:0.1、0.15、0.2,分别看通过率、逾期率、总收益。通常逾期率和通过率存在一个交差点,那个点才是业务上最赚的点。这个工作需要和运营的人一起拍板,但你能给出曲线就比拍脑袋强。

用Python做个人信用评估,说实话门槛不在机器学习算法本身,而在于你对数据生成过程的理解——标签怎么来的,缺失掩盖了什么,时间窗口怎么切。我第一次做这个项目时就因为漏了时间穿越,模型线下模拟多赚了30%的收益,上报给业务方差点要投产,直到我用分箱图检查才发现其中的一个特征引用了未来3个月的还款数据。那种感觉就是:机器学习给了你一把锋利的刀,但握刀的手必须是审慎的,否则切到的不是坏账,是自己的饭碗。之后我每次建模都会强制自己在代码里加一个“截止日期”参数,所有特征工程只能基于这个日期之前的数据,源头上杜绝穿越。希望这个习惯也能帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 0:34:27

YOLOv8水下管道检测识别:从数据集整理到模型训练与部署全流程解析

简介&#xff1a;面向海洋工程与基础设施巡检场景&#xff0c;这套基于YOLOv8的水下管道检测资料包&#xff0c;为需要快速落地目标检测方案的开发者和巡检人员&#xff0c;提供了从数据集到训练模型再到部署参考的一站式支持。压缩包共2000个文件&#xff0c;其中以1985个VOC格…

作者头像 李华
网站建设 2026/10/11 0:21:10

5G网络切片资源隔离性验证:测试框架设计与pytest自动化实践

去年做运营商5G专网验证项目时&#xff0c;客户提了一个相当刁钻的需求&#xff1a;两个网络切片必须做到“绝对隔离”&#xff0c;而且要用数据证明&#xff0c;不能拍脑袋。场景是工业园区混合组网&#xff0c;自动化产线走uRLLC切片&#xff0c;办公区刷视频走eMBB切片。客户…

作者头像 李华
网站建设 2026/10/11 0:10:50

Python机器学习全套代码:从数据预处理到模型评估的完整流程

简介&#xff1a;面向数据建模与机器学习初学者的Python代码资源包&#xff0c;系统覆盖广义可加模型GAM、梯度提升决策树GBDT、分类回归树CART、BP神经网络和深度神经网络DNN等常用算法&#xff0c;每个模型都附带独立数据集&#xff0c;并配有从数据读取、训练到验证的完整代…

作者头像 李华
网站建设 2026/10/11 0:01:41

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里&#xff0c;有A同学跑来问我&#xff1a;选什么毕设题目最稳妥&#xff0c;既能让评审老师觉得工作量够&#xff0c;又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

作者头像 李华
网站建设 2026/10/10 23:52:11

Java实现冰岛人家族关系判断:五代以内共同祖先算法与PTA满分代码

PTA团体程序设计天梯赛的L2-030《冰岛人》是一道看似简单、实则边界极多的家族关系判断题&#xff0c;尤其在Java提交时&#xff0c;稍不注意就会超时或者被“五代以内”这个说法带偏。这篇文章把我从读题、设计数据结构、到最终Java满分通过的全过程完整拆开&#xff0c;重点说…

作者头像 李华
网站建设 2026/10/10 23:51:56

土豆目标检测数据集:YOLO与VOC双格式实战指南

简介&#xff1a;本资源是面向农业AI与目标检测初学者及实践者的土豆目标检测专用数据集&#xff0c;适用于YOLO系列、Faster R-CNN等主流检测模型的训练与验证&#xff0c;可支撑智能分拣、田间监测、品质分级等实际场景建模。压缩包共310个文件&#xff0c;含152张JPEG图像、…

作者头像 李华