简介:这份资源面向金融风控方向的数据科学从业者与机器学习学习者,聚焦信用卡违约预测这一典型信贷风险评估场景,帮助读者掌握从数据预处理到模型融合的完整建模思路。压缩包内共1个文件,为单个predict.py脚本,包体约4KB,轻量便于直接阅读与二次修改。脚本预计涵盖数据加载清洗、特征工程、逻辑回归与随机森林等多模型训练、k折交叉验证、bagging/boosting/stacking融合策略,以及AUC-ROC、精确率、召回率、F1等评估指标,并包含对测试集或新数据的违约概率预测。目前已有591人学习下载,适合希望理解模型融合如何降低过拟合、提升泛化能力的中级学习者,可借此积累特征选择、模型对比与融合调参的实践经验,为金融风控项目提供可复用的代码参考。
1. 信用卡违约预测:为什么 99% 的准确率反而说明模型废了
信用卡违约预测分析是金融风控里最经典的机器学习落地场景之一。数据集通常包含用户的授信额度、账单金额、还款状态、年龄、性别等字段,标签是下个月是否违约。很多人第一次跑这个数据集,用逻辑回归随手一fit,发现准确率 99%,兴奋地以为模型可以上线了——然后被现实狠狠打脸。因为违约样本往往只占 1% 到 5%,模型只要把所有样本都预测成“不违约”,准确率就能轻松超过 95%。这就是类别不平衡给信用卡违约预测分析埋的第一个坑。
这个方向适合两类人:一是想找一个完整机器学习项目练手的开发者,二是需要在风控场景里做模型融合和阈值调优的从业者。它涉及的核心技术点包括:不平衡数据处理、特征工程、多模型对比、模型融合、评估指标选择。整条链路走通一遍,你对机器学习落地会有完全不同的理解。下面我按自己实际做过的流程,从数据到融合模型,把每一步拆开讲。
2. 数据理解与特征工程:把账单表变成模型能吃的矩阵
2.1 先搞清楚字段含义再动手
信用卡违约数据集常见结构是:一张用户属性表(ID、性别、教育、婚姻、年龄),一张还款状态表(每月还款状态),一张账单金额表(每月账单),一张还款金额表(每月实还)。很多人拿到数据直接merge然后丢进模型,结果特征含义都没搞明白,后面调参全是玄学。
我一般先做三件事:确认ID唯一性、确认标签分布、确认各表时间对齐关系。还款状态字段通常用-2到8的编码表示:-2无消费、-1正常还款、0使用循环授信、1到8表示逾期天数递增。这些编码是有序的,不能当类别变量直接one-hot,但也不能简单当连续值,因为-2和-1的语义跳变很大。
import pandas as pd import numpy as np # 假设四张表已经读入:df_user, df_status, df_bill, df_pay # 确认ID唯一 assert df_user['ID'].is_unique, "用户表ID不唯一" # 标签分布 label_dist = df_user['default'].value_counts(normalize=True) print(label_dist) # 典型输出:0约77%,1约23%(不同版本差异大,以实际为准) # 还款状态编码分布 status_cols = [c for c in df_status.columns if c != 'ID'] print(df_status[status_cols].stack().value_counts().sort_index())逻辑说明:先做唯一性断言,避免merge后行数膨胀。标签分布决定后续要不要做重采样。还款状态编码分布帮你判断哪些编码是稀有值,稀有编码可以合并。
参数说明:normalize=True输出比例而非计数,方便快速判断不平衡程度。stack()把宽表变长表,一次看所有月份的编码分布。
2.2 特征工程:账单和还款的比值比原始金额更有用
原始账单金额和还款金额直接丢进模型,效果通常一般。我一般会构造几类衍生特征:额度使用率(账单/授信额度)、还款比例(实还/账单)、逾期月数统计、还款状态趋势。其中额度使用率是风控里最强的单特征之一,因为它直接反映用户的负债压力。
# 额度使用率:每月账单除以授信额度 for i in range(1, 7): df_user[f'util_{i}'] = df_bill[f'BILL_AMT{i}'] / (df_user['LIMIT_BAL'] + 1) # 还款比例:每月实还除以账单,账单为0时置0 for i in range(1, 7): bill = df_bill[f'BILL_AMT{i}'] pay = df_pay[f'PAY_AMT{i}'] df_user[f'pay_ratio_{i}'] = np.where(bill > 0, pay / bill, 0) # 逾期统计:还款状态大于0的月份数 df_user['late_months'] = (df_status[status_cols] > 0).sum(axis=1) df_user['max_late'] = df_status[status_cols].max(axis=1)逻辑说明:分母加1避免除零。np.where保证账单为0时还款比例置0而不是无穷大。逾期统计把多个月份的信息压缩成两个强特征,减少维度同时保留核心信号。
参数说明:range(1,7)对应6个月账单,实际列名以数据集为准。max_late取最大值而非均值,因为一次严重逾期比多次轻微逾期风险更高。
2.3 类别不平衡处理:不要一上来就SMOTE
类别不平衡是信用卡违约预测分析的核心难点。常见做法有:过采样(SMOTE)、欠采样、类别权重、阈值调整。我的经验是:先试类别权重,再试阈值调整,最后才考虑SMOTE。因为SMOTE在特征空间插值,可能生成不存在的用户画像,在风控场景里解释性差。
from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report X = df_user.drop(columns=['ID', 'default']) y = df_user['default'] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, stratify=y, random_state=42 ) # 类别权重方案 clf = LogisticRegression(class_weight='balanced', max_iter=1000) clf.fit(X_train, y_train) y_prob = clf.predict_proba(X_test)[:, 1] # 阈值调整:默认0.5,改成0.3看召回提升 y_pred = (y_prob > 0.3).astype(int) print(classification_report(y_test, y_pred))逻辑说明:stratify=y保证训练测试集标签比例一致。class_weight='balanced'自动按类别频率反比加权。阈值从0.5降到0.3,牺牲精确率换召回率,风控场景通常更看重召回。
参数说明:max_iter=1000防止逻辑回归不收敛。阈值0.3不是固定值,要用验证集上的PR曲线选最优。
3. 多模型对比与融合:单模型天花板怎么破
3.1 基线模型:逻辑回归和决策树先跑通
任何项目我都先跑两个基线:逻辑回归和决策树。逻辑回归给你一个可解释的线性基准,决策树给你一个非线性基准。两者差距大,说明特征里有非线性关系;两者都差,说明特征工程不到位。
from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import roc_auc_score, average_precision_score models = { 'LR': LogisticRegression(class_weight='balanced', max_iter=1000), 'DT': DecisionTreeClassifier(max_depth=5, class_weight='balanced', random_state=42), 'RF': RandomForestClassifier(n_estimators=200, max_depth=8, class_weight='balanced', random_state=42) } for name, model in models.items(): model.fit(X_train, y_train) prob = model.predict_proba(X_test)[:, 1] print(f"{name} AUC: {roc_auc_score(y_test, prob):.4f} " f"AP: {average_precision_score(y_test, prob):.4f}")逻辑说明:AUC衡量排序能力,AP(平均精确率)在不平衡数据上比AUC更敏感。两个指标一起看,避免被AUC虚高误导。
参数说明:max_depth=5限制树深防过拟合。n_estimators=200是随机森林常用起点,再多收益递减。
3.2 融合策略:投票、加权、Stacking怎么选
模型融合是提升信用卡违约预测效果的关键手段。常见三种:硬投票(多数表决)、软投票(概率平均)、Stacking(用元模型学权重)。我的经验是:软投票通常比硬投票好,Stacking在小数据集上容易过拟合。
from sklearn.ensemble import VotingClassifier, StackingClassifier from sklearn.linear_model import LogisticRegression # 软投票融合 voting = VotingClassifier( estimators=[('lr', models['LR']), ('rf', models['RF']), ('dt', models['DT'])], voting='soft' ) voting.fit(X_train, y_train) voting_prob = voting.predict_proba(X_test)[:, 1] print(f"Voting AUC: {roc_auc_score(y_test, voting_prob):.4f}") # Stacking融合 stacking = StackingClassifier( estimators=[('lr', models['LR']), ('rf', models['RF'])], final_estimator=LogisticRegression(class_weight='balanced'), cv=5 ) stacking.fit(X_train, y_train) stack_prob = stacking.predict_proba(X_test)[:, 1] print(f"Stacking AUC: {roc_auc_score(y_test, stack_prob):.4f}")逻辑说明:软投票要求每个基模型都能输出概率。Stacking用5折交叉验证生成元特征,再用逻辑回归学权重。cv=5是常用值,数据量小可以降到3。
参数说明:final_estimator选逻辑回归而非复杂模型,因为元特征维度低,复杂模型容易过拟合。如果基模型相关性高,融合收益有限,可以先看模型间预测相关系数。
3.3 阈值优化:用PR曲线找最佳切点
融合模型输出概率后,阈值决定最终分类。默认0.5在不平衡数据上通常不是最优。我一般画PR曲线,找F1最大或召回满足约束下的最高精确率点。
from sklearn.metrics import precision_recall_curve, f1_score precision, recall, thresholds = precision_recall_curve(y_test, voting_prob) f1_scores = 2 * precision * recall / (precision + recall + 1e-8) best_idx = np.argmax(f1_scores) best_threshold = thresholds[best_idx] print(f"Best threshold: {best_threshold:.4f}, F1: {f1_scores[best_idx]:.4f}") # 应用最佳阈值 final_pred = (voting_prob > best_threshold).astype(int) print(classification_report(y_test, final_pred))逻辑说明:precision_recall_curve返回的thresholds长度比precision少1,索引要对应。1e-8防止除零。最佳阈值要在验证集上选,不能在测试集上选,否则是数据泄露。
参数说明:如果业务要求召回不低于0.8,就在recall>=0.8的区间里找precision最大的阈值,而不是直接取F1最大。
4. 避坑与排查:信用卡违约预测里最容易翻车的五个点
4.1 用准确率评估模型,结果上线全错
现象:训练集准确率99%,测试集准确率98%,但违约用户一个没抓到。原因:类别不平衡下,模型退化成多数类预测器。解决:改用AUC、AP、F1、召回率评估,准确率只做参考。我一般要求至少看AUC和召回两个指标。
4.2 还款状态当连续值直接丢进模型
现象:逻辑回归系数解释不通,还款状态-2的系数比8还大。原因:-2表示无消费,8表示严重逾期,语义上不是线性关系。解决:把-2和-1合并成“正常”,0单独一档,1-8按逾期程度分箱,或者做woe编码。
4.3 训练测试集划分前做了全局标准化
现象:测试集AUC比验证集高很多,上线后效果暴跌。原因:标准化时用了全量数据的均值和方差,测试集信息泄露到训练过程。解决:先划分再标准化,用Pipeline把预处理和模型绑在一起。
from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler pipe = Pipeline([ ('scaler', StandardScaler()), ('clf', LogisticRegression(class_weight='balanced', max_iter=1000)) ]) pipe.fit(X_train, y_train) # scaler只fit训练集4.4 SMOTE之后没做交叉验证,过拟合严重
现象:SMOTE后训练集AUC 0.99,测试集AUC 0.72。原因:SMOTE在划分前对全量数据过采样,合成样本进入测试集。解决:SMOTE只在训练折内做,用imblearn的Pipeline保证。
from imblearn.pipeline import Pipeline as ImbPipeline from imblearn.over_sampling import SMOTE imb_pipe = ImbPipeline([ ('smote', SMOTE(random_state=42)), ('clf', RandomForestClassifier(n_estimators=200, random_state=42)) ]) # 交叉验证时smote只在训练折内执行4.5 融合模型权重不可解释,业务方不认
现象:Stacking效果比单模型好2个点,但业务方问“为什么这个人被拒”答不上来。原因:元模型是黑盒,特征贡献不透明。解决:用SHAP解释融合模型,或者退一步用加权软投票,权重按单模型AUC分配,至少能说清每个模型的贡献。
5. 进阶技巧:用校准和代价敏感把模型推到业务可用
模型融合和阈值调优做完,AUC可能到0.78左右,但业务方往往还关心“预测概率准不准”。逻辑回归和随机森林输出的概率通常有偏,需要做概率校准。我一般用等渗回归或Platt缩放,在验证集上校准,再画校准曲线确认。
from sklearn.calibration import CalibratedClassifierCV, calibration_curve # 对融合模型做等渗校准 calibrated = CalibratedClassifierCV(voting, method='isotonic', cv=5) calibrated.fit(X_train, y_train) cal_prob = calibrated.predict_proba(X_test)[:, 1] # 校准曲线 fraction_pos, mean_pred = calibration_curve(y_test, cal_prob, n_bins=10) for fp, mp in zip(fraction_pos, mean_pred): print(f"预测均值{mp:.3f} -> 实际违约率{fp:.3f}")逻辑说明:method='isotonic'适合样本量大的场景,'sigmoid'适合小样本。校准曲线对角线越近越好。如果某段预测均值0.3但实际违约率0.5,说明模型在这段低估了风险。
参数说明:cv=5内部交叉验证,避免校准过拟合。n_bins=10是常用分箱数,样本少可以降到5。
另一个进阶方向是代价敏感学习。风控里漏判一个违约用户的损失,通常是误拒一个好用户的10倍以上。可以把class_weight设成{0:1, 1:10},或者自定义损失函数。我一般先跟业务确认代价比,再调权重,而不是拍脑袋设。
# 代价敏感:违约类权重设为10 cost_sensitive = RandomForestClassifier( n_estimators=300, max_depth=8, class_weight={0: 1, 1: 10}, random_state=42 ) cost_sensitive.fit(X_train, y_train) cs_prob = cost_sensitive.predict_proba(X_test)[:, 1] print(f"Cost-sensitive AUC: {roc_auc_score(y_test, cs_prob):.4f}")最后说一个我踩过的坑:有次调参调了一周,AUC从0.76提到0.79,结果业务方说“我们只关心逾期30天以上的用户,短逾期不算”。回头重新定义标签,之前的工作一半白做。所以做信用卡违约预测分析,第一件事不是跑模型,是跟业务对齐标签定义和评估口径。这个习惯帮我省了无数次返工。
希望帮到你。
本文还有配套的精品资源,点击获取