ARTICLE DETAIL

资讯详情

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

信用评分模型首选逻辑回归:从WOE编码到评分卡监控的Python实战

信用评分模型首选逻辑回归:从WOE编码到评分卡监控的Python实战 简介这份资源围绕逻辑回归算法构建信用评分模型面向对风控建模感兴趣的新手与有一定基础的学习者可用于毕业设计、课程实践、作业任务或实训项目。内容覆盖数据预处理与特征处理、变量转换为WOE形式、计算信息值并完成变量选择、将选定特征转化为WOE格式以及根据输入特征数值自动生成评分结果形成从数据处理到评分输出的完整链路。资源包共10个文件以csv数据文件、py脚本、xls数据字典、md说明文档及zbak备份文件为主压缩包约7.68MB目录结构清晰便于按模块查阅与复现。已有41人学习下载。读者可借此掌握评分卡建模的核心流程理解WOE与IV在变量筛选中的作用并参考脚本与数据字典完成模型搭建与结果验证适合作为风控评分入门与项目实践的参考材料。1. 信用评分模型为什么首选逻辑回归从业务可解释性说起银行风控部门经常遇到一个尴尬局面用梯度提升树训出来的违约预测模型AUC 比逻辑回归高了 0.02但提交到监管审查时被打了回来——原因很简单审查方要求逐条解释「这个客户为什么被拒」而树模型给出的是几百个分裂节点的加权路径没法写进拒绝原因说明书。逻辑回归不一样每个特征的系数直接对应「该特征每变化一个单位违约几率比变化多少倍」天然满足可解释性要求。这也是为什么在信用评分这个场景里逻辑回归至今仍是行业基线模型不是因为它最强而是因为它最「讲得清」。信用评分模型的核心任务是把借款人的历史行为、当前负债、基本信息等特征映射为一个分数用来预测未来一段时间内违约的概率。逻辑回归做这件事的逻辑很直接先把违约概率通过 logit 变换拉成线性空间再用线性回归去拟合最后反变换回概率。整个链路没有黑匣子每一步都能追溯。适合谁风控建模工程师、金融数据科学从业者以及任何需要构建「可解释二分类模型」的人。接下来的内容我会按实际建模流程从数据处理一路讲到评分卡刻度化把参数、代码和踩过的坑都摊开说。2. 逻辑回归做信用评分的数学底子与特征工程2.1 从线性回归到逻辑回归sigmoid 到底做了什么线性回归输出的是连续值取值范围是负无穷到正无穷但违约概率必须在 0 到 1 之间。逻辑回归的解法是引入 sigmoid 函数p(y1|x) 1 / (1 exp(-z))其中 z w·x b当 z 趋近正无穷时p 趋近 1z 趋近负无穷时p 趋近 0。这个变换把线性输出压缩到了概率空间。反过来把概率 p 变换成 logitlogit(p) ln(p / (1-p)) w·x blogit 的物理含义是「违约几率比的对数」。系数 w_i 表示特征 x_i 每增加一个单位违约几率比的对数变化量。如果 w_i 0.5意味着 x_i 增加 1违约几率比变为原来的 exp(0.5) ≈ 1.65 倍。这个解释在写拒绝原因时非常有用。训练逻辑回归用的损失函数是交叉熵损失也叫对数损失L(w) -1/N * Σ [y_i * ln(p_i) (1-y_i) * ln(1-p_i)]这个损失函数是凸函数梯度下降能保证收敛到全局最优。相比最小二乘损失交叉熵对错误分类的惩罚更大——预测概率偏离真实标签越远损失增长越快。这也是为什么逻辑回归对特征尺度敏感需要用正则化控制。2.2 信用评分场景下的特征工程WOE 编码与 IV 筛选信用评分模型的特征工程和一般分类模型有本质区别。一般模型用 one-hot 或 target encoding 就够了但信用评分领域有一套自己的编码体系——WOEWeight of Evidence编码。WOE 的计算公式WOE_i ln( (Good_i / Good_total) / (Bad_i / Bad_total) )其中 Good_i 是第 i 个分箱中好客户数量Bad_i 是坏客户数量。WOE 编码的好处是把非线性关系线性化同时让每个分箱的 WOE 值直接反映该分箱的风险方向。IVInformation Value用来衡量单个特征的预测能力IV Σ (Good_i/Good_total - Bad_i/Bad_total) * WOE_iIV 的经验判断标准IV 范围预测能力 0.02几乎无预测力0.02 ~ 0.1弱预测力0.1 ~ 0.3中等预测力0.3 ~ 0.5强预测力 0.5可疑需检查是否数据泄漏下面是一个完整的 WOE 编码和 IV 计算函数import pandas as pd import numpy as np def calculate_woe_iv(df, feature, target): 计算单个特征的 WOE 和 IV df: 包含特征和目标变量的 DataFrame feature: 特征列名 target: 目标列名0好客户1坏客户 # 按特征值分箱统计 grouped df.groupby(feature)[target].agg([count, sum]) grouped.columns [total, bad] grouped[good] grouped[total] - grouped[bad] # 防止除零 grouped[bad] grouped[bad].replace(0, 0.5) grouped[good] grouped[good].replace(0, 0.5) # 计算 WOE grouped[bad_rate] grouped[bad] / grouped[bad].sum() grouped[good_rate] grouped[good] / grouped[good].sum() grouped[woe] np.log(grouped[good_rate] / grouped[bad_rate]) # 计算 IV grouped[iv] (grouped[good_rate] - grouped[bad_rate]) * grouped[woe] iv_total grouped[iv].sum() return grouped, iv_total这段代码的逻辑说明先按特征值分组统计好坏客户数然后计算每个分箱的 WOE 和 IV。注意replace(0, 0.5)是为了避免某个分箱没有坏客户时 log 无穷大这是信用评分建模的常见做法。IV 总和大于 0.5 时不要高兴太早先检查特征里是否混入了标签泄漏的字段比如「历史逾期次数」这种直接和违约相关的变量。参数说明target列必须是 0/1 编码0 代表好客户未违约1 代表坏客户违约。如果你的数据里是反的WOE 符号会全部颠倒模型系数解释也会反过来。3. 用 Python 从零搭建信用评分模型数据、训练与评估3.1 数据准备与分箱连续变量怎么切才合理信用评分模型对连续变量的处理不是直接扔进去而是先做分箱binning再对每个箱做 WOE 编码。分箱的好处是降低异常值影响、处理缺失值、让非线性关系线性化。常见的分箱方法有三种等频分箱、等距分箱、卡方分箱。我一般用卡方分箱因为它会考虑目标变量分布让每个箱内的好坏客户比例差异最大化。下面是基于optbinning库的卡方分箱示例from optbinning import OptimalBinning import pandas as pd # 假设 df 是原始数据age 是连续特征target 是标签 df pd.read_csv(credit_data.csv) # 初始化分箱器 optb OptimalBinning( nameage, dtypenumerical, solvercp, # 使用约束规划求解 min_bin_size0.05, # 每个箱至少占总样本 5% max_n_bins6, # 最多分 6 箱 monotonic_trendauto # 自动判断单调性 ) # 拟合分箱 optb.fit(df[age], df[target]) # 查看分箱结果 binning_table optb.binning_table.build() print(binning_table[[Bin, Count, Count (%), Event rate, WoE, IV]]) # 转换数据 df[age_woe] optb.transform(df[age], metricwoe)逻辑说明OptimalBinning会自动寻找最优切分点min_bin_size0.05保证每个箱不会太薄max_n_bins6控制模型复杂度。monotonic_trendauto会让分箱结果尽量保持单调——年龄越大违约率越低或越高这种单调性在业务上更容易解释。参数说明min_bin_size设太小会导致过拟合设太大会丢失区分度0.05 是经验值。max_n_bins一般不超过 8超过 8 箱后 WOE 编码的稳定性会下降。如果某个特征的 IV 低于 0.02直接扔掉不要犹豫。3.2 逻辑回归训练正则化、类别权重与阈值选择数据准备好之后用 sklearn 训练逻辑回归from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score, classification_report import numpy as np # 假设 X 是 WOE 编码后的特征矩阵y 是标签 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) # 初始化逻辑回归模型 lr LogisticRegression( penaltyl2, # L2 正则化 C1.0, # 正则化强度的倒数越小正则化越强 class_weightbalanced,# 自动调整类别权重 solverlbfgs, # 拟牛顿法 max_iter1000, # 最大迭代次数 random_state42 ) # 训练 lr.fit(X_train, y_train) # 预测概率 y_pred_proba lr.predict_proba(X_test)[:, 1] # 评估 auc roc_auc_score(y_test, y_pred_proba) print(fAUC: {auc:.4f}) # 查看系数 coef_df pd.DataFrame({ feature: X.columns, coefficient: lr.coef_[0] }).sort_values(coefficient, ascendingFalse) print(coef_df)逻辑说明penaltyl2是信用评分模型的默认选择因为 L2 正则化会让系数平滑收缩不会像 L1 那样把某些系数直接压成 0——信用评分模型通常希望保留所有入模特征方便业务解释。class_weightbalanced在好坏样本不均衡时很重要信用评分场景坏客户占比通常只有 3%~8%不加权的话模型会偏向预测好客户。参数说明C是正则化强度的倒数C 越小正则化越强。我一般从 1.0 开始试如果 AUC 在训练集和测试集差距超过 0.05说明过拟合把 C 降到 0.1 或 0.01。max_iter设 1000 基本够用如果报「未收敛」警告先检查特征是否做了标准化——逻辑回归对特征尺度敏感WOE 编码后的特征天然在 [-3, 3] 左右一般不需要额外标准化。阈值选择默认 0.5 不一定最优。信用评分场景要根据通过率和坏账率的业务目标来定。比如业务要求通过率 70%那就把阈值设在测试集概率分布的 30% 分位点。3.3 模型评估AUC、KS 与评分卡刻度化信用评分模型的核心评估指标是 AUC 和 KSfrom sklearn.metrics import roc_curve import matplotlib.pyplot as plt # 计算 KS fpr, tpr, thresholds roc_curve(y_test, y_pred_proba) ks max(tpr - fpr) print(fKS: {ks:.4f}) # 绘制 KS 曲线 plt.plot(thresholds, tpr, labelTPR) plt.plot(thresholds, fpr, labelFPR) plt.plot(thresholds, tpr - fpr, labelKS) plt.legend() plt.xlabel(Threshold) plt.ylabel(Rate) plt.title(KS Curve) plt.show()AUC 衡量的是模型排序能力KS 衡量的是模型区分好坏客户的最大能力。信用评分模型一般要求 AUC 0.7KS 0.3。如果 AUC 低于 0.65说明特征工程没做到位回去检查 IV 筛选和分箱逻辑。评分卡刻度化是把逻辑回归输出的概率转成 300~850 分的标准分# 评分卡刻度化 # 设定基准分和 PDOPoints to Double the Odds base_score 600 base_odds 50 # 基准 odds pdo 20 # 每增加 20 分odds 翻倍 # 计算因子和偏移量 factor pdo / np.log(2) offset base_score - factor * np.log(base_odds) # 将逻辑回归系数转换为评分卡分数 # score offset - factor * (w·x) def scorecard_transform(X, lr_model, factor, offset): 将逻辑回归输出转换为评分卡分数 log_odds lr_model.decision_function(X) scores offset - factor * log_odds return scores scores scorecard_transform(X_test, lr, factor, offset) print(fScore range: {scores.min():.0f} ~ {scores.max():.0f})逻辑说明factor和offset是评分卡的两个核心参数。factor控制分数刻度pdo20意味着 odds 翻倍时分数增加 20 分。offset控制基准分base_score600且base_odds50意味着当 odds 为 50:1 时分数为 600 分。这套刻度化方案是行业通用做法方便业务方理解和使用。4. 信用评分模型落地时最容易翻车的五个地方4.1 样本时间窗口选错导致 AUC 虚高现象模型在测试集上 AUC 0.85上线后三个月 KS 掉到 0.15。原因训练样本的时间窗口和上线后的业务环境不一致。比如用 2022 年疫情期间的数据训练当时坏客户集中爆发模型学到了「疫情」这个隐藏变量。上线后疫情结束特征分布漂移模型失效。解决训练样本要覆盖至少一个完整的经济周期通常用 12~24 个月的表现期数据。观察期和表现期之间要留至少 3 个月的缓冲避免「刚放款就违约」的样本混进来。4.2 WOE 编码在训练集和测试集上不一致现象训练集 AUC 0.82测试集 AUC 0.71差距超过 0.1。原因WOE 编码时用了全量数据计算分箱边界和 WOE 值导致测试集信息泄漏到训练过程。或者训练集和测试集的分箱边界不一致同一个特征值在两个集合里映射到了不同的 WOE。解决分箱和 WOE 编码只能在训练集上拟合然后 transform 到测试集。如果测试集出现训练集没见过的分箱用最接近的箱的 WOE 值填充或者单独设一个「未知」箱。4.3 多重共线性让系数解释翻车现象模型里「月收入」的系数是正的「负债收入比」的系数也是正的但业务上收入越高违约率越低负债收入比越高违约率越高——两个系数方向都对但「月收入」的系数大得离谱。原因月收入和负债收入比高度相关逻辑回归在共线性下系数估计不稳定方差膨胀。解决训练前计算 VIF方差膨胀因子VIF 5 的特征要处理。常见做法是保留业务含义更清晰的那个或者用 PCA 降维后再入模。信用评分场景一般不推荐 PCA因为降维后主成分没法解释。4.4 缺失值填充方式引入偏差现象模型上线后缺失值多的客户群体评分普遍偏高或偏低。原因用均值或中位数填充缺失值忽略了「缺失本身可能就是信息」。比如「工作年限」缺失的客户可能是自由职业者或失业者违约风险和普通工薪族不同。解决把缺失值单独设为一个分箱计算该分箱的 WOE。如果缺失率超过 30%考虑直接扔掉这个特征或者用「是否缺失」作为新特征入模。4.5 评分卡刻度化后分数分布不合理现象评分卡上线后大部分客户集中在 580~620 分高分和低分客户极少。原因base_odds和pdo设置不合理导致分数压缩在中段。或者逻辑回归的 log-odds 分布本身太集中。解决先看 log-odds 的分布如果标准差小于 1说明模型区分度不够回去检查特征工程。如果 log-odds 分布正常但分数集中调整pdo和base_odds让分数分布拉开。一般要求评分卡分数覆盖 300~850 的范围且各分数段都有一定客户量。5. 用评分卡监控报表提前发现模型衰减模型上线不是终点而是起点。信用评分模型最怕的是「静默衰减」——模型慢慢失效但没人发现直到坏账率飙升才反应过来。我一般会建一张评分卡监控报表每周跑一次看三个核心指标分数分布迁移、特征 PSI、KS 趋势。分数分布迁移看的是当前客群和训练客群的评分分布是否一致。如果当前客群的评分均值比训练时偏移超过 20 分或者分布形状明显变化说明客群结构变了。特征 PSIPopulation Stability Index衡量单个特征的分布变化def calculate_psi(expected, actual, bins10): 计算 PSI expected: 训练集特征分布 actual: 当前特征分布 # 等频分箱 breakpoints np.percentile(expected, np.linspace(0, 100, bins 1)) breakpoints[0] -np.inf breakpoints[-1] np.inf expected_counts np.histogram(expected, binsbreakpoints)[0] / len(expected) actual_counts np.histogram(actual, binsbreakpoints)[0] / len(actual) # 防止除零 expected_counts np.where(expected_counts 0, 0.0001, expected_counts) actual_counts np.where(actual_counts 0, 0.0001, actual_counts) psi np.sum((actual_counts - expected_counts) * np.log(actual_counts / expected_counts)) return psiPSI 的判断标准小于 0.1 说明分布稳定0.1~0.25 说明有轻微变化需要关注大于 0.25 说明分布显著变化模型需要重新训练。我一般对每个入模特征都算 PSI如果超过 3 个特征的 PSI 大于 0.25直接触发模型重训流程。KS 趋势看的是模型区分能力随时间的变化。把每周的 KS 画成折线图如果连续 4 周 KS 下降超过 20%说明模型在衰减。这时候不要急着重训先排查是不是数据管道出了问题——比如某个特征的数据源延迟了导致特征值全是默认值。最后说一个我踩过的坑有一次模型 KS 突然从 0.35 掉到 0.12排查了一整天最后发现是上游数据表的一个字段类型从 int 变成了 stringWOE 编码时全部映射到了「未知」箱。所以监控报表里一定要加一个「特征缺失率」和「特征值域」的检查别只盯着模型指标。希望帮到你。本文还有配套的精品资源点击获取
返回列表