Python金融风控建模实战:从数据清洗到模型监控的完整流程

发布时间:2026/10/10 18:26:59
Python金融风控建模实战:从数据清洗到模型监控的完整流程 简介这份资源面向金融风控方向的学生与开发者提供一套基于机器学习的Python大数据风控建模实战项目可直接用于毕业设计、期末大作业或课程设计。项目围绕信贷风控场景展开涵盖数据预处理、特征工程、模型训练与评估等完整流程代码注释详尽新手也能看懂并快速部署运行。压缩包共106个文件约20.13MB其中40个py脚本承载核心建模逻辑19个pkl保存训练好的模型16个csv提供german、LoanStats等风控数据集另有png图表、ipynb笔记、whl依赖包及文档说明结构清晰、便于按模块查阅。目前已有1159人学习下载项目经严格调试功能完善、界面美观、操作简单适合作为高分课设或毕设参考也能帮助读者掌握金融风控建模的完整思路与落地方法。1. 金融风控建模为什么不能直接套用 Kaggle 那套流程很多做数据分析和算法岗的朋友第一次接触金融大数据风控建模习惯性地把 Kaggle 竞赛那一套流程搬过来读 CSV、做特征工程、上 XGBoost、调参、看 AUC然后觉得大功告成。但真正在信贷场景落地过模型的人都知道这套流程在金融风控里会翻车——不是模型效果不好而是模型根本没法上线或者上线后带来的是合规风险和业务损失。Python 金融大数据风控建模实战核心要解决的不是「怎么把 AUC 刷到 0.8」而是「怎么在强监管、高噪音、极端不平衡的真实信贷数据上构建一个可解释、可追溯、可部署的机器学习模型」。它适合三类人想从互联网算法岗转金融风控的工程师、在银行或消金公司做数据建模但缺乏系统方法论的从业者、以及需要理解模型决策逻辑的风控策略人员。这篇文章会从数据到模型到部署把每个环节的选型理由、参数设置和踩坑经验讲清楚代码基于 Python 生态配合文档说明让你能照着复现一套完整的风控建模流程。2. 风控数据从哪来、怎么洗信贷原始数据的处理链路2.1 风控建模的数据源和标签定义金融风控建模的第一步不是建模是搞清楚数据从哪来、标签怎么定。常见的数据源包括申请信息年龄、收入、职业、学历、人行征信报告信贷历史、查询记录、逾期记录、第三方数据运营商、电商、社保公积金、以及行为数据APP 埋点、设备指纹。这些数据在 Python 里通常以结构化表的形式存在用 pandas 读取后需要做多表关联。标签定义是风控建模里最容易埋雷的地方。信贷场景的标签通常是「是否逾期」但逾期有不同定义口径DPD30逾期超过30天、DPD60、DPD90甚至 FPD首次还款逾期。不同口径对应的业务含义和模型目标完全不同。我一般会先和业务方确认清楚这个模型是用于贷前审批、贷中提额还是贷后催收贷前审批用 FPD 或 DPD30 更敏感贷后催收用 DPD90 更能反映真实坏账。import pandas as pd import numpy as np # 读取申请信息和征信数据 apply_df pd.read_csv(apply_info.csv) credit_df pd.read_csv(credit_report.csv) # 多表关联以申请ID为主键 df apply_df.merge(credit_df, onapply_id, howleft) # 标签定义DPD30逾期超过30天为坏样本 df[label] ((df[overdue_days] 30) (df[overdue_days].notna())).astype(int) # 查看标签分布 print(df[label].value_counts(normalizeTrue))这段代码的逻辑很直接先关联多源数据再根据逾期天数定义二分类标签。参数上需要注意overdue_days字段的缺失值处理——未逾期用户的逾期天数通常是空值不能直接填充为0否则会把未逾期和逾期0天混为一谈。我一般会保留缺失用notna()判断后再做逻辑运算。2.2 缺失值处理与异常值检测的实操参数金融数据的缺失率往往高得离谱征信字段缺失30%以上是常态。处理缺失值有三种常见策略删除、填充、保留。我的经验是缺失率超过70%的字段直接删掉缺失率在30%到70%之间的字段做填充并加缺失指示变量缺失率低于30%的字段用中位数或众数填充。异常值检测在风控里尤其重要因为信贷数据里经常出现「年龄200岁」「收入9999999」这种脏数据。我一般用 IQR 方法做初步筛查再结合业务规则做二次过滤。# 缺失值处理 missing_rate df.isnull().mean() # 删除缺失率超过70%的列 cols_to_drop missing_rate[missing_rate 0.7].index.tolist() df df.drop(columnscols_to_drop) # 对缺失率在30%-70%的列填充并加指示变量 cols_to_fill missing_rate[(missing_rate 0.3) (missing_rate 0.7)].index.tolist() for col in cols_to_fill: df[col _is_missing] df[col].isnull().astype(int) df[col] df[col].fillna(df[col].median()) # 异常值处理年龄限制在18-70岁 df[age] df[age].clip(18, 70) # 收入用IQR截断 Q1 df[income].quantile(0.25) Q3 df[income].quantile(0.75) IQR Q3 - Q1 df[income] df[income].clip(Q1 - 1.5*IQR, Q3 1.5*IQR)参数说明missing_rate 0.7这个阈值不是固定的如果某个字段业务上非常重要比如征信查询次数即使缺失率高也要保留用缺失指示变量标记。clip(18, 70)是硬性业务规则年龄超出这个范围的申请件本身就有欺诈嫌疑。IQR 的 1.5 倍是标准做法但在收入这种长尾分布字段上有时候会用 3 倍 IQR 避免过度截断。注意缺失指示变量在风控模型里往往有很强的预测力因为「缺失」本身可能就是一种风险信号——比如征信报告缺失可能意味着用户没有信贷记录是白户风险特征和普通用户完全不同。3. 特征工程从原始字段到风控模型的入模变量3.1 风控特征体系的搭建逻辑风控模型的特征工程和推荐系统、图像识别完全不同它更依赖业务理解和统计加工而不是自动特征提取。一个成熟的风控特征体系通常包含几个大类人口统计学特征、信贷历史特征、查询行为特征、负债能力特征、行为时序特征。人口统计学特征包括年龄、性别、学历、婚姻状况、职业类型等。这类特征稳定性高但区分度有限通常作为基础变量。信贷历史特征是核心包括历史贷款笔数、历史逾期次数、最大逾期天数、当前负债余额等。查询行为特征反映用户的资金饥渴程度比如近3个月查询次数、近6个月查询机构数。负债能力特征包括收入负债比、信用卡使用率、授信额度使用率。行为时序特征则是从APP埋点数据里加工出来的比如最近一次登录距今天数、申请频率变化趋势。在 Python 里这些特征加工通常用 pandas 的 groupby 和 rolling 窗口函数实现。我一般会先写一个特征加工脚本把每个特征的业务逻辑和计算口径注释清楚方便后续维护和审计。# 信贷历史特征加工 # 历史逾期次数 df[hist_overdue_cnt] df.groupby(user_id)[overdue_flag].transform(sum) # 近3个月查询次数 df[query_3m] df.groupby(user_id)[query_date].transform( lambda x: (x (pd.Timestamp.now() - pd.DateOffset(months3))).sum() ) # 收入负债比 df[dti] df[total_debt] / (df[income] 1) # 信用卡使用率 df[credit_utilization] df[credit_card_balance] / (df[credit_card_limit] 1)这段代码里transform(sum)是对每个用户的所有记录求和得到历史累计逾期次数。query_3m用了一个 lambda 函数计算近3个月的查询次数注意这里的时间窗口是动态的实际生产中会用申请日期作为基准而不是pd.Timestamp.now()。dti和credit_utilization都加了1防止除零这是风控特征加工里的标准操作。3.2 特征筛选与 WOE 编码的实操细节特征筛选在风控建模里比在其他领域更重要因为入模变量太多会导致模型可解释性下降监管审计也过不了。我一般分三步走先做缺失率和方差过滤再做 IV 值筛选最后做相关性分析去冗余。IVInformation Value是风控特征筛选的核心指标它衡量一个特征对好坏样本的区分能力。IV 值的经验阈值小于0.02预测力极弱0.02到0.1弱0.1到0.3中等0.3到0.5强大于0.5需要警惕过拟合。我一般保留 IV 大于0.1的特征。WOEWeight of Evidence编码是风控建模的标配它把连续变量分箱后转换成对数值让逻辑回归模型能捕捉非线性关系。Python 里可以用optbinning库做自动分箱和 WOE 编码也可以用scorecardpy库。import scorecardpy as sc # 自动分箱 bins sc.woebin(df, ylabel, x[age, income, hist_overdue_cnt, query_3m]) # WOE转换 df_woe sc.woebin_ply(df, bins) # 查看分箱结果 for var, bin_df in bins.items(): print(f变量: {var}) print(bin_df[[bin, count, bad_rate, woe, iv]])sc.woebin会自动对每个变量做最优分箱返回分箱边界、每箱的样本数、坏样本率、WOE 值和 IV 值。sc.woebin_ply把原始变量替换成 WOE 值。参数上需要注意min_perc_bin控制每箱最小样本占比默认0.05如果某箱样本太少会导致 WOE 估计不稳定可以适当调大到0.1。提示WOE 编码必须在训练集上计算然后应用到验证集和测试集。如果全量数据一起做 WOE会造成信息泄露模型在测试集上的表现会虚高。我一般用sc.woebin在训练集上分箱然后用sc.woebin_ply分别转换训练集和测试集。4. 模型训练与调参逻辑回归、XGBoost 和评分卡4.1 逻辑回归评分卡的构建流程逻辑回归是风控建模的 baseline也是监管最认可的模型形式。它的优势在于可解释性强每个特征的系数可以直接换算成评分业务方容易理解。构建评分卡的流程是WOE 编码后的特征 → 逻辑回归训练 → 系数显著性检验 → 评分转换。from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score, roc_curve import numpy as np # 准备WOE编码后的特征 feature_cols [col for col in df_woe.columns if col.endswith(_woe)] X df_woe[feature_cols] y df_woe[label] # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) # 逻辑回归训练 lr LogisticRegression( penaltyl2, C1.0, class_weightbalanced, solverlbfgs, max_iter1000 ) lr.fit(X_train, y_train) # 预测概率和AUC y_pred_proba lr.predict_proba(X_test)[:, 1] auc roc_auc_score(y_test, y_pred_proba) print(f测试集AUC: {auc:.4f}) # 输出系数 coef_df pd.DataFrame({ feature: feature_cols, coef: lr.coef_[0] }).sort_values(coef, ascendingFalse) print(coef_df)参数说明class_weightbalanced是处理样本不平衡的关键风控场景坏样本率通常只有1%到5%不加这个参数模型会偏向预测好样本。C1.0是正则化强度的倒数C 越小正则化越强我一般会在0.1到10之间调。solverlbfgs是默认的优化器适合小数据集如果特征维度超过几千可以用saga。评分转换的公式是score base_score (sum(coef * woe) intercept) * factor其中 factor 和 base_score 根据业务需求设定通常让分数落在300到850之间类似 FICO 评分。4.2 XGBoost 在风控场景的调参策略XGBoost 在风控建模里通常用来做特征筛选和模型融合它的非线性拟合能力比逻辑回归强但可解释性差。我的做法是先用 XGBoost 跑一遍看特征重要性和 AUC如果比逻辑回归提升明显超过0.03就考虑用 XGBoost 做主力模型否则用逻辑回归评分卡。import xgboost as xgb from sklearn.metrics import roc_auc_score # XGBoost训练 xgb_model xgb.XGBClassifier( n_estimators500, max_depth4, learning_rate0.05, subsample0.8, colsample_bytree0.8, scale_pos_weightlen(y_train[y_train0]) / len(y_train[y_train1]), eval_metricauc, early_stopping_rounds50, random_state42 ) xgb_model.fit( X_train, y_train, eval_set[(X_test, y_test)], verboseFalse ) # 预测和评估 y_pred_xgb xgb_model.predict_proba(X_test)[:, 1] auc_xgb roc_auc_score(y_test, y_pred_xgb) print(fXGBoost测试集AUC: {auc_xgb:.4f}) # 特征重要性 importance pd.DataFrame({ feature: feature_cols, importance: xgb_model.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance.head(20))参数说明max_depth4是风控场景的常用值树太深容易过拟合而且可解释性差。learning_rate0.05配合n_estimators500是比较稳健的组合如果 AUC 不够可以调低学习率到0.01并增加树的数量。scale_pos_weight是正负样本权重比等价于逻辑回归的class_weightbalanced。early_stopping_rounds50防止过拟合验证集 AUC 连续50轮不提升就停止训练。注意XGBoost 的scale_pos_weight和逻辑回归的class_weight作用类似但 XGBoost 对样本不平衡的敏感度更低有时候不加这个参数反而效果更好。我一般会跑两遍对比看哪个 AUC 更高。5. 风控模型上线前必须排查的五个坑5.1 坑一时间穿越导致 AUC 虚高现象模型在测试集上 AUC 0.85上线后 KS 只有 0.2效果断崖式下跌。原因特征加工时用了未来信息。比如计算「近3个月查询次数」时用了申请日期之后的数据或者 WOE 编码在全量数据上做测试集信息泄露到训练集。解决所有特征必须基于申请时点之前的数据计算用pd.Timestamp严格截断。WOE 编码只在训练集上做测试集用训练集的边界转换。我一般会在特征加工脚本里加一个as_of_date参数所有时间窗口都相对于这个日期计算。5.2 坑二样本不平衡导致概率校准失效现象模型输出的违约概率集中在0.01到0.05之间但实际坏样本率是3%概率没有区分度。原因用了class_weightbalanced或scale_pos_weight后模型输出的概率不再是真实概率而是调整后的相对概率。解决如果业务需要真实概率比如计算预期损失必须做概率校准。Python 里可以用sklearn.calibration.CalibratedClassifierCV做 Platt 校准或 Isotonic 校准。如果只是排序比如审批通过率控制不需要校准。5.3 坑三特征缺失率在训练集和线上不一致现象离线训练时某个特征缺失率5%线上调用时缺失率40%模型效果大幅下降。原因离线特征加工逻辑和线上特征服务逻辑不一致或者线上数据源本身缺失率就高。解决上线前必须做特征一致性校验用同一批样本分别走离线和线上特征加工对比每个特征的分布和缺失率。差异超过5%的特征要排查原因。我一般会写一个校验脚本输出每个特征的 PSIPopulation Stability IndexPSI 大于0.1就要警惕。5.4 坑四模型分数分布漂移没有监控现象模型上线三个月后审批通过率从60%降到40%但模型代码没改过。原因客群分布发生变化或者前端流量渠道变化导致入模特征分布漂移。解决上线后必须监控模型分数的 PSI 和特征 PSI。PSI 小于0.1表示稳定0.1到0.25表示轻微漂移大于0.25表示显著漂移需要重新训练。Python 里可以用scorecardpy的perf_psi函数计算。5.5 坑五忽略业务规则导致模型不可用现象模型 AUC 很高但业务方拒绝使用因为模型拒绝的客户里有大量高价值客户。原因模型只优化了统计指标没有考虑业务约束。比如某些职业医生、公务员历史逾期率低但模型可能因为其他特征把他们拒掉。解决建模前和业务方确认硬规则比如「年龄小于22岁直接拒绝」「当前有逾期直接拒绝」这些规则不进入模型作为前置过滤。模型只处理硬规则通过后的客户。我一般会在模型评估时单独看硬规则拦截的客群和模型拒绝的客群重叠度确保没有误杀。6. 用 PSI 和 KS 做模型监控一个可复用的 Python 脚本模型上线不是终点而是起点。风控模型需要持续监控核心指标是 KS 和 PSI。KS 衡量模型区分好坏样本的能力PSI 衡量模型分数和特征的稳定性。我一般每周跑一次监控脚本输出 KS 趋势图和 PSI 热力图。import pandas as pd import numpy as np from scipy.stats import ks_2samp def calculate_ks(y_true, y_pred): 计算KS值 fpr, tpr, thresholds roc_curve(y_true, y_pred) ks max(tpr - fpr) return ks def calculate_psi(expected, actual, buckets10): 计算PSI # 等频分箱 breakpoints np.percentile(expected, np.linspace(0, 100, buckets 1)) breakpoints[0] -np.inf breakpoints[-1] np.inf expected_perc np.histogram(expected, binsbreakpoints)[0] / len(expected) actual_perc np.histogram(actual, binsbreakpoints)[0] / len(actual) # 避免除零 expected_perc np.where(expected_perc 0, 0.0001, expected_perc) actual_perc np.where(actual_perc 0, 0.0001, actual_perc) psi np.sum((actual_perc - expected_perc) * np.log(actual_perc / expected_perc)) return psi # 模拟训练集分数和线上分数 train_scores lr.predict_proba(X_train)[:, 1] online_scores lr.predict_proba(X_test)[:, 1] ks_value calculate_ks(y_test, online_scores) psi_value calculate_psi(train_scores, online_scores) print(f线上KS: {ks_value:.4f}) print(f分数PSI: {psi_value:.4f}) # 特征PSI监控 feature_psi {} for col in feature_cols: feature_psi[col] calculate_psi(X_train[col], X_test[col]) psi_df pd.DataFrame({ feature: list(feature_psi.keys()), psi: list(feature_psi.values()) }).sort_values(psi, ascendingFalse) print(psi_df.head(10))这段脚本的核心是calculate_psi函数它先对期望分布训练集做等频分箱然后计算实际分布线上在每个箱的占比最后用 PSI 公式求和。参数buckets10是分箱数一般10到20之间太少不敏感太多噪音大。0.0001是防止除零和 log 零的平滑项。实际使用中我会把这段脚本封装成定时任务每周跑一次输出 KS 和 PSI 的周环比。如果 KS 下降超过20%或者 PSI 超过0.25就触发告警需要排查是数据问题还是客群变化。如果是客群变化考虑重新训练模型如果是数据问题排查特征加工链路。我自己的习惯是每次模型上线前先把监控脚本跑一遍确认训练集和测试集的 PSI 在0.1以内KS 在0.3以上。上线后前两周每天跑一次稳定后改为一周一次。这个习惯帮我提前发现过好几次特征漂移避免了线上事故。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询