信用风险预测模型实战:从数据到评分卡与SHAP解释
简介这份PDF文献面向金融风控从业者、数据科学学习者及商业银行风险管理研究人员聚焦机器学习算法在信用风险预测中的建模实践。资源以单篇PDF形式呈现压缩包约1.5MB内含1个PDF文件完整收录了基于Logistic回归、决策树、Adaboost、GradientBoosting与LGB等模型的对比实验并围绕西南财经大学“新网银行杯”竞赛数据展开分析。文中详细梳理了高维稀疏数据、无标签样本、多产品客群及好坏样本不平衡等真实业务挑战涵盖缺失值处理、特征选取、降维与不降维结果比较等关键环节最终得出LGB模型AUC指标最优的结论。目前已有538人学习适合希望了解信用风险预测建模流程、掌握多模型评估思路的读者参考借鉴。1. 信用风险预测模型从一份 PDF 标题到能跑通的评分卡银行风控部门最常被问的一句话是这个客户到底会不会逾期过去靠专家规则和人工审批现在越来越多团队转向机器学习。信用风险预测模型要解决的核心问题很具体——给定一笔申请或一个存量客户的历史行为输出一个违约概率再按概率切分客群、定授信额度和利率。它适合有结构化数据、有明确好坏标签、需要可解释结果的场景比如消费信贷、小微企业贷、供应链金融。标题里那份 PDF 大概率是一篇论文或技术报告但落到工程上真正要交付的不是论文而是一条能复现、能上线、能解释的建模流水线。这篇笔记就按这条流水线走一遍从数据到模型到排错把信用风险预测模型讲透。2. 信用风险预测模型的数据底座标签定义与特征工程2.1 好坏样本怎么定义观察期和表现期怎么切信用风险建模第一步不是选算法是定义标签。常见做法是滚动率分析加账龄分析取一个观察时点往前看一段时间的行为往后看一段时间是否发生逾期。比如观察期取 6 个月表现期取 12 个月表现期内出现 M1逾期 1 到 30 天就标为坏样本从未逾期标为好样本。这里有个血泪经验表现期太短坏样本还没暴露出来模型学到的全是“暂时正常”表现期太长好样本被稀释且早期行为与远期表现的相关性下降。消费信贷一般用 12 个月表现期小微企业贷可以拉到 18 个月。定义完标签还要处理样本不均衡。信用风险场景坏样本占比通常 1% 到 10%直接训练会让模型偏向多数类。常见做法是分层抽样保持原始比例再在训练时用class_weightbalanced或scale_pos_weight调整权重而不是简单过采样因为过采样会扭曲概率校准。2.2 特征工程从原始流水到 WOE 编码信用风险模型的特征来源主要有四类申请信息年龄、收入、职业、征信信息查询次数、负债率、历史逾期、行为信息还款记录、交易流水、第三方数据运营商、电商。原始特征不能直接喂给逻辑回归因为量纲差异大且非线性关系弱。行业里最稳的做法是分箱加 WOE 编码。下面这段代码演示用optbinning做有监督分箱并计算 WOE这是信用评分卡最常用的预处理方式import pandas as pd import numpy as np from optbinning import OptimalBinning # df 包含特征和标签target 为 0/1 def woe_transform(df, feature_cols, target_col): woe_maps {} for col in feature_cols: optb OptimalBinning(namecol, dtypenumerical, solvercp) optb.fit(df[col].values, df[target_col].values) # 输出分箱明细包含每箱的 WOE 和 IV binning_table optb.binning_table.build() woe_map {} for _, row in binning_table.iterrows(): if row[Bin] in (Special, Missing): continue # 解析区间并映射 WOE woe_map[row[Bin]] row[WoE] woe_maps[col] woe_map df[col _woe] optb.transform(df[col].values, metricwoe) return df, woe_maps # 调用示例 feature_cols [age, income, debt_ratio, query_3m, overdue_12m] df, woe_maps woe_transform(df, feature_cols, target)逻辑说明OptimalBinning用条件推断树做单调分箱保证 WOE 与目标单调相关这对逻辑回归的可解释性很关键。binning_table里的 IV 值可以筛特征IV 小于 0.02 的特征基本没区分度大于 0.5 的要警惕数据泄露。参数上dtype选 numerical 或 categoricalsolver选 cp 适合中小规模数据数据量大时换 mip 更快。WOE 编码后还要做相关性筛选和 VIF 检验避免多重共线性。一般保留 IV 大于 0.02 且两两相关系数低于 0.7 的特征。这一步做完数据底座才算稳。3. 模型选型与训练逻辑回归、树模型和集成策略怎么选3.1 逻辑回归为什么仍是信用评分的基线信用风险预测模型有个特殊约束监管和业务都要求可解释。逻辑回归的系数直接对应 WOE 的贡献能输出标准评分卡这是树模型和神经网络给不了的。所以行业惯例是先用逻辑回归做基线再用集成模型冲 AUC最后用 SHAP 或评分卡做解释。逻辑回归训练本身不复杂关键是正则化和概率校准。下面是一个带 L2 正则和类别权重的训练示例from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score, ks_2samp import numpy as np # X 为 WOE 编码后的特征y 为标签 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy, random_state42 ) lr LogisticRegression( penaltyl2, C0.1, # 正则强度越小越强 class_weightbalanced, solverlbfgs, max_iter1000 ) lr.fit(X_train, y_train) # 评估AUC 和 KS y_pred lr.predict_proba(X_test)[:, 1] auc roc_auc_score(y_test, y_pred) ks ks_2samp(y_pred[y_test 1], y_pred[y_test 0]).statistic print(fAUC: {auc:.4f}, KS: {ks:.4f})参数说明C控制正则强度信用风险场景一般取 0.01 到 1太小会欠拟合太大会过拟合。class_weightbalanced自动按类别频率反比加权。评估不能只看 AUCKS 在风控里更常用一般要求 KS 大于 0.3AUC 大于 0.7 才算可用。训练完还要看系数符号是否符合业务常识比如“历史逾期次数”的 WOE 系数应为正如果为负说明特征有问题。3.2 树模型和集成模型XGBoost、LightGBM 的调参边界当特征里有大量非线性交互或者 WOE 编码损失了信息树模型能补上。XGBoost 和 LightGBM 在信用风险预测里用得最多因为能处理缺失值、支持单调约束、训练快。但树模型不能直接输出评分卡解释要靠 SHAP。下面是一个带单调约束的 LightGBM 训练示例单调约束能保证“逾期次数越多违约概率越高”这种业务逻辑不被模型学反import lightgbm as lgb from sklearn.model_selection import train_test_split # monotone_constraints 对应特征顺序1 表示单调递增-1 递减0 无约束 mono [1 if col in [overdue_12m, query_3m, debt_ratio] else 0 for col in X.columns] params { objective: binary, metric: auc, learning_rate: 0.05, num_leaves: 31, max_depth: 6, min_child_samples: 50, subsample: 0.8, colsample_bytree: 0.8, monotone_constraints: mono, is_unbalance: True, verbose: -1 } dtrain lgb.Dataset(X_train, y_train) dval lgb.Dataset(X_test, y_test, referencedtrain) model lgb.train( params, dtrain, num_boost_round500, valid_sets[dval], callbacks[lgb.early_stopping(50), lgb.log_evaluation(100)] )逻辑说明monotone_constraints是信用风险场景的后悔药能防止模型学到反常识关系。min_child_samples设大一点50 到 100避免过拟合因为坏样本少叶子节点太细会记住噪声。early_stopping用验证集 AUC 早停一般 50 轮不提升就停。LightGBM 的is_unbalance和 XGBoost 的scale_pos_weight作用类似但注意它会影响概率输出上线前要做概率校准。模型选型的结论逻辑回归做基线LightGBM 或 XGBoost 做主力两者 AUC 差距通常在 0.02 到 0.05。如果业务要求强解释逻辑回归加评分卡就够了如果追求排序能力集成模型更优。常见做法是两者都跑用集成模型的分数排序用逻辑回归的分数做解释。4. 模型评估与上线AUC、KS、PSI 和评分卡刻度4.1 离线评估AUC、KS、Lift 怎么看信用风险预测模型的离线评估不能只看准确率因为坏样本少全预测为好样本也有 90% 以上准确率。核心指标是 AUC、KS 和 Lift。AUC 衡量排序能力KS 衡量好坏样本的最大区分度Lift 看头部客群的坏样本浓度。下面这段代码计算 KS 和分箱 Liftimport numpy as np import pandas as pd from sklearn.metrics import roc_auc_score def evaluate(y_true, y_pred, n_bins10): auc roc_auc_score(y_true, y_pred) # KS df pd.DataFrame({y: y_true, p: y_pred}) df df.sort_values(p, ascendingFalse) df[cum_bad] df[y].cumsum() / df[y].sum() df[cum_good] (1 - df[y]).cumsum() / (1 - df[y]).sum() ks (df[cum_bad] - df[cum_good]).abs().max() # Lift df[bin] pd.qcut(df[p], n_bins, labelsFalse, duplicatesdrop) lift df.groupby(bin)[y].mean() / df[y].mean() return auc, ks, lift auc, ks, lift evaluate(y_test, y_pred) print(fAUC{auc:.4f}, KS{ks:.4f}) print(lift)参数说明n_bins一般取 10看头部两箱的 Lift 是否显著大于 1。KS 大于 0.3 可用大于 0.4 优秀。AUC 和 KS 要同时看AUC 高但 KS 低说明排序好但区分度不够。4.2 评分卡刻度把概率转成 300 到 850 的分数业务方不关心概率关心分数。评分卡刻度用score offset factor * ln(odds)其中odds是坏好比。常见设定是基准分 600基准 odds 为 50每 20 分 odds 翻倍。下面代码把逻辑回归概率转成评分import numpy as np def prob_to_score(prob, base_score600, base_odds50, pdo20): # pdo: 每翻倍需要的分数 factor pdo / np.log(2) offset base_score - factor * np.log(base_odds) odds prob / (1 - prob) score offset factor * np.log(odds) return score # 注意逻辑回归输出的是坏样本概率odds 为坏好比 score prob_to_score(y_pred)逻辑说明pdo是 points to double the odds风控里常用 20 或 50。分数越高违约概率越低。上线前要检查分数分布是否合理一般要求分数在 300 到 850 之间且分布不能太集中。4.3 上线监控PSI 和变量稳定性模型上线不是终点监控才是。信用风险模型最怕客群漂移PSIPopulation Stability Index是标配。PSI 小于 0.1 稳定0.1 到 0.25 要警惕大于 0.25 就要重新训练。下面代码计算 PSIdef psi(expected, actual, n_bins10): breakpoints np.percentile(expected, np.linspace(0, 100, n_bins 1)) expected_perc np.histogram(expected, breakpoints)[0] / len(expected) actual_perc np.histogram(actual, breakpoints)[0] / len(actual) # 避免除零 expected_perc np.where(expected_perc 0, 0.0001, expected_perc) actual_perc np.where(actual_perc 0, 0.0001, actual_perc) return np.sum((actual_perc - expected_perc) * np.log(actual_perc / expected_perc))参数说明n_bins取 10 或 20expected是训练集分数actual是线上分数。除了分数 PSI还要监控每个特征的 PSI定位是哪个变量漂移了。常见做法是每周跑一次 PSI超过阈值就触发人工排查。5. 避坑与排查信用风险建模里最容易翻车的五件事5.1 数据泄露用未来信息预测过去现象离线 AUC 高达 0.95上线后 KS 掉到 0.1。原因特征里混入了表现期内的信息比如用“当前逾期状态”预测“未来是否逾期”。解决严格按时间切分训练集和测试集特征只取观察时点之前的数据做特征时加时间窗口约束。5.2 样本不均衡处理不当导致概率失真现象模型输出的违约概率普遍偏高业务方按概率定利率亏钱。原因过采样或class_weight改变了先验分布概率不再校准。解决训练时用权重上线前用 Platt scaling 或 isotonic regression 做概率校准校准集要用原始分布。5.3 WOE 编码在测试集上映射失败现象测试集出现训练集没有的分箱区间WOE 映射为 NaN。原因分箱边界只基于训练集测试集有超出范围的取值。解决分箱时预留Special和Missing箱测试集超出范围的值映射到边界箱或单独一箱代码里加fillna兜底。5.4 单调约束设反导致业务逻辑矛盾现象SHAP 显示“历史逾期次数”越多违约概率越低。原因单调约束方向设错或者特征编码时 WOE 符号反了。解决训练前检查每个特征的 WOE 单调性monotone_constraints按业务常识设置训练后看 SHAP 依赖图验证。5.5 PSI 监控只看分数不看特征现象分数 PSI 正常但某个关键特征 PSI 超阈值模型实际已失效。原因多个特征漂移相互抵消分数分布看起来稳定。解决分数 PSI 和特征 PSI 同时监控特征 PSI 超阈值就排查数据源不要等分数出问题才动手。6. 进阶技巧用 SHAP 做单客户解释和拒绝原因输出信用风险模型上线后业务方最常提的需求是这个客户为什么被拒监管也要求拒绝要有理由。逻辑回归可以用系数乘 WOE 算贡献树模型就得靠 SHAP。下面这段代码用 SHAP 输出单客户的 Top 5 贡献特征import shap import numpy as np # model 为训练好的 LightGBM 模型 explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_test) # 取第一个客户 customer_idx 0 customer_shap shap_values[customer_idx] feature_names X_test.columns # 按绝对值排序取 Top 5 top_idx np.argsort(np.abs(customer_shap))[::-1][:5] for i in top_idx: direction 推高 if customer_shap[i] 0 else 拉低 print(f{feature_names[i]}: {customer_shap[i]:.4f} ({direction}违约概率))逻辑说明TreeExplainer对树模型是精确解速度快。shap_values的符号表示该特征对违约概率的推动方向正值推高负值拉低。业务方看到的是“历史逾期次数多推高违约概率 0.12”比“WOE 系数 0.8”直观得多。参数上注意X_test要和训练特征顺序一致类别特征要提前编码。如果模型是逻辑回归直接用coef_ * X算贡献即可不需要 SHAP。还有一个实用技巧是分数分群监控。把评分卡分数切成 10 档每周统计每档的坏样本率和 PSI画成趋势图。如果某一档坏样本率突然上升说明该客群风险在变化可以针对性调整策略。我一般会把这个监控做成自动化报表每周一早上跑比等月报发现问本文还有配套的精品资源点击获取