资讯动态

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

发布时间:2026/9/14 4:50:38 来源:尧图企业网站定制
简介这是一份基于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:50AUC 的增益空间会变小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_size0.3, stratifyy, random_state42 ) smote SMOTE( sampling_strategy0.3, k_neighbors5, random_state42 ) X_resampled, y_resampled smote.fit_resample(X_train, y_train)sampling_strategy0.3表示过采样后少数类数量是多数类的 30%继续往上加会进一步放大过拟合风险。官方默认是 1.0也就是两类各占一半。在实际信用卡场景中把少数类补到 30% 已经足够因为 SMOTE 造出的样本是插值样本不能无限补充。k_neighbors5是近邻数量值越大生成的样本越偏向全局分布可能削弱局部模式值太小则容易生成离群样本。特别注意SMOTE 必须在训练集上单独执行如果先对全量数据过采样再划分会产生严重的数据泄漏因为同一批疑似客户可能同时存在于训练集和测试集。提示如果用 LightGBM 或 XGBoost 这类树模型SMOTE 不是必须的。树模型天然能处理不平衡真正重要的反而是正则化参数和早停轮数。SMOTE 对逻辑回归和神经网络这类参数模型效果更明显。3.3 模型对比与结果解读在高风险识别里常见做法是同时跑逻辑回归、随机森林和 LightGBM用同一套特征和同一套切分方式做横向对比。逻辑回归的优势在可解释性和稳定性随机森林在中小数据集上表现稳健LightGBM 在特征量较大、样本较多时增益明显。模型特征处理AUCKS训练耗时逻辑回归原始特征 标准化0.7720.4212s随机森林原始特征0.7950.43838sLightGBM原始特征0.8230.47615sLightGBM 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, bins10): temp df[[feature, target]].copy() temp[bin] pd.qcut(temp[feature], qbins, duplicatesdrop) grouped temp.groupby(bin, as_indexFalse)[target].agg( goodcount, badsum ) 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 groupedpd.qcut做的是等频分箱让每个箱子里样本数量尽量相等避免某些档位样本过少导致 WOE 波动。duplicatesdrop处理分箱边界上重复值防止报错。分母加上 1e-6 是为了规避某个分箱里坏客户占比为 0 时对数无定义的问题。等频分箱只是起点实际建模还会根据 WOE 单调性手工合并相邻分箱让“额度使用率越高WOE 越大”这类业务规律在编码上自然成立。4.2 概率到评分offset 与 factor 的换算评分卡标准公式是score offset factor × ln(odds)其中 odds 是好客户概率与坏客户概率的比值。这个公式的意义是当 odds 翻倍时分数固定增加或减少 factor 分。我习惯用“分数越高风险越低”的方向。设定两对标定参数odds20:1 时基准分 600 分odds 翻倍增加 20 分对应 factor 和 offset 分别为import numpy as np factor 20 / np.log(2) offset 600 - factor * np.log(20) print(ffactor: {factor:.4f}) # 28.8539 print(foffset: {offset:.4f}) # 513.585520 / np.log(2)的推导逻辑是odds 从 20:1 变成 40:1 时ln(odds) 增加了 ln(2)分数增加 factor × ln(2)所以要增加 20 分就需要 factor 等于 20 除以 ln(2)。offset 则保证分数在 odds20: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 └── 复现步骤.mdconfig.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 大幅下滑不要先怀疑参数优先检查那段时间的特征缺失率、某类字段是否出现了系统性的数值漂移。这个验证脚本短却能把“做过特征工程”“理解时间窗口”“掌握模型评估”三件事一次性证明给评审看。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价