网站seo优化方案,网站如何做视频链接地址,网络服务器与网站建设,室内设计师招聘简章本文引用自#xff1a;
金融风控#xff1a;信用评分卡建模流程 - 知乎 (zhihu.com)
在原文的基础上加上了一部分自己的理解#xff0c;转载在CSDN上作为保留记录。
本文涉及到的数据集可直接从天池上面下载#xff1a;
Give Me Some Credit给我一些荣誉_数据集-阿里云…本文引用自
金融风控信用评分卡建模流程 - 知乎 (zhihu.com)
在原文的基础上加上了一部分自己的理解转载在CSDN上作为保留记录。
本文涉及到的数据集可直接从天池上面下载
Give Me Some Credit给我一些荣誉_数据集-阿里云天池 (aliyun.com)
正文
信用评分卡是一种常用的金融风控手段其主要是通过建立一套计分规则然后根据客户的各项属性来匹配计分规则最终得到客户的风险评分。根据评分结果来选择是否进行授信或划分不同的授信额度和利率以降低金融交易过程中的损失风险。信用评分卡有一套完整的开发流程本文将试图从评分卡建立所涉及的背景知识评分卡建立的基本流程两个方面来从整体上理解信用评分卡的作用以及建模方法。
1. 信用评分卡
顾名思义评分卡是一张有分数刻度和相应阈值的表。对于一个客户可以根据他的一系列信息找到对应的分数最终进行汇总来量化这个客户将为本次的交易所带来的风险。由Fair Isaac公司开发的FICO系列评分卡是信用评分卡的始祖。如图1所示FICO通过客户的年龄住房以及收入情况来进行评分每一项指标都有一定的阈值范围落入不同的阈值范围就有相应的得分最终的得分是所有指标得分的总分。这种评分手段操作简单易于理解。评分卡按照不同的使用场景主要分为三类
1A卡Application Card即申请评分卡主要是用于贷前审批。在此阶段主要利用用户的外部征信数据、资产质量数据或过往平台表现复贷来衡量用户的信用情况。以初步筛选出信用良好的客户进行授信。
2B卡Behavior Card即行为评分卡主要是用于贷中阶段。即用户已经申请并获得了相应的贷款用于动态评估客户在未来某一阶段的逾期风险进而调整额度或利率等以减少损失
3C卡Collection Card即催收评分卡主要是用于贷后管理。即用户此时已经出现逾期情况需要制定合理的催收策略以尽可能减少逾期带来的损失。此时根据C卡评分来优化贷后管理策略实现催收资源的合理配置 图1 FICO评分卡
类似FICO这种静态的评分卡最大的优点在于操作简单可解释性强。但随着时间的推移目标客群会不断发生变化这种静态的方式难以满足需求。于是基于机器学习模型的信用评分卡展现出了更大的潜力。其能够根据数据的变化去动态调整不同特征的权重从而不断迭代以适应新的数据模式。在风控领域为了提高信用评分卡的可解释性通常采用逻辑回归模型来进行评分卡的建模。在下一节中将使用Kaggle上的Give me credit card数据来从0到1建立一个信用评分卡从中梳理出评分卡的常规建模流程。在建模期间也会穿插介绍其中涉及的一些重要概念理解这些概念背后的原理才能明白做这一步的含义是什么。
2. 信用评分卡建模流程
2.1 探索性数据分析EDA
EDA主要是利用各种统计分析的手段来从整体上了解数据的情况包括数据的构成数据的质量数据的含义等。以便后续针对特定的问题制定合理的数据预处理方案完成特征工程的工作
1. 特征释义
本次采用的数据集包括12个特征其中有效特征为11个一个Uname0特征。各个特征的具体含义如下。
SeriousDlqin2yrs超过90天或更糟的逾期拖欠是用户的标签01分别表示未逾期和逾期RevolvingUtilizationOfUnsecuredLines除了房贷车贷之外的信用卡账面金额即贷款金额/信用卡总额度age贷款人年龄NumberOfTime30-59DaysPastDueNotWorse借款人逾期30-59天的次数DebtRatio负债比率每月债务、赡养费、生活费/每月总收入MonthlyIncome月收入NumberOfOpenCreditLinesAndLoans开放式信贷和贷款数量开放式贷款分期付款如汽车贷款或抵押贷款和信贷如信用卡的数量NumberOfTimes90DaysLate借款者有90天或更高逾期的次数NumberRealEstateLoansOrLines包括房屋净值信贷额度在内的抵押贷款和房地产贷款数量NumberOfTime60-89DaysPastDueNotWorse借款人逾期60-89天的次数NumberOfDependents不包括本人在内的家属数量
2. 数据基本情况
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
df_train pd.read_csv(./data/cs-training.csv)
df_test pd.read_csv(./data/cs-test.csv)
df_all pd.concat([df_train,df_test])
print(train size:,len(df_train))
print(test size:,len(df_test))
print(df_all.info()) 数据集总共包含251503条数据其中训练集有150000条数据测试集有101503条数据。MonthlyIncome 和 NumberOfDependents有缺失值由于缺失并不严重因此予以保留后续进行插补。
3. 异常值检测
这里采用3西格玛原则从各个特征中检测异常值并进行统计以了解数据中异常值的情况
#异常检测
def review_outlier(x,lower,upper):根据3-西格玛方法检测出特征中的异常值if x lower or x upper:return Truereturn False
df_outlier_train df_train.copy()
k 3.0for fea in df_outlier_train.columns:lower df_outlier_train[fea].mean() - k*df_outlier_train[fea].std()upper df_outlier_train[fea].mean() k*df_outlier_train[fea].std()outlier df_outlier_train[fea]\.apply(review_outlier,args(lower,upper))print({} 异常值数量{}.format(fea,outlier.sum())) NumberOfOpenCreditLinesAndLoans以及NumberRealEstateLoansOrLines的异常值数量较大。从数据的角度而言这两个特征可以考虑剔除或删除有异常的数据但从业务角度而言这两个特征与逾期风险可能存在较大的关联所以最终还是保留这两个特征。
4. 描述性统计
sns.countplot(xSeriousDlqin2yrs,datadf_train) 从标签的分布来看样本中的好客户占比明显高于坏客户意味着后续需要进行数据的平衡避免模型过度倾向于将样本预测为好客户。
#对数据中的好坏客户的年龄分布进行统计
bad df_train[df_train[SeriousDlqin2yrs]1].index
good df_train[df_train[SeriousDlqin2yrs]0].index
fig,ax plt.subplots(2,1,figsize[6,6])
fig.subplots_adjust(hspace0.5)
sns.distplot(df_train.loc[bad,age],axax[0])
ax[0].set_title(bad customer)
sns.distplot(df_train.loc[good,age],axax[1])
ax[1].set_title(good customer)
print(坏客户年龄统计\n,df_train.loc[bad,age].describe())
print(好客户年龄统计\n,df_train.loc[good,age].describe()) 从频率分布图的形状来看好坏客户的年龄分布整体上都符合正态分布符合统计学的概念。其中坏客户和好客户的年龄均值分别为45.9和52.8坏客户的年龄略低于好客户的年龄。好客户中年龄最小值为0岁年龄最大值为109岁这不太符合常规的借贷业务客群特征考虑可能是异常值后续需要进行处理。
5. 变量显著性检验
ps: 这里涉及到变量之间检验方法 该问题预测变量X是每个特征的值反应变量Y是bad”good”类别这里做两样本T检验的目的是为了观察类别为bad的特征和类别为good的特征是否为有显著差异而两样本T检验是为了判别两类别均值是否相等。对于分类问题来说我们可以粗略的认为分类后的样本均值距离越远分的越好类间距离所以T检验在该问题中可以用来判断某一特征对于分类“bad”,good是否有显著差异有显著差异的话说明该特征对于分类是有正向作用的
下面这个帖子详细讲述了T检验的用法。
双样本T检验——机器学习特征工程相关性分析实战 - 知乎
这里主要是想观察同一个特征在不同标签的客户群体中的差异若有显著差异则表明该特征与客户是否逾期有显著的相关性后续建模需要保留这部分特征反之可以进行特征过滤初步筛选掉一些不重要的特征降低模型的复杂度。这里采用scipy库中的ttest_ind来检验特征的在不同客群中的差异通过Levene来确定方差齐性。定义p小于0.001时分布具有显著差异。
#变量显著性检验
from scipy.stats import levene,ttest_ind
bad df_train[df_train[SeriousDlqin2yrs]1].index
good df_train[df_train[SeriousDlqin2yrs]0].index
fea_pvalue {}
for fea in df_train.columns[2:]:p_values {}_, pvalue_l levene(train_x.loc[bad,fea], train_x.loc[good,fea])# 当p0.05时不能拒绝原假设即认为方差对齐if pvalue_l 0.05:_, p_values ttest_ind(train_x.loc[bad,fea], train_x.loc[good,fea], equal_varTrue)fea_pvalue[fea] p_valueselse:_, p_values ttest_ind(train_x.loc[bad,fea], train_x.loc[good,fea], equal_varFalse)fea_pvalue[fea] p_valuesfea_pvalue pd.DataFrame(fea_pvalue,index[0]).T.rename(columns{0:p-value})
fea_pvalue 最终所有特征的p值均小于0.001表示都具有显著差异因此后续所有特征都可以考虑进入模型。
2.2 数据清洗与预处理
经过前一步的探索性数据分析后我们发现数据中存在着缺失值和异常值在数据预处理阶段需要进行处理。鉴于变量显著性检验中的结果即所有特征都与逾期风险有显著的相关性因此对于与缺失和异常值的处理均不采用删除特征的方式来进行。对于异常值先将其替换为缺失值最后再与缺失值进行统一的插补。对于数据不平衡问题可以通过对少数样本进行增采样或对多数样本进行减采样的方式来保障类别的平衡。当然这里也能够在模型训练阶段为不同类别的样本制定不同的权重从而使得模型不会过度偏向占比较大的类别。这是根据前面探索性分析后制定的初步数据预处理方案主要是针对数据质量的问题。在风控建模中还需要涉及到数据分箱WOE编码变量筛选等过程主要是提升特征的表达能力解决的是模型性能的问题这些相关的数据处理细节将在后面逐步展开
1. 数据清洗
删除数据中的无效列名
df_train df_train.drop(columns[Unnamed: 0])
df_test df_test.drop(columns[Unnamed: 0])
from sklearn.model_selection import train_test_split
一般在进行建模之前都需要将数据拆分成三份即训练集测试集和验证集。训练集是用于模型的训练拟合。验证集是在模型训练阶段与训练集配合使用来选择一些超参数或制定优化模型的策略。测试集主要是为了检验模型的拟合程度泛化能力等。一般来讲需要将数据按照时间来进行划分子集。比如有2020-01-01到2022-01-01的数据那么可以选择最后一年的数据来作为验证集和测试集其中两个子集各占半年。而第一年的数据作为训练集来训练模型。这样能够检验模型的跨时间稳定性。由于我们案例中使用的数据没有时间的标识且测试集没有标签所以这里将训练集随机拆分7:3成训练集和验证集以模拟这一场景。
from sklearn.model_selection import train_test_split
#拆分数据将训练集拆分成训练数据和验证数据以检测特征和模型的稳定性等指标
train,valid train_test_split(df_train,test_size0.3,random_state0)
train.reset_index(dropTrue,inplaceTrue)
valid.reset_index(dropTrue,inplaceTrue)
print(train size:{}.format(len(train)))
print(valid size:{}.format(len(valid)))
print(test size:{}.format(len(df_test))) 2. 异常值过滤和缺失插补
对于异常值的处理方法主要有三种一是直接删除二是替换为缺失值后进行插补三是通过分箱将异常值单独作为一个分组看待。在这里以第二种方法进行处理即把异常值视为缺失值。
k 3.0
#使用3西格玛原则检测异常值各个特征的均值和标准差以训练集计算验证集和测试集直接使用
for fea in train.columns:if feaSeriousDlqin2yrs:continuelower train[fea].mean() - k*train[fea].std()upper train[fea].mean() k*train[fea].std()if_outlier_train train[fea].apply(review_outlier,args(lower,upper))if_outlier_val valid[fea].apply(review_outlier,args(lower,upper))if_outlier_test df_test[fea].apply(review_outlier,args(lower,upper))out_index_train np.where(if_outlier_train)[0]out_index_val np.where(if_outlier_val)[0]out_index_test np.where(if_outlier_test)[0]#替换为空值train.loc[out_index_train,fea] np.nan valid.loc[out_index_val,fea] np.nandf_test.loc[out_index_test,fea] np.nan
数据插补一般选用均值或中位数连续变量此外根据3西格玛原则可以通过生成均值加减三倍标准差范围内的随机数进行插补但由于这份数据集中特征的标准差很大数据分布比较分散所以生成的随机数可能会是一个异常值比如年龄为负数因此这里以中位数来插补。注意中位数是由训练集计算出来的验证集和测试集直接使用。
#以特征的中位数插补数据
median train.median()
train.fillna(median,inplaceTrue)
valid.fillna(median,inplaceTrue)
df_test.fillna(median,inplaceTrue)
print(train.info()) 3. 特征分箱
在风控建模过程中常常需要对变量进行分箱处理主要是将连变量进行离散化处理形成类别变量类别变量可以进行适当的合并。分箱的核心目标就是为了提升模型整体的稳定性。比如将异常值和缺失值单独作为一个分箱使得模型对这些值不太敏感。另一方面对于LR这种线性模型分箱也能够使得特征带有一些非线性的特性从而提升模型的非线性表达能力。在风控中常用的分箱方法有等频分箱等距分箱聚类分箱卡方分箱Best-KS分箱等。更加详细的讨论可以参照之前写的文章风控建模中的分箱方法——原理与代码实现。这里使用toda库来实现对特征进行卡方分箱。toad是由厚本金融风控团队内部孵化后开源并坚持维护的标准化评分卡库。其功能全面、性能稳健、运行速度快是风控建模中常使用的一个模型开发库。有关toad库的基本使用可以参考下面几篇文章。
Zain MeiToad | Pyhon评分卡工具轻松实现风控模型开发168 赞同 · 32 评论文章编辑
评分卡建模Toad库的使用_Labryant的博客-CSDN博客_python toad库blog.csdn.net/lc434699300/article/details/105232380编辑
promise信贷评分卡建模库Toad简单应用12 赞同 · 1 评论文章编辑
import toad
combiner toad.transform.Combiner()
#以训练集拟合得出分箱节点
combiner.fit(train,train[SeriousDlqin2yrs],methodchi,min_samples0.05,exclude[SeriousDlqin2yrs])
bins combiner.export()
bins #按照生成的分箱节点对数据进行分箱处理
train_bin combiner.transform(train)
valid_bin combiner.transform(valid)
test_bin combiner.transform(df_test)
test_bin 分箱转换后每一个特征的值都会被分配一个编号表示该特征落在某一个分箱中。
分箱的效果可以通过坏客户占比的单调性来进行初步判断以及作为分箱调整的依据。这里坏客户占比的单调性是指在每一个分箱中坏客户的占比是否随着分箱的取值范围的变化而产生单调变化具体的变化方向需要结合业务知识来考量。这里主要是考虑到业务的可解释性问题比如对于负债比理论上负债比越高那么客户的逾期风险也就越高也就是说在负债比较高的分箱中坏客户的占比应该是更大的。下面通过绘制Bivar图来观察每个特征分箱后的情况以便进行适当的分箱调整。
from toad.plot import badrate_plot,bin_plotfor fea in train_bin.columns:if fea SeriousDlqin2yrs:continuebin_plot(frametrain_bin,xfea,targetSeriousDlqin2yrs,ivFalse) 上面展示了几个特征的Bivar图示例除了DebtRatio外其他特征基本上满足单调性的要求其变化方向也与业务上的理解比较接近比如NumberOfTime30-59DaysPastDueNotWorse逾期30-59天的次数增加时客户最终逾期的风险也在增加。对于DebtRatio可以通过手动调整分箱节点进行分箱合并来保障一定的单调性。从图中可以看到第四个分箱坏客户占比下降不符合整体上升的趋势因此将其合并到第三个分箱中。
#DebtRatio原先的分箱节点[0.354411397, 0.49562442100000004, 3.61878453]
#重新设置分箱规则
adj_bin {DebtRatio: [0.354411397,0.49562442100000004]}
combiner.set_rules(adj_bin)
#根据新规则重新分箱
train_bin combiner.transform(train)
valid_bin combiner.transform(valid)
test_bin combiner.transform(df_test)
bin_plot(frametrain_bin,xDebtRatio,targetSeriousDlqin2yrs,ivFalse) 重新分箱后可以看到DebtRatio也能够呈现出单调性随着负债比的增加客户的逾期风险也在增加符合业务上的理解。此外在实际中我们还需要观察一个特征分箱在训练集以及验证集/测试集中的坏客户占比情况以确定分箱是否具有跨时间稳定性。若分箱在训练集上的坏客户占比与验证集/测试集上同一个分箱的坏客户占比差异很大训练出来的模型不稳定且易过拟合则需要考虑进行重新分箱。以DebtRatio的负样本关联图来进行说明
#标识样本是训练样本还是验证样本
train_bin[sample_type] [train] * len(train_bin)
valid_bin[sample_type] [valid] * len(valid_bin)
#绘制负样本关联图
badrate_plot(framepd.concat([train_bin,valid_bin]),xsample_type,targetSeriousDlqin2yrs,byDebtRatio)train_bin train_bin.drop(columns[sample_type])
valid_bin valid_bin.drop(columns[sample_type]) 上图每一条线代表的是一个分箱在两个数据集中坏客户占比的连线。这里三个分箱三条线没有出现交叉表明同一个分箱中坏客户占比在两个数据集上的差异不大。若出现交叉假设上图的0和1出现交叉意味着这两个分箱中坏客户占比在不同的数据集中差异很大可以考虑将0和1进行合并。使得分箱中的坏客户占比在训练集和验证集上较为接近。
特征分箱后需要面临的一个问题是分箱的编码。前面也展示过分箱后每一个特征的值都会被赋予一个分箱的编号。这个编号只是对分箱的一种简单编码形式。这种编码无法定量地反映分箱内的样本占比情况。我们习惯以线性的方式来判断变量的作用即x越大时y就越大或越小。WOE编码就是通过对比分箱内的坏好客户的占比跟总体的坏好客户占比来衡量分箱对于预测结果的“贡献”。对于这种“贡献”可以这么去理解。我们为了预测样本是好客户还是坏客户需要知道一些信息或者说收集一些证据特征那么当这些信息非常有用时特征的一个分箱内几乎全是坏客户也就是当样本的该特征落在这个分箱时我们能够很有把握地认为该样本是坏客户。这个证据显然非常重要那就需要为它赋予一个更大的权重且这个权重对于预测样本为坏客户起到正向作用。总的来说WOE的绝对值越大表示分箱内的样本分布与总体的样本分布差异越大我们能够从分箱的角度来区分好坏样本。而正负则表示这种差异的方向即更容易认为分箱内的样本是好样本还是坏样本。下面是每个分箱的WOE值的计算方法有兴趣进一步了解可以参照之前的文章风控建模指标PSIIV和WOE理解。使用Toad库的WOETransformer()可以很方便地将分箱后的数据转换为WOE编码。 #对分箱结果进行WOE编码
woe_t toad.transform.WOETransformer()
train_woe woe_t.fit_transform(train_bin,train_bin[SeriousDlqin2yrs],exclude[SeriousDlqin2yrs])
valid_woe woe_t.transform(valid_bin)
test_woe woe_t.transform(test_bin)
train_woe 4. 特征筛选
一般而言我们在建模前期根据业务理解以及其他先验知识或现有数据情况可能选择了非常多的特征。但并不意味这些特征都是需要进入模型训练。一方面这些特征有些可能并没有重要的业务指导意义另一方面当模型纳入过多特征时容易使得模型变得复杂有出现过拟合的风险。当然本案例中的特征数量不多且基本上都有重要的业务含义基本不涉及特征筛选。但为了整个建模流程能够完整下面还是对特征进行筛选重点在于理解筛选的流程以及背后的含义。
在建模的过程中对于一个特征的考量主要考虑几个方面即特征的稳定性细微变化不应当引起预测结果的显著变化特征的可解释性符合业务理解特征的预测能力有效区分好坏客户。所以在进行特征筛选的过程中同样是遵循这几项原则有目的地筛选最终用于模型训练的特征。在本案例中特征的可解释性基本上是满足的每一个特征的含义在特征解释部分已经给出。后面主要是从特征的稳定性以及特征的预测能力两个方面来进行筛选。
1通过IVInformation Value值确定特征的预测能力
经过WOE编码后的特征被赋予了一个代表特征预测能力的权重即WOE值各个分箱WOE值的求和。但一般我们并不根据WOE来进行特征筛选。一个重要的原因是WOE值并不考虑分箱内样本在总体样本中的占比情况。也就是说当一个分箱的样本占比很低时尽管其WOE值很高里面大部分是坏客户或好客户但本身样本落在这个分箱的概率就非常小所以该分箱对于整体样本的预测贡献是不大的。而IV值弥补了这一缺陷其是分箱WOE值的加权求和。这里的权重就是分箱内坏客户以及好客户在各自总体中的占比情况也就是考虑了前面所提到的分箱中样本占总体的情况。当分箱中的样本很少时这个权重也会非常小此时就算分箱的WOE值很高最终加权求和的结果也会很低。下面是IV的计算公式以及不同取值的业务含义。 下面使用Toad的quality()函数来计算每个特征的IV值并过滤掉IV小于0.1的特征。
IV toad.quality(train_woe,SeriousDlqin2yrs,iv_onlyTrue).loc[:,iv].round(2)
IV 可以看到RevolvingUtilizationOfUnsecuredLinesNumberOfTimes90DaysLateNumberOfTime30-59DaysPastDueNotWorseage四个特征的IV值大于0.1因此予以保留。
2通过PSIPopulation Stability Index衡量特征的跨时间稳定性
在风控建模中稳定性甚至比准确性更重要。这里的稳定性是模型评分或特征分箱在不同的数据集一般认为是训练数据和跨时间验证集上的分布差异。PSI是衡量模型或特征跨时间稳定性的一个重要指标。在机器学习建模的过程中一个基本的假设是“历史样本的分布与未来样本的分布一致”这样我们才有可能利用历史样本来训练模型并应用在未来的样本上。但是随着时间的推移客群的属性难免会发生一些细微的变化。如果一个模型或特征是稳定的那么这种细微的变化不应当引起模型的评分或特征的人群分布产生太大的变化。所以当模型评分或者特征分箱中的样本占比分布在训练样本上的预期分布跟验证数据上的实际分布差异小则认为模型或特征足够稳定其能够在不同的数据集上有相似的表现。我们将随机拆分出来的验证集假设为是跨时间的样本。特征的PSI就是要衡量一个特征在训练集和验证集上的分布差异如果这种差异很小PSI小则表明这个特征是稳定的不会因为时间的推移特征发生细微变化而使得人群产生非常大的变化。有关PSI的详细讨论可以参照之前的文章风控建模指标PSIIV和WOE理解。下面是PSI的计算公式以及不同取值的业务含义 使用Toda的PSI()函数可以很方便地计算特征的PSI值。根据PSI大于0.25来过滤不稳定的特征。
#计算PSI
psi toad.metrics.PSI(train_woe,valid_woe)
psi 可以看到前面根据IV值筛选出的四个特征的PSI都小于0.1所以不需要进一步过滤。
2.3 评分卡建模
1. 生成最终的数据集
根据数据预处理阶段得到的结果我们完成了对数据的异常过滤和缺失数据插补完成了特征的分箱WOE编码以及根据IV和PSI进行特征的筛选。最终模型使用四个特征来进行训练包括RevolvingUtilizationOfUnsecuredLinesNumberOfTimes90DaysLateNumberOfTime30-59DaysPastDueNotWorseage这四个特征。根据这个结果确定最终用于模型训练和验证的数据集。
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score,roc_curve
import xgboost as xgb
#确定最终用于模型训练和验证的数据集
train_set train_woe[[RevolvingUtilizationOfUnsecuredLines,NumberOfTimes90DaysLate,NumberOfTime30-59DaysPastDueNotWorse,age,SeriousDlqin2yrs]]
valid_set valid_woe[train_set.columns]
test_set test_woe[train_set.columns]
#特征以及目标变量
fea_lst [RevolvingUtilizationOfUnsecuredLines, NumberOfTimes90DaysLate,NumberOfTime30-59DaysPastDueNotWorse,age]
target SeriousDlqin2yrs
2. 模型选择
机器学习模型有很多尽管目前XGBoost神经网络等模型效果更好。但在风控建模中最常用的还是逻辑回归模型。主要是因为逻辑回归模型简单易用可解释性强。当模型出现问题时能够更加容易找到原因各个特征的系数也能够结合业务知识来进行评估和解释。然而逻辑回归对于非线性问题的处理能力较差。所以在建模阶段可以同时建立一个更为复杂的辅助模型如XGBoost。通过观察逻辑回归和辅助模型的模型表现来进一步调整用于逻辑回归训练的特征。比如假设下图是XGBoost生成的一个决策过程。那么客户年龄以及负债比就可在组合成一个新的特征。如年龄大于25岁且负债比大于1作为一个组合特征若客户满足这一情况则标记该特征为1否则标记为0。同样地年龄大于25岁且负债比小于1作为一个组合特征若客户满足这一情况则标记该特征为1否则标记为0。这就根据XGBoost这种复杂模型生成的一些规则来交叉组合形成一些新的特征从而使得逻辑回归模型也能够像XGBoost一样具有处理非线性问题的能力提升逻辑回归的性能 3. 模型评估 在我们的评分卡建模中最终使用AUC和KS来进行模型的评估。 3. 预训练模型
预训练模型的作用在于优化和调整用于模型训练的特征或模型超参数。即利用训练集进行模型训练在验证集上进行一系列的评估。根据评估结果选择最佳的模型超参数或重新去调整特征。下面将通过建立辅助模型XGBoot来观察是否有必要进行特征的交叉。另一方面通过正向和方向训练验证的方法来观察是否需要对特征进行调整。这里正向的意思是以训练集训练模型以验证集评估模型反向则是反过来以验证集来训练模型。通过观察正向和反向的评估结果如果差异较大则可能代表模型的稳定性很差或训练集和验证集的差异很大需要对特征进一步调整优化以过滤掉一些不稳定的特征或重新制定划分数据集的策略。
定义逻辑回归模型
def lr_model(x, y, valx, valy, C): model LogisticRegression(CC, class_weightbalanced) model.fit(x,y) y_pred model.predict_proba(x)[:,1] fpr_dev,tpr_dev,_ roc_curve(y, y_pred) train_ks abs(fpr_dev - tpr_dev).max()dev_auc roc_auc_score(y_scorey_pred,y_truey)print(train_ks : , train_ks) y_pred model.predict_proba(valx)[:,1] fpr_val,tpr_val,_ roc_curve(valy, y_pred) val_ks abs(fpr_val - tpr_val).max()val_auc roc_auc_score(y_scorey_pred,y_truevaly)print(val_ks : , val_ks) plt.plot(fpr_dev, tpr_dev, labeldev:{:.3f}.format(dev_auc)) plt.plot(fpr_val, tpr_val, labelval:{:.3f}.format(val_auc)) plt.plot([0,1], [0,1], k--) plt.xlabel(False positive rate) plt.ylabel(True positive rate) plt.title(ROC Curve) plt.legend(locbest) plt.show()
定义XGBoost模型
def xgb_model(x, y, valx, valy): model xgb.XGBClassifier(learning_rate0.05, n_estimators400, max_depth2, min_child_weight1, subsample1, nthread-1, scale_pos_weight1, random_state1, n_jobs-1, reg_lambda300,use_label_encoderFalse) model.fit(x, y,eval_metriclogloss) y_pred model.predict_proba(x)[:,1] fpr_dev,tpr_dev,_ roc_curve(y, y_pred) train_ks abs(fpr_dev - tpr_dev).max() dev_auc roc_auc_score(y_scorey_pred,y_truey)print(train_ks : , train_ks) y_pred model.predict_proba(valx)[:,1] fpr_val,tpr_val,_ roc_curve(valy, y_pred) val_ks abs(fpr_val - tpr_val).max() val_auc roc_auc_score(y_scorey_pred,y_truevaly)print(val_ks : , val_ks) plt.plot(fpr_dev, tpr_dev, labeldev:{:.3f}.format(dev_auc)) plt.plot(fpr_val, tpr_val, labelval:{:.3f}.format(val_auc)) plt.plot([0,1], [0,1], k--) plt.xlabel(False positive rate) plt.ylabel(True positive rate) plt.title(ROC Curve) plt.legend(locbest) plt.show()
定义函数调用模型
def bi_train():train_x,train_y train_set[fea_lst],train_set[target]valid_x,valid_y valid_set[fea_lst],valid_set[target]test_x test_set[fea_lst]print(正向逻辑回归)lr_model(xtrain_x,ytrain_y,valxvalid_x,valyvalid_y,C0.1)print(反向向逻辑回归)lr_model(x valid_x,yvalid_y,valxtrain_x,valytrain_y,C0.1)print(XGBoost)xgb_model(xtrain_x,ytrain_y,valxvalid_x,valyvalid_y)bi_train() 从正向和反向逻辑回归的结果来看模型的性能并没有很大的差异。在正向模型中验证集上的AUC为0.836KS为0.53。模型表现出良好的性能。而在反向模型中验证集即正向中的训练集上的AUC为0.845KS为0.54。与正向模型相比KS差异不超过5%因此模型足够稳定不需要调整数据集或过滤不稳定的特征。从XGBoost的性能评估结果来看其AUC为0.847与正向模型中0.836相比并没有显著的提升。因此当前使用的特征不需要进行交叉组合来提升模型的非线性能力。
4. 模型训练
经过预训练过程的分析后当前的数据集以及特征基本上不需要进行改动因此将训练集与验证集合并重新训练模型作为最终的评分卡模型。 经过预训练过程的分析后当前的数据集以及特征基本上不需要进行改动因此将训练集与验证集合并重新训练模型作为最终的评分卡模型。
model LogisticRegression(C0.1, class_weightbalanced)
all_train pd.concat([train_set,valid_set],axis0)
model.fit(all_train[fea_lst],all_train[target])
#在测试集上预测标签
pro model.predict_proba(test_set[fea_lst])[:,1]至此我们已经完成了模型的训练和评估。由于这里使用的测试集是没有标签的因此无法评估最终模型在测试集上的表现。在实际建模中需要重新评估模型在测试集上的性能。同样包括AUCF1KS等。此外也需要评估模型在训练集和测试集上的PSI以验证模型的稳定性。
2.4 评分卡生成
在第一部分的信用评分卡介绍中我们看到实际应用的评分卡应当是是一张有分数刻度和相应阈值的表。逻辑回归的输出是一个概率即该样本是坏客户的概率。因此我们需要将这种概率进行转换形成一个分数。这就是评分卡建模的最后一步即评分卡的生成。
对于评分卡的生成原理我们先从感性的角度来理解。首先最终的评分应当是每个特征的得分的总和这样才能体现出每个特征的贡献。另一方面评分应当随着预测风险的增加或降低来相应地降分或加分。并且增加或减少多少分应该有一个固定的映射关系这个映射关系要与模型初始输出的概率p有关。这样我们才能够根据不同评分的差异来量化这个风险变化的大小。那么逻辑回归中有哪些地方可以涵盖这两个方面呢那就是对数几率。在逻辑回归中有如下关系。 #woe的编码规则
woe_map woe_t.export()
woe_map #得到特征分箱编号以及分箱woe值的表
woe_df []
for f in fea_lst:woes woe_map.get(f)for b in woes.keys():woe_df.append([f,b,woes.get(b)])
woe_df pd.DataFrame(columns[feature,bins,woe],datawoe_df)
woe_df #生成分箱区间
bins combiner.export()
bin_df []
for fea in fea_lst:f_cut bins.get(fea)f_cut [float(-inf)] f_cut [float(inf)]for i in range(len(f_cut)-1):bin_df.append([fea,i,pd.Interval(f_cut[i],f_cut[i1])])
bin_df pd.DataFrame(columns[feature,bins,interval],databin_df)
bin_df #生成评分卡
score_df pd.merge(woe_df,bin_df,on[feature,bins])[[feature,interval,woe]]
coef model.coef_
#每个特征的分箱数
bins_num [len(bins.get(i))1 for i in fea_lst]
coef np.repeat(coef,bins_num).
#模型拟合出来的参数
score_df[coef] coef
设定Odds为20:1时基准分600分放Odds增加2倍时分数减50即PDO50factor round(50 / np.log(2),0)
offset round(600 factor * np.log(20),0)
#BasicScore,即评分公式中的常数部分
basic_score round(offset - factor * model.intercept_[0],0)score_df[score] (-factor * score_df[coef]*score_df[woe]).round(0)
card score_df[[feature,interval,score]]
#将基准分加上
card card.append({feature:basic_score,interval:np.nan,score:basic_score},ignore_indexTrue)
card至此我们已经成功生成了信用评分卡后续使用时可以按照评分卡定义的分数刻度和阈值根据客户的属性来得到最终的得分。
2.5 验证评分卡的有效性
这里可以根据前面生成的评分卡写一个映射函数当传入一个客户样本是自动计算出得分最后来对比好坏客户的得分差异如果坏客户的得分小于好客户的得分那么表明评分卡是有效的。
#评分映射函数
def map_score(customer):score []for i in customer.index:#一个特征的计分区间fea_score card[card[feature]i]for _,row in fea_score.iterrows():#card中的interval列类型是pd.Interval直接用in来判断是否在区间内if customer.loc[i] in row[interval]:score.append(row[score])breakscore sum(score) card[card[feature]basic_score][score]return score.values[0]
#随机选择相同数量的好客户和坏客户
verify_bad all_train[all_train[target]1].sample(frac0.3)
verify_good all_train[all_train[target]0].sample(nlen(verify_bad))bad_scores []
good_scores []
#计算坏客户的得分
for _, customer in verify_bad.iterrows():s map_score(customer.loc[fea_lst])bad_scores.append(s)
#计算好客户的得分
for _, customer in verify_good.iterrows():s map_score(customer.loc[fea_lst])good_scores.append(s) ver_score_df pd.DataFrame(columns[bad,good],datanp.array([bad_scores,good_scores]).T)
print(好客户得分均值:{:.2f}\n坏客户得分均值:{:.2f}.format(ver_score_df[good].mean(),ver_score_df[bad].mean()))
_,pv ttest_ind(ver_score_df[bad],ver_score_df[good],equal_varFalse)
print(pvalue:,pv) 从结果中可以看到坏客户的得分均值小于好客户得分且差异具有统计意义p值小于0.001。表明我们生成 的评分卡是有效的对于坏客户的确会得到一个低的评分。
3. 总结
本文从信用卡评分的基础概念开始理解信用评分卡在风控中发挥的作用。第二部分使用公开的信用数据集从0到1建立了一个信用评分卡。包括数据的探索性分析数据预处理评分卡建模评分卡生成以及最后的有效性验证。在建模过程中也交叉地介绍了一些理论概念这也有助于理解每一个步骤具体含义。整体上梳理了风控中信用评分卡的建模流程。