news 2026/9/14 4:50:31

Python信用卡客户高风险识别:从特征工程到评分卡的完整建模实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python信用卡客户高风险识别:从特征工程到评分卡的完整建模实践

简介:这是一份基于Python实现的信用卡客户高风险识别毕业设计项目,面向计算机相关专业在校生、课程设计及实训场景,解决从数据探索、清洗到聚类建模的完整流程。围绕历史信用风险、经济风险与收入风险三个维度,通过K-Means算法构建识别模型,并用手肘法确定最优K值,配合雷达图展示结果,覆盖逾期、呆账、个人及家庭收入等多类指标。压缩包共包含7个文件,以5个Python脚本(对应任务模块)、1个CSV数据文件和1个README说明文档组成,整体大小约861KB,结构清晰,便于直接运行与二次修改。目前已有283人学习下载。代码经过测试运行成功,答辩平均分达到96分,下载后可查看说明文件,也可私聊咨询远程教学,适合作为毕设、课设或入门进阶的参考素材,能帮助读者快速复现信用卡高风险识别方案。

1. 基于python的信用卡客户高风险识别到底在解决什么问题

先抛一个反常识结论:在信用卡客群上,一个把所有人都预测成“正常”的模型,准确率也能到 97% 以上,可它连一个高风险客户都找不出来。基于 python 做信用卡客户高风险识别,本质上不是训练一个“更准”的分类器,而是要在极不平衡的样本里,把占比可能不到 3% 的高风险客户尽可能稳地挑出来。这个任务涉及明确的逾期口径定义、观察期与表现期对齐、特征工程、过采样/欠采样、模型对比,以及最后把预测概率转换成业务可用的分数和名单。整条链路跑下来成本最高的从来不是模型代码本身,而是数据口径和特征的可解释性。适合刚进入风控数据方向的应届生、做数据方向毕业设计的同学,以及想把机器学习落地到信贷业务场景的后端研发和数据分析师。

2. 高风险识别的数据准备:观察期、清洗与特征派生

2.1 逾期标签不对齐,模型再强也白搭

信用卡客户风险识别最致命的坑不是模型效果差,而是标签定义错了还不知道。常见的逾期口径有 M0(未还但仍在宽限期)、M1(逾期 1 期)、M2(逾期 2 期)。业界更倾向用“从观察期起算,未来 6 个月内是否到达 M2”作为二分类目标,因为 M2 意味着客户已经连续跨两个账期未还,资金链紧张程度远超短期遗忘。

观察期与表现期是两个不重叠的时间窗。观察期用于提取特征:消费频次、还款金额、额度使用率、征信查询次数;表现期用于判定好坏客户。一个客户只有在观察期表现正常时才进入样本,否则他的特征本身就带着“已经在逾期”的噪声,模型会把“逾期中”和“未来逾期”混为一谈。

2.1.1 时间窗口的切分方法

建议按固定日(比如每个月的 1 号或 15 号)切分观察期截止日,再向后推 6 个月作为表现期。这样切出来的样本天然呈月度快照结构,后续做时间回溯验证也有基础。随机抽样切训练集/测试集在这个场景几乎一定会出问题——信用卡数据在时间上高度相关,同一个月的行为特征被切进训练集和测试集,模型评估结果虚高,到下一批数据上立刻失效。

2.2 数据字段与缺失值处理

一张标准的信用卡客户宽表,至少包含客户基本信息、卡片信息、账单行为、还款行为、征信摘要五大类字段。下面这张表是我在处理这类数据时常见的字段构成和缺失处理方式:

字段类别典型字段字段说明缺失处理
基本信息年龄、户籍、学历、婚姻客户自然属性缺失比例超过 30% 建议剔除
卡片信息卡等级、额度、激活时长授信与额度使用基础“未激活”单独设档
账单行为近 6 期消费金额、取现金额消费活跃度按 0 填充或均值填充
还款行为近 6 期还款率、最低还款次数风险区分度最高缺失按最差口径处理
征信摘要查询次数、其他机构负债多头借贷信号缺失映射成“未知档位”

注意:信用卡数据的“缺失”不一定真是缺失,有时代表“没有发生”,比如没有取现记录、没有最低还款记录。直接删行或统一填充 0,会把“没做过某动作”错误理解成“做了但数据丢了”。处理前要先看业务字典。

2.3 基于 pandas 的高风险特征派生

原始字段直接进模型,很难表达“收入波动”“还款意愿变化”这类动态风险信号。所以特征工程的目标是行为趋势化。下面这段代码用 pandas 生成一批在信用卡风控实践中常用到的高区分度特征:

import pandas as pd import numpy as np def build_risk_features(df): """ df: 以客户ID为主键的宽表,每行一个客户,一个观察期快照 返回: 新增特征后的DataFrame """ feat = df.copy() # 1. 额度使用率:当前欠款 / 总授信额度 feat['utilization_rate'] = feat['balance'] / (feat['credit_limit'] + 1e-6) # 2. 近6个月平均还款率:还款金额 / 出账金额 feat['avg_repay_rate_6m'] = ( feat['repay_amount_6m'] / (feat['bill_amount_6m'] + 1e-6) ).clip(0, 2) # 3. 最低还款行为占比 feat['min_payment_ratio'] = feat['min_payment_count'] / 6 # 4. 额度使用率的趋势:最近3个月均值 / 前3个月均值 - 1 before = feat['balance_avg_3m_before'] after = feat['balance_avg_3m_after'] feat['util_trend'] = np.where( before > 0, (after - before) / before, after - before ) # 5. 征信查询次数激增标志 feat['query_surge_flag'] = ( feat['query_count_last_3m'] > 2 * feat['query_count_before_3m'] ).astype(int) return feat

这段代码里几个参数值得细看。clip(0, 2)对还款率做截断是为了防止退款、人工冲正导致的异常高值把样本分布拉偏;还款率超过 2 倍出账额,基本是账务异常,不是真实还款能力。min_payment_ratio取 6 个月的最小还款次数占比,比例越高说明客户越长期处于“只还最低”的状态,这通常对应资金链偏紧。util_trend构造的是额度的先降后升趋势,消费回落之后再快速上行,常见于临逾期前的舀卡行为。这些派生特征的共同特点是不依赖未来数据,所有输入都来自观察期及观察期之前,从构造上规避了信息穿越。

2.4 数据切分:按时间切而不是按行随机切

训练集、验证集、测试集的切分必须严格按观察期月份完成。我一般用最近 3 个观察月做测试集,再往前 2 个月做验证集,其余历史月份做训练集。这样得到的模型效果肯定比随机切分低一点,但更接近真实上线后的水平。

train = data[data['obs_month'] < '2024-03-01'] val = data[(data['obs_month'] >= '2024-03-01') & (data['obs_month'] < '2024-05-01')] test = data[data['obs_month'] >= '2024-05-01']

按时间切分之后,模型评估结果会逼近真实场景,因为 5 月之后出现的高风险客户,其行为本来就是训练集里没有完全见过的。如果测试集 AUC 仍然稳定,说明模型学到的不是某一段时间的偶然规律,而是可迁移的风险模式。

3. 不平衡样本下的 python 建模:SMOTE 过采样与模型对比

3.1 用准确率评估高风险识别是伪命题

信用卡坏客户占比通常在 2%~5% 之间,一个把所有样本都判为“正常”的空模型,准确率是 95%。这完全不能说明模型有效,只能说明数据不平衡。高风险识别最常用的两个指标是 AUC 和 KS。AUC 衡量的是模型把正样本排在负样本前面的概率,KS 衡量的是好坏样本累计分布的最大差异。这两个指标对样本不平衡不敏感,所以默认优先看它们,而不是准确率。

from sklearn.metrics import roc_auc_score, roc_curve auc = roc_auc_score(y_test, pred_prob) fpr, tpr, _ = roc_curve(y_test, pred_prob) ks = max(tpr - fpr)

AUC 和 KS 各有一个需要注意的点。AUC 在 0.75 以上就具备基本区分能力,但如果正负样本比例达到 1:50,AUC 的增益空间会变小,0.80 和 0.82 之间看似只有 0.02 的差异,实际对应的高风险客群排序变化可能相当明显。KS 反映的是“排序的最优切分点”,但最优切分点对应的阈值未必是业务想要的阈值,所以 KS 适合做模型对比,不适合直接决定业务切分点。

3.2 SMOTE 过采样提升召回,但也有代价

不平衡样本的经典处理手段有下采样、过采样和 SMOTE。下采样会丢掉大量正常样本,模型方差变大;简单过采样重复拷贝少数类样本,容易过拟合到重复样本上。SMOTE 的思路是在少数类样本之间做插值,对每个少数类样本,取其 k 个近邻并在连线上生成新样本。它的实现方式很简洁:

from imblearn.over_sampling import SMOTE from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, stratify=y, random_state=42 ) smote = SMOTE( sampling_strategy=0.3, k_neighbors=5, random_state=42 ) X_resampled, y_resampled = smote.fit_resample(X_train, y_train)

sampling_strategy=0.3表示过采样后少数类数量是多数类的 30%,继续往上加会进一步放大过拟合风险。官方默认是 1.0,也就是两类各占一半。在实际信用卡场景中,把少数类补到 30% 已经足够,因为 SMOTE 造出的样本是插值样本,不能无限补充。k_neighbors=5是近邻数量,值越大生成的样本越偏向全局分布,可能削弱局部模式;值太小则容易生成离群样本。特别注意,SMOTE 必须在训练集上单独执行,如果先对全量数据过采样再划分,会产生严重的数据泄漏,因为同一批疑似客户可能同时存在于训练集和测试集。

提示:如果用 LightGBM 或 XGBoost 这类树模型,SMOTE 不是必须的。树模型天然能处理不平衡,真正重要的反而是正则化参数和早停轮数。SMOTE 对逻辑回归和神经网络这类参数模型效果更明显。

3.3 模型对比与结果解读

在高风险识别里,常见做法是同时跑逻辑回归、随机森林和 LightGBM,用同一套特征和同一套切分方式做横向对比。逻辑回归的优势在可解释性和稳定性,随机森林在中小数据集上表现稳健,LightGBM 在特征量较大、样本较多时增益明显。

模型特征处理AUCKS训练耗时
逻辑回归原始特征 + 标准化0.7720.4212s
随机森林原始特征0.7950.43838s
LightGBM原始特征0.8230.47615s
LightGBM + SMOTE原始特征 + 过采样0.8310.48220s

单看表格,LightGBM + SMOTE 指标最高,但实际落地时不会直接把这个组合定成最终方案。风控场景中对模型解释性要求高,监管或业务方要能说清“哪些特征导致这个客户被判断为高风险”。逻辑回归配合 WOE 分箱,能直观给出每个特征档位的风险贡献度,因此很多团队最终上线用的是逻辑回归评分卡,LightGBM 只作为影子模型或特征筛选工具。底层的评估方法比模型本身更关键,任何模型的 AUC 提升如果不符合 0.01 这个量级,在真实业务上都很难带来可感知的名单变化。

4. 从 python 预测概率到评分卡:WOE、IV 与阈值切分

4.1 最优分箱与 WOE 编码

模型输出的概率无法直接给业务使用,所以一般会把概率换算成整数分数。换算前先做 WOE 编码。WOE 的公式是:

WOE_i = ln(坏客户占比 / 好客户占比)

WOE 表达的是“这个特征档位对坏客户的区分能力”,值为 0 表示该档好坏比例与总体一致,没有任何区分度;值越大表示该档客户坏客户占比越高。IV 是 WOE 在全特征上的加权求和,用来衡量特征的整体预测能力,一般经验值是 IV 在 0.02~0.1 之间有一定区分度,0.1~0.3 区分度明显,超过 0.5 需要警惕是不是特征本身包含了未来信息。

import pandas as pd import numpy as np def compute_woe_iv(df, feature, target, bins=10): temp = df[[feature, target]].copy() temp['bin'] = pd.qcut(temp[feature], q=bins, duplicates='drop') grouped = temp.groupby('bin', as_index=False)[target].agg( good='count', bad='sum' ) grouped['good'] = grouped['good'] - grouped['bad'] total_good = grouped['good'].sum() total_bad = grouped['bad'].sum() grouped['bad_rate'] = grouped['bad'] / total_bad grouped['good_rate'] = grouped['good'] / total_good grouped['woe'] = np.log( (grouped['bad_rate'] + 1e-6) / (grouped['good_rate'] + 1e-6) ) grouped['iv'] = (grouped['bad_rate'] - grouped['good_rate']) * grouped['woe'] return grouped

pd.qcut做的是等频分箱,让每个箱子里样本数量尽量相等,避免某些档位样本过少导致 WOE 波动。duplicates='drop'处理分箱边界上重复值,防止报错。分母加上 1e-6 是为了规避某个分箱里坏客户占比为 0 时对数无定义的问题。等频分箱只是起点,实际建模还会根据 WOE 单调性手工合并相邻分箱,让“额度使用率越高,WOE 越大”这类业务规律在编码上自然成立。

4.2 概率到评分:offset 与 factor 的换算

评分卡标准公式是:

score = offset + factor × ln(odds)

其中 odds 是好客户概率与坏客户概率的比值。这个公式的意义是:当 odds 翻倍时,分数固定增加或减少 factor 分。我习惯用“分数越高风险越低”的方向。设定两对标定参数:odds=20:1 时基准分 600 分,odds 翻倍增加 20 分,对应 factor 和 offset 分别为:

import numpy as np factor = 20 / np.log(2) offset = 600 - factor * np.log(20) print(f"factor: {factor:.4f}") # 28.8539 print(f"offset: {offset:.4f}") # 513.5855

20 / np.log(2)的推导逻辑是:odds 从 20:1 变成 40:1 时,ln(odds) 增加了 ln(2),分数增加 factor × ln(2),所以要增加 20 分就需要 factor 等于 20 除以 ln(2)。offset 则保证分数在 odds=20:1 的客户身上恰好等于 600 分。换算成每个特征上的打分时,将线性回归模型得到的权重乘以对应 WOE,再整体乘 factor,最后做成一张“特征档位 → 加分/减分”的映射表。

4.3 高风险分数阈值怎么切

评分卡分数分布出来后,需要结合业务容量切阈值。通常做法是看不同分数段下的坏客户占比:

分数区间风险等级建议处置策略
700 分以上低风险保持额度,常规运营
600~700 分中低风险适当降额或提高关注频次
500~600 分中高风险限制提额,进入人工抽审
500 分以下高风险进入催收优先队列,停止提额

阈值切分不能只盯着 KS 最大值。KS 最大点在 620 分,但如果业务上只能承受 5% 的客户进入人工审核,那么对应分数一般在 560 分或更低。常见做法是输出评分分布表、每段坏客户率、每段累计召回率三个指标,然后让业务负责人挑“累计召回率达到 60% 时所需覆盖客群比例最低”的切点。

5. 源码工程化:一套能交付验收的 python 风控代码怎么组织

5.1 目录结构与一键运行入口

标题里“源码+数据+文档说明”的三要素,决定了代码从拿到手到跑出结果,中间不能有超过一页纸的配置说明。我通常会按下面的结构组织工程:

credit_risk/ ├── config.py ├── main.py ├── requirements.txt ├── data/ │ ├── raw/ │ └── processed/ ├── features/ │ ├── base_features.py │ └── woe_transformer.py ├── models/ │ ├── train_lr.py │ ├── train_lgb.py │ └── eval.py ├── notebooks/ │ └── eda_demo.ipynb └── docs/ ├── 数据说明.md ├── 建模报告.md └── 复现步骤.md

config.py是全局配置,包括文件路径、观察期范围、表现期月数、SMOTE 采样比例、随机种子。main.py是主入口,按“读数据 → 特征复用 → 训练 → 评估 → 输出报告”的顺序串联。评审的人拿到代码后只需要分别设置data/raw下的原始表和运行python main.py就能复现。不是所有代码都需要进main.py,探索性分析放在notebooks/里,但模型训练、特征生成、评估这些核心链路必须脚本化,不能依赖 notebook 的单元格顺序。

5.2 文档说明的“可验收”标准

文档说明最容易被忽略的是“结论与数据的对应关系”。建模报告里写“LightGBM 优于逻辑回归”时,必须同时列出两者使用的是什么特征集、什么样本范围、什么切分方式。只看 AUC 数字不够,同一个模型在特征差异下指标可能完全相反。我一般会在建模报告里固定放三样东西:标签定义(含时间窗口)、特征清单(含对应 IV 值)、运行结果表(含 AUC/KS 以及训练集和测试集之间的差值)。训练集 AUC 0.87、测试集 0.83,这个差值在可接受范围;如果训练集 0.95 而测试集 0.70,文档里就要明确写“模型存在过拟合”,而不是给结论打圆场。

5.3 用时间回溯验证代码没有未来函数

这个技巧能显著提升源码的可信度。做法是从原始数据中按不同观察期切出多份样本,比如观察到 2024 年 1 月、2024 年 3 月、2024 年 5 月,每次都在当前观察期上完整重跑训练与评估,最后把 AUC 和 KS 的输出列成一张时间序列表。

def train_eval_on_month(data, obs_month, config): train_data = data[data['obs_month'] < obs_month] test_data = data[data['obs_month'] == obs_month] model = train_model(train_data, config['feature_cols']) auc, ks = evaluate(model, test_data, config['feature_cols']) return obs_month, auc, ks for month in ['2024-01-01', '2024-03-01', '2024-05-01']: result = train_eval_on_month(data, month, config) print(f"观察期: {result[0]}, AUC: {result[1]:.4f}, KS: {result[2]:.4f}")

如果三个观察期上的指标差异在 0.02 以内,说明整个数据管道在时间维度上是稳定的,没有“不小心用到了未来字段”。如果某个观察期 AUC 大幅下滑,不要先怀疑参数,优先检查那段时间的特征缺失率、某类字段是否出现了系统性的数值漂移。这个验证脚本短,却能把“做过特征工程”“理解时间窗口”“掌握模型评估”三件事一次性证明给评审看。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 4:48:44

Claude 跑 MCP 增强 Selenium:模型 Key 用 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 4:48:22

FOC电流采样硬核指南:单/双/三电阻选型与ADC时序避坑

1. 这不是理论推导&#xff0c;是炸过三次MOS管后写下的电流采样实操手册FOC电流采样这件事&#xff0c;说白了就是让控制器“看清”电机绕组里真实流过的电流。但现实很骨感&#xff1a;你按教科书接好单电阻、设好ADC触发点、跑通SVPWM&#xff0c;一上电——“砰”一声&…

作者头像 李华
网站建设 2026/9/14 4:47:48

背包客主题静态网站实战:HTML+CSS+JS与Nginx部署全记录

做了个背包客主题的静态网站&#xff0c;项目名叫“千年之恋”&#xff0c;听着挺文艺&#xff0c;其实就是一个纯粹的HTMLCSSJavaScript前端练手作品。我做这个项目的出发点很简单&#xff1a;想用网页把一个古镇的传说和一路上的风景、住宿、车票、手绘地图这些旅行碎片串起来…

作者头像 李华
网站建设 2026/9/14 4:46:09

MiGPT:3步把小爱音箱接入ChatGPT,改造成你的专属AI语音助手

MiGPT&#xff1a;3步把小爱音箱接入ChatGPT&#xff0c;改造成你的专属AI语音助手 【免费下载链接】mi-gpt &#x1f3e0; 将小爱音箱接入 ChatGPT 和豆包&#xff0c;改造成你的专属语音助手。 项目地址: https://gitcode.com/GitHub_Trending/mi/mi-gpt 深夜你问&quo…

作者头像 李华
网站建设 2026/9/14 4:46:05

粒子群优化算法在无人机三维路径规划中的MATLAB实现与调参指南

简介&#xff1a;资源为基于粒子群算法&#xff08;PSO&#xff09;的无人机三维路径规划MATLAB实现&#xff0c;面向计算机、电子信息工程、数学等专业的课程设计、期末大作业及毕业设计。代码采用参数化编程&#xff0c;结构清晰、注释齐全&#xff0c;涉及三维环境地图构建、…

作者头像 李华