资讯动态

信用卡高风险识别:从逻辑回归到XGBoost的完整风控建模指南

发布时间:2026/10/4 12:11:50 来源:尧图企业网站定制
简介面向计算机相关专业在校生与毕业设计者这份Python实战资源围绕信用卡客户高风险识别任务完整提供从数据探索、数据清洗到K-Means聚类建模与雷达图可视化的全流程方案。项目以历史信用风险、经济风险、收入风险三个维度构建属性采用手肘法确定最优K值并针对逾期、呆账、个人收入与家庭收入等指标进行分布分析适合课程设计、实训作业或毕设项目快速上手与二次扩展。压缩包共7个文件包含5个Python脚本、1份README说明文档与1个CSV数据集覆盖数据处理、模型训练、结果可视化和项目说明等环节整体大小仅861KB结构紧凑便于对照学习。上线后已有283人学习下载源码测试通过且答辩评价较高配套README能厘清文件组织与运行方式也可在现有代码基础上修改功能用于其他相似场景。1. 为什么毕业设计选信用卡高风险识别一个能讲清原理又能落地的选题信用卡高风险识别是金融风控里最经典也最容易讲清楚的选题。很多人以为这类项目必须上深度学习其实在银行信贷场景逻辑回归和XGBoost依然占据主流原因只有一个业务需要解释风险从哪来而不是只告诉我概率。这个选题的价值在于它有一整套标准流程从客户历史交易、账单、个人信息里提炼特征训练分类模型输出风险评分或等级最后还要面对样本不平衡和时间漂移这些真实问题。适合Python基础尚可、想用完整项目串联pandas、sklearn、xgboost和可视化工具的在校生也适合刚入行想做风控模型的工程师。下面按建模顺序把原理、代码和坑一次讲透。2. 从业务到特征信用卡高风险识别的建模对象与数据准备做这个题很多人第一反应是找开源数据集。常见做法是拿UCI的Credit Card Default或者Lending Club的公开数据但毕业设计如果只用现成csv答辩容易变成调参演示。我一般建议自己构造或整理一份含客户基本信息、账单、交易流水三张表的模拟数据这样特征工程才有东西可做。下面按业务口径和代码一步步说。2.1 高风险客户在业务上指什么逾期、套现与欺诈的三类标签先定义标签。信贷风控里“高风险”通常是三类逾期、套现、欺诈。逾期指客户未在还款日足额还款套现指通过虚假交易获取现金欺诈指身份伪造或申请时提供虚假信息。毕业设计数据集中最容易建模的是逾期因为逾期有明确的客观标准M0是未逾期M1是逾期30天以内M2是逾期60天以内以此类推。一般把观察期之后未来90天是否出现M1逾期作为正样本也就是“坏客户”其余为“好客户”。这里有个关键选择为什么不直接用“是否违约”而用“未来90天是否M1”因为信用卡的违约认定流程长达180天如果一杆子定义到M3样本量太小且时间窗口太长毕业设计周期内很难构建稳定的观察期与表现期。常见做法是观察期12个月、表现期3个月把表现期内任一账单逾期超过30天的样本标记为1。这个口径在答辩时也容易解释你预测的是“短期内客户风险恶化的概率”。同时要把三类风险区分开。如果数据里有欺诈标签建议单独建二分类不要和逾期混在一个标签里。混在一起会让模型学到的规则变得模糊逾期客户往往有收入波动特征欺诈客户往往有设备或地址异常特征两个分布完全不同。我见过有人把欺诈和逾期合并成正样本结果AUC和KS都很难看而且业务上说不清楚风险来源。2.2 特征工程的常用口径用Python把原始表加工成训练集假设你手里有三张原始表customer.csv客户属性年龄、性别、收入、学历、开户时长、bill.csv每期账单应还金额、最低还款额、实际还款额、账单日、transaction.csv每笔交易金额、商户类别、时间。下面代码把三张表合并成一份客户级特征表。import pandas as pd import numpy as np cust pd.read_csv(customer.csv, parse_dates[open_date]) bill pd.read_csv(bill.csv, parse_dates[bill_date, due_date, pay_date]) trans pd.read_csv(transaction.csv, parse_dates[trans_time]) # 客户表基础特征 fd cust.copy() # 账单表聚合近6期还款行为 bill_stat bill[bill[bill_date] 2022-01-01].groupby(cust_id).agg( bill_mean(bill_amt, mean), bill_max(bill_amt, max), pay_ratio(pay_amt, mean), # 平均还款额 min_pay_ratio(min_pay_amt, mean), # 平均最低还款额 overdue_times(is_overdue, sum), # 逾期次数 pay_lag_days(pay_delay_days, max) # 最长还款延迟天数 ).reset_index() # 交易表聚合近3个月高频特征 trans[month] trans[trans_time].dt.to_period(M) trans_stat trans[trans[month] pd.Period(2022-01)].groupby(cust_id).agg( tx_cnt(trans_amt, count), tx_amt_mean(trans_amt, mean), cash_ratio(is_cash, mean), # 取现交易占比 night_ratio(is_night, mean) # 夜间交易占比 ).reset_index() train fd.merge(bill_stat, oncust_id, howleft) \ .merge(trans_stat, oncust_id, howleft) train.to_pickle(train_feature.pkl)这段代码的逻辑很简单先把客户属性表和聚合后的账单、交易特征做一次left join。bill_mean反映客户平均负债水平pay_ratio反映还款意愿overdue_times是过去逾期次数的直接度量这三个变量在风控模型里通常是强特征。交易表里的cash_ratio和night_ratio用于捕捉异常用卡行为频繁取现、凌晨交易多往往和资金链紧张有关。参数说明窗口长度我一般选近6期账单和近3个月交易。窗口太长会把早期过于陈旧的行为纳入窗口太短则统计量波动大。如果你发现bill_stat后客户数少很多别慌常见原因是部分客户早期账单缺失可以用howleft保留所有客户缺失值在后续填充为0或中位数。读取数据这一步最好把所有日期列一次性parse_dates否则后面聚合时会因字符串比较出各种诡异结果。2.3 样本不平衡处理过采样、欠采样与SMOTE的取舍信用卡高风险客户占比通常在2%到5%直接训练逻辑回归会被负样本淹没。常见处理方案有三种欠采样、过采样、SMOTE。欠采样简单但丢失大量好样本过采样直接复制正样本容易过拟合SMOTE在正样本之间插值效果稳定且不影响测试集分布。下面给出SMOTE的标准用法。from imblearn.over_sampling import SMOTE from sklearn.model_selection import train_test_split X train.drop(label, axis1) y train[label] # 先切分再对训练集做SMOTE测试集保持原始分布 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) smote SMOTE(random_state42, sampling_strategy0.3, k_neighbors5) X_train_res, y_train_res smote.fit_resample(X_train, y_train) print(原始正样本数:, (y_train 1).sum(), SMOTE后正样本数:, (y_train_res 1).sum())这里最容易出错的是在切分前对整个数据集做SMOTE那样会让同样的正样本同时出现在训练和测试里造成数据泄漏测试指标虚高。正确顺序一定是先切分再对训练集做重采样。sampling_strategy0.3表示把正样本扩到负样本的30%不是扩到1:1。1:1在风控里没必要反而会改变先验概率让模型输出概率偏高。k_neighbors5是最常用的插值邻居数数据量大时可以降到3。如果没法安装imblearn退而求其次可以用class_weight。逻辑回归设置class_weightbalancedXGBoost设置scale_pos_weightsum_neg/sum_pos也能缓解不平衡。但SMOTE的好处是不改变模型本身答辩时可以对比三种方式的AUC和KS说服力更强。数据字典也要写进文档说明里每个特征名、含义、生成时间、缺失比例这些信息在答辩时比代码更值钱。3. 模型选型与训练从逻辑回归到XGBoost的对比实验先明确目标高风险识别模型不只是要概率还要能解释。因此建议至少训练一个可解释的基线模型再训练一个集成模型做效果对比。下面分三块说选型理由和具体参数。3.1 基线模型逻辑回归的可解释性与概率校准from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler lr Pipeline([ (scaler, StandardScaler()), (lr, LogisticRegression( C1.0, class_weightbalanced, solverliblinear, random_state42 )) ]) lr.fit(X_train_res, y_train_res)逻辑回归对特征尺度敏感所以先做标准化。solverliblinear适合小样本二分类C1.0是正则化强度的默认值越大越容易过拟合。这里用class_weightbalanced是额外保险即使你之前做过SMOTE也不冲突两个机制叠加会让模型更偏向正样本需要通过验证集AUC和KS来判断是否过度。可解释性在这个模型里有明确产出训练完可以输出每个特征的系数绝对值越大说明对风险贡献越大。答辩时可以说“年龄的系数为-0.32表示年龄每增加一个标准差对数几率下降0.32”这比黑盒模型容易讲清楚。另外逻辑回归的原始概率可能过度自信风控场景需要概率校准常见做法是使用CalibratedClassifierCV做isotonic回归。from sklearn.calibration import CalibratedClassifierCV calibrated_lr CalibratedClassifierCV(estimatorlr, methodisotonic, cv5) calibrated_lr.fit(X_train_res, y_train_res) prob_cal calibrated_lr.predict_proba(X_test)[:, 1]methodisotonic不假设概率曲线形状数据量大时比sigmoid校准更准cv5用五折生成校准曲线避免过拟合。校准后的概率可以直接用于评分卡换算。如果时间紧张这一步可以作为加分内容写进文档说明但不影响主流程。3.2 提升模型XGBoost/LightGBM的推荐参数import xgboost as xgb xgb_model xgb.XGBClassifier( n_estimators200, max_depth4, learning_rate0.05, subsample0.8, colsample_bytree0.8, min_child_weight3, scale_pos_weight10, eval_metricauc, random_state42 ) xgb_model.fit(X_train_res, y_train_res)这些参数是我在风控数据上常用的起点。max_depth4而不是默认的6因为信贷特征通常有几十维深度太大容易过拟合min_child_weight3进一步约束叶子节点权重对不平衡数据有稳定作用。scale_pos_weight10对应负正样本比约10:1如果做过SMOTE可以把这值调低到2到5。subsample和colsample_bytree都取0.8增加随机性降低方差。LightGBM的推荐参数类似num_leaves31降为15feature_fraction0.8bagging_fraction0.8bagging_freq1。两者效果接近但XGBoost在答辩时解释更广很多教材默认讲它。我建议两个都跑一遍把AUC和KS写到对比表格里结论更完整。注意不同版本对参数名的兼容性文档说明里要写明跑通代码所需的版本号。3.3 评估指标为什么准确率在风险识别里是陷阱指标公式高风险场景的意义Accuracy(TPTN)/(PN)样本99%为负时全预测负也有99%准确率无参考价值PrecisionTP/(TPFP)被识别为高风险的客户中真坏占比决定催收成本RecallTP/(TPFN)坏客户被找出的比例决定风险遗漏程度AUCROC曲线下面积排序能力不分阈值适合对比模型KSmax(TPR-FPR)区分度风控评分卡最常用准确率在样本极不平衡时是陷阱。假设数据集里坏客户只有3%一个把所有客户都预测为好的模型准确率是97%但完全没识别风险。高风险识别的核心目标是“把坏人排在前面”而不是把好人分类正确。所以评估时重点看AUC、KS、Recall某个阈值下的Precision。毕业设计里常见的错误是拿accuracy_score当主要指标答辩老师一问“样本不平衡时准确率有什么意义”就卡住了。还要画PR曲线而不是只看ROC。ROC在负样本占绝大优势时看起来依然漂亮但精确率可能极低。PR曲线对高风险场景更敏感横轴召回率、纵轴精确率曲线越靠近右上角越好。建议在matplotlib里把两张图并排画放入论文实验结果部分。4. 高风险识别系统的最小实现训练、评估与评分卡输出这一章给可复用的代码骨架把模型训练、阈值选择和封装串起来相当于一个最小可运行的模块。照着这个结构写答辩演示时直接跑脚本就能出结果。4.1 数据切分与交叉验证的代码骨架风控数据不能用普通K折因为时间顺序隐含了未来信息。用TimeSeriesSplit按时间切分训练集永远是测试集之前的数据。from sklearn.model_selection import TimeSeriesSplit X_sorted X.sort_index() # 假设索引是时间 y_sorted y.sort_index() tscv TimeSeriesSplit(n_splits4) auc_scores [] for train_idx, val_idx in tscv.split(X_sorted): X_tr, X_val X_sorted.iloc[train_idx], X_sorted.iloc[val_idx] y_tr, y_val y_sorted.iloc[train_idx], y_sorted.iloc[val_idx] model xgb.XGBClassifier(max_depth4, learning_rate0.05, n_estimators100) model.fit(X_tr, y_tr) val_prob model.predict_proba(X_val)[:, 1] auc_scores.append(roc_auc_score(y_val, val_prob)) print(TimeSeriesSplit AUC:, auc_scores)TimeSeriesSplit(n_splits4)会把数据按顺序切为5段第1次用第1段训练、第2段验证第2次用前2段训练、第3段验证依此类推。这样能模拟模型实际上线时的表现——用过去预测未来。如果你的数据没有显式时间字段可以按客户开户月份排序。至少毕业设计要说明为什么不用train_test_split这一点是加分项。如果数据里同一客户有多期记录还需要在切分前按cust_id分组保证同客户的所有记录进入同一边。否则时间切分也白搭客户A的第1期在训练集、第2期在测试集模型等于记住了这个客户的身份测试指标虚高。4.2 输出风险评分与阈值选择from sklearn.metrics import precision_recall_curve prob xgb_model.predict_proba(X_test)[:, 1] # 将概率转为标准评分600分对应好坏比20:1每增加40分好坏比翻倍 factor 40 / np.log(2) offset 600 - factor * np.log(20) score offset - factor * np.log(prob / (1 - prob)) prec, rec, thres precision_recall_curve(y_test, prob) # 选择召回率大于0.7且精确率最高的阈值 valid [(p, r, t) for p, r, t in zip(prec, rec, thres) if r 0.7] best max(valid, keylambda x: x[0]) if valid else (0, 0, 0.5) print(best threshold:, best[2], precision:, best[0], recall:, best[1])这里把模型输出的概率换算成信贷业务常用的标准评分公式是score offset - factor * log(odds)。为什么是减号因为概率越高好坏比越低风险越高所以分数要越低。factor40/np.log(2)表示好坏比翻倍时分数变化40分这个刻度你自己定答辩时说明理由即可。阈值选择我用的是“召回率至少0.7时精确率最高”的规则实际业务里可以据此生成风险名单。评分卡的好处是把概率转成整数范围业务人员好理解。比如低于480分进入拒绝名单480到580进入人工审核高于580正常放行。你可以把这段逻辑写进文档说明并画一张“分数分布与坏账率”的柱状图图一放出来答辩老师就知道你懂业务落地。4.3 把模型包成可复用的Python模块import joblib class CreditRiskModel: def __init__(self, modelNone, threshold0.5): self.model model self.threshold threshold def train(self, X, y): self.model.fit(X, y) return self def predict_proba(self, X): return self.model.predict_proba(X)[:, 1] def predict(self, X): return (self.predict_proba(X) self.threshold).astype(int) def save(self, path): joblib.dump({model: self.model, threshold: self.threshold}, path) classmethod def load(cls, path): obj joblib.load(path) return cls(modelobj[model], thresholdobj[threshold]) risk_model CreditRiskModel(modelxgb_model, thresholdbest[2]) risk_model.save(credit_risk_model.joblib)这个类把训练、预测、保存封装在一起避免答辩演示时把一堆代码抄来抄去。load用类方法实现可以直接从磁盘恢复模型和阈值。joblib是sklearn官方推荐的序列化方式比pickle更安全也支持大数组。注意保存时把阈值一起存否则每次预测要重新计算阈值容易出错。封装之后你可以写一个predict.py脚本从Excel读取一批新客户特征输出每人的风险评分和是否进入人工审核形成一张结果表。这样“源码数据文档说明”的三件套就完整了别人拿到后能直接跑通而不是看你一屏代码发呆。5. 避坑指南高风险识别模型从数据泄漏到过拟合的5个常见问题这一章全是血泪经验。每一条几乎都在毕业设计中被反复踩过按“现象→原因→解决”写清楚希望能帮你少熬夜。5.1 现象训练集AUC 0.98测试集0.62原因最常见的是切分时没有按时间或者在做SMOTE前就切分导致同一客户的数据同时出现在训练和测试集。比如一张客户表里有cust_id同一客户的多条记录被随机切分到两边模型记住了客户ID测试时直接命中。解决先按cust_id去重保证一个客户只出现在一边。切分用时间序列或按客户分组切分。检查代码顺序SMOTE必须在切分之后。我一般会在切分后打印训练集和测试集的cust_id交集数量如果交集不为0说明逻辑有误。另外还要检查特征中是否存在与标签强相关的泄漏字段比如“未来是否还款”这种只有事后才知道的变量。5.2 现象正样本全被预测成负样本原因样本不平衡太严重模型没有学到正样本模式。尤其是逻辑回归不带class_weight、XGBoost不设scale_pos_weight时模型为了最小化损失函数把所有人都判为负。解决先查看正样本占比如果低于5%设置class_weightbalanced或scale_pos_weight为负正比。另一个技巧是训练后查看预测概率分布如果预测概率最大才0.1说明模型倾向明显需要加重正样本权重。也可以使用predict_proba而不是直接predict先观察概率分位数再定阈值。5.3 现象特征里混入了未来字段原因构造特征时用了表现期之后的数据。比如你用未来180天的逾期次数作为特征这和标签完全相关测试集AUC必然虚高。典型错误是用账单日之后才产生的变量比如“当前应还金额”虽然是账单日的数据但账单日之后客户才可能还款这个字段不该出现在特征里。解决对每个特征标注生成日期并与标签表现期起始日对比。规则是特征只能使用表现期之前已产生的信息。比如“过去6期账单中平均应还金额”可以用“本期账单是否已在未来被还清”不能用。答辩时这个点是导师最容易追问的务必在数据处理时保留一份时间线说明并写进文档说明文档。5.4 现象SMOTE之后验证集分布失真原因对全量数据做SMOTE后再切分验证集被污染或者对验证集也做了SMOTE。正确做法是只对训练集重采样验证集保持原始负样本优势。如果验证集也过采样了评估出的概率和业务实际概率完全脱节阈值也没有参考价值。解决严格按“切分 - 重采样”顺序。用pipeline把重采样和模型捆绑避免在交叉验证中漏掉。我在写代码时会在train_test_split之后立即打印重采样前后的样本数量确认测试集数量没变化。如果使用imblearn的make_pipeline重采样器在交叉验证中只处理训练折不会污染验证折。5.5 现象文档里的参数和实际运行版本对不上原因从网上找的源码里XGBClassifier(...)的参数名是老版本比如eta被换成learning_rate或者num_rounds在sklearn API里不存在。最常见的是xgb_model xgb.XGBClassifier(objectivebinary:logistic)但新版本要求传入的eval_metric写法不同。解决先查看自己环境里的版本python -c import xgboost; print(xgboost.__version__)。然后根据版本调整参数。如果源码是GitHub或CSDN上下来的用pip freeze对比requirements里的版本列表一个个对齐。实在不行用model.get_params()打印当前默认参数对照修改。这个坑特别浪费时间我一般会在代码里写一个兼容分支用Python的hasattr判断参数是否被支持。6. 验证模型可靠性从AUC到PSI让答辩和上线都有底气最后写验证方法和一个具体技巧。模型不是训练完就结束可靠性验证才是区分“调包”和“工程能力”的分水岭。6.1 用时间序列切分代替随机切分上一章提到TimeSeriesSplit这里补充一个更贴近业务的验证方式按月份逐步滚动。比如用前8个月训练、第9个月验证再用前9个月训练、第10个月验证。这样能观察模型在不同月份的稳定性如果某个月AUC骤降说明该月出现了新的风险模式。滚动验证的代码和TimeSeriesSplit类似只是每次验证集是单个时间窗口更容易画折线图。把每个月的AUC画成折线比单一数字更有说服力。6.2 计算PSI监控特征漂移模型上线后最怕特征分布变化。PSIPopulation Stability Index是风控的常用监控指标计算方法是把训练集分布和当前分布各分箱比较箱内占比差异。def psi(expected, actual, bins10): # expected为训练集特征actual为新数据特征 edges pd.qcut(expected, bins, retbinsTrue)[1] exp_hist, _ np.histogram(expected, binsedges) act_hist, _ np.histogram(actual, binsedges) exp_pct exp_hist / len(expected) 1e-6 act_pct act_hist / len(actual) 1e-6 return sum((act_pct - exp_pct) * np.log(act_pct / exp_pct))PSI小于0.1说明特征稳定0.1到0.25说明有漂移需要关注大于0.25说明分布显著变化模型可能失效。在毕业设计中即使没有真实新数据你也可以从数据集中按时间先后切成两部分用早期训练、晚期验证顺便报告PSI。这个指标在文档说明里写一段能明显提升专业度。6.3 落地到本地的完整操作清单实际跑通这个项目环境是关键。优先安装Python 3.8到3.10之间的版本太新或太旧都可能撞上xgboost与imblearn的依赖问题。然后安装numpy、pandas、scikit-learn、xgboost、imblearn、joblib、matplotlib。我习惯用conda create -n risk python3.8建独立环境再逐个pip install避免系统里其他项目干扰。数据文件用pickle保存特征表因为csv读大文件慢而且类型容易丢。运行顺序是先跑特征工程脚本生成train_feature.pkl再跑训练脚本最后用已经保存的joblib模型文件做预测演示。我的个人习惯是在每个脚本开头加入assert检查文件是否存在、样本量是否为零跑挂了能立刻看到是哪一步。答辩演示前我会把随机种子固定为同一个值保证两次运行结果完全一致。如果同学复现你的项目也要告诉他“先装requirements.txt里的固定版本不要直接pip install最新版”。希望这些落地的习惯能帮到你至少让你在答辩或验收时少一点手忙脚乱。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑