简介:这份资源面向金融风控方向的学生与开发者,提供一套基于机器学习的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, on='apply_id', how='left') # 标签定义:DPD30,逾期超过30天为坏样本 df['label'] = ((df['overdue_days'] >= 30) & (df['overdue_days'].notna())).astype(int) # 查看标签分布 print(df['label'].value_counts(normalize=True))这段代码的逻辑很直接:先关联多源数据,再根据逾期天数定义二分类标签。参数上需要注意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(columns=cols_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(months=3))).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 值筛选,最后做相关性分析去冗余。
IV(Information Value)是风控特征筛选的核心指标,它衡量一个特征对好坏样本的区分能力。IV 值的经验阈值:小于0.02预测力极弱,0.02到0.1弱,0.1到0.3中等,0.3到0.5强,大于0.5需要警惕过拟合。我一般保留 IV 大于0.1的特征。
WOE(Weight of Evidence)编码是风控建模的标配,它把连续变量分箱后转换成对数值,让逻辑回归模型能捕捉非线性关系。Python 里可以用optbinning库做自动分箱和 WOE 编码,也可以用scorecardpy库。
import scorecardpy as sc # 自动分箱 bins = sc.woebin(df, y='label', 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_size=0.3, random_state=42, stratify=y ) # 逻辑回归训练 lr = LogisticRegression( penalty='l2', C=1.0, class_weight='balanced', solver='lbfgs', max_iter=1000 ) 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', ascending=False) print(coef_df)参数说明:class_weight='balanced'是处理样本不平衡的关键,风控场景坏样本率通常只有1%到5%,不加这个参数模型会偏向预测好样本。C=1.0是正则化强度的倒数,C 越小正则化越强,我一般会在0.1到10之间调。solver='lbfgs'是默认的优化器,适合小数据集,如果特征维度超过几千可以用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_estimators=500, max_depth=4, learning_rate=0.05, subsample=0.8, colsample_bytree=0.8, scale_pos_weight=len(y_train[y_train==0]) / len(y_train[y_train==1]), eval_metric='auc', early_stopping_rounds=50, random_state=42 ) xgb_model.fit( X_train, y_train, eval_set=[(X_test, y_test)], verbose=False ) # 预测和评估 y_pred_xgb = xgb_model.predict_proba(X_test)[:, 1] auc_xgb = roc_auc_score(y_test, y_pred_xgb) print(f"XGBoost测试集AUC: {auc_xgb:.4f}") # 特征重要性 importance = pd.DataFrame({ 'feature': feature_cols, 'importance': xgb_model.feature_importances_ }).sort_values('importance', ascending=False) print(importance.head(20))参数说明:max_depth=4是风控场景的常用值,树太深容易过拟合,而且可解释性差。learning_rate=0.05配合n_estimators=500是比较稳健的组合,如果 AUC 不够可以调低学习率到0.01并增加树的数量。scale_pos_weight是正负样本权重比,等价于逻辑回归的class_weight='balanced'。early_stopping_rounds=50防止过拟合,验证集 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_weight='balanced'或scale_pos_weight后,模型输出的概率不再是真实概率,而是调整后的相对概率。
解决:如果业务需要真实概率(比如计算预期损失),必须做概率校准。Python 里可以用sklearn.calibration.CalibratedClassifierCV做 Platt 校准或 Isotonic 校准。如果只是排序(比如审批通过率控制),不需要校准。
5.3 坑三:特征缺失率在训练集和线上不一致
现象:离线训练时某个特征缺失率5%,线上调用时缺失率40%,模型效果大幅下降。
原因:离线特征加工逻辑和线上特征服务逻辑不一致,或者线上数据源本身缺失率就高。
解决:上线前必须做特征一致性校验,用同一批样本分别走离线和线上特征加工,对比每个特征的分布和缺失率。差异超过5%的特征要排查原因。我一般会写一个校验脚本,输出每个特征的 PSI(Population Stability Index),PSI 大于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, buckets=10): """计算PSI""" # 等频分箱 breakpoints = np.percentile(expected, np.linspace(0, 100, buckets + 1)) breakpoints[0] = -np.inf breakpoints[-1] = np.inf expected_perc = np.histogram(expected, bins=breakpoints)[0] / len(expected) actual_perc = np.histogram(actual, bins=breakpoints)[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', ascending=False) print(psi_df.head(10))这段脚本的核心是calculate_psi函数,它先对期望分布(训练集)做等频分箱,然后计算实际分布(线上)在每个箱的占比,最后用 PSI 公式求和。参数buckets=10是分箱数,一般10到20之间,太少不敏感,太多噪音大。0.0001是防止除零和 log 零的平滑项。
实际使用中,我会把这段脚本封装成定时任务,每周跑一次,输出 KS 和 PSI 的周环比。如果 KS 下降超过20%或者 PSI 超过0.25,就触发告警,需要排查是数据问题还是客群变化。如果是客群变化,考虑重新训练模型;如果是数据问题,排查特征加工链路。
我自己的习惯是:每次模型上线前,先把监控脚本跑一遍,确认训练集和测试集的 PSI 在0.1以内,KS 在0.3以上。上线后前两周每天跑一次,稳定后改为一周一次。这个习惯帮我提前发现过好几次特征漂移,避免了线上事故。希望帮到你。
本文还有配套的精品资源,点击获取