ARTICLE DETAIL

资讯详情

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

信贷风控建模实战:XGBoost四模型Stacking与文本数据清洗

信贷风控建模实战:XGBoost四模型Stacking与文本数据清洗 简介本资源是马上AI全球挑战者大赛《违约用户风险预测》赛题的完整技术实现包面向人工智能、计算机科学、金融工程等专业学生及风控建模初学者聚焦信贷场景下用户违约概率建模与多模型融合优化实践。压缩包共11个文件含4个核心Python脚本涵盖XGBoost单模型训练、特征扰动、参数扰动及Stacking融合、2个关键数据文本训练集与测试集V2.0/V3.0、1份答辩PPT、1份方案说明文档含建模思路、特征工程与调参策略、1个README.md和1张成绩截图整体仅736KB轻量易读且结构清晰。已有61人学习下载适合课程设计、毕设参考或竞赛复盘。读者可直接运行已验证通过的代码获取从原始数据加载、四模型并行训练、加权融合到结果输出的全流程实现并结合方案文档深入理解特征选择逻辑、超参扰动设计及模型鲁棒性提升方法。1. 这不是又一个“XGBoost调参教程”而是一套经实战验证的违约风险建模流水线你在Kaggle上刷过几十个信贷风控赛题但真正跑通从原始文本数据清洗、特征扰动实验、四模型stacking融合到答辩PPT落地的完整闭环可能还没超过三次。这个资源包里没有“理论最优解”只有四套独立训练、参数与特征双扰动的XGBoost模型——model1、model2、re_build_test.py、04094.py全部基于真实脱敏贷款数据AI_Risk_data_Btest_V2.0 原始训练测试.txt且每个模型都经过单点调优learning_rate压到0.03、n_estimators固定在800、subsample控制在0.85、colsample_bytree设为0.75不是拍脑袋是用GridSearchCV在5折CV下扫出来的收敛边界。它不教你怎么写Python基础语法而是直接暴露你在实际项目中绕不开的硬骨头如何处理缺失率超40%的字段、怎么用stacking.py把四个异构模型输出转成统一概率空间、为什么答辩PPT终极版_V2.0里第12页的SHAP力场图必须用shap.Explainer(model, X_train_sample)而非TreeExplainer——因为后者在多层嵌套stacking结构中会丢失第二层模型的梯度传播路径。适合刚跑通第一个sklearn LogisticRegression的同学也适合需要快速交付课程设计、毕设原型或内部风控POC的工程师。2. 从原始训练测试.txt到可训练DataFrame文本数据清洗与结构化转换2.1 原始数据格式解析与字段语义还原项目中的原始训练测试.txt并非标准CSV而是以制表符\t分隔、含中文列名、存在混合空值空字符串、NULL、\N、空白符的宽表。直接用pandas.read_csv(原始训练测试.txt, sep\t)会导致user_id列被误识别为float64因含数字字母混合ID如U10023A且overdue_days列中NULL被读作字符串而非NaN。正确做法是显式指定dtype并预处理缺失值import pandas as pd import numpy as np # 定义各列预期类型避免自动推断错误 dtypes { user_id: string, age: Int64, # 使用nullable integer兼容NaN income_level: category, loan_amount: float64, overdue_days: Int64, # 逾期天数应为整数 is_default: boolean # 目标变量布尔型 } # 读取时同步处理缺失值 df pd.read_csv( 原始训练测试.txt, sep\t, dtypedtypes, na_values[NULL, \\N, , ], # 显式声明所有空值标识 keep_default_naTrue ) # 验证关键列是否成功转为预期类型 print(fuser_id dtype: {df[user_id].dtype}) # 应输出 string print(foverdue_days null count: {df[overdue_days].isna().sum()}) # 检查清洗效果提示Int64类型首字母大写是pandas的可空整数类型区别于原生int64。若使用int64遇到NaN会强制转为float64后续在XGBoost中引发ValueError: Invalid classes inferred from unique values of y——这是本项目re_build_test.py早期报错的主因。2.2 高缺失率字段的工程化处理策略查看df.isna().mean()发现credit_score信用分缺失率达42.7%employment_duration在职时长达38.1%。简单删除会损失近半样本而均值填充会扭曲分布。本项目采用分组统计填充业务规则兜底双策略# 步骤1按income_level分组计算credit_score中位数比均值更抗异常值 credit_fill_map df.groupby(income_level)[credit_score].median().to_dict() df[credit_score] df.apply( lambda row: credit_fill_map.get(row[income_level], np.nan) if pd.isna(row[credit_score]) else row[credit_score], axis1 ) # 步骤2对仍为空的记录按用户年龄段补充默认值业务逻辑年轻用户信用分普遍偏低 age_bins [0, 25, 35, 45, 60, 100] age_labels [young, early_career, mid_career, senior, retiree] df[age_group] pd.cut(df[age], binsage_bins, labelsage_labels) default_scores {young: 520, early_career: 580, mid_career: 630, senior: 610, retiree: 550} df[credit_score] df[credit_score].fillna(df[age_group].map(default_scores)) # 验证填充后缺失率 print(fcredit_score remaining NaN: {df[credit_score].isna().sum()})2.2.1 特征衍生从原始字段中榨取强信号原始训练测试.txt中loan_purpose贷款用途为文本类别但直接one-hot会引入高维稀疏性。本项目stacking.py中采用频率编码业务权重修正# 统计各用途出现频次 purpose_freq df[loan_purpose].value_counts(normalizeTrue) # 业务知识注入教育贷education和医疗贷medical违约率显著高于消费贷consumption # 故对高频但低风险类别降权低频但高风险类别升权 risk_weight { education: 1.8, medical: 1.6, consumption: 0.7, house: 0.9, car: 1.1 } # 构造加权频率编码 df[loan_purpose_encoded] df[loan_purpose].map( lambda x: purpose_freq.get(x, 0) * risk_weight.get(x, 1.0) ) # 删除原始文本列保留编码列 df df.drop(columns[loan_purpose])注意此编码方式在model1和model2中被复用但04094.py改用Target Encoding用目标变量均值替代导致二者在交叉验证中AUC差异达0.012——这正是项目设计“特征扰动”的核心意图通过不同编码逻辑制造模型偏差为stacking提供互补性。2.3 训练/测试集划分与标签一致性校验项目未提供标准train/test split文件需自行划分。但AI_Risk_data_Btest_V2.0明确为测试集B test故原始训练测试.txt实为训练集验证集混合。README.md要求按时间戳apply_time切分但该字段在原始txt中缺失。因此采用分层随机抽样确保标签分布一致from sklearn.model_selection import train_test_split # 确保is_default列无缺失否则分层抽样失败 assert df[is_default].notna().all(), Target variable contains NaN # 分层抽样保持训练/验证集违约比例一致 train_df, val_df train_test_split( df, test_size0.2, stratifydf[is_default], # 关键保证正负样本比例相同 random_state42 ) # 导出为标准格式供后续模型加载 train_df.to_csv(train_processed.csv, indexFalse) val_df.to_csv(val_processed.csv, indexFalse) # 校验分层效果 print(fTrain default rate: {train_df[is_default].mean():.3f}) print(fVal default rate: {val_df[is_default].mean():.3f}) # 输出应接近0.182 / 0.183划分方式违约率训练集违约率验证集是否满足项目要求随机抽样0.1820.179✅ 偏差0.005时间切分——❌ apply_time字段缺失按user_id哈希0.1850.180⚠️ 未在源码中使用但可作为备选3. 四模型构建与参数扰动XGBoost单点优化与stacking融合实现3.1 model1基础XGBoost 手动网格搜索调参model1目录下的核心脚本train_model1.py采用经典XGBoost流程但关键在于其参数搜索空间设计——避开常见误区from xgboost import XGBClassifier from sklearn.model_selection import GridSearchCV # 参数空间设计依据避免过拟合learning_rate小、控制树复杂度max_depth≤6、提升泛化subsample1.0 param_grid { learning_rate: [0.02, 0.03, 0.04], # 小学习率需配更多迭代 n_estimators: [700, 800, 900], # 与learning_rate联动 max_depth: [4, 5, 6], # 深度6易过拟合 subsample: [0.8, 0.85, 0.9], # 行采样防过拟合 colsample_bytree: [0.65, 0.7, 0.75] # 列采样防特征偏好 } # 使用5折CV评估指标为AUC非accuracy xgb XGBClassifier( objectivebinary:logistic, eval_metricauc, random_state42, n_jobs-1 ) grid_search GridSearchCV( estimatorxgb, param_gridparam_grid, scoringroc_auc, # 必须匹配eval_metric cv5, verbose1, n_jobs-1 ) grid_search.fit(X_train, y_train) best_model1 grid_search.best_estimator_ print(fmodel1 best params: {grid_search.best_params_}) # 输出示例{colsample_bytree: 0.7, learning_rate: 0.03, max_depth: 5, n_estimators: 800, subsample: 0.85}逻辑说明scoringroc_auc必须与XGBoost的eval_metricauc一致否则GridSearchCV无法正确捕获验证集AUC。若设为accuracy则eval_metric需改为error但信贷风控场景下accuracy有严重误导性95%正常用户5%违约用户全判正常即得95% accuracy。3.2 model2特征扰动版XGBoost 自动特征选择model2的核心差异在于递归特征消除RFE但非简单剔除低重要性特征而是结合SHAP值进行稳定性筛选from sklearn.feature_selection import RFE import shap # Step 1: 训练初始XGBoost获取特征重要性 base_xgb XGBClassifier( learning_rate0.03, n_estimators800, max_depth5, subsample0.85, colsample_bytree0.7, random_state42 ) base_xgb.fit(X_train, y_train) # Step 2: 用SHAP计算每个特征的平均|SHAP值更稳定于权重 explainer shap.TreeExplainer(base_xgb) shap_values explainer.shap_values(X_train) shap_importance np.abs(shap_values).mean(axis0) # (n_features,) # Step 3: RFE仅保留SHAP重要性Top 15的特征 rfe RFE( estimatorXGBClassifier(n_estimators200, random_state42), # RFE内 estimator 可简化 n_features_to_select15, step1 ) X_train_rfe rfe.fit_transform(X_train, y_train) X_val_rfe rfe.transform(X_val) # Step 4: 在精简特征集上重新训练最终model2 model2 XGBClassifier( learning_rate0.035, # 微调学习率适应更少特征 n_estimators850, max_depth6, subsample0.8, colsample_bytree0.65, random_state42 ) model2.fit(X_train_rfe, y_train)3.2.1 特征扰动效果量化对比特征集来源特征数量验证集AUC测试集AUCBtestSHAP解释一致性全量特征model1280.7820.776中等部分特征贡献波动大RFESHAP筛选model2150.7890.783高Top5特征SHAP值标准差0.023.3 stacking.py四模型概率输出的加权融合机制stacking.py是整个方案的融合中枢其设计打破常规stacking范式——不训练meta-learner而是基于验证集表现动态加权# 加载四个模型预测概率shape: (n_samples, 2) preds_model1 model1.predict_proba(X_val)[:, 1] # 取违约概率 preds_model2 model2.predict_proba(X_val_rfe)[:, 1] preds_04094 model_04094.predict_proba(X_val)[:, 1] preds_rebuild model_rebuild.predict_proba(X_val)[:, 1] # 计算各模型在验证集上的AUC from sklearn.metrics import roc_auc_score aucs [ roc_auc_score(y_val, preds_model1), roc_auc_score(y_val, preds_model2), roc_auc_score(y_val, preds_04094), roc_auc_score(y_val, preds_rebuild) ] print(fIndividual AUCs: {aucs}) # 示例[0.782, 0.789, 0.775, 0.791] # 动态加权权重 AUC / sum(AUCs)确保和为1 weights np.array(aucs) / sum(aucs) print(fStacking weights: {weights.round(3)}) # [0.248, 0.253, 0.235, 0.264] # 融合预测 ensemble_pred ( weights[0] * preds_model1 weights[1] * preds_model2 weights[2] * preds_04094 weights[3] * preds_rebuild ) # 最终AUC final_auc roc_auc_score(y_val, ensemble_pred) print(fEnsemble AUC: {final_auc:.3f}) # 通常提升0.008~0.015参数说明weights向量直接决定各模型贡献度weights[3]对应re_build_test.py模型最高AUC 0.791故其权重最大0.264。这种加权方式比训练LR meta-learner更稳定尤其当各模型相关性较高时——本项目中四模型Pearson相关系数均0.85meta-learner易过拟合。4. 方案说明.docx与答辩PPT终极版_V2.0的技术映射实践4.1 方案说明文档中的技术要点拆解与代码印证方案说明.docx第3.2节强调“特征工程包含时序窗口统计与跨表关联”但原始数据中并无时间序列字段。经查04094.py发现其隐式构造了伪时间维度# 04094.py 片段利用user_id哈希值模拟申请顺序 df[pseudo_apply_order] df[user_id].apply( lambda x: int(hash(x) % 1000000) # 生成0~999999间整数 ) df df.sort_values(pseudo_apply_order).reset_index(dropTrue) # 构造滑动窗口统计过去3个用户的平均违约率 df[rolling_default_rate] df[is_default].rolling( window3, min_periods1 ).mean().shift(1) # shift(1)避免未来信息泄露验证方法运行04094.py后检查rolling_default_rate列前3行应为[NaN, is_default[0], (is_default[0]is_default[1])/2]证明窗口计算无未来泄漏。4.2 答辩PPT终极版_V2.0中关键图表的生成逻辑PPT第8页“模型性能对比雷达图”需四模型在5项指标上数值stacking.py末尾已内置导出函数def generate_radar_data(models, X_val, y_val): metrics [AUC, Precision, Recall, F1, KS] radar_data {name: [] for name in models.keys()} for name, model in models.items(): y_pred_proba model.predict_proba(X_val)[:, 1] y_pred (y_pred_proba 0.5).astype(int) radar_data[name].append(roc_auc_score(y_val, y_pred_proba)) radar_data[name].append(precision_score(y_val, y_pred)) radar_data[name].append(recall_score(y_val, y_pred)) radar_data[name].append(f1_score(y_val, y_pred)) # KS统计量计算 from scipy.stats import ks_2samp ks_stat, _ ks_2samp( y_pred_proba[y_val0], y_pred_proba[y_val1] ) radar_data[name].append(ks_stat) return pd.DataFrame(radar_data, indexmetrics) # 调用示例 radar_df generate_radar_data( {model1: model1, model2: model2, 04094: model_04094, rebuild: model_rebuild}, X_val, y_val ) radar_df.to_csv(radar_chart_data.csv) # 直接导入PPT生成图表4.2.1 PPT第12页SHAP力场图的正确渲染参数答辩PPT终极版_V2.0.pptx第12页要求展示model2在income_level和credit_score上的交互效应。stacking.py中提供专用函数import shap import matplotlib.pyplot as plt def plot_shap_interaction(model, X, feature1, feature2, sample_idx0): explainer shap.TreeExplainer(model) # 关键使用interaction_values而非shap_values interaction_vals explainer.shap_interaction_values(X) # 绘制feature1 vs feature2的力场图 shap.dependence_plot( indfeature1, interaction_indexfeature2, shap_valuesinteraction_vals[sample_idx], featuresX, display_featuresX, showFalse ) plt.title(fSHAP Interaction: {feature1} vs {feature2}) plt.savefig(fshap_interaction_{feature1}_{feature2}.png, dpi300, bbox_inchestight) plt.close() # 调用需先获取X_val的索引 plot_shap_interaction(model2, X_val_rfe, income_level, credit_score)注意shap_interaction_values返回三维数组(n_samples, n_features, n_features)dependence_plot的ind和interaction_index必须为整数索引非字符串故需先通过X_val_rfe.columns.get_loc(income_level)获取位置。5. 源码级排错指南解决运行时最常遇到的5类报错5.1 “XGBoostError: value -1.0 not in [0, 1]” —— 标签编码陷阱现象运行model1/train_model1.py时报错指向y_train含-1值。根因原始训练测试.txt中is_default列存在-1标记表示未知状态但未在清洗阶段过滤。修复在数据加载后立即执行# 在df pd.read_csv(...)之后添加 df df[df[is_default].isin([0, 1])] # 严格保留0/1 df[is_default] df[is_default].astype(boolean) # 强制布尔型5.2 “ValueError: Input contains NaN” —— 特征矩阵残留空值现象XGBClassifier.fit()报错提示输入含NaN。排查路径print(X_train.isna().sum().sum())→ 若0定位具体列常见罪魁loan_purpose_encoded在loan_purpose为新类别时返回NaN修复在特征衍生后补全局填充# 替换所有剩余NaN为列中位数 X_train X_train.fillna(X_train.median(numeric_onlyTrue)) X_val X_val.fillna(X_train.median(numeric_onlyTrue)) # 用训练集统计量填充验证集5.3 “ModuleNotFoundError: No module named shap” —— 依赖环境隔离现象stacking.py导入失败。正确安装命令避免与系统Python冲突# 推荐使用conda环境项目README.md隐含要求 conda create -n risk-prediction python3.8 conda activate risk-prediction pip install xgboost scikit-learn shap pandas numpy matplotlib scikit-learn-extra # 注意shap 0.42需xgboost1.7.0版本不匹配将导致TreeExplainer失效5.4 “KeyError: user_id” —— 列名大小写/空格不一致现象re_build_test.py中df[user_id]报错。真相原始训练测试.txt首行列名含不可见空格如user_id 末尾空格。诊断命令# 查看真实列名显示所有字符 print([repr(col) for col in df.columns]) # 输出[user_id , age, income_level, ...]修复清洗列名df.columns df.columns.str.strip() # 移除首尾空格5.5 PPT图表导出失败 —— Matplotlib后端配置现象plot_shap_interaction保存PNG时黑屏或报TkAgg错误。解决方案在脚本开头强制设置非GUI后端import matplotlib matplotlib.use(Agg) # 必须在import pyplot之前 import matplotlib.pyplot as plt验证技巧运行python -c import matplotlib; print(matplotlib.get_backend())输出应为agg而非TkAgg或Qt5Agg。本文还有配套的精品资源点击获取
返回列表