尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Python实战:从零构建金融风控预测模型,解决数据不平衡与特征工程难题

Python实战:从零构建金融风控预测模型,解决数据不平衡与特征工程难题 简介机器学习中的分类问题是预测建模的核心任务之一旨在根据输入特征将样本划分到预定义的类别中。其基本原理是通过算法学习历史数据中的模式构建一个从特征到类别标签的映射函数。在金融科技领域分类模型的技术价值尤为突出它能够自动化、精准化地处理海量数据识别复杂非线性关系从而支持关键业务决策。典型的应用场景包括信用评分、欺诈检测和客户流失预警等。本文聚焦于金融信贷中的个贷违约预测这是一个典型的二分类问题但面临数据高度不平衡的挑战。文章深入探讨了如何使用SMOTE等过采样技术和调整类别权重来应对这一难题并详细介绍了基于业务理解的特征工程方法如构建负债收入比等衍生特征以提升模型对风险客户的识别能力。1. 项目缘起从业务痛点到一个Python预测模型的诞生在金融信贷业务里最让风控和业务部门头疼的事情之一就是“坏账”。你放出去的贷款总有一部分人因为各种原因还不上钱这笔钱就成了银行的损失。传统上银行依赖专家经验、规则引擎和简单的统计模型来做审批和预警但面对海量的客户数据和复杂的还款行为这些方法越来越力不从心。我最近用业余时间基于公开可获取的思路和一份模拟数据集用Python完整地走了一遍“个贷违约预测”的建模流程。这不仅仅是一个技术Demo它更像是一次对信贷风控核心逻辑的深度还原和实操演练。通过这个项目你能清晰地看到如何从一堆杂乱的客户信息数据开始一步步清洗、分析、构建特征最终训练出一个能相对准确判断客户“好坏”的机器学习模型。整个过程涉及数据科学、机器学习以及金融风控的交叉知识对于想进入金融科技领域的数据从业者或者对信贷业务逻辑感兴趣的技术人员来说是一次绝佳的动手机会。2. 理解风控预测的核心什么是个贷违约预测在深入代码之前我们必须先搞清楚我们要解决的究竟是什么问题。这决定了我们后续所有技术选型和评估标准。2.1 预测的本质一个二分类问题个贷违约预测本质上是一个二分类Binary Classification问题。我们的目标是为每一个贷款申请客户或在贷客户打上一个标签“好客户”会正常还款或“坏客户”会违约。在建模时我们通常将“违约”定义为正例Positive Class 标签为1将“正常”定义为负例Negative Class 标签为0。这样模型输出的就是一个介于0到1之间的概率值表示该客户违约的可能性有多大。注意定义谁是正例很关键它直接影响模型评估指标的选择。在风控中我们更关心能否准确地抓住“坏人”违约者所以通常将“违约”设为正例。2.2 业务目标与模型目标的衔接模型预测概率本身不是终点它必须服务于业务决策。常见的业务应用场景包括贷前审批在放款前根据模型预测的违约概率结合其他规则决定是否批准贷款、以及批准多少额度、什么利率。这时模型需要一个阈值Threshold。例如设定阈值为0.3那么预测概率高于0.3的申请将被拒绝或进入人工复核。贷中监控对于已放款的客户定期如每月用其最新的行为数据如还款记录、消费行为通过模型重新计算违约概率对概率升高的客户提前预警采取催收或风险化解措施。客户分层根据风险评分将客户分为不同等级如A、B、C、D级用于差异化营销或风险定价。因此我们构建的模型最终要能输出一个稳定、可解释的风险评分并能通过调整阈值来平衡业务中的“误杀”拒绝了好客户损失利息收入和“漏杀”通过了坏客户造成本金损失。2.3 项目数据集的构成与挑战一个典型的个贷违约预测数据集通常会包含以下几类信息这些也是我们后续特征工程的主要来源客户基本信息年龄、职业、教育程度、婚姻状况、居住城市等。这些是静态的、相对稳定的信息。财务状况收入水平、负债收入比、资产情况、历史信用记录长度等。贷款信息本次申请的贷款金额、期限、利率、贷款用途、担保方式等。历史信用表现如果有过去信用卡的逾期次数、逾期天数、其他贷款的还款记录等。这是最具预测力的信息但往往难以获取。外部数据如征信分、多头借贷信息、消费行为数据等。本项目使用的模拟数据集虽然并非真实银行数据但会尽可能模拟上述字段的结构。我们面临的典型挑战包括数据不平衡违约客户正例的数量通常远少于正常客户负例比例可能达到1:99甚至更悬殊。直接在这种数据上训练模型会倾向于把所有样本都预测为“正常”导致对违约客户完全失效。特征缺失客户信息填写不完整存在大量空值。特征异常收入为0或极高年龄超过合理范围等。数据泄露不小心使用了未来才能知道的信息如“是否最终违约”衍生出的指标作为特征导致模型在训练时表现虚假的优秀上线后完全失效。3. 环境搭建与核心工具链选型工欲善其事必先利其器。一个高效、稳定且易于复现的分析环境是项目成功的基础。下面是我选择的工具链及理由。3.1 Python环境管理Anaconda的绝对优势我强烈推荐使用Anaconda来管理Python环境尤其是数据科学项目。理由如下隔离性可以为这个项目创建一个独立的环境例如命名为loan_risk里面只安装项目所需的包避免与系统或其他项目的包版本冲突。包管理简便通过conda install命令可以轻松安装大多数科学计算包如numpy, pandas, scikit-learn它会自动处理复杂的C库依赖这是pip有时做不到的。集成性自带Jupyter Notebook/Lab非常适合进行探索性数据分析EDA和交互式建模。创建环境的命令如下# 创建一个名为loan_risk的Python3.9环境 conda create -n loan_risk python3.9 # 激活环境 conda activate loan_risk3.2 核心库介绍与安装在激活的loan_risk环境中我们需要安装以下核心库# 使用conda安装基础科学计算包 conda install numpy pandas scipy matplotlib seaborn jupyter # 使用pip安装机器学习相关库conda通道可能版本较旧 pip install scikit-learn xgboost lightgbm catboost imbalanced-learn # 用于模型解释 pip install shap # 用于生成分析报告可选但推荐 pip install pandas-profiling ydata-profilingpandas numpy数据操作的基石。pandas的DataFrame是处理表格数据的标准容器numpy提供高效的数值计算。scikit-learn机器学习的“瑞士军刀”。提供了从数据预处理标准化、编码、特征选择、到模型训练逻辑回归、随机森林、评估交叉验证、ROC曲线的全套工具。它是本项目的主力。XGBoost/LightGBM/CatBoost三大梯度提升树GBDT框架。它们在结构化数据的表格分类任务上尤其是金融风控领域表现通常优于传统的随机森林或逻辑回归是当前竞赛和工业界的首选。我会在后续对比它们的使用。imbalanced-learn专门处理不平衡数据集的库。提供了过采样如SMOTE、欠采样、以及结合采样等多种策略是解决风控数据不平衡问题的利器。shap模型解释领域的王牌工具。可以将复杂的树模型预测结果分解到每个特征的贡献度让我们知道模型是“凭啥”做出判断的这对于通过风控模型评审至关重要。matplotlib seaborn数据可视化。Seaborn基于matplotlib提供了更美观、更简单的统计图形接口。3.3 项目目录结构规划一个清晰的项目结构能极大提升协作效率和代码可维护性。建议按如下方式组织loan_default_prediction/ ├── data/ # 存放所有数据 │ ├── raw/ # 原始数据只读 │ ├── processed/ # 清洗处理后的数据 │ └── external/ # 外部数据如有 ├── notebooks/ # Jupyter Notebook用于EDA和实验 │ ├── 01_eda.ipynb │ ├── 02_feature_engineering.ipynb │ └── 03_model_experiment.ipynb ├── src/ # 源代码模块化 │ ├── __init__.py │ ├── data_preprocessing.py │ ├── feature_engineering.py │ ├── modeling.py │ └── utils.py ├── models/ # 保存训练好的模型文件.pkl或.joblib ├── reports/ # 生成的分析报告、图表 ├── config.yaml # 配置文件数据库连接、路径、参数等 ├── requirements.txt # 项目依赖包列表 └── README.md # 项目说明文档这种结构将数据、探索性代码、生产代码、产出物清晰分离。src下的模块化代码最终可以被一个主脚本如train.py调用实现从数据到模型的一键化训练。4. 数据探索性分析用眼睛“看见”风险在动任何模型之前我们必须深入了解数据。探索性数据分析EDA的目标是发现数据的特点、问题以及潜在的规律。这个过程我通常在Jupyter Notebook中完成。4.1 数据加载与初窥首先使用pandas加载数据集假设为CSV格式文件loan_data.csv。import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns sns.set_style(whitegrid) # 设置显示所有列 pd.set_option(display.max_columns, None) # 加载数据 df pd.read_csv(./data/raw/loan_data.csv) print(f数据集形状: {df.shape}) print(df.head()) print(df.info()) print(df.describe())df.info()会告诉我们每列的非空值数量、数据类型是发现缺失值的第一步。df.describe()则展示数值型特征的统计摘要均值、标准差、分位数等能快速发现异常值比如年龄为200岁。4.2 目标变量分布直面不平衡现实接下来首要任务是查看目标变量假设列名为is_default的分布。target_dist df[is_default].value_counts(normalizeTrue) print(f目标变量分布:\n{target_dist}) plt.figure(figsize(6,4)) sns.countplot(xis_default, datadf) plt.title(Distribution of Loan Default (0No, 1Yes)) plt.show()如果违约比例is_default1远低于10%我们就确认了这是一个严重不平衡数据集。这是风控数据的常态也决定了我们不能用准确率Accuracy作为核心评估指标而应关注精确率Precision、召回率Recall和F1-Score尤其是召回率我们抓住了多少比例的违约客户。4.3 单变量与多变量分析单变量分析查看每个特征的分布。对于连续型特征如收入、贷款金额绘制直方图Histogram或核密度估计图KDE查看其分布形态是否偏态、是否有异常值。fig, axes plt.subplots(2, 3, figsize(15, 8)) num_cols [income, loan_amount, debt_to_income_ratio, credit_history_length, age, loan_term] for idx, col in enumerate(num_cols): ax axes[idx//3, idx%3] sns.histplot(df[col], kdeTrue, axax) ax.set_title(fDistribution of {col}) plt.tight_layout() plt.show()对于类别型特征如职业、教育程度绘制柱状图查看各类别的样本数量。同时可以计算每个类别下违约率的差异初步判断该特征是否与违约相关。# 分析‘education’与违约的关系 edu_default_rate df.groupby(education)[is_default].mean().sort_values(ascendingFalse) print(edu_default_rate) edu_default_rate.plot(kindbar) plt.title(Default Rate by Education Level) plt.ylabel(Default Rate) plt.show()多变量分析相关性分析计算数值特征之间的皮尔逊相关系数并绘制热力图。这有助于发现高度相关的特征后续可以考虑去除或合并以降低模型复杂度、避免多重共线性。plt.figure(figsize(12, 8)) # 选择数值型列 numeric_df df.select_dtypes(include[np.number]) correlation_matrix numeric_df.corr() sns.heatmap(correlation_matrix, annotTrue, fmt.2f, cmapcoolwarm, center0) plt.title(Correlation Matrix of Numerical Features) plt.show()特征与目标的关系对于重要的连续特征可以绘制箱线图Boxplot或小提琴图Violin Plot按违约与否分组观察其特征分布的差异。plt.figure(figsize(10, 6)) sns.boxplot(xis_default, ydebt_to_income_ratio, datadf) plt.title(Debt-to-Income Ratio vs. Default Status) plt.show()如果违约组和非违约组在某个特征上的分布有显著差异那么这个特征很可能是一个强预测因子。4.4 发现数据问题并记录通过EDA我们可能会发现缺失值某些特征如annual_income存在大量空值。异常值age列中出现0或150等不合理数值income列中有个别极大值可能是录入错误或特殊富豪客户。数据不一致employment_length列中有的写“5 years”有的写“5”需要统一。类别不平衡某些类别特征如job_type的某个类别样本极少。潜在泄露特征是否存在如days_past_due已逾期天数这类在贷前根本不可能知道的信息。将这些问题一一记录它们是下一步数据预处理和特征工程的输入。5. 数据预处理与特征工程将原始数据转化为模型“语言”原始数据就像未经加工的矿石特征工程就是冶炼和提纯的过程目的是提取出对预测目标最有用的信息。这一步直接决定了模型性能的上限。5.1 缺失值处理策略缺失值不能直接丢给模型必须处理。策略因特征类型和缺失原因而异连续型特征均值/中位数/众数填充最常用。如果数据分布对称用均值如果存在偏态或异常值用中位数更稳健。对于风控数据我通常优先使用中位数填充因为它对异常值不敏感。from sklearn.impute import SimpleImputer # 对收入用中位数填充 median_imputer SimpleImputer(strategymedian) df[income] median_imputer.fit_transform(df[[income]])基于模型的填充用其他特征预测缺失值。更复杂但可能更准确。例如用年龄、职业、教育程度来预测收入。可以使用KNN或随机森林回归。增加缺失指示器创建一个新的布尔特征标记该位置是否缺失。有时“缺失”本身也是一种信息例如拒绝填写收入可能意味着收入不稳定。类别型特征单独作为一个类别如‘Unknown’填充。用众数出现最频繁的类别填充。5.2 异常值检测与处理异常值可能是错误也可能是特殊但真实的个案如超高净值客户。粗暴删除可能损失信息。检测方法标准差法假设数据服从正态分布将超出均值±3倍标准差的值视为异常。但金融数据常不服从正态分布。分位数法IQR更稳健。计算第一四分位数Q1和第三四分位数Q3定义异常值为小于Q1 - 1.5*IQR或大于Q3 1.5*IQR的值。Q1 df[loan_amount].quantile(0.25) Q3 df[loan_amount].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR outliers df[(df[loan_amount] lower_bound) | (df[loan_amount] upper_bound)]处理方法删除仅当确信是数据录入错误且占比极小时使用。缩尾Winsorization将超出指定分位数如1%和99%的值用该分位数的值替换。这是风控中处理极端值的常用方法能保留样本但削弱极端值的影响。lower_limit df[income].quantile(0.01) upper_limit df[income].quantile(0.99) df[income] np.where(df[income] lower_limit, lower_limit, df[income]) df[income] np.where(df[income] upper_limit, upper_limit, df[income])视为缺失值并用中位数等填充。分箱Binning将连续变量离散化异常值会被归入最高或最低的箱中。5.3 类别型特征编码机器学习模型只能处理数值。需要将文本类别转化为数字。标签编码Label Encoding为每个类别分配一个整数如[‘高中’ ‘本科’ ‘硕士’]-[0, 1, 2]。适用于有序类别如学历等级。对于无序类别模型可能会错误地认为数字大小有意义。独热编码One-Hot Encoding为每个类别创建一个新的二进制特征0或1。这是处理无序类别的标准方法。但若类别很多如“职业”有上百种会产生大量稀疏特征增加计算负担和过拟合风险。from sklearn.preprocessing import OneHotEncoder encoder OneHotEncoder(sparse_outputFalse, dropfirst) # dropfirst避免多重共线性 encoded_features encoder.fit_transform(df[[education, job_type]]) # 将编码后的特征合并回原DataFrame需处理列名目标编码Target Encoding / Mean Encoding用该类别下目标变量的均值对于回归或违约率对于分类来替换类别。例如“职业学生”的违约率是5%就用0.05替换所有“学生”。这种方法能捕捉类别与目标的关系但容易引起过拟合需要配合交叉验证进行平滑处理。category_encoders库提供了稳健的实现。5.4 创造新特征领域知识的体现这是特征工程中最有价值的部分需要结合对信贷业务的理解。衍生比率特征负债收入比DTI 总月负债 / 月收入。这是风控的核心指标。贷款收入比LTI 贷款金额 / 年收入。月供收入比 月还款额 / 月收入。分箱与离散化将连续年龄分为[18-25, 26-35, 36-50, 51-65, 65]。将收入分为[低 中 高]。分箱后可以作为类别特征处理或者进行目标编码能更好地捕捉非线性关系并且对异常值更鲁棒。交互特征年龄 * 收入或许年轻高收入者和年轻低收入者的风险模式不同。贷款期限 * 贷款利率反映总利息负担。时间维度特征如果有时间序列数据历史逾期次数、最大逾期天数、最近一次逾期距今月份数等。基于过去6/12个月的消费、还款行为计算出的趋势指标如月均消费增长率。实操心得特征工程不是一蹴而就的。我通常先构建一个基础特征集原始特征少量关键衍生特征训练一个基线模型。然后通过特征重要性分析如树模型提供的feature_importances_或SHAP值找出重要特征再围绕它们进行更精细的加工和创造。这是一个迭代的过程。6. 应对不平衡数据让模型“看见”少数派在不平衡数据上直接训练模型会严重偏向多数类。我们必须采取策略让模型“公平”地学习。6.1 评估指标的选择放弃准确率首先要确立正确的评估标准。假设我们有10000个样本其中9900个正常100个违约。一个将所有样本都预测为“正常”的傻瓜模型准确率高达99%。但这毫无用处。我们更关心在所有的违约客户中我们抓住了多少召回率 Recall/Sensitivity以及在我们预测为违约的客户中有多少真的是违约精确率 PrecisionF1-Score是精确率和召回率的调和平均数是一个不错的综合指标。ROC-AUC接收者操作特征曲线下面积。它衡量的是模型将正例违约排在负例正常前面的能力。AUC越接近1越好0.5相当于随机猜测。AUC对类别不平衡相对不敏感是风控模型常用的核心指标。PR-AUC精确率-召回率曲线下面积。在正例非常少的不平衡场景下PR-AUC有时比ROC-AUC更能反映模型在正例上的表现。在sklearn中我们可以方便地计算这些指标from sklearn.metrics import classification_report, roc_auc_score, average_precision_score # y_true 真实标签 y_pred 预测标签 y_pred_proba 预测概率 print(classification_report(y_true, y_pred)) print(fROC-AUC: {roc_auc_score(y_true, y_pred_proba)}) print(fPR-AUC (Average Precision): {average_precision_score(y_true, y_pred_proba)})6.2 采样技术改变数据分布过采样Oversampling增加少数类样本。随机过采样简单复制少数类样本。容易导致过拟合。SMOTESynthetic Minority Oversampling Technique在少数类样本之间进行插值生成“合成”新样本。这是目前最流行的方法之一。from imblearn.over_sampling import SMOTE smote SMOTE(random_state42) X_resampled, y_resampled smote.fit_resample(X_train, y_train)ADASYN在SMOTE基础上根据样本密度自适应地生成不同数量的新样本关注那些更难学习的少数类样本。欠采样Undersampling减少多数类样本。随机欠采样随机丢弃多数类样本。会损失大量信息。Tomek Links移除多数类中与少数类样本非常接近的样本“边界”样本使类别边界更清晰。Cluster Centroids使用K-Means对多数类进行聚类然后用聚类中心代替整个簇实现有代表性的欠采样。结合采样如SMOTEENN先SMOTE过采样再用Edited Nearest Neighbours清理噪声样本。注意事项采样必须在训练集上进行绝对不能在整个数据集上做也绝对不能影响测试集标准的流程是先划分训练集和测试集然后在训练集内部进行交叉验证和采样。测试集必须保持原始分布以评估模型在真实不平衡世界中的表现。6.3 算法层面的调整改变模型目标许多算法本身提供了处理不平衡的选项。调整类别权重Class Weight在训练时给少数类的样本更高的权重让模型更关注它们。Scikit-learn的许多模型如LogisticRegression,RandomForestClassifier,SVC都支持class_weight参数。可以设置为‘balanced’让算法自动按类别频率的倒数调整权重。from sklearn.ensemble import RandomForestClassifier model RandomForestClassifier(class_weightbalanced, random_state42)使用对不平衡不敏感的算法梯度提升树如LightGBM, XGBoost本身对不平衡数据有一定鲁棒性并且它们也提供了scale_pos_weightXGBoost或is_unbalanceLightGBM等参数来进一步调整。我的常用策略我会先尝试在不采样的情况下使用带有class_weightbalanced的逻辑回归或随机森林作为基线。然后尝试SMOTE过采样结合LightGBM/XGBoost。通过交叉验证对比ROC-AUC和PR-AUC选择效果最好的组合。通常树模型配合适当的采样或权重调整效果最佳。7. 模型构建、训练与评估寻找最优风险识别器现在我们有了干净、平衡或加权的训练数据可以开始构建和选择模型了。7.1 数据划分与交叉验证首先将特征X和目标y分离并划分训练集和测试集。测试集在整个训练过程中绝对不能触碰它用于最终评估模型的泛化能力。from sklearn.model_selection import train_test_split # 假设df是经过预处理和特征工程后的DataFrame X df.drop(columns[is_default, loan_id]) # 去掉目标列和ID列 y df[is_default] # 划分训练集和测试集通常70%-80%用于训练 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, stratifyy, random_state42)stratifyy参数非常重要它保证在划分后训练集和测试集中正负样本的比例与原始数据集一致。为了在训练集上可靠地评估模型性能并调参我们使用K折交叉验证K-Fold Cross-Validation。特别是对于不平衡数据使用分层K折交叉验证Stratified K-Fold更好它能确保每一折中类别比例相同。from sklearn.model_selection import StratifiedKFold, cross_val_score cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) # 以ROC-AUC为评估指标进行交叉验证 model RandomForestClassifier(class_weightbalanced, random_state42) cv_scores cross_val_score(model, X_train, y_train, cvcv, scoringroc_auc) print(f交叉验证ROC-AUC得分: {cv_scores.mean():.4f} (/- {cv_scores.std()*2:.4f}))7.2 模型选型与对比我们将对比几种在风控领域常用的模型逻辑回归Logistic Regression线性模型可解释性强是风控模型的传统主力。它可以输出概率并且可以通过系数大小判断特征影响的方向和程度。作为优秀的基线模型。随机森林Random Forest集成树模型能捕捉非线性关系对异常值和缺失值不敏感能输出特征重要性。抗过拟合能力较强。梯度提升树Gradient Boosting TreesXGBoost速度、精度和功能性的标杆提供了丰富的正则化选项防止过拟合。LightGBM微软出品采用基于直方图的算法和Leaf-wise生长策略训练速度更快内存消耗更小在大数据集上表现优异是当前工业界风控建模的“标配”之一。CatBoost擅长处理类别特征无需单独编码且对过拟合有很好的抵抗。我会用交叉验证的ROC-AUC来初步比较这些模型。通常LightGBM和XGBoost会表现最好。7.3 超参数调优让模型发挥最佳性能模型有很多“旋钮”超参数如树的深度、学习率等需要调整以获得最佳性能。手动调参费时费力我们使用自动化工具。网格搜索GridSearchCV指定一组超参数值穷举所有组合。适用于参数较少的情况。随机搜索RandomizedSearchCV在指定的参数分布中随机采样一定数量的组合。效率更高尤其当参数空间很大时。贝叶斯优化Bayesian Optimization使用如scikit-optimize或optuna库根据历史评估结果智能地选择下一组要尝试的参数通常能以更少的尝试找到更优解。以下是一个使用GridSearchCV对LightGBM进行调优的示例from lightgbm import LGBMClassifier from sklearn.model_selection import GridSearchCV # 定义模型 lgb LGBMClassifier(random_state42, verbosity-1, n_jobs-1) # n_jobs-1使用所有CPU核心 # 定义要搜索的参数网格 param_grid { n_estimators: [100, 200, 300], max_depth: [3, 5, 7, -1], # -1表示不限深度 learning_rate: [0.01, 0.05, 0.1], num_leaves: [31, 63, 127], # 叶子节点数与max_depth相关 subsample: [0.8, 1.0], # 样本采样比例 colsample_bytree: [0.8, 1.0], # 特征采样比例 } # 使用分层K折交叉验证进行网格搜索 grid_search GridSearchCV( estimatorlgb, param_gridparam_grid, cvStratifiedKFold(n_splits3, shuffleTrue, random_state42), # 调参时可以用少一些的折数加快速度 scoringroc_auc, n_jobs-1, verbose1 ) grid_search.fit(X_train, y_train) print(f最佳参数: {grid_search.best_params_}) print(f最佳交叉验证ROC-AUC: {grid_search.best_score_:.4f}) # 获取最佳模型 best_lgb_model grid_search.best_estimator_7.4 模型评估与阈值选择用最佳模型在测试集上进行最终评估。记住测试集是模型从未见过的数据。from sklearn.metrics import roc_curve, precision_recall_curve, confusion_matrix # 预测概率 y_pred_proba_test best_lgb_model.predict_proba(X_test)[:, 1] # 计算测试集ROC-AUC test_auc roc_auc_score(y_test, y_pred_proba_test) print(f测试集 ROC-AUC: {test_auc:.4f}) # 绘制ROC曲线 fpr, tpr, thresholds_roc roc_curve(y_test, y_pred_proba_test) plt.figure() plt.plot(fpr, tpr, labelfLightGBM (AUC {test_auc:.3f})) plt.plot([0, 1], [0, 1], k--) # 对角线 plt.xlabel(False Positive Rate) plt.ylabel(True Positive Rate) plt.title(ROC Curve on Test Set) plt.legend() plt.show()模型输出的是违约概率。我们需要一个阈值来将其转化为“违约”或“不违约”的决策。阈值的选择是一个业务决策取决于我们更看重召回率抓住更多坏人还是精确率减少误杀好人。如果业务目标是严格控制坏账率宁愿错杀好客户也不放过坏客户可以选择一个较低的阈值如0.3提高召回率。如果业务目标是扩大业务规模容忍一定坏账但希望尽可能减少对好客户的误拒可以选择一个较高的阈值如0.7提高精确率。我们可以通过分析不同阈值下的精确率-召回率曲线来辅助决策precisions, recalls, thresholds_pr precision_recall_curve(y_test, y_pred_proba_test) # 找到使F1-Score最大的阈值 f1_scores 2 * (precisions * recalls) / (precisions recalls) optimal_idx np.argmax(f1_scores) optimal_threshold thresholds_pr[optimal_idx] print(f基于最大F1-Score的最佳阈值: {optimal_threshold:.3f}) print(f对应精确率: {precisions[optimal_idx]:.3f}, 召回率: {recalls[optimal_idx]:.3f}) # 使用选定的阈值做最终预测 final_threshold 0.4 # 根据业务需求调整 y_pred_final (y_pred_proba_test final_threshold).astype(int) # 输出分类报告和混淆矩阵 print(classification_report(y_test, y_pred_final)) cm confusion_matrix(y_test, y_pred_final) sns.heatmap(cm, annotTrue, fmtd, cmapBlues) plt.xlabel(Predicted) plt.ylabel(Actual) plt.title(Confusion Matrix) plt.show()混淆矩阵能直观地展示真正例TP实际违约且被预测为违约抓对了。假正例FP实际正常但被预测为违约误杀。真负例TN实际正常且被预测为正常放对了。假负例FN实际违约但被预测为正常漏杀。业务人员可以根据混淆矩阵和成本误杀损失利息漏杀损失本金来量化模型带来的价值。8. 模型解释与部署准备从黑盒到白盒在金融风控领域模型的可解释性至关重要。监管要求、内部风控评审都需要知道模型为什么做出某个决策。8.1 全局解释哪些特征最重要树模型如LightGBM自带特征重要性属性可以告诉我们哪些特征对模型预测的贡献最大。importances best_lgb_model.feature_importances_ feature_names X_train.columns # 创建DataFrame并按重要性排序 feat_imp_df pd.DataFrame({feature: feature_names, importance: importances}) feat_imp_df feat_imp_df.sort_values(importance, ascendingFalse).reset_index(dropTrue) plt.figure(figsize(10, 8)) sns.barplot(ximportance, yfeature, datafeat_imp_df.head(20)) plt.title(Top 20 Feature Importances (LightGBM)) plt.tight_layout() plt.show()通常你会发现debt_to_income_ratio负债收入比、credit_history_length信用历史长度、annual_income年收入等特征排名靠前这符合业务直觉。8.2 局部解释为什么这个客户被拒绝对于单个客户的预测我们可以使用SHAP (SHapley Additive exPlanations)值进行解释。SHAP值基于博弈论公平地将模型预测值分配到每个特征上。import shap # 初始化JS可视化用于Notebook shap.initjs() # 创建一个解释器 explainer shap.TreeExplainer(best_lgb_model) # 计算测试集前100个样本的SHAP值计算全部可能较慢 shap_values explainer.shap_values(X_test.iloc[:100]) # 1. 单个样本的解释例如第一个被预测为高风险的用户 shap_index 0 # 选择你想解释的样本索引 shap.force_plot(explainer.expected_value[1], shap_values[1][shap_index], X_test.iloc[shap_index]) # 2. 汇总图看特征的整体影响方向 shap.summary_plot(shap_values[1], X_test.iloc[:100])力Force图展示单个预测中每个特征是如何将基础值所有样本的平均预测推向最终预测值的。红色特征推高预测概率增加风险蓝色特征推低预测概率降低风险。汇总图每个点代表一个样本。x轴是SHAP值对预测的影响大小颜色表示特征值的大小。可以看特征值与风险的关系例如debt_to_income_ratio越高SHAP值越大即风险越高。当模型拒绝一个客户的贷款申请时风控人员可以调出该客户的SHAP解释图清晰地告诉客户“您的申请被拒绝主要是因为您的负债收入比过高0.15且信用历史较短0.08。” 这大大增强了模型的透明度和可信度。8.3 模型持久化与部署准备训练好的模型需要保存下来以便在新的数据上做预测。import joblib # 或使用 pickle # 保存模型 model_filename ./models/lgbm_default_predictor_v1.pkl joblib.dump(best_lgb_model, model_filename) print(f模型已保存至 {model_filename}) # 加载模型 loaded_model joblib.load(model_filename) # 对新数据进行预测 # new_data 是经过完全相同预处理和特征工程后的DataFrame new_pred_proba loaded_model.predict_proba(new_data)[:, 1]在实际部署中你可能会封装成API服务使用Flask或FastAPI创建一个Web服务接收客户数据返回风险评分和决策建议。集成到风控系统将模型预测逻辑写成函数嵌入到银行现有的信贷审批流程中。定期监控与更新模型会随着时间“老化”因为客户行为和宏观经济在变化。需要定期如每季度用新数据评估模型性能必要时重新训练或更新。9. 项目总结与避坑指南走完整个流程你会发现一个完整的风控预测项目远不止“调个包跑个模型”那么简单。它融合了业务理解、数据处理、算法应用和工程实践。最后分享几点我在这个项目复现和实际工作中的深刻体会第一数据质量决定天花板特征工程决定你能摸多高。花在数据清洗和特征工程上的时间通常占整个项目的60%-70%。一个聪明的特征如“近三个月查询征信次数”可能比换一个更复杂的模型提升更大。务必警惕数据泄露确保所有特征在预测时都是可获得的。第二不平衡处理是风控建模的必修课。不要只看准确率。ROC-AUC和PR-AUC是你的好朋友。多尝试几种采样方法和算法内置的类别权重调整并在验证集上仔细评估其对召回率抓坏人能力的影响。第三模型解释性与性能同等重要。特别是在金融领域一个无法解释的“黑盒”模型即使AUC再高也很难通过内审和监管的挑战。LightGBM/XGBoost SHAP 是目前兼顾性能与可解释性的黄金组合。在特征设计时也要尽量使用业务上可理解的变量。第四交叉验证是防止过拟合的保险绳。永远不要用测试集来调参或做模型选择。坚持使用交叉验证来评估模型在训练集上的泛化能力。随机种子random_state要固定确保结果可复现。第五从实验到生产有很长的路要走。Notebook里的模型只是原型。要将其转化为生产系统你需要考虑特征工程的代码如何模块化、自动化模型如何版本管理预测服务如何保证低延迟、高并发预测结果如何与业务系统联动监控指标如何设计如模型稳定性PSI、预测分数分布变化这是一个更大的系统工程。这个基于Python的个贷违约预测项目就像一张精细的“风险地图”绘制指南。它从数据源头开始教你如何清理、加工信息如何选择合适的“绘图工具”模型如何校准“图例”阈值以适应不同的业务场景最后还教你如何向别人解释这张地图为什么这么画。希望这份详尽的指南能帮你不仅跑通代码更能理解背后每一步的“为什么”真正掌握用数据驱动信贷风险决策的核心能力。本文还有配套的精品资源点击获取
返回列表