(六十二)基于logistic回归的信用评级和分类模型评估

案例数据介绍

本案例中的企业从事个人汽车金融服务,向购车的个人提供信用贷款。该公司的风控部门根据贷款申请者的基本属性、信贷历史、历史信用情况、贷款标的物的情况等信息构建贷款违约顶测模型,其中是否违约bad_ind是因变量。数据来自《Python数据科学:技术详解与商业实践》第八章。

import numpy as np
from sklearn.linear_model import LogisticRegression,LogisticRegressionCV
from sklearn.model_selection import train_test_split
import pandas as pd
from sklearn.metrics import classification_report,confusion_matrix
import matplotlib.pyplot as plt
accepts = pd.read_csv(r'C:\Users\accepts2.csv').dropna()
#%%
##数据说明:本数据是一份汽车贷款违约数据
##名称---中文含义
##application_id---申请者ID
##account_number---帐户号
##bad_ind---是否违约
##vehicle_year---汽车购买时间
##vehicle_make---汽车制造商
##bankruptcy_ind---曾经破产标识
##tot_derog---五年内信用不良事件数量(比如手机欠费消号)
##tot_tr---全部帐户数量
##age_oldest_tr---最久账号存续时间(月)
##tot_open_tr---在使用帐户数量
##tot_rev_tr---在使用可循环贷款帐户数量(比如信用卡)
##tot_rev_debt---在使用可循环贷款帐户余额(比如信用卡欠款)
##tot_rev_line---可循环贷款帐户限额(信用卡授权额度)
##rev_util---可循环贷款帐户使用比例(余额/限额)
##fico_score---FICO打分,越高信用越好
##purch_price---汽车购买金额(元)
##msrp---建议售价
##down_pyt---分期付款的首次交款
##loan_term---贷款期限(月)
##loan_amt---贷款金额
##ltv---贷款金额/建议售价*100
##tot_income---月均收入(元)
##veh_mileage---行使里程(Mile)
##used_ind---是否二手车
Out[2]: 
      application_id  account_number  bad_ind  ...  veh_mileage used_ind weight
0            2314049           11613        1  ...      24000.0        1   1.00
1              63539           13449        0  ...         22.0        0   4.75
3            8725187           15359        1  ...      10000.0        1   1.00
4            4275127           15812        0  ...         14.0        0   4.75
5            8712513           16979        0  ...          1.0        0   4.75
             ...             ...      ...  ...          ...      ...    ...
5840         2291068        10005156        0  ...      45000.0        1   4.75
5841         7647192        10005616        0  ...         21.0        0   4.75
5842         5993246        10006591        0  ...      25000.0        1   4.75
5843         4766566        10010208        0  ...          0.0        0   4.75
5844         1928782        10010219        0  ...         12.0        0   4.75

[4105 rows x 25 columns]

下面对数据进行预处理,并生成一些衍生比值指标:

##衍生变量计算函数:
def divMy(x,y):
    if x==np.nan or y==np.nan:
        return np.nan
    elif y==0:
        return -1
    else:
        return x/y
##历史负债收入比:tot_rev_line/tot_income
accepts["dti_hist"]=accepts[["tot_rev_line","tot_income"]].apply(lambda x:divMy(x[0],x[1]),axis = 1)
##本次新增负债收入比:loan_amt/tot_income
accepts["dti_mew"]=accepts[["loan_amt","tot_income"]].apply(lambda x:divMy(x[0],x[1]),axis = 1)
##本次贷款首付比例:down_pyt/loan_amt
accepts["fta"]=accepts[["down_pyt","loan_amt"]].apply(lambda x:divMy(x[0],x[1]),axis = 1)
##新增债务比:loan_amt/tot_rev_debt
accepts["nth"]=accepts[["loan_amt","tot_rev_debt"]].apply(lambda x:divMy(x[0],x[1]),axis = 1)
##新增债务额度比:loan_amt/tot_rev_line
accepts["nta"]=accepts[["loan_amt","tot_rev_line"]].apply(lambda x:divMy(x[0],x[1]),axis = 1)
##bankruptcy_ind的'Y'和'N'转换为1和0
accepts["bankruptcy_ind"]=accepts["bankruptcy_ind"].apply(lambda x:1 if x=='Y' else (0 if x=='N' else np.nan))

Logistic回归

Logistic回归通过logit转换将取值为正负无穷的线性方程的值域转化为(0,1) ,正好与概率的取值范围一致,如下所示:
在这里插入图片描述
其中 Pi / (1-Pi) 可解释为在样本中违约的概率是不违约的概率的多少倍。在scikit-learn中,与逻辑回归有关的主要是LogisticRegression、LogisticRegressionCV等。主要区别是LogisticRegressionCV使用了交叉验证来选择正则化系数C,而LogisticRegression需要自己每次指定一个正则化系数(其中C参数为正则化系数λ的倒数,默认为1)。

一、LogisticRegression()

sklearn.linear_model.LogisticRegression(penalty=‘l2’, dual=False, tol=0.0001, C=1.0,fit_intercept=True, intercept_scaling=1, class_weight=None, random_state=None,solver=‘liblinear’, max_iter=100, multi_class=‘ovr’, verbose=0,warm_start=False, n_jobs=1),其中最常使用的参数有正则化选择参数:penalty,优化算法选择参数:solver,分类方式选择参数:multi_class,类型权重参数:class_weight以及样本权重参数:sample_weight等,具体可参阅sun_shengyun:sklearn逻辑回归类库使用小结

正则化选择参数penalty
  • LogisticRegression默认带了正则化项L2。penalty参数可选择的值为"l1"和"l2"。
  • 上篇博客也说到,在调参时如果主要目的只是为了解决过拟合,一般penalty选择L2正则化就够了。如果选择L2发现还是过拟合,即预测效果差时,就可以考虑L1正则化。另外,如果模型的特征非常多,我们希望一些不重要的特征系数归零,从而让模型系数稀疏化的话,也可以使用L1正则化。
  • penalty参数的选择会影响我们损失函数优化算法参数solver的选择。如果是L2正则化,那么4种可选的算法{‘newton-cg’, ‘lbfgs’, ‘liblinear’, ‘sag’}都可选。但如果是L1正则化,就只能选择‘liblinear’了。这是因为L1正则化的损失函数不是连续可导的,而{‘newton-cg’, ‘lbfgs’,‘sag’}这三种优化算法时都需要损失函数的一阶或者二阶连续导数。
优化算法选择参数solver
  • solver参数决定了我们对逻辑回归损失函数的优化方法,有4种算法可以选择,分别是:liblinear、 lbfgs、newton-cg、sag。
  • L1、liblinear:适用于小数据集;如果选择L2正则化发现还是过拟合,就可以考虑L1正则化;如果模型的特征非常多,希望一些不重要的特征系数归零,也可以使用L1正则化。
  • L2、liblinear:libniear只支持多元逻辑回归的OvR,不支持MvM,但MVM相对精确。
  • L2、lbfgs/newton-cg/sag:较大数据集,支持one-vs-rest(OvR)和many-vs-many(MvM)两种分类方式选择参数下的多元逻辑回归。
  • L2、sag:如果样本量非常大,比如大于10万,sag是第一选择;但不能用于L1正则化。
分类方式选择参数multi_class
  • multi_class参数决定了分类方式的选择,有 ovr和multinomial两个值可以选择,默认是 ovr。
  • ovr即前面提到的one-vs-rest(OvR),而multinomial即前面提到的many-vs-many(MvM)。如果是二元逻辑回归(即只有两种可能结果,如违约和不违约),ovr和multinomial并没有任何区别,区别主要在多元逻辑回归上。
类型权重参数class_weight
  • 考虑误分类代价敏感、分类类型不平衡的问题,class_weight可选 : ‘balanced’, default: None
  • class_weight参数用于标示分类模型中各种类型的权重,可以不输入,即不考虑权重,或者说所有类型的权重一样。如果选择输入的话,可以选择balanced让类库自己计算类型权重,或者我们自己输入各个类型的权重,比如对于0,1的二元模型,我们可以定义class_weight={0:0.9,1:0.1},这样类型0的权重为90%,而类型1的权重为10%。
  • 如果class_weight选择balanced,那么类库会根据训练样本量来计算权重。某种类型样本量越多,则权重越低,样本量越少,则权重越高。
样本权重参数sample_weight
  • 当样本是高度失衡的,导致样本不是总体样本的无偏估计,从而可能导致我们的模型预测能力下降。调节样本权重的方法有两种,第一种是在class_weight使用balanced。第二种是在调用fit函数时,通过sample_weight来自己调节每个样本权重
  • 如果上面两种方法都用到了,那么样本的真正权重是class_weight*sample_weight。

这里我们选择C=0.01(即λ=100),penalty = ‘l2’,solver=‘lbfgs’。下面根据数据集中25个特征(排除application_id、account_number、vehicle_make)以及bad_ind进行多变量逻辑回归:

data=accepts.iloc[:,4:]#25个特征数据
target=accepts.bad_ind
train_X,test_X,train_y,test_y = train_test_split(data,target,test_size = 0.3,random_state = 0)
logit=LogisticRegression(C = 0.01,penalty = 'l2',solver='lbfgs',max_iter = 100000).fit(train_X,train_y)
coef = pd.Series(logit.coef_[0,:],index = train_X.columns)
###绘制特征系数大小排名图
from pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei']
mpl.rcParams['axes.unicode_minus']=False
plt.figure(figsize=(8,6))
coef.sort_values().plot(kind = "barh",width = 0.35)
plt.title("模型中各特征的系数")
plt.grid()
plt.show()

在这里插入图片描述
可以看到25个特征中有不少特征系数几乎为0,因此可以剔除这些特征再进行逻辑回归,此处不再赘述。下面是LogisticRegression自带的一些方法,可以查看对测试集的预测效果:

prob0_test = logit.predict_proba(test_X)[:,0]#预测属于0的概率
prob1_test = logit.predict_proba(test_X)[:,1]#预测属于1的概率
pred_test = logit.predict(test_X)#预测标签
test_table = pd.DataFrame({'预测标签':pred_test,'预测为0的概率':prob0_test,'预测为1的概率':prob1_test})
test_table.head(16)
Out[106]: 
    预测标签   预测为0的概率   预测为1的概率
0      0  0.877870  0.122130
1      0  0.592357  0.407643
2      0  0.967154  0.032846
3      0  0.874919  0.125081
4      0  0.552422  0.447578
5      0  0.611668  0.388332
6      0  0.904147  0.095853
7      0  0.834194  0.165806
8      0  0.664811  0.335189
9      0  0.890139  0.109861
10     0  0.653965  0.346035
11     0  0.670883  0.329117
12     0  0.707951  0.292049
13     0  0.858788  0.141212
14     0  0.990260  0.009740
15     1  0.439046  0.560954
print(logit.score(train_X,train_y),logit.score(test_X,test_y))#返回平均准确率
0.8172641837800209 0.8051948051948052

可以看到第一列为预测结果,二、三列为预测为0(不违约)和1(违约)的概率。在第15条数据中,由于预测为0的概率小于预测为1的概率,因此分类标签为1。训练集和测试集的准确率均在80%以上且相差不大,拟合效果较好。

二、LogisticRegressionCV()

交叉验证法(cross validation)先将数据集划分为 k 个大小相似的互斥子集,每次用 k-1 个子集的并集作为训练集,余下的那个子集作为测试集,这样就可获得 k 组训练/测试集,从而可进行 k 次训练和测试,最终返回的是这 k 个测试结果的均值。交叉验证法评估结果的稳定性和保真性在很大程度上取决于 k 的取值,为强调这一点,通常把交叉验证法称为 k 折交叉验证 (k-fold cross validation),k 最常用的取值是 5、10、20等。下图给出了10折交叉验证的示意:
在这里插入图片描述

LogisticRegressionCV(*, self, Cs=10, fit_intercept=True, cv=None, dual=False, penalty=‘l2’, scoring=None, solver=‘lbfgs’, tol=1e-4, max_iter=100, class_weight=None, n_jobs=None, verbose=0, refit=True, intercept_scaling=1., multi_class=‘auto’, random_state=None, l1_ratios=None),其中:

  • Cs表示产生C的个数,例如Cs=10,会产生相应的10个C:10(-4), 10(-3.11),10(-2.22), … , 10(-3.11), 104,LogisticRegressionCV会自动进行交叉验证,从上面10个C里面挑出最佳的C;
  • 交叉验证参数cv,默认fold数量为3,使用3折交叉验证;
  • 并行数n_jobs,默认值1,-1表示与CPU个数一致。
  • 其它参数含义可参考官方文档。
logitCV=LogisticRegressionCV(Cs = 10,penalty = 'l2',solver='lbfgs',max_iter = 100000).fit(train_X,train_y)
print(logitCV.score(train_X,train_y),logitCV.score(test_X,test_y))#返回给出的数据和标签的平均准确率
0.8169161155586495 0.8051948051948052
logitCV.C_#搜索出的最佳得分下的C值
Out[114]: array([1291.54966501])

查准率、查全率及F1值

sklearn中的classification_report函数用于显示主要分类指标的文本报告,在报告中显示每个类的查准率、查全率及F1值等信息。其中左边的一列为分类的标签名、准确度、宏平均和加权平均等,support列为每个标签的出现次数/总和,precision、recall、f1-score三列分别为各个类别的查准率、查全率及F1值

print(confusion_matrix(test_y,pred_test,labels=[0,1]))#LogisticRegression方法下的混淆矩阵
print(classification_report(test_y,pred_test))#LogisticRegression方法下的分类结果报告
[[970  18]
 [222  22]]
              precision    recall  f1-score   support

           0       0.81      0.98      0.89       988
           1       0.55      0.09      0.15       244

    accuracy                           0.81      1232
   macro avg       0.68      0.54      0.52      1232
weighted avg       0.76      0.81      0.74      1232

对于二分类问题,可将样例根据其真实类别与学习器预测类别的组合划分为真正例(true positive)、假正例(false positive)、真反例(true negative)、 假反例(false negative)四种情形,令TP、FP、TN、FN分别表示其对应的样例数,则显然有TP+FP+TN+FN=样例总数。分类结果的混淆矩阵如下:
在这里插入图片描述
查准率和查全率是一对矛盾的度量。一般来说,查准率高时,查全率往往偏低;而查全率高时,查准率往往偏低。F1是基于查准率与查全率的调和平均定义的:
在这里插入图片描述
对混淆矩阵结果的判断可参考Vincent__Lai:如何通过Recall和Precise快速判断自己模型的问题。在一些应用中,对查准率和查全率的重视程度有所不同。例如在商品推荐系统中,为了尽可能少打扰用户,更希望推荐内容确是用户感兴趣的,此时查准率更重要;而在逃犯信息检索系统中,更希望尽可能少漏掉逃犯,此时查全率更重要。而本案例的贷款发生违约的后果严重,因此查全率更加重要,后续工作应该是着力于提高标签1预测的查全率,比如修改分类的默认阈值0.5、提高标签为1样本的权重等。下面通过修改class_weight='balanced’来提高查全率:

logit3 = LogisticRegression(C = 0.01,penalty = 'l2',solver='lbfgs',class_weight\
= 'balanced',max_iter = 100000).fit(train_X,train_y)
pred_test3 = logit3.predict(test_X)
print(classification_report(test_y, pred_test3))
              precision    recall  f1-score   support

           0       0.91      0.65      0.76       988
           1       0.35      0.75      0.47       244

    accuracy                           0.67      1232
   macro avg       0.63      0.70      0.62      1232
weighted avg       0.80      0.67      0.70      1232

可以看到标签1的查全率大大提高了。

ROC和AUC

ROC全称是"受试者工作特征" (Receiver Operating Characteristic) 曲线。ROC 曲线的纵轴是"真正例率" (True Positive Rate,简称 TPR),横轴是"假正例率" (False Positive Rate,简称 FPR),基于前文混淆矩阵中的符号,两者分别定义为:
在这里插入图片描述
显示 ROC 曲线的图称为 ROC 图,下图给出了一个示意图,显然对角线对应于"随机猜测"模型,而点 (0,1) 则对应于将所有正例排在所有反例之前的"理想模型"。
在这里插入图片描述
绘图过程很简单:给定m+个正例和m-个反例,根据学习器预测结果对样例进行排序,然后把分类阈值设为最大,即把所有样例均预测为反例,此时真正例率和假正例率均为0,在坐标 (0,0) 处标记一个点。然后,将分类阈值依次设为每个样例的预测值,即依次将每个样例划分为正例。设前一个标记点坐标为 (x,y), 当前若为真正例,则对应标记点的坐标为 (x,y+1/m+);当前若为假正例,则对应标记点的坐标为 (x+1/m-,y),然后用线段连接相邻点即得。

进行学习器的比较时,若一个学习器的 ROC 曲线被另一个学习器的曲线完全"包住",则可断言后者的性能优于前者;若两个学习器的 ROC 曲线发生交叉,则难以断言两者孰优孰劣。此时如果一定要进行比较,则较为合理的判据是比较 ROC 曲线下的面积,即AUC (Area Under ROC Curve),如上图所示。

sklearn.metrics.roc_curve函数可绘制ROC曲线。fpr, tpr, thresholds = roc_curve(y_true, y_score, pos_label = None, sample_weight = None, drop_intermediate = True):

  • y_true是真实的样本标签,默认为{0,1}或者{-1,1}。如果要设置为其它值,则 pos_label 参数要设置为特定值。
  • y_score是预测为正label的概率;
  • pos_label即标签中认定为正的label;
  • sample_weight即采样权重,可选择取其中的一部分进行计算;
  • drop_intermediate(default=True)即可选择去掉一些对于ROC性能不利的阈值,使得到的曲线有更好的表现性能。
  • 返回值fpr, tpr, thresholds即前文提到的假正例率、真正例率和阈值。

下面以LogisticRegression得出的结果为例,画出ROC曲线,计算AUC值。此处正label为0,因此pos_label=0,y_score为预测标签为0的概率prob0_test:

from sklearn.metrics import roc_curve,auc
false_positive_rate,true_positive_rate,thresholds=roc_curve(test_y,prob0_test,pos_label=0)
roc_auc=auc(false_positive_rate, true_positive_rate)
plt.title('ROC')
plt.plot(false_positive_rate, true_positive_rate,'b',label='AUC = %0.2f'% roc_auc)
plt.legend(loc='best')
plt.plot([0,1],[0,1],'r--')#随机猜测45°线
plt.ylabel('TPR')
plt.xlabel('FPR')
plt.show()

在这里插入图片描述

参考文献

https://blog.csdn.net/sun_shengyun/article/details/53811483
https://blog.csdn.net/w1301100424/article/details/84546194
http://www.sofasofa.io/forum_main_post.php?postid=1000383
周志华《机器学习》,清华大学出版社
常国珍等《Python数据科学:技术详解与商业实践》,机械工业出版社

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值