本案例中的企业从事个人汽车金融服务,向购车的个人提供信用贷款。该公司的风控部门根据贷款申请者的基本属性、信贷历史、历史信用情况、贷款标的物的情况等信息构建贷款违约顶测模型,其中是否违约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回归通过logit转换将取值为正负无穷的线性方程的值域转化为(0,1) ,正好与概率的取值范围一致,如下所示:
其中 Pi / (1-Pi) 可解释为在样本中违约的概率是不违约的概率的多少倍。在scikit-learn中,与逻辑回归有关的主要是LogisticRegression、LogisticRegressionCV等。主要区别是LogisticRegressionCV使用了交叉验证来选择正则化系数C,而LogisticRegression需要自己每次指定一个正则化系数(其中C参数为正则化系数λ的倒数,默认为1)。
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逻辑回归类库使用小结。
这里我们选择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%以上且相差不大,拟合效果较好。
交叉验证法(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),其中:
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])
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全称是"受试者工作特征" (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):
下面以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数据科学:技术详解与商业实践》,机械工业出版社