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

资讯详情

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

Python金融风控建模实战:评分卡与特征工程全拆解

Python金融风控建模实战:评分卡与特征工程全拆解 简介这是一份面向金融风控与数据分析学习者的Python实战源码包围绕金融大数据风控建模全流程展开覆盖数据预处理、信用评分、风险预测、欺诈检测、聚类分析与可视化等核心环节。包内提供可直接运行的代码与配套数据集适合正在学习风控建模或希望提升工程实践能力的初中级Python用户。资源共106个文件以Python源码为主包含40个py脚本、16个csv数据文件、19个pkl模型文件及3个ipynb案例笔记整体压缩包约19.67MB结构清晰便于按模块查阅。已有1001人学习使用。通过运行这些源码读者可实际操作逻辑回归、随机森林、XGBoost等模型理解数据清洗与特征工程的具体写法并借助可视化图表与训练好的模型结果快速构建起个人风控建模知识体系为后续业务落地打下基础。1. 一份Python金融大数据风控建模实战压缩包到底值不值得解压如果你手里的压缩包叫“Python金融大数据风控建模实战.zip”大概率你正处在两个阶段之一要么是刚入门数据科学想找一个不像教材那么枯燥、能直接跑出结果的项目要么是做传统信贷或支付风控想从SQL Boy和报表工具往Python建模上转。这个标题最值钱的地方不是把Python、大数据、风控三个词放在一起而是它暗示了一条完整的链路从原始信贷数据到风控特征从模型训练到信用分输出。我的建议是别急着双击解压。先想清楚你要从这个包里获取什么。如果只是想要代码网上单点教程一大把但如果需要一个能对着数据动手、能理解评分卡怎么从概率变成分数、能知道特征分箱为什么存在的完整流程这类实战包确实值得拆开研究。适合谁适合已经会用pandas读数据、但没系统做过风控建模的人也适合业务侧转技术、想看懂模型文档但缺实操手感的人。下面我按自己平时做项目的顺序把这个玩法拆给你看。2. 拆开ZIP前先搞懂金融风控建模的数据流与特征工程绝大多数风控建模实战包的目录结构都能猜个大概data文件夹放原始样本notebook或src文件夹放特征工程和训练脚本可能有几个csv、一个requirements.txt外加一份说明文档。但真正决定这个包能不能让你学到东西的不是代码量而是它是否讲清楚数据怎么变成特征、特征怎么变成分数。这一章我们先把这条链路打通。2.1 从原始信贷数据到建模宽表常见字段与衍生逻辑金融风控建模的数据一般来自三张表客户基本信息表、历史借贷行为表、申请时点信息表。实战包里常见的原始字段包括年龄、性别、收入、职业、学历、负债率以及历史逾期次数、近6个月查询次数、信用卡使用率等。这些字段单独看都很好理解但建模前必须把多张表拼成一张“一行一个样本”的宽表。拼接的核心是聚合逻辑。比如一个用户有多笔历史贷款那么每笔贷款的逾期状态需要聚合成“历史逾期笔数”“最大逾期天数”“最近一次逾期距今月数”。实战中常见代码是import pandas as pd # loan_history: 每笔贷款的还款记录 # 字段: user_id, loan_date, overdue_days, repay_status loan pd.read_csv(loan_history.csv) # 按用户聚合历史行为 agg loan.groupby(user_id).agg( 总笔数(loan_date, count), 逾期笔数(overdue_days, lambda x: (x 0).sum()), 最大逾期天数(overdue_days, max), 最近贷款日期(loan_date, max) ).reset_index() # 计算最近一笔贷款距今的月份数假设建模基准日 base_date pd.Timestamp(2023-06-30) agg[最近贷款距今月数] ((base_date - pd.to_datetime(agg[最近贷款日期])).dt.days / 30).astype(int) # 与主表合并 base pd.read_csv(application_base.csv) wide base.merge(agg, onuser_id, howleft)这段代码的逻辑是先用groupby按用户维度把多行历史记录压成一行再和申请主表左连接。注意left join保留了所有申请用户没有历史借贷行为的用户聚合字段会是NaN后面需要填充比如缺失逾期笔数填0缺失收入填中位数。参数上“最大逾期天数”比“平均逾期天数”更常用因为风控关注的是极端风险而不是平均风险。这个聚合过程是你在实战包里最需要仔细看的环节——很多包会直接给你宽表但不会告诉你宽表是怎么来的。2.2 特征分箱与WOE编码为什么金融风控不直接喂原始特征如果直接把“年龄28”这种原始值丢给逻辑回归模型也能跑但有两个问题一是对非线性关系拟合很差比如年龄和违约风险是U型曲线二是单个特征的可解释性弱业务方会问“为什么年龄系数是-0.03”。所以在评分卡模式下普遍做法是先分箱再用WOE编码替代原始值。分箱就是把连续变量切成若干区间。等距分箱简单但容易踩空桶等频分箱按分位数切、每箱样本量一致在风控里更稳。分箱之后每一箱计算WOEWeight of Evidence公式是WOE ln(箱内坏样本占比 / 箱内好样本占比)WOE为正说明该箱风险低于整体为负说明风险高于整体。用WOE替换原始值后逻辑回归的输入就变成了一列有序的编码值系数的含义也更直观。2.3 用Python实现等频分箱与WOE计算的参考代码下面这段代码是实战包中特征工程部分的常见骨架你可以对照自己的数据改字段名后直接复现import numpy as np import pandas as pd def woe_bin_conversion(df, col, target, bins5): 等频分箱 WOE编码 df: 宽表数据 col: 待分箱特征 target: 0-1标签列1为坏样本 bins: 分箱数 # 等频分箱按分位数切重复分位点会报错先drop_duplicates try: cuts pd.qcut(df[col], qbins, duplicatesdrop) except ValueError: # 特征值种类太少时退化为类别型 cuts df[col].astype(category) grouped df.groupby(cuts, observedFalse)[target].agg( 总样本(size), 坏样本(sum) ).reset_index() grouped[好样本] grouped[总样本] - grouped[坏样本] # 计算整体好/坏占比用于WOE total_good grouped[好样本].sum() total_bad grouped[坏样本].sum() # 平滑处理避免除零 eps 0.5 grouped[坏占比] (grouped[坏样本] eps) / (total_bad eps * len(grouped)) grouped[好占比] (grouped[好样本] eps) / (total_good eps * len(grouped)) grouped[WOE] np.log(grouped[坏占比] / grouped[好占比]) # IV值衡量特征预测力 grouped[IV] (grouped[坏占比] - grouped[好占比]) * grouped[WOE] # 将WOE映射回原表 df[col _WOE] df[col].map(grouped.set_index(区间)[WOE]).astype(float) return df, grouped # 使用示例假设wide是宽表label是好坏标签 wide, woe_table woe_bin_conversion(wide, 年龄, label, bins5)参数说明bins5表示按五分位切分样本量少时建议改成4或3否则箱内坏样本过少导致WOE不稳定。duplicatesdrop会在特征值重复过多时自动减少箱数这是等频分箱最常见的翻车点。eps平滑项是为了防止某个箱内坏样本为0时对数计算出无穷大。IV值是WOE的加权和业内常说IV小于0.02基本无预测力0.1以上算强特征——这个阈值可以在实战包里核对不同包取值略有差异。3. 把实战包跑起来环境准备、解压与最小可运行流程拿到zip后第一步不是读代码而是确认环境。Python版本、依赖库版本、甚至操作系统都会影响你能不能跑通。这一章给你一条从解压到出分的最小路径。3.1 Python环境与依赖安装先看requirements.txt再动手大部分这类实战包都会带一个requirements.txt里面列了pandas、numpy、scikit-learn、openpyxl等包。别直接pip install -r requirements.txt很多老包的版本号会和你的Python 3.10冲突。我一般这样做mkdir risk_model_demo cd risk_model_demo # 解压注意如果zip是伪加密会报错见第4章 unzip Python金融大数据风控建模实战.zip -d ./ python -m venv .venv source .venv/bin/activate # Windows下使用 .venv\Scripts\activate pip install --upgrade pip # 先看依赖清单再安装 cat requirements.txt # 如果里面有类似scikit-learn0.22这种老版本建议去掉版本号重新安装 pip install pandas numpy scikit-learn openpyxl逻辑说明创建虚拟环境是必须的避免污染系统Python。不直接按requirements装的原因是很多老项目写死了旧版本装上会和当前Python解释器不兼容。如果包里有pandas的.read_excel()调用openpyxl必须装否则Excel文件读不进来。这一步的坑在于实战包可能没有requirements.txt只给一个notebook那你需要从import语句反推依赖包。3.2 数据加载与训练测试集划分不要用random.shuffle随机切风控建模的时间序列特征很强直接随机划分会让模型“偷看未来”。很多实战包为了省事用train_test_split这在实际业务上是危险的。正确做法是按时间划分import pandas as pd from sklearn.model_selection import train_test_split # 宽表已生成确保有时间字段 data pd.read_csv(wide_table.csv, parse_dates[申请日期]) data data.sort_values(申请日期).reset_index(dropTrue) # 按时间前80%做训练后20%做测试 cut_point int(len(data) * 0.8) train data.iloc[:cut_point] test data.iloc[cut_point:] # 如果包内没有时间字段只能用分层采样 if 申请日期 not in data.columns: train, test train_test_split( data, test_size0.2, stratifydata[label], random_state42 ) # 特征列排除ID、时间、标签 exclude_cols [user_id, 申请日期, label] features [c for c in data.columns if c not in exclude_cols] X_train, y_train train[features], train[label] X_test, y_test test[features], test[label] print(f训练集样本量: {len(X_train)}, 测试集样本量: {len(X_test)})参数说明stratify保证训练和测试的坏样本比例一致防止切出来的测试集全变成好样本。random_state固定下来方便复现。排序后切分是最接近真实风控场景的做法模型上线后面对的就是未来数据如果测试集来自过去评估指标会虚高。你能从这个包学到的最有价值的习惯就是拒绝随机划分。3.3 评分卡尺度转换从概率到信用分的映射模型训练完之后业务上不给概率给整数分。常见映射公式是Score offset factor * ln(odds)假设基准分600分odds好/坏比每翻倍分数增加20分那么factor和offset可以这样求import numpy as np def prob_to_score(prob, base_score600, base_odds50, pdo20): prob: 模型预测的坏样本概率 base_score: 基准分数odds50时 pdo: odds翻倍时分数增量 odds (1 - prob) / np.maximum(prob, 1e-6) factor pdo / np.log(2) offset base_score - factor * np.log(base_odds) score offset factor * np.log(odds) return np.round(score).astype(int) # 假设逻辑回归预测概率 from sklearn.linear_model import LogisticRegression model LogisticRegression(C1.0, class_weightbalanced) model.fit(X_train, y_train) test[概率] model.predict_proba(X_test)[:, 1] test[信用分] prob_to_score(test[概率]) print(test[[user_id, 概率, 信用分]].head())参数说明base_odds50表示好样本是坏样本50倍时的基准分。class_weightbalanced处理样本不平衡是风控模型的标准配置。pdo越小分数对不同风险水平的区分度越高常用20到30。这里的可解释性逻辑是分数每高20分用户相对风险就减半。你需要在实战包里找到建模基准日的定义否则这个分数的绝对意义会失真。4. 常见问题避坑ZIP伪加密、乱码与过拟合的排查记录这里我把解压和数据阶段最常踩的坑按“现象→原因→解决”写出来每一条都是血泪经验遇到能少走半天弯路。4.1 解压报错“password required”但包没加密ZIP伪加密现象双击zip正常命令行unzip却提示输入密码或者用Python的zipfile读取时报RuntimeError: File is encrypted。用WinRAR能看到文件但解压失败。原因ZIP文件格式里有个标志位某些工具或打包者手滑把“加密标志”误置为1但实际没加密。这种叫ZIP伪加密多见于从某些网盘或论坛下载的实战包。解决用Python直接修标志位或者用7-Zip的“修复压缩文件”功能。Python修法是import zipfile import shutil source Python金融大数据风控建模实战.zip target Python金融大数据风控建模实战_fixed.zip src open(source, rb) data bytearray(src.read()) src.close() # 遍历zip中央目录把加密标志位通用位字段第0位清零 # 这是一个已知的通用修复方法 count 0 for i in range(len(data) - 4): if data[i:i4] bPK\x01\x02: # 中央目录项签名 flag_offset i 10 flags int.from_bytes(data[flag_offset:flag_offset2], little) if flags 0x1: data[flag_offset:flag_offset2] (flags ~0x1).to_bytes(2, little) count 1 out open(target, wb) out.write(data) out.close() print(f修复了 {count} 个条目的伪加密标志) # 验证 with zipfile.ZipFile(target, r) as zf: print(zf.namelist()[:5])注意这个操作只对“伪加密”有效真加密的包改标志位只会让解压出乱码。判断方法是用7-Zip打开看是否弹出输入密码窗口如果弹了但密码又提示错误才考虑伪加密。参数上PK\x01\x02是中央目录签名的Magic Number不要用PK\x03\x04去扫那是本地文件头扫出来会改错位置。4.2 Excel或CSV读进来中文列名变乱码现象pd.read_csv(原始数据.csv)读出来列名变成“用户ID锟斤拷”或者一堆菱形问号Excel读出来有“姓名”但合并后对不上。原因老数据源常是GBK或GB2312编码而Python默认用UTF-8读取。实战包里的数据文件可能混合编码有的列是UTF-8、有的是GBK。解决先探测编码再统一转换。import pandas as pd import chardet # 先用二进制读取探测编码 with open(原始数据.csv, rb) as f: raw f.read(100000) # 只读前100KB足够探测 result chardet.detect(raw) print(result) # 按探测结果读取 df pd.read_csv(原始数据.csv, encodingresult[encoding] or utf-8) # 如果列名仍有乱码重命名 df.columns [c.encode(latin-1).decode(gbk) if ? in c else c for c in df.columns]踩坑记录chardet对短文件判断不准如果文件只有几十行直接指定encodinggbk更稳。还有一个常见情况是文件用UTF-8编码但带BOM头pandas会读出一个含\n的列名用encodingutf-8-sig解决。实战包里如果同时给了csv和xlsx优先用xlsxopenpyxl对编码处理更省心。4.3 样本不平衡导致模型全预测好客户现象训练完逻辑回归准确率95%但AUC只有0.52坏客户一个都没抓住。打印分类报告发现recall为0。原因信贷数据中坏样本比例通常低于5%模型为了降低损失把所有样本都预测为好客户。实战包如果没做平衡处理会有这个问题。解决不要用准确率评估。改用AUC和KS训练时加class_weight或对坏样本过采样。from sklearn.metrics import roc_auc_score, classification_report # 检查标签分布 print(data[label].value_counts(normalizeTrue)) # 用class_weightbalanced训练 model LogisticRegression(class_weightbalanced, max_iter1000) model.fit(X_train, y_train) # 评估用AUC y_pred_prob model.predict_proba(X_test)[:, 1] print(AUC:, roc_auc_score(y_test, y_pred_prob)) # 决策阈值不要用0.5按业务坏样本率找阈值坑位说明class_weightbalanced会让概率整体偏高但这不影响排序AUC和KS只跟顺序有关。如果包内训练代码没有平衡处理你看到的分数分布会全部挤在高分段这不是建模错误是没做权重调整。遇到过拟合的典型表现是训练AUC接近0.9测试AUC只有0.6这时回头检查特征数是否超过样本量的十分之一。4.4 特征泄漏模型把“未来”算进去了现象模型在测试集上AUC高达0.98看起来很好但业务领导问为什么上线后完全失效。原因这是最隐蔽的坑。某个特征在样本时间点之后才能获取比如用“6个月后是否结清”去预测“当前是否违约”或者数据里包含了申请被拒后才有的信息。实战包里的宽表通常是预设好的但你自己聚合历史数据时容易放进去。解决从特征生成逻辑层面做时间约束# 错误示范使用全局最大逾期天数 agg loan.groupby(user_id)[overdue_days].max() # 正确做法只统计建模基准日之前90天内发生的行为 cutoff_date base_date - pd.Timedelta(days90) history loan[loan[loan_date] cutoff_date] agg history.groupby(user_id)[overdue_days].max()这个坑很难靠代码检查发现只能靠字段说明和数据字典排查。我在实战包里见过把“催收成功标记”当特征的情况这种字段在申请点是不存在的。拿到宽表后第一件事是拿当时的字段口径文档逐列核对生成时点。如果你手上的资料没有数据字典那就把可疑字段删掉宁可少特征也不要跑出虚假的高分模型。5. 把单模型升级成可落地的风控流水线验证与监控进阶跑通一个实战包很容易但要让人信服你做的风控建模能投入生产还差两步样本外验证和上线后监控。这里给你一个我在项目里常用的验证组合拳。第一个是把K折交叉验证改成“时间序列交叉验证”。普通KFold随机打乱样本会破坏时间依赖我一般按时间顺序切5段训练集用前1段到第i-1段验证集用第i段。这样出来的性能指标才接近真实上线表现。对应代码不复杂就是用TimeSeriesSplit类实际效果比随机K折要悲观但也更真实。第二个是输出分数分段稳定性报表。建模报告里不要只说AUC还要给出每个分数段的坏样本率。一般用下面这个表格分数段样本数坏样本数坏样本率300-4001203967.98%400-50023101205.19%500-60051201502.93%600-70061201101.80%700-8005010400.80%如果坏样本率随着分数单调递减说明评分卡排序能力合格。如果中间出现倒挂比如600-700段坏样本率反而高于500-600段那就要去看是不是该区间内有异常聚集或者某个特征分箱的WOE方向反了。上线之后至少做两类监控一个是PSIPopulation Stability Index每月比较入模特征分布和训练分布的偏移另一个是每日KS和坏样本率趋势。PSI超过0.25就说明特征漂移严重需要重新训练或调整评分切点。这些监控代码不需要重写模型只需要复用训练时的预处理管线生成当前月宽表后套用同样的分箱边界计算WOE和分数。最后说一个我个人不会跳过的习惯每次建模完把特征列表、分箱边界、模型系数、评分映射参数存成一个JSON配置文件而不是只存pickle模型。原因很实际——三个月后业务方问你“这个分数怎么算出来的”如果你丢给他一个pickle他根本没法复核。而把公式和参数明文暴露出来任何新人都能对着跑一遍。这个习惯让我少背了很多黑锅也让评分卡模型真正做到了“既建模、又可解释”。希望这份拆解能帮你在打开那个zip之前先建立正确的方向感。模型跑通不是终点能说清楚每个分数为什么是这个值、并且让业务敢用才是这套实战训练真正值钱的地方。本文还有配套的精品资源点击获取
返回列表