ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

信用风险预测模型实战:从157维脱敏数据到0.7887 AUC的完整复现

信用风险预测模型实战:从157维脱敏数据到0.7887 AUC的完整复现 简介这份PDF文献面向金融风控从业者、数据科学学习者与高校研究人员聚焦商业银行信用风险评估这一核心课题系统梳理了机器学习方法在违约预测中的应用路径。资源为单份PDF文档压缩包约1.5MB内容完整、便于检索与引用适合作为课程作业、论文写作或风控建模的参考文献。文中以西南财经大学“新网银行杯”竞赛脱敏数据为实证基础围绕高维稀疏、无标签样本与好坏样本不平衡等难点依次展开缺失值分析与填充、特征选取、模型训练与评估等环节并对比Logistic回归、决策树、Adaboost、GradientBoosting与LGB等模型的AUC表现最终得出LGB指标最优的结论。读者可借此掌握从数据清洗到模型比对的完整建模思路理解AUC评估与半监督方法的应用要点为信用风险预测实践提供可复用的方法参考。目前已有538人学习。1. 信用风险预测模型从 157 维脱敏数据到 0.7887 AUC 的完整复现路径银行信贷场景里坏样本占比往往低得让人头疼。这份来自新网银行杯竞赛的脱敏数据集15000 条带标签训练样本中高风险客户只有 691 条正负比达到 21:1157 维特征全部匿名且缺失值统一用 -99 填充。更棘手的是数据里还混着 10000 条无标签样本和 10000 条测试集客群变量 cust_group 被明令禁止入模。就是在这样的条件下原作者用 Logistic 回归、决策树、Adaboost、GradientBoosting 和 LGB 五类模型跑出了一套完整方案最终 LGB 拿到加权 AUC 0.7887。这篇笔记拆的就是这份 PDF 里的技术路线——数据怎么洗、特征怎么选、模型怎么调、坑在哪。如果你正在做信贷风控建模、半监督学习落地或者只是想找一个真实脱敏数据练手这份材料值得跟着走一遍。2. 数据解析与缺失值处理-99 不是数值是信号2.1 三个文件的分工与标签分布竞赛给的数据分三个文件角色完全不同。train_xy.csv 是带标签训练集15000 条含 y 字段train_x.csv 是无标签训练集10000 条除 y 外字段与 train_xy 一致test_all.csv 是测试集10000 条同样无 y。特征维度 157其中 x1 到 x95 是数值型x96 到 x157 是类别型。标签 y 中 0 代表低风险14309 条1 代表高风险691 条。cust_group 有三个取值分布也不均衡group0 有 14544 条低风险、456 条高风险group1 有 2487 条低风险、129 条高风险group2 有 3489 条低风险、106 条高风险。这个分布意味着两件事第一不能直接用准确率做评估指标否则全预测 0 就能拿 95% 以上第二分层抽样必须按 y 和 cust_group 联合分层否则某折验证集里可能一个坏样本都没有。常见做法是用StratifiedKFold对 y 分层但如果要兼顾客群分布可以构造一个组合标签y * 10 cust_group再做分层。2.2 缺失值统计与 -99 的语义原始数据里缺失值统一用 -99 表示但 -99 本身是一个合法数值直接送进模型会被当成真实值参与计算。所以第一步必须把 -99 替换成np.nan再做缺失率统计。原文图 2 显示有大量特征缺失率高达 100%这些列对模型没有任何信息量应该直接删除。剩下有缺失的列按缺失率分档处理。import pandas as pd import numpy as np # 读取带标签训练集 train_xy pd.read_csv(train_xy.csv) train_x pd.read_csv(train_x.csv) test_all pd.read_csv(test_all.csv) # 将 -99 替换为 NaN注意只对特征列操作不动 id 和 y feature_cols [c for c in train_xy.columns if c.startswith(x_)] train_xy[feature_cols] train_xy[feature_cols].replace(-99, np.nan) train_x[feature_cols] train_x[feature_cols].replace(-99, np.nan) test_all[feature_cols] test_all[feature_cols].replace(-99, np.nan) # 统计缺失率 missing_rate train_xy[feature_cols].isnull().mean().sort_values(ascendingFalse) # 删除缺失率 100% 的列 drop_cols missing_rate[missing_rate 1.0].index.tolist() train_xy.drop(columnsdrop_cols, inplaceTrue) train_x.drop(columnsdrop_cols, inplaceTrue) test_all.drop(columnsdrop_cols, inplaceTrue)这段代码的关键点在于替换 -99 必须在划分特征列之后做避免把 id 或 y 里的 -99 误伤删除 100% 缺失列要在三个数据集上同步操作保证特征维度一致。参数上missing_rate 1.0是硬阈值实际比赛中也可以放宽到 0.95但原文选择了最保守的做法。2.3 数值型均值填充与类别型哑变量处理原文对数值型特征用均值填充对类别型特征引入哑元变量。这里有一个容易翻车的地方均值填充必须在训练集上计算均值然后应用到验证集和测试集不能各自算各自的均值。否则验证集的信息会泄露到填充过程里导致线下 AUC 虚高。from sklearn.preprocessing import StandardScaler # 区分数值型和类别型 num_cols [c for c in feature_cols if c.startswith(x_) and int(c.split(_)[1]) 95] cat_cols [c for c in feature_cols if c.startswith(x_) and int(c.split(_)[1]) 95] # 数值型训练集均值填充验证/测试集用训练集均值 num_means train_xy[num_cols].mean() train_xy[num_cols] train_xy[num_cols].fillna(num_means) train_x[num_cols] train_x[num_cols].fillna(num_means) test_all[num_cols] test_all[num_cols].fillna(num_means) # 类别型引入哑元变量缺失单独作为一类 for df in [train_xy, train_x, test_all]: for col in cat_cols: df[col] df[col].fillna(-1).astype(int).astype(str) # 合并后做 one-hot保证三个数据集类别对齐 combined pd.concat([train_xy[cat_cols], train_x[cat_cols], test_all[cat_cols]], axis0) combined_dummies pd.get_dummies(combined, columnscat_cols) train_xy_dummies combined_dummies.iloc[:len(train_xy), :] train_x_dummies combined_dummies.iloc[len(train_xy):len(train_xy)len(train_x), :] test_dummies combined_dummies.iloc[len(train_xy)len(train_x):, :] # 归一化 scaler StandardScaler() train_xy[num_cols] scaler.fit_transform(train_xy[num_cols]) train_x[num_cols] scaler.transform(train_x[num_cols]) test_all[num_cols] scaler.transform(test_all[num_cols])逻辑说明均值填充和归一化都遵循「训练集 fit其他 transform」的原则。类别型特征先填充 -1 再转字符串是为了让缺失值成为一个独立类别而不是被忽略。one-hot 编码在合并数据上做是为了保证三个数据集的哑变量列完全一致。参数上fillna(-1)里的 -1 是任意选的非原始类别值只要不与已有类别冲突即可。注意原文提到对填充完的数据进行归一化但归一化只对数值型特征做类别型 one-hot 后的 0/1 列不需要再标准化。3. 特征选择与模型对比为什么 157 维不降维反而更好3.1 随机森林特征重要性排序与 TOP25 选取原文用随机森林对 157 个特征做重要性排序取 TOP25 作为候选特征。但后面又做了一个反直觉的决定不降维让模型自动选择特征。这个决策的逻辑是157 维本身不算高维而且特征是匿名的降维后很难解释保留了哪些信息树模型自带特征选择能力强行降维反而可能丢掉弱信号。from sklearn.ensemble import RandomForestClassifier # 只用带标签训练集做特征重要性 X_train train_xy.drop(columns[id, y, cust_group], errorsignore) y_train train_xy[y] rf RandomForestClassifier(n_estimators200, max_depth8, random_state42, n_jobs-1) rf.fit(X_train, y_train) # 取 TOP25 importances pd.Series(rf.feature_importances_, indexX_train.columns) top25 importances.sort_values(ascendingFalse).head(25).index.tolist()参数上n_estimators200是经验值再大边际收益递减max_depth8是为了防止单棵树过拟合因为正负样本极度不平衡。random_state42保证可复现。TOP25 的选取没有固定标准原文取 25 是基于后续模型对比的实验结果实际可以试 20、30、50 几个档位。3.2 五类模型的 AUC 对比与降维/不降维差异原文对比了 Logistic 回归、决策树、Adaboost、GradientBoosting 四类模型在降维和不降维两种条件下的表现。降维方式应该是用了某种特征选择或 PCA但原文没有明确写具体方法从结果表看不降维的加权 AUC 普遍高于降维。以 GradientBoosting 为例降维后加权 AUC 0.73528不降维后 0.762。Logistic 回归降维后 0.7202不降维后 0.741。这个差距说明匿名特征里存在非线性交互线性降维会破坏这些结构。模型降维加权 AUC不降维加权 AUCLogistic0.72020.741AdaBoost0.68960.708Decision Tree0.733270.762GradientBoosting0.735280.762评估指标是加权 AUCAUC 0.3×AUC1 0.3×AUC2 0.4×AUC3其中 AUC1、AUC2、AUC3 分别对应三个客群的 AUC。这个加权方式意味着 group3 的权重最高达到 0.4所以模型在 group3 上的表现对最终得分影响最大。从原文结果看group3 的 AUC 普遍高于 group1 和 group2说明 group3 的样本区分度更好。3.3 过拟合现象与 5 折分层交叉验证原文图 5 显示 Logistic 回归和 GradientBoosting 的 ROC 曲线都存在过拟合。过拟合的原因有两个一是数据量小15000 条带标签样本里只有 691 个正样本模型很容易记住少数类二是模型本身复杂度高比如 GradientBoosting 在不限制深度时会把训练集拟合得很完美。解决方法是 5 折分层交叉验证。分层的意思是每一折里正负样本比例与全集一致避免某一折验证集里正样本太少导致 AUC 波动大。from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score import lightgbm as lgb skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) auc_scores [] for train_idx, val_idx in skf.split(X_train, y_train): X_tr, X_val X_train.iloc[train_idx], X_train.iloc[val_idx] y_tr, y_val y_train.iloc[train_idx], y_train.iloc[val_idx] model lgb.LGBMClassifier( n_estimators500, learning_rate0.05, num_leaves31, subsample0.8, colsample_bytree0.8, random_state42 ) model.fit(X_tr, y_tr) y_pred model.predict_proba(X_val)[:, 1] auc_scores.append(roc_auc_score(y_val, y_pred)) print(f5 折 AUC 均值: {np.mean(auc_scores):.4f}, 标准差: {np.std(auc_scores):.4f})参数说明n_estimators500配合learning_rate0.05是 LGB 的常用组合学习率低需要更多树来补偿num_leaves31控制单棵树复杂度太大容易过拟合subsample0.8和colsample_bytree0.8是行采样和列采样进一步降低过拟合风险。5 折的均值反映模型泛化能力标准差反映稳定性如果标准差超过 0.02说明数据划分对结果影响大需要检查分层是否到位。4. LGB 模型调优与半监督学习无标签数据怎么用4.1 LGB 参数调优的实操路径原文最终选择 LGB 作为最终模型得到 AUC10.74482、AUC20.76577、AUC30.84287、加权 AUC0.7887。这个结果明显高于其他四个模型。LGB 的优势在于直方图算法对稀疏数据的处理效率高而且自带缺失值处理机制对匿名特征友好。调参路径建议从默认参数开始先调num_leaves和learning_rate再调subsample和colsample_bytree最后调min_child_samples和reg_alpha/reg_lambda。常见做法是用GridSearchCV或Optuna做自动搜索但要注意正负样本不平衡时scale_pos_weight需要设置为负正样本比即 14309/691≈20.7。# 设置正负样本权重 scale_pos_weight (y_train 0).sum() / (y_train 1).sum() model lgb.LGBMClassifier( n_estimators800, learning_rate0.03, num_leaves63, max_depth7, subsample0.8, colsample_bytree0.7, min_child_samples50, reg_alpha0.1, reg_lambda0.1, scale_pos_weightscale_pos_weight, random_state42 )scale_pos_weight的作用是让损失函数对少数类更敏感但不宜设置过大否则模型会过度预测正类导致 AUC 反而下降。实际调参时可以先设 1再逐步增加到 20 左右观察验证集 AUC 变化。4.2 半监督学习无标签样本的两种用法原文提到可以使用监督与半监督方法综合预测但没有展开具体实现。常见做法有两种一是自训练Self-training先用带标签数据训练一个初始模型对无标签数据预测伪标签把置信度高的样本加入训练集重新训练二是协同训练Co-training用两个不同视角的特征集分别训练模型互相给无标签数据打标签。# 自训练伪标签示例 model lgb.LGBMClassifier(n_estimators500, learning_rate0.05, random_state42) model.fit(X_train, y_train) # 对无标签数据预测 X_unlabeled train_x.drop(columns[id, cust_group], errorsignore) pseudo_proba model.predict_proba(X_unlabeled)[:, 1] # 选取置信度高于 0.9 或低于 0.1 的样本 high_conf_idx np.where((pseudo_proba 0.9) | (pseudo_proba 0.1))[0] X_pseudo X_unlabeled.iloc[high_conf_idx] y_pseudo (pseudo_proba[high_conf_idx] 0.5).astype(int) # 合并重新训练 X_augmented pd.concat([X_train, X_pseudo], axis0) y_augmented pd.concat([y_train, pd.Series(y_pseudo)], axis0) model_final lgb.LGBMClassifier(n_estimators800, learning_rate0.03, random_state42) model_final.fit(X_augmented, y_augmented)伪标签的阈值选择是关键太高则加入的样本太少太低则引入噪声。常见做法是先用 0.9/0.1 的严格阈值观察验证集 AUC 是否提升如果提升不明显再放宽到 0.8/0.2。注意伪标签样本的权重可以调低比如设sample_weight0.5避免噪声主导训练。4.3 多客群 AUC 的加权计算与验证原文的评估指标是 AUC 0.3×AUC1 0.3×AUC2 0.4×AUC3其中 AUC1、AUC2、AUC3 分别对应 cust_group 取 0、1、2 的样本。计算时需要按客群拆分验证集分别算 AUC 再加权。def weighted_auc(y_true, y_pred, groups): aucs {} for g in groups.unique(): mask groups g if mask.sum() 0 and len(np.unique(y_true[mask])) 1: aucs[g] roc_auc_score(y_true[mask], y_pred[mask]) else: aucs[g] 0.5 # 样本不足时给随机值 weighted 0.3 * aucs.get(0, 0.5) 0.3 * aucs.get(1, 0.5) 0.4 * aucs.get(2, 0.5) return weighted, aucs # 在验证集上计算 val_groups train_xy.iloc[val_idx][cust_group] weighted, aucs weighted_auc(y_val, y_pred, val_groups) print(f加权 AUC: {weighted:.4f}, 各客群 AUC: {aucs})这个函数里有一个边界处理如果某个客群在验证集里只有一个类别AUC 无法计算给 0.5 是保守做法。实际比赛中如果某客群样本太少可以考虑在分层时保证每个客群都有足够正负样本。5. 避坑与排查脱敏数据建模的五个血泪教训5.1 缺失值 -99 未替换导致模型学到假信号现象模型训练集 AUC 很高但验证集 AUC 只有 0.5 左右完全随机。原因-99 被当成真实数值参与均值填充和归一化模型把 -99 当作一个强特征但这个特征在验证集和测试集里分布不同。解决在数据加载后第一件事就是把 -99 替换为np.nan再做任何统计和填充。检查方法是看特征最小值如果大量特征最小值是 -99说明替换没做干净。5.2 归一化在划分数据集之后做导致信息泄露现象线下交叉验证 AUC 0.85提交测试集后 AUC 只有 0.72。原因先划分训练集和验证集再各自做归一化验证集的均值和方差信息泄露到了训练过程。解决归一化必须在训练集上fit然后transform验证集和测试集。同理均值填充、WOE 编码、目标编码都要遵循这个原则。5.3 分层抽样只按 y 分层忽略客群分布现象5 折交叉验证的 AUC 标准差很大某几折 AUC 明显偏低。原因只按 y 分层某些折里 group2 的样本极少而 group2 的 AUC 权重是 0.4导致加权 AUC 波动大。解决构造组合标签y * 10 cust_group再做分层保证每折里各客群的正负样本比例与全集一致。如果某客群样本太少可以考虑在评估时对该客群做上采样或调整权重。5.4 LGB 的 scale_pos_weight 设置过大导致 AUC 下降现象设置scale_pos_weight20后训练集 AUC 提升到 0.95但验证集 AUC 从 0.78 降到 0.74。原因过大的正样本权重让模型过度预测正类牺牲了排序能力而 AUC 衡量的是排序质量不是分类准确率。解决scale_pos_weight从 1 开始逐步增加每次增加 5观察验证集 AUC 变化找到峰值后停止。或者改用is_unbalanceTrue让 LGB 自动处理。5.5 伪标签阈值过低引入噪声样本现象加入无标签数据自训练后验证集 AUC 不升反降。原因伪标签阈值设了 0.6/0.4大量低置信度样本被加入训练集噪声淹没了真实信号。解决伪标签阈值至少设 0.9/0.1并且给伪标签样本设sample_weight0.3~0.5。更稳妥的做法是先用带标签数据训练一个强模型只对无标签数据中模型预测置信度极高的样本打伪标签且伪标签样本数量不超过带标签样本的 30%。6. 从 0.7887 再往上走三个可落地的进阶技巧第一个技巧是特征交互。157 维匿名特征虽然不能解释含义但可以用 LGB 的interaction_constraints参数强制某些特征组合交互或者手动构造数值型特征的比值、差值。常见做法是对 TOP25 特征两两做除法生成 C(25,2)300 个新特征再用随机森林筛一遍。注意除法要处理分母为 0 的情况加一个极小值1e-6。第二个技巧是模型融合。LGB 单模型 AUC 0.7887如果把 LGB、XGBoost、CatBoost 三个模型的预测概率做加权平均权重用验证集 AUC 归一化通常能提升 0.005~0.01。更稳的做法是 Stacking用 5 折交叉验证生成三个模型的 OOF 预测再用一个 Logistic 回归做元学习器。元学习器的输入是三个模型的预测概率输出是最终概率。from sklearn.linear_model import LogisticRegression # 假设 lgb_oof, xgb_oof, cat_oof 是三个模型的 OOF 预测 stack_X np.column_stack([lgb_oof, xgb_oof, cat_oof]) meta_model LogisticRegression() meta_model.fit(stack_X, y_train) final_pred meta_model.predict_proba(stack_X)[:, 1]第三个技巧是阈值移动。AUC 不依赖阈值但实际业务中需要把概率转成 0/1 决策。正负比 21:1 时默认阈值 0.5 会导致大量正类被漏掉。常见做法是用验证集画 KS 曲线取 KS 最大处的阈值或者按业务要求设定召回率下限反推阈值。我一般会同时看 KS 和 F1选一个平衡点。从那以后我每次拿到脱敏数据第一件事就是检查缺失值编码第二件事就是确认归一化和填充的 fit/transform 边界第三件事就是跑一遍分层交叉验证看标准差。这三步走完线下 AUC 和线上 AUC 的差距通常能控制在 0.02 以内。希望帮到你。本文还有配套的精品资源点击获取
返回列表