
简介面向金融风控领域的数据分析师、建模工程师及高校学生这份以Python源码为核心的实战资源包聚焦信用风险建模与大数据风控分析通过实操帮助读者掌握数据清洗、特征工程、模型训练与评估等完整流程。压缩包共106个文件含40个py源码、16个csv数据、19个pkl模型文件、3个ipynb交互式Notebook以及png可视化图片和依赖说明文档整体大小约19.67MB按不同风控模块组织便于查阅与二次开发。目前已有1001人学习适合希望深入理解金融风控模型构建逻辑的读者。源码覆盖逻辑回归、随机森林、XGBoost等信用评分模型K-Means与DBSCAN聚类分析Isolation Forest异常检测等欺诈识别方法并配有标准化、归一化等数据预处理示例同时涉及时间序列分析、生存分析、实时风控系统及模型验证优化交叉验证、网格搜索等进阶主题。借助这些可运行代码和结果图片读者能直观对比不同模型效果加深对金融风控建模全流程的理解并快速迁移到实际业务场景中。1. 金融风控建模不是黑匣子这份 Python 源码包到底能做什么干过信贷风控的人看到 LoanStats_2019Q1.csv 和 german.csv 这两个文件名基本就知道这份压缩包要讲什么了一套从数据清洗到信用评分建模的完整 Python 实战流程。这两个数据集一个是 Lending Club 的公开贷款表现数据一个是 UCI 的德国信用数据是国内外风控建模教学用得最多的标准素材。这个压缩包里的 Python 源码把缺失值处理、WOE 分箱、逻辑回归、随机森林、XGBoost、异常检测、模型验证串成了一条能直接跑通的工程链路而不是那种只丢几个 sklearn 函数就结束的示例代码。适合谁来看我心里有三类人一是刚转到风控岗位的数据分析师想快速理解“一个贷款申请进来之后模型评分到底怎么算出来的”二是做信贷、支付、电商金融的同学手头有用户数据但不知道从哪一步开始建模三是准备金融科技方向求职的学生要的不是理论而是能写进简历的实操项目。这份资源最大的价值是让你在最短时间里重跑一遍工业级风控建模的学习路径同时看清模型上线前必须盯住的那些坑。2. 数据预处理从 LoanStats 与 German 原始表到可用特征集风控建模这条链路里数据预处理花的时间通常比调模型还多。LoanStats_2019Q1.csv 的特点是行数多、字段杂、缺失率高适合练习怎么处理脏数据german.csv 相对干净更适合练习特征构造和 WOE 分箱。如果你直接拿原始表喂给 sklearn绝大多数算法要么报错、要么跑出“伪高分”所以这一步必须先聊透。2.1 缺失值与异常值先看分布再动手我的一般做法是先用 info() 和 isnull().sum() 快速摸底再按业务字段逐列判断而不是代码一开头就 fillna(0)。在风控里0 很容易变成一个被模型记住的“魔法数字”某列缺失填充成 0 之后模型可能会学到“填 0 的人风险更高”但这个结论毫无业务含义。import pandas as pd import numpy as np # german.csv 的目标字段 credit_risk1 是好客户2 是坏客户 df pd.read_csv(german.csv, header0) print(shape:, df.shape) print(df.isnull().sum().sort_values(ascendingFalse).head(10))这段代码先看两个基本信息数据集规模和缺失字段分布。german.csv 通常没有缺失值但 LoanStats 里有大量空列比如某些字段只在特定贷款状态下才填。实际项目里缺失率超过 70% 的列可以直接删除60%~70% 的要结合业务评估低于 30% 的考虑插补而不是全部交给算法去猜。异常值也要分两种看数值异常和业务逻辑异常。贷款金额为负、利率超过 100%、逾期天数大于贷款期限这些在 LoanStats 里偶尔会出现。常规做法是先 describe() 看极值再按业务约束写过滤条件。# 按业务规则过滤明显异常值 loan loan[ (loan[loan_amnt] 0) (loan[int_rate].notna()) ] loan[int_rate] loan[int_rate].str.rstrip(%).astype(float) / 100 print(loan[int_rate].describe())这里的关键操作是文本转数值Lending Club 的利率原始值是“11.49%”这种字符串。rstrip(%) 去掉百分号后才能转 float否则这一列会被 Pandas 读成 object 类型任何模型都跑不出合理结果。这类“脏格式”问题在真实数据里比缺失值更常见而且更隐蔽重跑这套源码时我建议你把这些转换单独抽成一个函数方便后面复用。2.2 特征工程WOE 分箱与单调性检查在信用风控领域WOEWeight of Evidence分箱是比直接标准化更常用的特征处理方法。原因在于逻辑回归要求特征和目标之间有线性关系而年龄、收入、负债率这些原始变量大多是非线性的分箱后把每个箱子的好坏占比取对数就能得到与违约概率呈单调关系的入模特征。# 等频分箱 WOE 计算q5 表示分成 5 个箱 def woe_iv(df, feature, target): tmp df[[feature, target]].copy() tmp[bin] pd.qcut(tmp[feature], q5, duplicatesdrop) grouped tmp.groupby(bin, as_indexFalse)[target].agg([sum, count]) grouped.columns [bad, count] grouped[good] grouped[count] - grouped[bad] # 加平滑项防止 bad0 时 log 崩溃 grouped[bad_rate] (grouped[bad] 0.5) / grouped[bad].sum() grouped[good_rate] (grouped[good] 0.5) / grouped[good].sum() grouped[woe] np.log(grouped[good_rate] / grouped[bad_rate]) iv ((grouped[good_rate] - grouped[bad_rate]) * grouped[woe]).sum() return grouped, iv grouped, iv woe_iv(df, duration, credit_risk) print(grouped) print(IV:, round(iv, 4))两个参数值得注意q 控制分箱数量一般 4~8 箱比较合适分太多会过拟合分太少会丢失信息平滑项加 0.5 是经验值不处理的话某个箱里 bad 为 0np.log(0) 会直接给出 inf整列 WOE 全部失效。IV 值用来判断变量预测能力小于 0.02 基本没用0.1 以上算可用超过 0.5 要小心是否过度拟合。做完 WOE 之后还要做一次单调性检查。如果 bad_rate 的走势是“先高再低再高”说明变量和风险不是简单的线性关系。此时要么手动调整箱边界要么换另一个变量硬塞进逻辑回归只会得到难解释的系数。这一步在源码包里通常用一段可视化代码完成把分箱后的 bad_rate 画成折线图一眼就能看出趋势。2.3 样本不平衡别让“坏客户”被淹没真实信贷数据里好客户占比经常超过 90%坏客户不到 10%。不做任何处理时模型会学到“全判 0 也有 90% 准确率”这在风控场景里没有任何决策价值。第一种补救措施是给模型加类别权重。from sklearn.utils import class_weight classes np.array([0, 1]) weights class_weight.compute_class_weight(balanced, classesclasses, yy_train) print(weights)compute_class_weight 会把少数类的权重自动放大少数类的损失被放大模型就会更关注坏客户。后面建模时再配 class_weightbalanced效果基本等同手动加权。更进一步的业务定制是不同产品的违约损失不同可以在平衡权重基础上再乘一个损失倍数让模型对“贵”的坏客户更敏感。源码包一般只做到 class_weight 这一层后面的倍数关系需要你自己加但值得加。3. 建模实战逻辑回归、XGBoost 与 Isolation Forest 的选型和实现数据准备妥当后进入建模。风控场景里模型选择有一个优先级相同效果下优先选可解释的模型解释不清楚的才上复杂模型。这也是逻辑回归仍然是很多金融机构提交监管时的默认模型的原因。3.1 逻辑回归为什么它仍是风控基线逻辑回归的系数方向明确每个特征对应的系数直接表示“该变量每增加一个单位违约概率上升或下降多少”。这种可解释性在风控里是硬需求——评审委员会要签字监管检查要解释客户投诉要回溯都需要模型不是黑匣子。from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X_woe, y, test_size0.3, random_state42, stratifyy ) pipe Pipeline([ (scaler, StandardScaler()), (lr, LogisticRegression(penaltyl1, solverliblinear, class_weightbalanced, max_iter1000)) ]) pipe.fit(X_train, y_train) print(intercept:, pipe.named_steps[lr].intercept_)这里有两个非常容易踩的参数配合penaltyl1 时solver 必须是 liblinear 或 saga默认的 lbfgs 只支持 L2写着写着就报错这是 sklearn 最常见的拒绝方式。数据量中等偏小用 liblinear上了百万行用 saga。前面加 StandardScaler 是因为 L1 正则对量纲敏感缩放前特征单位不同系数大小没法横向比较反而会影响变量筛选结果。3.2 树模型与集成方法随机森林先筛选XGBoost 再提升随机森林在风控项目里很少直接做最终上线模型它的主要价值在特征筛选。先用浅层随机森林输出 feature_importances_把排名靠后的变量剔除再拿精简后的特征集建模训练速度更快逻辑回归的稳定性也更好。from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier( n_estimators200, max_depth6, min_samples_leaf50, random_state42, n_jobs-1, ) rf.fit(X_train, y_train) importance pd.Series(rf.feature_importances_, indexX_train.columns) print(importance.sort_values(ascendingFalse).head(15))参数上min_samples_leaf 我习惯调到 50 甚至 100风控数据量级通常很大叶子节点样本太少会让模型记住个例上线后波动会非常大。max_depth 控制在 6 左右也是同一思路树越深过拟合概率越高。n_jobs-1 表示用满所有 CPU 核心这一步在数据量大时能省出大量时间。XGBoost 的作用是在同一套特征集上把 AUC 再往上推几个点欺诈检测或者贷中监控这类追求召回率的场景尤其适合。import xgboost as xgb model xgb.XGBClassifier( n_estimators300, max_depth4, learning_rate0.05, subsample0.8, colsample_bytree0.8, eval_metricauc, early_stopping_rounds20, ) model.fit(X_train, y_train, eval_set[(X_test, y_test)], verboseFalse)early_stopping_rounds20 是我认为最重要的防过拟合设置连续 20 轮测试集 AUC 没有提升就终止训练而不是跑满 300 棵。subsample 和 colsample_bytree 都压缩到 0.8相当于每棵树只看 80% 的样本和 80% 的特征树与树之间的差异性更大整体方差更小。提示xgboost 版本较新时早期停止接口有变化但 eval_metricauc 要写全默认的 rmse 在分类问题里会给出误导性的训练曲线。3.3 欺诈检测Isolation Forest 找异常交易有标签时直接用 XGBoost 做分类没有标签时退一步做无监督异常检测。Isolation Forest 在风控里是从头开始的最好选择因为原理直观——异常点几下就被“隔离”出来计算代价比聚类低很多对高维特征也不敏感。from sklearn.ensemble import IsolationForest iso IsolationForest( n_estimators200, contamination0.02, random_state42, ) loan[anomaly] iso.fit_predict(X_anomaly_features) # outlier 会被标记为 -1 print(loan[anomaly].value_counts())contamination0.02 表示你预估样本里约有 2% 异常这个值建议参考历史坏账率而不是随意拍板。输出的 -1 样本拉出来人工核查可能发现三种情况真实欺诈、低质量客户、数据录入错误。后两种在金融里同样有价值——低质量客户可以转人工审核数据错误说明上游系统有 bug。这块源码往往还附了一段抽样查看异常样本的代码建议保留它对理解“模型认为谁异常”很有帮助。4. 模型验证与调优交叉验证、KS 值与 PSI 稳定性监控模型建好不等于能用。风控模型的核心是排序能力和稳定性排序能力用 AUC 和 KS 衡量稳定性用 PSI 监控。这里先聊清楚指标边界再给代码。4.1 交叉验证与网格搜索不要只信一次划分只跑一遍 train_test_split 就宣布模型上线这是我见过最多的翻车现场。随机划分带来的评估波动足以让 AUC 变动 0.02~0.03而 0.02 的差别在风控业务里可能对应着几百万的资产损失。from sklearn.model_selection import GridSearchCV param_grid { max_depth: [3, 4, 5], min_child_weight: [1, 3, 5], colsample_bytree: [0.6, 0.8], } grid GridSearchCV( xgb.XGBClassifier(learning_rate0.05, eval_metricauc), param_grid, cv5, scoringroc_auc, n_jobs-1, ) grid.fit(X_train, y_train) print(grid.best_params_, grid.best_score_)cv5 是常用折数样本少用 3样本多可用 10。scoring 参数必须用 roc_auc不能用 accuracy理由依旧是不平衡数据下 accuracy 的欺骗性太强。网格搜索本身也有成本参数组合多时五折乘几十组就是几百次训练建议先粗后细第一轮拿大步长圈范围第二轮再微调。如果特征数特别多可以换 RandomizedSearchCV在参数空间里随机撒点通常更快。4.2 AUC、KS、PSI三个指标各管一段下面这张表把上线前和上线后要盯的指标列全。指标计算逻辑风控用途常规参考线AUCROC 曲线下面积区分好坏客户的能力0.72 以上可用0.78 以上算优秀KS好坏样本累积分布最大差值模型排序能力0.3 以上可接受0.45 以上需怀疑过拟合PSI分箱占比偏移监控上线后特征漂移0.1 以下安全0.1~0.25 需关注0.25 建议回退AUC 是业内通用沟通语言但对高分段的细微差异不够敏感KS 更直观地反映“打分排序后坏客户是不是被压到了后面”PSI 则专盯上线后的稳定性。三者各有边界不能互相替代。# PSI 简易实现训练集分箱做基准线上数据比对 def calc_psi(expected, actual, bins10): edges pd.qcut(expected, qbins, retbinsTrue)[1] exp_ratio pd.cut(expected, edges, include_lowestTrue).value_counts(normalizeTrue).sort_index() act_ratio pd.cut(actual, edges, include_lowestTrue).value_counts(normalizeTrue).sort_index() psi ((act_ratio - exp_ratio) * np.log(act_ratio / exp_ratio)).sum() return psi这段代码的用途是把“线上数据与训练数据分布是否一致”量化成一个数字。PSI 超过 0.25 时模型大概率已经失效常见原因是客群结构变化或外部宏观环境冲击这时候不是重新训练那么简单要先找到漂移来自哪个特征。实际监控中PSI 通常按周或按月计算画成趋势图一旦连续两周向上拐就要触发排查。4.3 上线前的合规与监控配置风控模型还有一层容易被新手忽略的合规要求决策可解释。GDPR 及国内相关法规都要求能说清拒绝客户的理由所以入模特征清单、WOE 分箱表、逻辑回归系数、KS/AUC 这些中间产物必须完整存档。import joblib # 保存模型与分箱配置线上接口直接加载 joblib.dump(pipe, model/logistic_model.pkl) joblib.dump(bin_config, model/woe_bin_config.pkl)这里还要养成版本习惯每次迭代都要留带版本号的存档不能只留“最终版”。审计回溯依赖的就是这些 pkl 加特征映射表缺失任何一块都会让项目陷入被动。压缩包里的源码一般会演示到模型保存这一步你可以在它基础上加一层按日期命名的目录成本很低但收益很高。5. 风控建模避坑手册五个最容易翻车的细节这一章我直接把重跑这类源码时碰到的最典型的五个坑列出来每一条都是代码能跑、结果看着合理但稍微扩展就露馅的类型。5.1 建模前用全量数据做了标准化现象训练 AUC 达到 0.83模型上线后效果断崖式下跌。原因在 train_test_split 之前就用全量样本计算均值和方差做缩放测试集信息提前泄露到训练过程模型看似很准实际学到的分布已经被“污染”。解决先切分再只在训练集上拟合 scaler然后用同一套参数变换测试集Pipeline 可以自动保证这一步。5.2 WOE 分箱时用了整个样本现象训练集 AUC 0.80测试集只有 0.65落差特别大。原因分箱边界基于全量数据分布测试集样本的排序信息在分箱阶段就被间接用到了。解决分箱只使用训练集得到 bin 边界后测试集用同一组边界做 cut而不是重新分箱。5.3 用 accuracy 评估不平衡数据现象模型报告准确率 93%业务方非常满意一查坏客户召回率几乎是 0。原因好客户占比 93%机器全判好就有 93% 准确率。解决看混淆矩阵、召回率、精确率和 AUC重点盯坏客户这一类的召回与查准不要被总体准确率带偏。如果业务对坏客户有明确的损失比例要求还要结合代价敏感学习调整阈值。5.4 german.csv 编码问题现象pd.read_csv(german.csv) 直接报 UnicodeDecodeError或者读出来全是乱码。原因这个数据集在 UCI 默认是 latin-1 编码传到 Windows 后经常被存成 ANSI 或 GBKutf-8 解不了。解决读取时显式指定编码。df pd.read_csv(german.csv, encodinglatin-1)5.5 好客户和坏客户的标签定义反了现象模型系数方向与业务直觉完全相反收入越高违约概率越大。原因german.csv 的目标字段里 1 是好客户、2 是坏客户很多人默认 0 好 1 坏转换时没做显式映射整个目标变量方向就错了。解决建模前统一标签语义并做校验。df[target] df[credit_risk].map({1: 0, 2: 1}) print(df[target].value_counts())这五个坑如果都能提前绕开你的建模流程才算具备进入真实风控业务的基本条件。我在第一次跑这套源码时5.1 和 5.2 都踩过这也是为什么我把它们放在最前面。6. 让模型说人话评分卡刻度与上线前版本管理习惯最后这一步是把模型输出的违约概率转成业务部门看得懂的分数。风控团队日常不太习惯“你违约概率是 5%”这种表达更习惯“这个客户分数是 680过”。转换逻辑其实不复杂先定基准分和基准坏好比再定 odds 每翻一倍需要再加多少分。import math def prob_to_score(p, base_score600, base_odds1/20, pdo50): # 分数 600 时坏好比为 1:20odds 每翻一倍加 50 分 factor pdo / math.log(2) offset base_score - factor * math.log(base_odds) return round(offset factor * math.log(p / (1 - p))) print(prob_to_score(0.05, base_score600, base_odds1/20, pdo50))base_score、base_odds、pdo 这三个参数通常是风控策略团队和模型团队一起定的。比如银行常见 600 分对应 1:20 的 bad rate每翻一倍好客户占比增加 50 分也就是说分数越高客户质量越好。业务拿到分数量表后就能直接定“650 以上通过580~650 人工审核580 以下拒绝”的准入策略。在这条流程的最后我会再加一个动作把每个特征的 WOE 分箱反解成评分卡明细表输出到 Excel。也就是说每个客户被扣分能看出“是因为收入落在低档位扣了 12 分还是因为负债率过高扣了 8 分”。这张表在应对客户投诉和监管解释时是硬通货也成了我一直保留的习惯不管是跑这个源码包里的示例还是做自己的真实项目最后都强制走一遍“输出评分映射表 保存模型版本 导出特征清单”这三步。模型训练本身只占整个风控项目三成工作量后面这些文档和校验才是决定模型能不能真正上线的关键。希望这次拆解能帮到你把这条链路完整跑通。本文还有配套的精品资源点击获取