ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python机器学习实战:个人信用评估与风控模型构建

Python机器学习实战:个人信用评估与风控模型构建 简介这是一份面向个人信用评估与贷款违约预测的Python机器学习课程设计资源适合高校相关专业学生、机器学习初学者作为课程设计或自学实战项目。项目选用阿里天池贷款违约预测赛题数据原始数据集超过120万条、包含47个特征字段覆盖数据清洗、缺失值填充、特征编码、相关性分析与PCA降维等完整预处理环节并通过网格搜索调参完成模型训练与预测具备较强的工程参考价值。压缩包共82个文件以Python源码、可视化图表、CSV数据及设计报告Word为主整体仅5.16MB其中13个py脚本按模块拆分62张png图展示特征分布、相关性热力图与降维结果另有README说明文档和数据文件便于对照学习。目前已有739人浏览学习。借助这份资源读者可获得一套完整可复现的信用评分建模方案包括代码结构、分析思路、报告撰写框架与结果可视化参考可直接迁移到类似信贷风控项目中。1. 个人信用评估为什么是机器学习的典型落地场景当银行客户经理拿到一笔消费贷款申请时最关心的是这个人未来会不会违约。个人信用评估就是把这个问题变成二分类预测用历史借贷数据、收入、负债率等字段训练模型对新申请做打分排序。用Python做这件事优势不在算法而在于从pandas清洗数据、scikit-learn训练模型到Flask上线接口整条链路用一个语言就能闭环。实际项目中信用卡申请、分期商城、小额贷款风控都会用到这类模型。数据工程师关心特征能否稳定产出算法工程师关心AUC和KS业务方关心每个拒绝理由能不能解释。这篇文章按完整流程走一遍数据准备、特征编码、模型训练、评估解释和落地接口代码可以直接改着用。想系统补机器学习基础可以对照周志华《机器学习》相关章节阅读。需要明确这里不依赖商业风控产品只用一个公开的信贷样本结构做演示。真实项目的字段含义、违约定义和数据分布不同代码骨架不变但参数、阈值和特征列表必须重新验证。2. 信用评估数据准备从原始字段到训练样本2.1 先定义标签逾期多久算违约机器学习任务的第一步不是选算法而是把业务语言翻译成标签。个人信用评估里常见做法是把“贷款发放后90天内出现M3逾期”或“连续逾期超过90天”定义为坏样本标记为1其余为好样本标记为0。这个定义直接影响模型效果不同机构对宽限期、展期、账号注销的处理不一样。建议在项目启动时就找业务方把这个定义写成文档签字确认后期模型上线后的监控和迭代都以它为准。如果样本时间跨度过长还要考虑时效性。用三年前的数据训练当前申请人群的收入结构和消费行为可能已经变了模型上线后表现会明显衰减。所以在数据准备阶段就要做时间切分保证训练集和验证集来自相邻时间窗口而不是把所有历史数据随机打散。这一点在信用评估里比在推荐系统里更严格因为违约率受宏观环境和信贷政策影响很大样本分布随时会迁移。2.2 常用特征类型数值变量、类别变量与时间变量信用评估的原始数据一般来自申请表单、征信报告和内部历史行为。把常用字段归纳成三类类型字段示例处理方式数值变量年龄、月收入、贷款金额、贷款期限缺失值填充、标准化、分箱类别变量职业、住房类型、贷款用途、教育程度独热编码、WOE编码时间变量首次申请时间、最近一次查询时间转为距离今天的天数时间变量容易被忽略。征信报告里的“最近3个月查询次数”本身就是一个数值但“首次开户距今天数”这类相对时间更能反映用户的金融历史长度。转换思路统一拿到日期字段后先做减法再进入后面的特征处理流程。2.3 用pandas完成数据加载与初步清洗先加载数据并查看结构。下面的代码假设数据已经导出为CSVimport pandas as pd import numpy as np df pd.read_csv(credit.csv) print(df.shape) print(df.info()) print(df.isnull().sum().sum())df.shape确认样本量和字段数df.info()查看每一列的类型和非空数量df.isnull().sum().sum()统计全局缺失个数。实际信用评估表经常有几百列千万不要一上来就全塞进模型。先删掉缺失率超过80%的列再按业务含义筛掉“未来信息”例如“审批通过后的实际放款金额”就不应该出现在模型特征里否则训练和上线时根本取不到等价的字段。2.4 缺失值处理中位数填充比均值稳定数值特征如月收入通常有缺失。常见做法是用中位数而不是均值填充因为收入分布右偏少数高收入样本会把均值拉高用均值填充等于给缺失样本注入不存在的收入水平。代码如下df[income].fillna(df[income].median(), inplaceTrue)inplaceTrue表示直接修改原DataFrame。类别特征缺失时不要轻易删除整行常见做法是保留一个“未知”类别。缺失本身可能携带风险信号某些渠道过来的申请就是容易缺字段这个规律对风控有区分作用。2.5 分箱与WOE编码让模型读到风险等级类别特征可以直接用OneHot编码但在信用评分领域WOE编码更常见。WOE的全称是Weight of Evidence计算方式是每个分箱内坏样本占比除以好样本占比再取对数。数值越大说明这个箱子的样本越偏“好”编码值本身携带风险方向输入到逻辑回归后系数也容易解释。def calc_woe(df, feature, target): grouped df.groupby(feature)[target].agg([sum, count]) grouped[bad] grouped[sum] grouped[good] grouped[count] - grouped[bad] total_bad grouped[bad].sum() total_good grouped[good].sum() grouped[woe] np.log( (grouped[bad] / total_bad) / (grouped[good] / total_good 1e-6) ) return grouped[woe]函数先用groupby按分箱后的特征统计坏样本数和总样本数再计算该箱内坏样本占总坏样本的比例、好样本占总好样本的比例最后取对数得到WOE。分母加1e-6是防止某个箱子的好样本数为0时除零。对数值特征先pd.qcut分成5箱再传入这个函数df[age_bin] pd.qcut(df[age], q5, duplicatesdrop) woe_map calc_woe(df, age_bin, label) df[age_woe] df[age_bin].map(woe_map)duplicatesdrop处理分位数重复导致分箱失败的情况。分箱个数不建议太多信用评估里5到10箱足够箱太多会把噪声也编码进去。2.6 训练集和验证集划分保持正负样本比例划分训练集和测试集时用stratify保证正负样本比例一致from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy )stratifyy会按目标列的比例分层抽样避免随机划分导致测试集里坏样本比例偏离整体。这一步看似基础但在违约率只有3%到5%的信用场景里不做分层很容易出现测试集里一个坏样本都没有后续所有评估指标都会失真。3. Python机器学习模型选择信用评估从逻辑回归到集成学习3.1 为什么逻辑回归仍是信用评估的标配个人信用评估的一个重要约束是可解释性。逻辑回归输出的是事件概率的对数几率线性组合系数可以通过公式直接换算成评分卡加分项。监管问起来、业务要解释拒绝原因时每个特征贡献多少都说得很清楚。逻辑回归不需要GPU、训练快、过拟合风险低在样本量只有几万的风控场景里效果不一定比深度学习差。实际项目中我一般会先跑逻辑回归作为基线。如果后续随机森林和梯度提升树能明显提升AUC再考虑换集成模型如果提升不到1%就倾向保留逻辑回归。模型维护不是看上线那一刻的效果而是看半年后特征漂移时哪个模型更容易定位问题。3.2 集成学习在信用评估里的用武之地随机森林和梯度提升树能自动捕捉特征之间的非线性关系例如“高负债且收入不稳定”这种组合风险。LightGBM和XGBoost在大型数据集上训练速度快但个人信用评估样本量往往只有几万到几十万树模型容易过拟合需要严格控制树的深度和学习率。如果只看最终AUCLightGBM通常比随机森林高一点但在特征解释上需要额外引入SHAP工程成本也随之增加。3.3 用scikit-learn训练三个基线模型特征准备好之后先建立三个模型。为了避免额外安装LightGBM这里用scikit-learn自带的梯度提升树代码可以直接跑from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.metrics import roc_auc_score models { lr: LogisticRegression(max_iter1000, class_weightbalanced), rf: RandomForestClassifier( n_estimators200, max_depth6, class_weightbalanced, random_state42 ), gbdt: GradientBoostingClassifier( n_estimators200, max_depth3, learning_rate0.05, random_state42 ), } for name, model in models.items(): model.fit(X_train, y_train) train_auc roc_auc_score(y_train, model.predict_proba(X_train)[:, 1]) test_auc roc_auc_score(y_test, model.predict_proba(X_test)[:, 1]) print(f{name}: train_auc{train_auc:.4f}, test_auc{test_auc:.4f})代码先定义一个字典保存三个模型循环训练并打印训练集和测试集的AUC。AUC计算用的是predict_proba输出的正样本概率而不是predict的类别这是评估排序能力的关键。逻辑回归加了class_weightbalanced来应对样本不平衡随机森林限制max_depth6防止树过深记住噪声梯度提升树学习率设到0.05配合200棵树如果训练集AUC明显高于测试集就降低学习率并增加树的数量。下表是三个模型最值得关注的参数和调节思路模型初始参数调参方向LogisticRegressionmax_iter1000, C1.0C越大正则化越弱特征共线性强时减小CRandomForestClassifiern_estimators200, max_depth6先固定n_estimators再调max_depthGradientBoostingClassifierlearning_rate0.05, n_estimators200降低learning_rate同时增加n_estimators调参不要一上来就上网格搜索。先用默认参数看训练集和测试集AUC的差距判断是欠拟合还是过拟合再决定调整方向。注意信用评估里追求训练集AUC接近1是危险信号特征里很可能混入了审批结果或未来信息。3.4 用交叉验证评估稳定性单次划分的AUC有偶然性。样本量不大时用交叉验证能看出模型对不同子集的稳定性from sklearn.model_selection import cross_val_score scores cross_val_score(model, X_train, y_train, cv5, scoringroc_auc) print(scores, scores.mean())cv5表示5折交叉验证把训练集分成5份轮流当验证集。注意这里model是上面循环结束后留下的gbdt模型。如果正样本很少cv值不要设太大否则每折里的坏样本数量可能不够AUC波动会很大。交叉验证分数如果方差超过0.05就要检查特征里是不是混入了时间跨度很大的样本。3.5 类别不平衡信用评估里最常见的坑实际信贷数据中坏样本占比常常低于5%。如果模型把所有样本都预测成好客户准确率也有95%但这个模型毫无用处。处理办法有三种调整class_weight、过采样少数类、下采样多数类。在个人信用评估中我一般先尝试class_weightbalanced因为不改变数据分布效果不明显再考虑SMOTE。SMOTE会生成合成的坏样本如果原始特征包含大量类别变量合成样本可能落在不存在的组合上需要谨慎检查。还有一种做法是改变决策阈值。模型输出的概率默认按0.5判定但在违约率很低的数据集里这个阈值并不合适。阈值选择要看业务上“拒绝一个好人”和“放给一个坏人”分别付出什么成本通常用收益矩阵找最优切分点而不是拍脑袋定0.5。4. 信用评估模型评估与解释AUC、KS和SHAP4.1 准确率在信用评估里的陷阱信用评估中准确率是最容易骗人的指标。坏样本占比5%时全部预测为好客户就有95%的准确率。实际业务更关心模型能不能把好客户和坏客户分得开也就是排序能力。AUC描述的是随机抽一个好客户和一个坏客户模型给坏客户打分更高的概率。AUC在0.7以上算有区分度0.85以上在多数信贷场景已经够用超过0.95反而要怀疑是不是特征里混进了未来信息。除了AUC风控领域还会看KS。KS是坏样本和好样本累积分布差值的最大值衡量模型把好坏客户分开的最大距离。KS超过0.3可以接受超过0.5要小心过拟合。有些团队在KS上做文章用验证集反复调参把KS刷到0.6上线后立刻崩原因就是模型记住了训练集噪声。4.2 计算AUC、KS并输出评估指标在Python里计算AUC和KS并不复杂from sklearn.metrics import roc_auc_score, roc_curve import numpy as np y_prob model.predict_proba(X_test)[:, 1] auc roc_auc_score(y_test, y_prob) fpr, tpr, thresholds roc_curve(y_test, y_prob) ks max(tpr - fpr) print(fAUC{auc:.4f}, KS{ks:.4f})roc_curve返回不同阈值下的假正率和真正率tpr - fpr的最大值就是KS。这里的model是第3章循环结束后留下的gbdt模型如果要对比多个模型需要在训练循环里把每个模型的y_prob单独保存再统一计算。上线前建议把逻辑回归、随机森林、梯度提升树三个模型的AUC和KS放在一张表里对比模型AUCKS结论逻辑回归0.750.35可作为基线随机森林0.810.42有提升可尝试上线梯度提升树0.820.43提升不明显考虑维护成本表格数字只是说明格式具体以自己跑出来的结果为准。4.3 用SHAP解释每个特征如何影响信用结果SHAP是目前解释机器学习模型的主流方案。它算出每个特征对每个样本预测贡献的Shapley值正负号代表把结果推向坏客户还是好客户。信用评估里监管和业务都会问“为什么拒绝这笔申请”SHAP能给出具体到个体的解释。安装shap后运行import shap explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_test) shap.summary_plot(shap_values, X_test)TreeExplainer适用于随机森林和梯度提升树。如果用逻辑回归应该换成LinearExplainer因为树解释器依赖树结构。summary_plot画出的彩色点图横轴是SHAP值颜色代表特征值大小。从图里能直观看出“年龄越小对违约预测的贡献越大”这类关系。SHAP计算量大几万样本时先对测试集抽样再解释否则内存会迅速占满。4.4 别忘了特征稳定性PSI信用评估模型上线后最怕的不是AUC低而是特征分布漂移。PSIPopulation Stability Index是风控常用的指标计算方式是把训练集中特征的分位数固定下来统计线上新数据落在每个分箱的占比再和训练集分布做比较。如果某个特征PSI超过0.25就要警惕该特征正在失效模型分数可能整体偏移。sklearn没有现成接口一般自己写一个分箱统计函数。这个指标跑起来不麻烦但很多人只在模型开发时看AUC忘了把PSI纳入监控结果上线三个月后AUC掉到0.6才想起来。5. 信用评估模型落地保存、评分卡映射与接口化5.1 把模型概率映射成信用分风控业务习惯用整数分数而不是概率。一个常用的线性映射是把概率转成对数几率再缩放到300到900分。假设oddsp/(1-p)评分公式是scoreoffsetfactor*log(odds)。先确定两个锚点odds1:1时分数为600odds翻倍分数增加20解出offset和factorimport numpy as np factor 20 / np.log(2) offset 600 - factor * np.log(1) score offset factor * np.log(y_prob / (1 - y_prob)) score np.clip(score, 300, 900).astype(int)factor由“odds翻倍加20分”决定offset让odds1:1落在600分。np.clip把分数限制在300到900避免极端概率产生超界分数。真实银行评分卡还会按每个特征的WOE和逻辑回归系数先加分再汇总上面的直接映射适合快速demo和模型对比。5.2 用joblib保存模型和特征名线上服务不能每次重新训练。常见做法是用joblib把训练好的模型和特征列名一起保存import joblib joblib.dump({model: model, feature_names: X_train.columns.tolist()}, model.joblib)把特征名和模型打包在一起比只存模型更稳妥。接口端按这个顺序组特征能避免训练和推理时特征顺序不一致的问题。5.3 用Flask暴露一个打分接口保存后写一个简单的Flask服务接收JSON格式的特征返回分数和风险等级from flask import Flask, request, jsonify import joblib import numpy as np app Flask(__name__) pkg joblib.load(model.joblib) model pkg[model] feature_names pkg[feature_names] app.route(/score, methods[POST]) def score(): data request.get_json() features np.array([data.get(name, 0) for name in feature_names]).reshape(1, -1) prob model.predict_proba(features)[0, 1] score int(np.clip(offset factor * np.log(prob / (1 - prob)), 300, 900)) return jsonify({score: score, probability: round(prob, 4)})request.get_json()拿到请求体按保存时的特征名顺序拼成一行数组再调用predict_proba得到违约概率最后换算成分数返回。注意feature_names的顺序必须和训练时一致否则分箱和模型系数全部错位。data.get(name, 0)在特征缺失时填0这个默认值要和训练时的填充策略保持一致最好显式抛错而不是静默填0。5.4 上线前必做的三个验证第一拿测试集里的典型样本打一遍分看结果是否符合业务直觉第二确认接口在峰值QPS下的耗时超过100毫秒时优先检查特征拼装是不是存在重复计算而不是立刻换模型第三记录每个请求的特征哈希和打分结果方便后续监控PSI和分数漂移。把特征哈希记录到日志里是排查线上特征错位最有效的手段。本文还有配套的精品资源点击获取
返回列表