资讯动态

Python逻辑回归实战:从零构建可解释的违约预测模型

发布时间:2026/9/23 12:00:55 来源:尧图企业网站定制
简介这份资源围绕Python实现逻辑回归预测违约可能展开面向希望入门机器学习分类任务的小白与进阶学习者也可直接用于毕设项目、课程设计、大作业或工程实训的初期立项。压缩包共3个文件包含1个py脚本、1个csv数据集和1个md说明文档整体约19KB体量轻巧便于快速下载与本地运行。其中csv文件提供建模所需的贷款样本数据py脚本给出逻辑回归从数据读取、特征处理到模型训练与预测的完整流程md文档则补充项目说明与使用指引。目前已有204人学习下载说明该案例在分类预测入门场景中具有一定参考价值。读者可借此掌握逻辑回归的基本原理与代码落地方式理解违约预测这类二分类问题的建模思路并在此基础上替换数据、调整特征或迁移到其他风控与信用评估场景形成可复用的项目模板。1. 违约预测这件事为什么逻辑回归依然是首选基线银行风控、消费金融、小贷平台、甚至电商平台的先用后付只要涉及这个人会不会还钱本质上都是一个二分类问题。基于 Python 实现逻辑回归预测违约可能说的就是拿一份带标签的历史借贷数据用逻辑回归训练出一个能输出违约概率的模型再把概率按阈值切成通过/拒绝或低风险/高风险。它不追求花哨追求的是可解释、可复现、上线快。我见过太多团队一上来就上 XGBoost 或者 LSTM结果风控评审会上被问为什么拒绝这个客户谁也答不上来。逻辑回归的系数直接对应特征方向odds ratio 一算就能给业务方解释清楚这是它在金融场景里活了这么多年还没被淘汰的原因。这篇文章面向两类人一类是刚学完 Python 语法、想找一个真实数据集练手的入门者另一类是手上有结构化数据、需要一个能快速上线且经得起审计的基线模型的从业者。整篇会从数据准备、特征工程、模型训练、阈值选择一路讲到避坑和进阶代码全部可跑。2. 数据准备与特征工程把原始借贷表变成模型能吃的矩阵2.1 违约预测的数据长什么样常见的违约数据集一般是一张宽表一行一个借款人字段大致分几类身份类年龄、职业、婚姻状况、额度类授信额度、已用额度、行为类近 6 个月逾期次数、查询次数、标签类是否违约0/1。公开数据集里比较典型的是 UCI 的 Default of Credit Card Clients3 万行、23 个特征标签是下个月是否违约非常适合拿来跑通整条链路。拿到数据第一件事不是急着 fit而是先确认三件事标签分布是否极度不平衡、有没有时间穿越特征、缺失值是什么形态。违约场景里正样本通常只占 5% 到 20%如果直接训练模型会倾向于全预测为 0准确率看着很高但毫无用处。时间穿越是更隐蔽的坑比如最后一次还款日期这种字段如果它是在违约发生之后才更新的那它就是在泄露答案。import pandas as pd import numpy as np # 读取数据假设是 UCI 信用卡违约数据集 df pd.read_excel(default_of_credit_card_clients.xls, header1) df df.rename(columns{default payment next month: label}) # 看标签分布判断不平衡程度 print(df[label].value_counts(normalizeTrue)) # 看缺失和类型 print(df.isnull().sum().sum()) print(df.dtypes.value_counts())这段代码做的是最基础的三件事统一标签列名、打印正负样本比例、确认缺失总量和字段类型。normalizeTrue让 value_counts 输出比例而不是计数方便一眼看出不平衡程度。如果正样本低于 10%后面训练时就要考虑class_weightbalanced。2.2 类别变量编码与数值变量分箱逻辑回归是线性模型它对类别变量没法直接处理必须编码。低基数类别比如性别、教育程度用 one-hot 就够了高基数类别比如职业代码有几十种用 one-hot 会让特征维度爆炸常见做法是目标编码或者按违约率分箱。数值变量方面年龄、额度这类连续值直接扔进去也行但金融场景里分箱往往效果更稳因为业务上本来就有年龄段额度档位的概念分箱后还能顺便处理异常值。from sklearn.preprocessing import StandardScaler # 低基数类别做 one-hot cat_cols [SEX, EDUCATION, MARRIAGE] df pd.get_dummies(df, columnscat_cols, drop_firstTrue) # 数值列标准化逻辑回归对尺度敏感 num_cols [c for c in df.columns if c not in [label] and df[c].dtype ! bool] scaler StandardScaler() df[num_cols] scaler.fit_transform(df[num_cols]) # 检查最终特征矩阵 print(df.shape) print(df.columns.tolist()[:10])drop_firstTrue是为了避免虚拟变量陷阱也就是 one-hot 之后各类别完全共线导致矩阵不可逆。标准化这一步很多人会跳过觉得逻辑回归无所谓但实际上如果不标准化梯度下降收敛会慢很多正则化项对不同尺度的特征惩罚也不公平。StandardScaler把每列变成均值 0、方差 1注意它是在训练集上 fit、在测试集上 transform不能拿全量数据 fit否则就是数据泄露。2.3 训练集测试集切分与不平衡处理切分不能随机切如果数据有时间字段要按时间切用过去预测未来才是真实上线场景。没有时间字段就随机切但要固定随机种子保证结果可复现。不平衡处理有三条路调class_weight、过采样、欠采样。我一般先用class_weightbalanced它不改变数据分布只是让损失函数里正样本权重更大最省事也最不容易翻车。from sklearn.model_selection import train_test_split X df.drop(columns[label]) y df[label] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) print(训练集正样本比例:, y_train.mean()) print(测试集正样本比例:, y_test.mean())stratifyy保证切分后训练集和测试集的正样本比例与原始数据一致避免小概率情况下测试集里正样本太少导致评估失真。random_state42是习惯用法换成别的数字也行但一旦定了就别改否则每次跑出来的指标都对不上排查问题时会被这个玄学坑到。3. 用 sklearn 训练逻辑回归参数、损失函数与评估指标3.1 逻辑回归到底在优化什么逻辑回归的假设是违约的对数几率log odds是特征的线性组合。写成公式就是 log(p/(1-p)) w·x b反解出 p 1/(1exp(-(w·xb)))也就是 sigmoid 函数。训练目标是最小化交叉熵损失加上正则化项防止过拟合。理解这一点很关键因为后面调参调的其实就是正则化强度和惩罚类型。sklearn 的LogisticRegression默认带 L2 正则C参数是正则化强度的倒数C 越小正则越强。penalty可以选 l1 或 l2l1 会让部分系数变成 0相当于自动做特征选择特征多的时候有用。solver决定优化算法l2 用 lbfgs 就行l1 要用 liblinear 或 saga。这些参数不是随便填的选错了要么跑不动要么结果不对。from sklearn.linear_model import LogisticRegression model LogisticRegression( penaltyl2, C1.0, class_weightbalanced, solverlbfgs, max_iter1000, random_state42 ) model.fit(X_train, y_train) # 看系数理解每个特征的方向和强度 coef pd.Series(model.coef_[0], indexX_train.columns) print(coef.sort_values(ascendingFalse).head(10))max_iter1000是因为加了 class_weight 之后收敛会慢默认的 100 经常不够会报 ConvergenceWarning。系数排序能直接告诉你哪些特征推高违约概率、哪些拉低这是逻辑回归相比树模型最大的可解释性优势。注意系数大小受标准化影响所以前面标准化那步不能省否则系数之间没法比。3.2 评估指标准确率是最没用的那个违约预测里准确率几乎可以忽略因为正样本少全预测为 0 也能有 80% 以上准确率。真正要看的是 AUC、KS、召回率和精确率。AUC 衡量的是模型把正样本排在负样本前面的能力0.5 是随机0.7 以上算可用0.75 以上在风控里算不错。KS 是风控行业常用指标等于正负样本累计分布差的最大值一般要求 0.3 以上。召回率决定你抓到了多少真正会违约的人精确率决定你误伤了多少好人这两个要按业务成本权衡。from sklearn.metrics import roc_auc_score, classification_report, confusion_matrix y_prob model.predict_proba(X_test)[:, 1] y_pred (y_prob 0.5).astype(int) print(AUC:, roc_auc_score(y_test, y_prob)) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred))predict_proba返回两列第二列才是正类概率别取错。阈值 0.5 只是默认值实际业务里几乎不会用 0.5因为误伤一个好人和一个漏掉一个坏人的成本完全不对等。confusion_matrix 能让你直观看到 FP 和 FN 各有多少classification_report 给出每类的精确率召回率 F1结合起来判断模型能不能上线。3.3 阈值选择把概率变成决策模型输出的是概率业务要的是决策。阈值定在哪里取决于你把哪类错误看得更重。如果漏掉一个违约客户的损失是误拒一个好客户的 5 倍那阈值就该往低调宁可多拒一些。做法是画 KS 曲线或者算不同阈值下的收益选业务收益最大的那个点。import numpy as np thresholds np.arange(0.1, 0.9, 0.05) for t in thresholds: pred (y_prob t).astype(int) tn, fp, fn, tp confusion_matrix(y_test, pred).ravel() recall tp / (tp fn) precision tp / (tp fp) if (tp fp) 0 else 0 print(f阈值 {t:.2f} 召回 {recall:.3f} 精确 {precision:.3f})这段循环把每个阈值下的召回和精确都打出来你就能看到权衡曲线。实际选的时候不是选召回最高的而是选业务上能接受的精确率下限对应的最大召回。比如业务要求误拒率不超过 10%那就在满足这个条件的阈值里选召回最高的。4. 避坑与排查违约预测里最容易翻车的五个地方4.1 现象AUC 0.9 但上线后效果崩了原因数据泄露。最常见的是用了违约后才知道的字段比如催收次数最后还款状态。训练时这些字段和标签高度相关模型学到的其实是答案。解决逐个字段问业务方这个值在决策时点能不能拿到拿不到的一律删掉。更稳妥的做法是按时间切分用 T 时刻之前的数据预测 T1 的违约模拟真实场景。4.2 现象模型系数方向反了收入越高违约概率越大原因多重共线性。收入和额度、已用额度之间高度相关逻辑回归在这种情况下系数会变得不稳定甚至符号反转。解决先算方差膨胀因子VIF超过 10 的特征考虑删掉或者合并。也可以用 L1 正则自动筛掉冗余特征或者用 PCA 降维但降维后解释性会下降风控场景慎用。4.3 现象训练集 AUC 0.85测试集 0.65原因过拟合。特征太多、样本太少或者 C 设得太大正则太弱。解决先把 C 从 1.0 降到 0.1 甚至 0.01 试试观察测试集指标是否回升。同时检查特征数量如果特征数接近样本数必须加正则。交叉验证比单次切分更能反映真实泛化能力用cross_val_score跑 5 折看 AUC 的均值和方差。4.4 现象ConvergenceWarning模型没收敛原因迭代次数不够、特征尺度差异大、或者 solver 和 penalty 不匹配。解决先把max_iter提到 2000如果还不行就检查标准化是否做了。l1 正则必须配 liblinear 或 saga用 lbfgs 会直接报错。saga 支持 l1 和 l2但收敛慢大数据集上要有耐心。4.5 现象正样本只有 3%模型全预测为 0原因类别极度不平衡损失函数被负样本主导。解决class_weightbalanced是第一选择它按类别频率自动调权重。如果还不行用 SMOTE 做过采样但要注意 SMOTE 只能在训练集上做测试集必须保持原始分布否则评估结果虚高。过采样后记得重新调阈值因为概率分布已经变了。5. 进阶技巧从基线模型到能上线的评分卡5.1 把概率转成标准评分风控行业习惯用评分而不是概率标准做法是 PDOPoints to Double the Odds转换。设定一个基准分和基准 odds以及每翻一倍 odds 需要多少分就能把逻辑回归的输出映射成 300 到 850 之间的整数分。这样业务方看到的是分数不是概率沟通成本低很多。import numpy as np # PDO 评分卡转换 base_score 600 base_odds 50 pdo 20 factor pdo / np.log(2) offset base_score - factor * np.log(base_odds) def prob_to_score(prob): odds prob / (1 - prob) return offset factor * np.log(odds) scores prob_to_score(y_prob) print(评分范围:, scores.min(), scores.max())base_score和base_odds是业务定的pdo20表示 odds 翻倍加 20 分。这个转换是单调的不改变模型的排序能力只是换了个刻度。上线时把 factor 和 offset 存下来线上直接算分不用每次重新训练。5.2 用 WOE 和 IV 做特征筛选WOEWeight of Evidence和 IVInformation Value是评分卡的标准工具。WOE 衡量每个分箱里正负样本的比例差异IV 是 WOE 的加权和用来判断单个特征的预测能力。IV 小于 0.02 基本没用0.1 到 0.3 算中等0.3 以上要警惕是不是数据泄露。用 IV 筛特征比看相关系数更贴合业务因为它是从违约区分度出发的。IV 范围预测能力处理建议 0.02几乎无删除0.02-0.1弱可选保留0.1-0.3中等保留0.3-0.5强保留但查泄露 0.5过强高度怀疑泄露5.3 监控与迭代模型上线不是终点。要监控两个东西特征分布漂移和模型指标衰减。特征漂移用 PSIPopulation Stability Index衡量PSI 小于 0.1 算稳定0.1 到 0.25 要警惕超过 0.25 就得重新训练。指标衰减就看每月的 AUC 和 KS如果连续两个月下降超过 5%基本可以确定数据分布变了。我一般会写一个定时脚本每月跑一次 PSI 和 AUC超过阈值就告警。def psi(expected, actual, bins10): breakpoints np.percentile(expected, np.linspace(0, 100, bins 1)) breakpoints[0] -np.inf breakpoints[-1] np.inf exp_pct np.histogram(expected, breakpoints)[0] / len(expected) act_pct np.histogram(actual, breakpoints)[0] / len(actual) exp_pct np.where(exp_pct 0, 1e-6, exp_pct) act_pct np.where(act_pct 0, 1e-6, act_pct) return np.sum((act_pct - exp_pct) * np.log(act_pct / exp_pct))这个 PSI 函数用训练集的分布做基准拿新数据来比。1e-6是防止 log(0) 报错。实际用的时候按特征逐个算哪个特征 PSI 高就重点查那个特征的上游数据源。逻辑回归做违约预测难点从来不在模型本身而在数据质量和业务理解。我自己的习惯是每接一个新数据集先花半天时间把每个字段的业务含义问清楚再动手写代码。这个习惯帮我避开了至少三次数据泄露事故。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价