尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python金融风控建模实战:从CSV数据到模型评估的完整源码解析

Python金融风控建模实战:从CSV数据到模型评估的完整源码解析 简介这份资源面向金融风控方向的学生与开发者提供一套基于机器学习的Python大数据风控建模完整实战项目可直接用于毕业设计、期末大作业或课程设计。项目围绕信贷风控场景展开涵盖数据预处理、特征工程、模型训练与评估等核心环节代码注释详尽新手也能读懂并快速部署运行。压缩包共106个文件约20.13MB其中40个py脚本承载建模主流程16个csv与19个pkl分别存放原始数据与训练好的模型文件另有3个ipynb便于分步演示20张png记录可视化结果并附文档说明与依赖whl包结构清晰、便于按模块查阅。目前已有1159人学习下载项目经过严格调试功能完善、运行稳定能帮助读者掌握从数据到模型落地的完整思路具备较高的参考与复用价值。1. 从两份 CSV 到一套能跑的风控模型这套源码到底值不值得拆金融风控建模这件事真正卡住新手的从来不是算法本身而是数据到手之后那一堆脏活。你拿到一份 LoanStats_2019Q1.csv几十万行、上百个字段缺失值、类别不平衡、时间穿越、特征泄漏全堆在一起再拿到一份 german.csv一千行、二十个字段看着干净实际上标签定义和业务口径又完全是另一套逻辑。这套 Python 金融大数据风控建模实战源码核心价值就在于它把这两类典型数据——一个是大规模信贷申请数据一个是经典德国信用数据集——的完整建模链路都走了一遍从数据加载、清洗、特征工程到模型训练、评估、阈值选取每一步都有代码注释和文档说明。适合谁如果你正在做期末大作业、课程设计或者毕业设计需要一套结构完整、逻辑自洽、能直接跑通的风控建模项目这套东西省掉的是你从零搭框架的时间。如果你是从业者想快速验证某个特征处理思路或模型对比方案它也能当个起点。但别指望它是生产级系统——它的定位是教学和作业场景下的完整实现不是线上风控引擎。下面我按实际拆包的顺序把这份资源的技术细节、使用方式和踩坑点讲清楚。2. 数据加载与清洗LoanStats 和 German 两套数据的处理差异2.1 两份数据集的结构差异与加载策略LoanStats_2019Q1.csv 是 LendingClub 风格的信贷数据典型特征是行数多、字段杂、缺失率高。german.csv 是经典的德国信用数据集行数少、字段规整、标签明确。这两份数据放在同一个项目里目的不是让你二选一而是让你看到不同规模、不同质量的数据在同一个建模框架下怎么分别处理。先看加载环节。LoanStats 这类数据直接pd.read_csv会踩两个坑一是低内存模式下 dtype 推断出错二是某些列混合了字符串和数值。常见做法是显式指定 dtype 和 usecols只加载建模需要的字段。import pandas as pd import numpy as np # LoanStats 加载指定低内存模式并只读取关键列 loan_cols [ loan_amnt, term, int_rate, grade, emp_length, home_ownership, annual_inc, purpose, dti, delinq_2yrs, fico_range_low, fico_range_high, inq_last_6mths, open_acc, pub_rec, revol_bal, revol_util, total_acc, loan_status ] loan_df pd.read_csv( LoanStats_2019Q1.csv, usecolsloan_cols, low_memoryFalse, skiprows1 # LendingClub 原始文件首行常有说明文字 ) # german 加载字段少直接读但要注意列名 german_df pd.read_csv(german.csv)逻辑说明usecols减少内存占用low_memoryFalse避免分块推断导致的类型不一致skiprows1是因为 LendingClub 原始导出文件第一行往往是描述性文字而非表头。german.csv 结构简单直接读取即可但后续要确认列名是否与代码中的引用一致——不同来源的 german.csv 列名可能是creditability也可能是Creditability大小写差异会导致 KeyError。参数方面low_memory设为 False 会一次性加载全部数据再推断类型内存占用高但类型稳定如果机器内存有限可以保持 True 但后续必须手动astype修正。skiprows的值要根据实际文件首行内容调整不是固定值。2.2 缺失值处理与标签定义LoanStats 的缺失值处理是这套代码里比较有参考价值的部分。它的策略不是一刀切填充而是分字段类型处理数值型用中位数填充并加缺失指示列类别型用众数填充或单独归为 Unknown 类。# 数值型缺失中位数填充 缺失指示列 num_cols [loan_amnt, annual_inc, dti, revol_bal, revol_util] for col in num_cols: loan_df[f{col}_missing] loan_df[col].isnull().astype(int) loan_df[col] loan_df[col].fillna(loan_df[col].median()) # 类别型缺失归为 Unknown cat_cols [emp_length, home_ownership, purpose] for col in cat_cols: loan_df[col] loan_df[col].fillna(Unknown) # 标签定义loan_status 映射为二分类 # Current 和 Fully Paid 归为 0Charged Off 和 Default 归为 1 status_map { Fully Paid: 0, Current: 0, Charged Off: 1, Default: 1 } loan_df loan_df[loan_df[loan_status].isin(status_map)] loan_df[target] loan_df[loan_status].map(status_map)这里有个关键决策为什么加缺失指示列因为在风控场景里这个字段缺失本身可能就是风险信号。比如收入字段缺失的申请人违约率往往高于收入字段完整的申请人。加一列 0/1 标记让模型自己决定要不要利用这个信息。标签定义部分要注意Current状态的贷款还没到期严格来说不应该直接归为负样本因为未来可能变成坏账。但教学项目里通常简化处理把Current和Fully Paid都当做好样本。如果你要做更严谨的建模应该只保留Fully Paid和Charged Off或者对Current做样本加权。german.csv 的标签通常是creditability列1 代表好客户0 代表坏客户需要根据代码里的实际引用做映射。常见做法是统一成 0/1 二分类1 表示违约。注意两份数据的标签定义方向可能相反LoanStats 里 1 是坏样本german 里可能 1 是好样本。建模前必须确认清楚否则 AUC 会低于 0.5这是血泪经验。3. 特征工程与模型训练从原始字段到可用特征的完整链路3.1 特征衍生与编码方案风控建模的特征工程核心就三件事把原始字段变成有业务含义的衍生特征、把类别型字段编码成数值、把连续型字段做分箱或标准化。这套代码在这三块都有实现我挑几个有代表性的讲。利率字段int_rate在原始数据里是字符串带百分号的比如 13.56%需要先清洗再衍生。常见做法是去掉百分号转浮点然后按利率区间分箱。# int_rate 清洗与分箱 loan_df[int_rate] loan_df[int_rate].str.replace(%, ).astype(float) # 按业务含义分箱低息、中息、高息 bins [0, 8, 15, 25] labels [low, medium, high] loan_df[int_rate_bin] pd.cut(loan_df[int_rate], binsbins, labelslabels) # 收入对数变换降低长尾分布影响 loan_df[log_annual_inc] np.log1p(loan_df[annual_inc]) # 负债收入比衍生 loan_df[loan_income_ratio] loan_df[loan_amnt] / (loan_df[annual_inc] 1)np.log1p而不是np.log是因为收入可能为 0log1p 等价于 log(1x)避免无穷大。loan_income_ratio加 1 是防止除零这是个小细节但很容易翻车。类别编码方面代码里用的是 WOEWeight of Evidence编码这是风控场景的标准做法。WOE 的计算逻辑是对每个类别计算该类别中坏样本占比与好样本占比的对数比。相比独热编码WOE 能保留类别与目标之间的单调关系而且不会导致维度爆炸。def calc_woe(df, feature, target): 计算单个特征的 WOE 编码 grouped df.groupby(feature)[target].agg([count, sum]) grouped.columns [total, bad] grouped[good] grouped[total] - grouped[bad] total_bad grouped[bad].sum() total_good grouped[good].sum() # 加 0.5 平滑防止除零 grouped[bad_rate] (grouped[bad] 0.5) / (total_bad 0.5) grouped[good_rate] (grouped[good] 0.5) / (total_good 0.5) grouped[woe] np.log(grouped[good_rate] / grouped[bad_rate]) return grouped[woe].to_dict() # 对 grade 字段做 WOE 编码 woe_map calc_woe(loan_df, grade, target) loan_df[grade_woe] loan_df[grade].map(woe_map)平滑项 0.5 是拉普拉斯平滑的简化版防止某个类别里坏样本数为 0 导致 log 无穷。实际项目中平滑系数可以调样本量大时用 0.5 足够样本量小时可以适当增大。3.2 模型训练与交叉验证代码里用的模型主要是逻辑回归和 XGBoost 两个。逻辑回归作为基线模型可解释性强系数方向能直接反映特征与风险的关系XGBoost 作为提升模型捕捉非线性交互。这种双模型对比是风控建模的标准套路。from sklearn.model_selection import StratifiedKFold, cross_val_score from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from xgboost import XGBClassifier # 准备特征矩阵 feature_cols [ loan_amnt, int_rate, log_annual_inc, dti, delinq_2yrs, inq_last_6mths, open_acc, revol_util, total_acc, loan_income_ratio, grade_woe ] X loan_df[feature_cols].fillna(0) y loan_df[target] # 逻辑回归管道标准化 模型 lr_pipe Pipeline([ (scaler, StandardScaler()), (clf, LogisticRegression( class_weightbalanced, max_iter1000, C1.0, random_state42 )) ]) # XGBoost 配置 xgb_clf XGBClassifier( n_estimators200, max_depth4, learning_rate0.05, subsample0.8, colsample_bytree0.8, scale_pos_weight3, eval_metricauc, random_state42 ) # 分层交叉验证 skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) lr_scores cross_val_score(lr_pipe, X, y, cvskf, scoringroc_auc) xgb_scores cross_val_score(xgb_clf, X, y, cvskf, scoringroc_auc) print(fLR AUC: {lr_scores.mean():.4f} ± {lr_scores.std():.4f}) print(fXGB AUC: {xgb_scores.mean():.4f} ± {xgb_scores.std():.4f})参数说明class_weightbalanced让逻辑回归自动按类别频率反比加权处理不平衡数据scale_pos_weight3是 XGBoost 里对应的参数值等于负样本数除以正样本数这里假设坏样本约占 25%。max_depth4是风控场景的常见选择树太深容易过拟合而且业务上也不需要对特征做太复杂的交互。subsample和colsample_bytree设为 0.8 是引入随机性防过拟合。交叉验证用StratifiedKFold而不是普通 KFold是因为正负样本比例在每折里必须保持一致否则某折可能全是好样本AUC 计算就失去意义。提示如果你的 AUC 在 0.5 附近徘徊先检查标签映射方向对不对再检查有没有把标签列本身混进特征里。这两个是新手最常见的翻车点。4. 避坑与排查跑这套代码时最容易卡住的五个地方4.1 文件路径与编码问题现象运行时报FileNotFoundError或UnicodeDecodeError。原因代码里写的相对路径是作者本机的目录结构你解压后文件位置不一样另外 german.csv 可能是 latin-1 编码而非 utf-8。解决把所有文件路径改成你本地的实际路径或者统一放到代码同级目录下用文件名直接引用。编码问题加encodinglatin-1参数试试如果还不行就chardet检测一下。4.2 pandas 版本导致的 API 差异现象append方法报 AttributeError或者fillna的method参数报 FutureWarning。原因pandas 1.4 之后append被废弃2.0 之后method参数被移除。作者写代码时的 pandas 版本可能比较老。解决df.append改成pd.concat([df1, df2])fillna(methodffill)改成fillna(methodffill)或直接用ffill()。先pip show pandas看版本再决定要不要降级。4.3 类别不平衡导致的模型失效现象模型把所有样本都预测为好客户准确率很高但 AUC 很低。原因坏样本占比太低模型倾向于多数类就能获得高准确率。解决除了class_weight和scale_pos_weight还可以用 SMOTE 过采样。但注意 SMOTE 只能在训练集上做不能在交叉验证之前对整个数据集做否则会造成数据泄漏。4.4 WOE 编码的边界情况现象WOE 编码后出现 inf 或极值模型系数爆炸。原因某个类别里坏样本数为 0log 计算出现无穷大。解决加平滑项代码里已经加了 0.5但如果样本量极小可以加到 1.0。另外检查是否有类别只有一个样本这种类别应该合并到其他类别里。4.5 特征泄漏的隐蔽形式现象交叉验证 AUC 高得离谱但换一份数据就崩了。原因某些特征在预测时不可得比如loan_status相关的衍生字段、贷后产生的字段。或者在做 WOE 编码时用了全量数据而非训练集数据。解决WOE 编码必须在每折训练集上单独计算再应用到验证集。代码里如果是在全量数据上算 WOE 再交叉验证那就是泄漏了。正确做法是把 WOE 计算封装进 Pipeline 或者手动在每折里做。5. 模型评估与阈值调优从 AUC 到实际通过率5.1 评估指标的选择逻辑风控模型不能只看 AUC。AUC 衡量的是排序能力但实际业务里你需要一个阈值来决定通过还是拒绝。这时候要看 KS 统计量和提升度。from sklearn.metrics import roc_curve, roc_auc_score def calc_ks(y_true, y_prob): 计算 KS 统计量 fpr, tpr, thresholds roc_curve(y_true, y_prob) ks max(tpr - fpr) return ks, thresholds[np.argmax(tpr - fpr)] # 用交叉验证的 out-of-fold 预测算 KS from sklearn.model_selection import cross_val_predict y_prob cross_val_predict(xgb_clf, X, y, cvskf, methodpredict_proba)[:, 1] ks_value, best_threshold calc_ks(y, y_prob) print(fKS: {ks_value:.4f}, Best threshold: {best_threshold:.4f})KS 的含义是在最优阈值下好样本和坏样本的累积分布差异最大值。风控场景里 KS 一般要求 0.3 以上0.4 以上算不错。best_threshold就是让 KS 最大的那个概率截断点。5.2 阈值调优与业务权衡拿到最优阈值之后还要看通过率和坏账率的权衡。比如阈值设 0.5通过率 70%坏账率 5%阈值设 0.3通过率 85%坏账率 8%。选哪个取决于业务的风险偏好。def threshold_analysis(y_true, y_prob, thresholds): 不同阈值下的通过率和坏账率 results [] for t in thresholds: pred (y_prob t).astype(int) approval_rate 1 - pred.mean() bad_rate y_true[pred 1].mean() if pred.sum() 0 else 0 results.append({ threshold: t, approval_rate: approval_rate, bad_rate: bad_rate }) return pd.DataFrame(results) # 分析 0.1 到 0.9 的阈值 thresholds np.arange(0.1, 1.0, 0.1) analysis threshold_analysis(y, y_prob, thresholds) print(analysis)这张表能直接拿去做业务汇报。通过率和坏账率的曲线通常是一个权衡阈值越高通过率越低坏账率也越低。找到业务能接受的坏账率上限反推阈值。5.3 特征重要性验证XGBoost 自带特征重要性但风控场景更关注的是特征方向是否合理。比如收入越高应该违约率越低如果模型学出来收入越高违约率越高那大概率是数据有问题。# 特征重要性 importance pd.DataFrame({ feature: feature_cols, importance: xgb_clf.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance) # 检查关键特征方向用逻辑回归系数 lr_pipe.fit(X, y) coef pd.DataFrame({ feature: feature_cols, coef: lr_pipe.named_steps[clf].coef_[0] }).sort_values(coef) print(coef)逻辑回归的系数符号就是特征方向。log_annual_inc系数应该为负收入越高风险越低int_rate系数应该为正利率越高风险越高。如果符号反了先检查特征有没有标准化、标签有没有搞反。从那以后我每次跑完模型都强制走一遍这个检查先看 KS再看阈值分析表最后对一遍逻辑回归系数方向。三步都过了才敢把结果拿出去。希望帮到你。本文还有配套的精品资源点击获取
返回列表