赞
踩
赛题以金融风控中的个人信贷为背景,要求选手根据贷款申请人的数据信息预测其是否有违约的可能,以此判断是否通过此项贷款,这是一个典型的分类问题。
任务:预测用户贷款是否违约
比赛地址:https://tianchi.aliyun.com/competition/entrance/531830/introduction
数据来自某信贷平台的贷款记录,总数据量超过120w,包含47列变量信息,其中15列为匿名变量。
为了保证比赛的公平性,将会从中抽取80万条作为训练集,20万条作为测试集A,20万条作为测试集B,同时会对employmentTitle、purpose、postCode和title等信息进行脱敏。
数据集包含三个下载文件
字段表
Field | Description |
---|---|
id | 为贷款清单分配的唯一信用证标识 |
loanAmnt | 贷款金额 |
term | 贷款期限(year) |
interestRate | 贷款利率 |
installment | 分期付款金额 |
grade | 贷款等级 |
subGrade | 贷款等级之子级 |
employmentTitle | 就业职称 |
employmentLength | 就业年限(年) |
homeOwnership | 借款人在登记时提供的房屋所有权状况 |
annualIncome | 年收入 |
verificationStatus | 验证状态 |
issueDate | 贷款发放的月份 |
purpose | 借款人在贷款申请时的贷款用途类别 |
postCode | 借款人在贷款申请中提供的邮政编码的前3位数字 |
regionCode | 地区编码 |
dti | 债务收入比 |
delinquency_2years | 借款人过去2年信用档案中逾期30天以上的违约事件数 |
ficoRangeLow | 借款人在贷款发放时的fico所属的下限范围 |
ficoRangeHigh | 借款人在贷款发放时的fico所属的上限范围 |
openAcc | 借款人信用档案中未结信用额度的数量 |
pubRec | 贬损公共记录的数量 |
pubRecBankruptcies | 公开记录清除的数量 |
revolBal | 信贷周转余额合计 |
revolUtil | 循环额度利用率,或借款人使用的相对于所有可用循环信贷的信贷金额 |
totalAcc | 借款人信用档案中当前的信用额度总数 |
initialListStatus | 贷款的初始列表状态 |
applicationType | 表明贷款是个人申请还是与两个共同借款人的联合申请 |
earliesCreditLine | 借款人最早报告的信用额度开立的月份 |
title | 借款人提供的贷款名称 |
policyCode | 公开可用的策略_代码=1新产品不公开可用的策略_代码=2 |
n系列匿名特征 | 匿名特征n0-n14,为一些贷款人行为计数特征的处理 |
提交结果为每个测试样本是1的概率,也就是y为1的概率。
评价方法为AUC评估模型效果(越大越好)。
注:AUC(Area Under Curve)被定义为 ROC曲线下与坐标轴围成的面积。
详细参见:
「机器学习」分类算法常见的评估指标
机器学习:评估指标
其次,除了要求的评价指标外,对于二分类问题其评价指标还有精确率、召回率、ROC、F值等
分析主要步骤如下
数据探索性分析是对数据进行初步分析,了解数据特征,观察数据类型,分析数据分布等等,为后续特征工程,以及建模分析都特别重要
例如
引用图片:https://zhuanlan.zhihu.com/p/259788410
首先导入必要模块
import warnings warnings.filterwarnings("ignore") import numpy as np import pandas as pd import seaborn as sns import matplotlib.pyplot as plt import statsmodels.formula.api as smf from sklearn.preprocessing import LabelEncoder from sklearn.feature_selection import SelectKBest from sklearn.model_selection import train_test_split from sklearn.model_selection import StratifiedKFold, KFold from sklearn.feature_selection import SelectKBest from sklearn.feature_selection import chi2 from sklearn.preprocessing import MinMaxScaler import xgboost as xgb import lightgbm as lgb from catboost import CatBoostRegressor # 评价指标 from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, log_loss plt.rcParams["font.sans-serif"]=["SimHei"] plt.rcParams["axes.unicode_minus"]=False
使用pandas读入数据,包括训练集与测试集
导入数据集(数据集过大可以进行瘦身处理)
train = pd.read_csv('train.csv')
test = pd.read_csv('testA.csv')
查看部分数据
train.head()
id | loanAmnt | term | interestRate | installment | grade | subGrade | employmentTitle | employmentLength | homeOwnership | … | n5 | n6 | n7 | n8 | n9 | n10 | n11 | n12 | n13 | n14 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 35000.0 | 5 | 19.52 | 917.97 | E | E2 | 320.0 | 2 years | 2 | … | 9.0 | 8.0 | 4.0 | 12.0 | 2.0 | 7.0 | 0.0 | 0.0 | 0.0 | 2.0 |
1 | 1 | 18000.0 | 5 | 18.49 | 461.90 | D | D2 | 219843.0 | 5 years | 0 | … | NaN | NaN | NaN | NaN | NaN | 13.0 | NaN | NaN | NaN | NaN |
2 | 2 | 12000.0 | 5 | 16.99 | 298.17 | D | D3 | 31698.0 | 8 years | 0 | … | 0.0 | 21.0 | 4.0 | 5.0 | 3.0 | 11.0 | 0.0 | 0.0 | 0.0 | 4.0 |
3 | 3 | 11000.0 | 3 | 7.26 | 340.96 | A | A4 | 46854.0 | 10+ years | 1 | … | 16.0 | 4.0 | 7.0 | 21.0 | 6.0 | 9.0 | 0.0 | 0.0 | 0.0 | 1.0 |
4 | 4 | 3000.0 | 3 | 12.99 | 101.07 | C | C2 | 54.0 | NaN | 1 | … | 4.0 | 9.0 | 10.0 | 15.0 | 7.0 | 12.0 | 0.0 | 0.0 | 0.0 | 4.0 |
5 rows × 47 columns
前面提到,整个数据包括80万条训练集,20万条测试集A,20万条测试集B
另外
训练集中有47列,其中包括46个特征列,1个标签列
测试集中只有46个特征列
# 样本个数和特征维度
train.shape
# (800000, 47)
test.shape
# (200000, 46)
查看特征名
train.columns
'''
Index(['id', 'loanAmnt', 'term', 'interestRate', 'installment', 'grade',
'subGrade', 'employmentTitle', 'employmentLength', 'homeOwnership',
'annualIncome', 'verificationStatus', 'issueDate', 'isDefault',
'purpose', 'postCode', 'regionCode', 'dti', 'delinquency_2years',
'ficoRangeLow', 'ficoRangeHigh', 'openAcc', 'pubRec',
'pubRecBankruptcies', 'revolBal', 'revolUtil', 'totalAcc',
'initialListStatus', 'applicationType', 'earliesCreditLine', 'title',
'policyCode', 'n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8',
'n9', 'n10', 'n11', 'n12', 'n13', 'n14'],
dtype='object')
'''
接下来查看数据集的一些基本信息(缺失情况、类型…)
train.info() ''' <class 'pandas.core.frame.DataFrame'> RangeIndex: 800000 entries, 0 to 799999 Data columns (total 47 columns): id 800000 non-null int64 loanAmnt 800000 non-null float64 term 800000 non-null int64 interestRate 800000 non-null float64 installment 800000 non-null float64 grade 800000 non-null object subGrade 800000 non-null object employmentTitle 799999 non-null float64 employmentLength 753201 non-null object homeOwnership 800000 non-null int64 annualIncome 800000 non-null float64 verificationStatus 800000 non-null int64 issueDate 800000 non-null object isDefault 800000 non-null int64 purpose 800000 non-null int64 postCode 799999 non-null float64 regionCode 800000 non-null int64 dti 799761 non-null float64 delinquency_2years 800000 non-null float64 ficoRangeLow 800000 non-null float64 ficoRangeHigh 800000 non-null float64 openAcc 800000 non-null float64 pubRec 800000 non-null float64 pubRecBankruptcies 799595 non-null float64 revolBal 800000 non-null float64 revolUtil 799469 non-null float64 totalAcc 800000 non-null float64 initialListStatus 800000 non-null int64 applicationType 800000 non-null int64 earliesCreditLine 800000 non-null object title 799999 non-null float64 policyCode 800000 non-null float64 n0 759730 non-null float64 n1 759730 non-null float64 n2 759730 non-null float64 n3 759730 non-null float64 n4 766761 non-null float64 n5 759730 non-null float64 n6 759730 non-null float64 n7 759730 non-null float64 n8 759729 non-null float64 n9 759730 non-null float64 n10 766761 non-null float64 n11 730248 non-null float64 n12 759730 non-null float64 n13 759730 non-null float64 n14 759730 non-null float64 dtypes: float64(33), int64(9), object(5) memory usage: 286.9+ MB '''
可以看到,许多特征存在缺失,特征的类型有dtypes: float64(33), int64(9), object(5)
对于缺失值的处理以及类型转换将在特征工程中说明
接下来查看一下数据的描述性分析
描述性统计
加深对数据分布、数据结构等的理解
看一下数据特征之间的两两关联关系
数据中空值的个数、0的个数、正值或负值的个数,
以及均值、方差、最小值、最大值、偏度、峰度等。
train.describe()
# train.describe().T
大致了解一下数据的分布、结构,简单的看一下特征值有没有什么异常
id | loanAmnt | term | interestRate | installment | employmentTitle | homeOwnership | annualIncome | verificationStatus | isDefault | … | n5 | n6 | n7 | n8 | n9 | n10 | n11 | n12 | n13 | n14 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 800000.000000 | 800000.000000 | 800000.000000 | 800000.000000 | 800000.000000 | 799999.000000 | 800000.000000 | 8.000000e+05 | 800000.000000 | 800000.000000 | … | 759730.000000 | 759730.000000 | 759730.000000 | 759729.000000 | 759730.000000 | 766761.000000 | 730248.000000 | 759730.000000 | 759730.000000 | 759730.000000 |
mean | 399999.500000 | 14416.818875 | 3.482745 | 13.238391 | 437.947723 | 72005.351714 | 0.614213 | 7.613391e+04 | 1.009683 | 0.199513 | … | 8.107937 | 8.575994 | 8.282953 | 14.622488 | 5.592345 | 11.643896 | 0.000815 | 0.003384 | 0.089366 | 2.178606 |
std | 230940.252015 | 8716.086178 | 0.855832 | 4.765757 | 261.460393 | 106585.640204 | 0.675749 | 6.894751e+04 | 0.782716 | 0.399634 | … | 4.799210 | 7.400536 | 4.561689 | 8.124610 | 3.216184 | 5.484104 | 0.030075 | 0.062041 | 0.509069 | 1.844377 |
min | 0.000000 | 500.000000 | 3.000000 | 5.310000 | 15.690000 | 0.000000 | 0.000000 | 0.000000e+00 | 0.000000 | 0.000000 | … | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
25% | 199999.750000 | 8000.000000 | 3.000000 | 9.750000 | 248.450000 | 427.000000 | 0.000000 | 4.560000e+04 | 0.000000 | 0.000000 | … | 5.000000 | 4.000000 | 5.000000 | 9.000000 | 3.000000 | 8.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 |
50% | 399999.500000 | 12000.000000 | 3.000000 | 12.740000 | 375.135000 | 7755.000000 | 1.000000 | 6.500000e+04 | 1.000000 | 0.000000 | … | 7.000000 | 7.000000 | 7.000000 | 13.000000 | 5.000000 | 11.000000 | 0.000000 | 0.000000 | 0.000000 | 2.000000 |
75% | 599999.250000 | 20000.000000 | 3.000000 | 15.990000 | 580.710000 | 117663.500000 | 1.000000 | 9.000000e+04 | 2.000000 | 0.000000 | … | 11.000000 | 11.000000 | 10.000000 | 19.000000 | 7.000000 | 14.000000 | 0.000000 | 0.000000 | 0.000000 | 3.000000 |
max | 799999.000000 | 40000.000000 | 5.000000 | 30.990000 | 1715.420000 | 378351.000000 | 5.000000 | 1.099920e+07 | 2.000000 | 1.000000 | … | 70.000000 | 132.000000 | 79.000000 | 128.000000 | 45.000000 | 82.000000 | 4.000000 | 4.000000 | 39.000000 | 30.000000 |
这里引用文章观点:https://blog.csdn.net/qq_43401035/article/details/108648912
数值类型
# 数值类型
numerical_feature = list(train.select_dtypes(exclude=['object']).columns)
numerical_feature
['id', 'loanAmnt', 'term', 'interestRate', 'installment', 'employmentTitle',
'homeOwnership', 'annualIncome', 'verificationStatus', 'isDefault', 'purpose',
'postCode', 'regionCode', 'dti', 'delinquency_2years', 'ficoRangeLow',
'ficoRangeHigh', 'openAcc', 'pubRec', 'pubRecBankruptcies', 'revolBal',
'revolUtil', 'totalAcc', 'initialListStatus', 'applicationType',
'title', 'policyCode', 'n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8',
'n9', 'n10', 'n11', 'n12', 'n13', 'n14']
一共有42个数值型变量(dtypes: float64(33), int64(9), object(5)
)
len(numerical_feature) ## 42
由于数值类型又可以分为连续变量、离散型变量和单值变量
因此接下来把数值型中的连续型变量和离散型变量区分开来:
# 连续型变量 serial_feature = [] # 离散型变量 discrete_feature = [] # 单值变量 unique_feature = [] for fea in numerical_feature: temp = train[fea].nunique()# 返回的是唯一值的个数 if temp == 1: unique_feature.append(fea) # 自定义变量的值的取值个数小于10就为离散型变量 elif temp <= 10: discrete_feature.append(fea) else: serial_feature.append(fea)
serial_feature
'''
['id', 'loanAmnt', 'interestRate', 'installment', 'employmentTitle',
'annualIncome', 'purpose', 'postCode', 'regionCode', 'dti',
'delinquency_2years', 'ficoRangeLow', 'ficoRangeHigh', 'openAcc',
'pubRec', 'pubRecBankruptcies', 'revolBal', 'revolUtil', 'totalAcc',
'title', 'n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8',
'n9', 'n10', 'n13', 'n14']
'''
对于连续型变量
查看某一个数值型变量的分布,查看变量是否符合正态分布,如果不符合正太分布的变量可以log化后再观察下是否符合正态分布。
正态化的原因:一些情况下正态非正态可以让模型更快的收敛,一些模型要求数据正态(eg. GMM、KNN),保证数据不要过偏态即可,过于偏态可能会影响模型预测结果。
可视化呈现
#每个数字特征得分布可视化
f = pd.melt(train, value_vars=serial_feature)
g = sns.FacetGrid(f, col="variable", col_wrap=3, sharex=False, sharey=False)
g = g.map(sns.distplot, "value")
可以单独查看一下贷款金额 loanAmnt 的分布情况
plt.figure(1 , figsize = (8 , 5))
sns.distplot(train.loanAmnt,bins=40)
plt.xlabel('loanAmnt')
对于违约与不违约两类样本的贷款金额分布情况
sns.kdeplot(train.loanAmnt[label[label==1].index], label='1', shade=True)#违约
sns.kdeplot(train.loanAmnt[label[label==0].index], label='0', shade=True)#没有违约
plt.xlabel('loanAmnt')
plt.ylabel('Density');
单独查看一下年收入的分布情况
plt.figure(1 , figsize = (8 , 5))
sns.distplot(train['annualIncome'])
plt.xlabel('annualIncome')
discrete_feature
'''
['term', 'homeOwnership', 'verificationStatus', 'isDefault',
'initialListStatus', 'applicationType', 'n11', 'n12']
'''
离散型变量的类型数情况
for f in discrete_feature:
print(f, '类型数:', train[f].nunique())
'''
term 类型数: 2
homeOwnership 类型数: 6
verificationStatus 类型数: 3
isDefault 类型数: 2
initialListStatus 类型数: 2
applicationType 类型数: 2
n11 类型数: 5
n12 类型数: 5
'''
离散型特征可视化呈现
df_ = train[discrete_feature] sns.set_style("whitegrid") # 使用whitegrid主题 fig,axes=plt.subplots(nrows=4,ncols=2,figsize=(8,10)) for i, item in enumerate(df_): plt.subplot(4,2,(i+1)) #ax=df[item].value_counts().plot(kind = 'bar') ax=sns.countplot(item,data = df_,palette="Pastel1") plt.xlabel(str(item),fontsize=14) plt.ylabel('Count',fontsize=14) plt.xticks(fontsize=13) plt.yticks(fontsize=13) #plt.title("Churn by "+ str(item)) i=i+1 plt.tight_layout() plt.show()
查看一下每个特征的分布情况
单值变量表示该特征只有一种类别,对于数值全部都一样的特征,可以考虑直接删除
unique_feature
'''
['policyCode']
'''
# 分类型特征
category_feature = list(filter(lambda x: x not in numerical_feature,list(train.columns)))
category_feature
['grade', 'subGrade', 'employmentLength', 'issueDate', 'earliesCreditLine']
对应info结果中的 (dtypes: float64(33), int64(9), object(5)
)
这里 "grade"为贷款等级,"subGrade"为贷款等级之子级,"employmentLength"为就业年限,"issueDate"为贷款发放的月份,"earliesCreditLine"为借款人最早报告的信用额度开立的月份,共有5个分类型特征。
查看一下这些分类型特征的结构,后面需要对其进行特征编码
train[category_feature] ''' grade subGrade employmentLength issueDate earliesCreditLine 0 E E2 2 years 2014-07-01 Aug-2001 1 D D2 5 years 2012-08-01 May-2002 2 D D3 8 years 2015-10-01 May-2006 3 A A4 10+ years 2015-08-01 May-1999 4 C C2 NaN 2016-03-01 Aug-1977 ... ... ... ... ... 799995 C C4 7 years 2016-07-01 Aug-2011 799996 A A4 10+ years 2013-04-01 May-1989 799997 C C3 10+ years 2015-10-01 Jul-2002 799998 A A4 10+ years 2015-02-01 Jan-1994 799999 B B3 5 years 2018-08-01 Feb-2002 [800000 rows x 5 columns] '''
分类型特征可视化呈现
df_category = train[['grade', 'subGrade']] sns.set_style("whitegrid") # 使用whitegrid主题 color = sns.color_palette() fig,axes=plt.subplots(nrows=2,ncols=1,figsize=(10,10)) for i, item in enumerate(df_category): plt.subplot(2,1,(i+1)) #ax=df[item].value_counts().plot(kind = 'bar') ax=sns.countplot(item,data = df_category) plt.xlabel(str(item),fontsize=14) plt.ylabel('Count',fontsize=14) plt.xticks(fontsize=13) plt.yticks(fontsize=13) #plt.title("Churn by "+ str(item)) i=i+1 plt.tight_layout() plt.show()
可以看出对于grade特征中A\B\C等级的贷款占比比较大
employmentLength就业年限可视化呈现
plt.figure(1 , figsize = (10 , 8))
sns.barplot(train["employmentLength"].value_counts(dropna=False),
train["employmentLength"].value_counts(dropna=False).keys())
plt.xticks(fontsize=13)
plt.yticks(fontsize=13)
plt.xlabel('employmentLength',fontsize=14)
plt.show()
可以看到,就业年限最多是 10+year
对于 issueDate 与 earliesCreditLine,统计一下每个类别的数量
for i in train[['issueDate', 'earliesCreditLine']]: print(train[i].value_counts()) print() ''' 2016-03-01 29066 2015-10-01 25525 2015-07-01 24496 2015-12-01 23245 2014-10-01 21461 ... 2007-08-01 23 2007-07-01 21 2008-09-01 19 2007-09-01 7 2007-06-01 1 Name: issueDate, Length: 139, dtype: int64 Aug-2001 5567 Aug-2002 5403 Sep-2003 5403 Oct-2001 5258 Aug-2000 5246 ... Jan-1946 1 Nov-1953 1 Aug-1958 1 Jun-1958 1 Oct-1957 1 Name: earliesCreditLine, Length: 720, dtype: int64 '''
查看目标变量(标签)是否平衡
若分类问题中各类别样本数量差距太大,则会造成样本不均衡的问题。样本不均衡不利于建立与训练出正确的模型,且不能做出合理的评估。
label=train.isDefault
label.value_counts()/len(label)
'''
0 0.800488
1 0.199513
Name: isDefault, dtype: float64
'''
sns.countplot(label)
可以看到,贷款违约与不违约的比例大约为1:4,样本较不平衡,这是金融风控模型评估的中常见的现象,大多数的人都是不会拖欠贷款的。
对于这种情况,考虑后续将进行采样等操作。
接下来,看一下目标变量和分类类别之间的分布关系
train_loan_fr = train.loc[train['isDefault'] == 1]
train_loan_nofr = train.loc[train['isDefault'] == 0]
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 8))
# 目标变量为1时候grade的分布
train_loan_fr.groupby("grade").size().plot.bar(ax=ax1)
# 目标变量为0时候grade的分布
train_loan_nofr.groupby("grade")["grade"].count().plot.bar(ax=ax2)
# 目标变量为1时候employmentLength的分布
train_loan_fr.groupby("employmentLength").size().plot.bar(ax=ax3)
# 目标变量为0时候employmentLength的分布
train_loan_nofr.groupby("employmentLength")["employmentLength"].count().plot.bar(ax=ax4)
plt.xticks(rotation=90);
查看一下正负样本的数据差异
把数据集按正负样本分成两份,查看变量的分布差异
train_positve = train[train['isDefault'] == 1]
train_negative = train[train['isDefault'] != 1]
f, ax = plt.subplots(len(numerical_feature),2,figsize = (10,80))
for i,col in enumerate(numerical_feature):
sns.distplot(train_positve[col],ax = ax[i,0],color = "blue")
ax[i,0].set_title("positive")
sns.distplot(train_negative[col],ax = ax[i,1],color = 'red')
ax[i,1].set_title("negative")
plt.subplots_adjust(hspace = 1)
如果缺失值过多会对整体的模型结果产生一定的影响,因此每次在建模之前都需要对数据的缺失值情况就行查看,若有缺失情况,需要在后续特征工程中进行填补
缺省值查看
# 去掉标签 X_missing = train.drop(['isDefault'],axis=1) # 查看缺失情况 missing = X_missing.isna().sum() missing = pd.DataFrame(data={'特征': missing.index,'缺失值个数':missing.values}) #通过~取反,选取不包含数字0的行 missing = missing[~missing['缺失值个数'].isin([0])] # 缺失比例 missing['缺失比例'] = missing['缺失值个数']/X_missing.shape[0] missing ''' 特征 缺失值个数 缺失比例 7 employmentTitle 1 0.000001 8 employmentLength 46799 0.058499 14 postCode 1 0.000001 16 dti 239 0.000299 22 pubRecBankruptcies 405 0.000506 24 revolUtil 531 0.000664 29 title 1 0.000001 31 n0 40270 0.050338 32 n1 40270 0.050338 33 n2 40270 0.050338 34 n3 40270 0.050338 35 n4 33239 0.041549 36 n5 40270 0.050338 37 n6 40270 0.050338 38 n7 40270 0.050338 39 n8 40271 0.050339 40 n9 40270 0.050338 41 n10 33239 0.041549 42 n11 69752 0.087190 43 n12 40270 0.050338 44 n13 40270 0.050338 45 n14 40270 0.050338 '''
可以看到employmentTitle、employmentLength、dti 以及匿名特征等字段存在缺省值,
从上面的结果可以看出train数据集中的47个字段有22个存在缺省值的情况。下面可视化一下缺省值数量占比。
一般对于缺失值,需要进行横纵对比
纵向(从列方向):如果nan存在的过多,说明这一列对label的影响几乎不起作用了,可以考虑删掉。如果缺失值很小一般可以选择填充。比如占到总数的50%,理论上对分析作用不大,这样就可以省略该字段。
横向(从行方向):如果在数据集中,某些样本数据的大部分列都是缺失的且样本足够的情况下可以考虑删除。
注意:对于一些模型,可以自动处理缺失值,例如 lightgbm 模型
# 可视化
(train.isnull().sum()/len(train)).plot.bar(figsize = (20,6),color=['#d6ecf0','#a3d900','#88ada6','#ffb3a7','#cca4e3','#a1afc9'])
缺失特征可视化呈现
可以看到,所有的特征缺失值都在10%以内,这里考虑全部保留。
总结
47列数据中有22列都缺少少量数据,存在一个唯一值特征 ‘policyCode’
f, ax = plt.subplots(1,1, figsize = (20,20))
cor = train[numerical_feature].corr()
sns.heatmap(cor, annot = True, linewidth = 0.2, linecolor = "white", ax = ax, fmt =".1g" )
可以看到,有些变量之间的相关性还是很强的,比如贷款总额loanAmnt 和分期付款金额installment 相关性为1,ficoRangeLow he ficoRangeHigh 相关性为1…,这种情况后面再特征选择时考虑删除。
基本的EDA探索完成后(还有一些可以继续探索),就可以进行特征工程啦,在数据挖掘中,大部分时间都是在做特征工程
特征工程包括数据预处理、缺失值以及异常值的处理、数据分桶处理以及特征交互、编码、选择
a) 数据清洗
数据清洗主要的目的是提取原始数据中的噪音部分。(重复数据、异常数据、缺失数据等)
b)特征预处理(Feature Prepossess)
特征预处理的目的是将数据的原始字段进行相应的编码(定性:独热编码、哈希编码等;
定量:取整、截断、二值化、分箱、放缩)、
变换(归一化、标准化、正态化),并进行缺失值的处理(插值、均值、中位数、众数、删除);
c)特征提取(Feature Extraction)
特征提取的目的是从原始数据中提取出心的特征字段,并将特征转换成特定的格式;
d)特征筛选(Feature Selection)
特征筛选的目的是筛选出较优的特征子集,以取得较好的泛化性能;
引用 贷款违约预测3-特征工程 观点:
重复值
train.duplicated().sum()
0
在比赛中数据预处理是必不可少的一部分,对于缺失值的填充往往会影响比赛的结果。
缺失值的处理,请参见
【缺失值处理】拉格朗日插值法—随机森林算法填充—sklearn填充(均值/众数/中位数)
传统地,
还要考虑
所以对于连续变量
首先剔除标签列
label = 'isDefault'
Y_label = train['isDefault']
numerical_feature.remove(label)
数值型特征(连续型和离散型)用中位数填补(这里为了方便,都用中位数填补)
# 训练集
train[numerical_feature] = train[numerical_feature].fillna(train[numerical_feature].median())
# 测试集
test[numerical_feature] = test[numerical_feature].fillna(train[numerical_feature].median())
分类型特征用众数填补
分类型特征查看
train[category_feature] ''' grade subGrade employmentLength issueDate earliesCreditLine 0 E E2 2 years 2014-07-01 Aug-2001 1 D D2 5 years 2012-08-01 May-2002 2 D D3 8 years 2015-10-01 May-2006 3 A A4 10+ years 2015-08-01 May-1999 4 C C2 NaN 2016-03-01 Aug-1977 ... ... ... ... ... 799995 C C4 7 years 2016-07-01 Aug-2011 799996 A A4 10+ years 2013-04-01 May-1989 799997 C C3 10+ years 2015-10-01 Jul-2002 799998 A A4 10+ years 2015-02-01 Jan-1994 799999 B B3 5 years 2018-08-01 Feb-2002 [800000 rows x 5 columns] '''
填补
# 训练集
train[category_feature] = train[category_feature].fillna(train[category_feature].mode())
# 测试集
test[category_feature] = test[category_feature].fillna(train[category_feature].mode())
填补之后,再次查看缺失值情况
train.isnull().sum() ''' id 0 loanAmnt 0 term 0 interestRate 0 installment 0 grade 0 subGrade 0 employmentTitle 0 employmentLength 46799 homeOwnership 0 annualIncome 0 verificationStatus 0 issueDate 0 isDefault 0 purpose 0 postCode 0 regionCode 0 dti 0 delinquency_2years 0 ficoRangeLow 0 ficoRangeHigh 0 openAcc 0 pubRec 0 pubRecBankruptcies 0 revolBal 0 revolUtil 0 totalAcc 0 initialListStatus 0 applicationType 0 earliesCreditLine 0 title 0 policyCode 0 n0 0 n1 0 n2 0 n3 0 n4 0 n5 0 n6 0 n7 0 n8 0 n9 0 n10 0 n11 0 n12 0 n13 0 n14 0 issueDateDT 0 dtype: int64 '''
可以看到,employmentLength 列还存在缺失值
特征 缺失值个数 缺失比例
8 employmentLength 46799 0.058499
train.employmentLength
'''
0 2 years
1 5 years
2 8 years
3 10+ years
4 NaN
799995 7 years
799996 10+ years
799997 10+ years
799998 10+ years
799999 5 years
Name: employmentLength, Length: 800000, dtype: object
'''
采用决策树来填补就业年限(employmentLength)
from sklearn.tree import DecisionTreeClassifier
empLenNotNullInd = train.employmentLength.notnull() # 不是空的行,返回True
columns = ['postCode','regionCode','employmentTitle','annualIncome'] # 用四个特征来预测employmentLength
train_empLen_X = train.loc[empLenNotNullInd,columns]
train_empLen_y = train.employmentLength[empLenNotNullInd]
DTC = DecisionTreeClassifier() # 实例化
DTC.fit(train_empLen_X ,train_empLen_y) # 训练
print(DTC.score(train_empLen_X ,train_empLen_y))# 0.9809320486828881
# 预测
for data in [train,test]:
empLenIsNullInd = data.employmentLength.isnull()
test_empLen_X = data.loc[empLenIsNullInd,columns]
empLen_pred = DTC.predict(test_empLen_X)
data.employmentLength[empLenIsNullInd] = empLen_pred
填补完毕
train.isnull().any().sum()
0
train['employmentLength'][:20] ''' 0 2 years 1 5 years 2 8 years 3 10+ years 4 5 years 5 7 years 6 9 years 7 1 year 8 5 years 9 6 years 10 10+ years 11 3 years 12 2 years 13 10+ years 14 2 years 15 2 years 16 9 years 17 < 1 year 18 10+ years 19 9 years Name: employmentLength, dtype: object '''
train['employmentLength'].value_counts(dropna=False).sort_index()
'''
1 year 55034
10+ years 276853
2 years 76435
3 years 68888
4 years 50893
5 years 54038
6 years 39517
7 years 37175
8 years 37903
9 years 31463
< 1 year 71801
Name: employmentLength, dtype: int64
'''
总结
异常值的存在很可能会影响模型的最终结果,但是当我们发现异常值的时候也不能马上就删除,应该先看看这个异常值是不是有特殊原因造成的,特别是在金融风控问题中,异常值的出现往往是存在意义的。
如果不是因为特殊原因造成的,可以先观察这个异常值出现的频率
若异常值只出现了一次,多半是输入错误,直接把异常值删除即可
若异常值出现了多次,可以和业务人员沟通,可能这是某种特殊表示,如果是人为造成的错误,留着是没有用,只要数据量不是太大,都可以删除
若异常值占到总数据量的10%以上,不能轻易删除。可以考虑把异常值替换成非异常但是非干扰的项,比如说用0来进行替换,或者把异常当缺失值,用均值或者众数来进行替换
通常,在进行EDA的时候会利用描述统计的方法,查看特征的均值、极大值、极小值等信息,结合常识来判断是否存在异常值。
比如,年龄值出现负数,人的身高出现非常大的值等等
除此之外,还有其他判断异常值的方法,例如
在统计学中,如果一个数据分布近似正态,那么大约 68% 的数据值会在均值的一个标准差范围内,大约 95% 会在两个标准差范围内,大约 99.7% 会在三个标准差范围内。
def find_outliers_by_3segama(data,fea):
data_std = np.std(data[fea])
data_mean = np.mean(data[fea])
outliers_cut_off = data_std * 3
lower_rule = data_mean - outliers_cut_off
upper_rule = data_mean + outliers_cut_off
data[fea+'_outliers'] = data[fea].apply(lambda x:str('异常值') if x > upper_rule or x < lower_rule else '正常值')
return data
得到特征的异常值后可以进一步分析变量异常值和目标变量的关系
data_train = train.copy()
for fea in numerical_feature:
data_train = find_outliers_by_3segama(data_train,fea)
print(data_train[fea+'_outliers'].value_counts())
print(data_train.groupby(fea+'_outliers')['isDefault'].sum())
print('*'*10)
箱型图包括上四
这里,没有对异常值进行处理…
对于时间数据
对于本赛题,时间数据有 issueDate ,可以将其转化成时间格式(issueDateDT特征表示数据日期离数据集中日期最早的日期(2007-06-01)的天数)
首先看一下这个issueDate
train['issueDate']
'''
0 2014-07-01
1 2012-08-01
2 2015-10-01
3 2015-08-01
4 2016-03-01
...
799995 2016-07-01
799996 2013-04-01
799997 2015-10-01
799998 2015-02-01
799999 2018-08-01
Name: issueDate, Length: 800000, dtype: object
'''
train.shape # (800000, 47)
训练集时间数据处理
import datetime
# 转化成时间格式 issueDateDT特征表示数据日期离数据集中日期最早的日期(2007-06-01)的天数
train['issueDate'] = pd.to_datetime(train['issueDate'],format='%Y-%m-%d')
startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')
train['issueDateDT'] = train['issueDate'].apply(lambda x: x-startdate).dt.days
train.shape # (800000, 48)
查看一下处理效果
train[['issueDate','issueDateDT']]
测试集时间数据处理
#转化成时间格式
test['issueDate'] = pd.to_datetime(train['issueDate'],format='%Y-%m-%d')
startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')
test['issueDateDT'] = test['issueDate'].apply(lambda x: x-startdate).dt.days
可视化
plt.figure(1 , figsize = (10 , 8))
plt.hist(train['issueDateDT'], label='train');
plt.hist(test['issueDateDT'], label='test');
plt.legend();
plt.title('Distribution of issueDateDT dates');
#train 和 test issueDateDT 日期有重叠 所以使用基于时间的分割进行验证是不明智的
注:这里issueDate特征先暂时不删除
这里处理 earliesCreditLine,将利用到 issueDate
这两个特征含义如下
Field | Description |
---|---|
issueDate | 贷款发放的月份 |
earliesCreditLine | 借款人最早报告的信用额度开立的月份 |
将issueDate贷款发放时的年份减去借款人最早报告的信用额度开立的年份,得到新的特征,即开卡年限CreditLine
train[['issueDate','earliesCreditLine']] ''' issueDate earliesCreditLine 0 2014-07-01 Aug-2001 1 2012-08-01 May-2002 2 2015-10-01 May-2006 3 2015-08-01 May-1999 4 2016-03-01 Aug-1977 ... ... 799995 2016-07-01 Aug-2011 799996 2013-04-01 May-1989 799997 2015-10-01 Jul-2002 799998 2015-02-01 Jan-1994 799999 2018-08-01 Feb-2002 [800000 rows x 2 columns] '''
参考:https://zhuanlan.zhihu.com/p/255105477
train_earliesCreditLine_year = train['earliesCreditLine'].apply(lambda x:x[-4:]).astype('int64')
test_earliesCreditLine_year = test['earliesCreditLine'].apply(lambda x:x[-4:]).astype('int64')
train_issueDate_year = train['issueDate'].astype('str').apply(lambda x:x[:4]).astype('int64')
test_issueDate_year = test['issueDate'].astype('str').apply(lambda x:x[:4]).astype('int64')
train['CreditLine'] = train_issueDate_year - train_earliesCreditLine_year
test['CreditLine'] = test_issueDate_year - test_earliesCreditLine_year
train = train.drop(['earliesCreditLine','issueDate'],axis=1)
test = test.drop(['earliesCreditLine','issueDate'],axis=1)
查看处理结果
train['CreditLine']
'''
0 13
1 10
2 9
3 16
4 39
..
799995 5
799996 24
799997 13
799998 21
799999 16
Name: CreditLine, Length: 800000, dtype: int64
'''
train.shape ## (800000, 47)
目前新增两个特征 issueDateDT、CreditLine
earliesCreditLine和issueDate 已经删除
对类别型特征进行转换,使其变为数值特征。具体有以下几种方法:
首先将 employmentLength 进行简单的处理,再进行编码
这里将就业年限特征转换为数值(把数字后面的years去掉并且把10+改成10,<1改成0)
def employmentLength_to_int(s):
if pd.isnull(s):
return s
else:
return np.int8(s.split()[0])
for data in [train, test]:
data['employmentLength'].replace(to_replace='10+ years', value='10 years', inplace=True)
data['employmentLength'].replace('< 1 year', '0 years', inplace=True)
data['employmentLength'] = data['employmentLength'].apply(employmentLength_to_int)
处理前效果
train['employmentLength'][:20] ''' 0 2 years 1 5 years 2 8 years 3 10+ years 4 5 years 5 7 years 6 9 years 7 1 year 8 5 years 9 6 years 10 10+ years 11 3 years 12 2 years 13 10+ years 14 2 years 15 2 years 16 9 years 17 < 1 year 18 10+ years 19 9 years Name: employmentLength, dtype: object '''
处理后效果
train['employmentLength']
'''
0 2
1 5
2 8
3 10
4 5
..
799995 7
799996 10
799997 10
799998 10
799999 5
Name: employmentLength, Length: 800000, dtype: int64
'''
接下来,对其余分类型特征进行编码
像等级grade、subGrade这种类别特征,虽然是表示类别的数据,但是信用评级是有高低大小之分的,是有优先级的,所以可以直接自映射,转化为数值类型。(也可以使用labelencode编码)
a2z = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
a2z_code = np.arange(1,27)
a2z_mapping = dict(zip(a2z, a2z_code))
for data in [train,test]:
data.loc[:,['grade','subGrade']] = data.loc[:,['grade','subGrade']].applymap(lambda g:g.replace(g[0], str(a2z.index(g[0])+1))).astype('int')
编码前结果
train[['grade','subGrade']]
编码后结果
train[['grade','subGrade']]
对于离散型特征,可以使用OneHotEncoder独热编码
要处理的特征有
train[['homeOwnership','verificationStatus','purpose']] ''' homeOwnership verificationStatus purpose 0 2 2 1 1 0 2 0 2 0 2 0 3 1 1 4 4 1 2 10 ... ... ... 799995 1 0 0 799996 0 2 4 799997 1 2 0 799998 0 2 4 799999 0 0 4 [800000 rows x 3 columns] '''
编码之前确定一下特征数
train.shape# (800000, 47)
独热编码
from sklearn.preprocessing import OneHotEncoder oh = OneHotEncoder(sparse=False) oh.fit(train[['homeOwnership','verificationStatus','purpose']]) OneHot1 = oh.transform(train[['homeOwnership','verificationStatus','purpose']]) OneHot2 = oh.transform(test[['homeOwnership','verificationStatus','purpose']]) OneHot1.shape# (800000, 23) ''' array([[0., 0., 1., ..., 0., 0., 0.], [1., 0., 0., ..., 0., 0., 0.], [1., 0., 0., ..., 0., 0., 0.], ..., [0., 1., 0., ..., 0., 0., 0.], [1., 0., 0., ..., 0., 0., 0.], [1., 0., 0., ..., 0., 0., 0.]]) ''' train = pd.concat([train, pd.DataFrame(OneHot1)], axis=1) test = pd.concat([test, pd.DataFrame(OneHot2)], axis=1) train = train.drop(['homeOwnership','verificationStatus','purpose'],axis=1) test = test.drop(['homeOwnership','verificationStatus','purpose'],axis=1) train.shape# (800000, 67)
引用 Datawhale零基础入门金融风控 Task3 特征工程 观点
常见的分箱方法有:
# 通过除法映射到间隔均匀的分箱中,每个分箱的取值范围都是loanAmnt/1000
train['loanAmnt_bin1'] = np.floor_divide(train['loanAmnt'], 1000)
## 通过对数函数映射到指数宽度分箱
train['loanAmnt_bin2'] = np.floor(np.log10(train['loanAmnt']))
train['loanAmnt_bin3'] = pd.qcut(train['loanAmnt'], 10, labels=False)
注:这里没有进行分箱
参见引用文章
引用 Datawhale零基础入门金融风控 Task3 特征工程 观点
额,这里也懒得弄
引用图片:https://blog.csdn.net/qq_38366112/article/details/114996847
1.人工判断与目标无关联特征为"id",需删除
train=train.drop(["id"],axis=1)
train.shape # (800000, 66)
test=test.drop(["id"],axis=1)
test.shape # (200000, 65)
2.求出各个特征与目标的相关系数,综合考虑排除corr小于0.01的特征
train.corr()["isDefault"].sort_values()
train=train.drop(["initialListStatus","n5","n11","n12","n8","postCode","policyCode"],axis=1)
test=test.drop(["initialListStatus","n5","n11","n12","n8","postCode","policyCode"],axis=1)
train.shape# (800000, 59)
删除特征"initialListStatus",“n5”,“n11”,“n12”,“n8”,“postCode”,“policyCode”,剩余59个特征
3.特征间高相关过滤
两两特征之间高于0.6的
# 显示相关性高于0.6的变量 def getHighRelatedFeatureDf(corr_matrix, corr_threshold): highRelatedFeatureDf = pd.DataFrame(corr_matrix[corr_matrix>corr_threshold].stack().reset_index()) highRelatedFeatureDf.rename({'level_0':'feature_x', 'level_1':'feature_y', 0:'corr'}, axis=1, inplace=True) highRelatedFeatureDf = highRelatedFeatureDf[highRelatedFeatureDf.feature_x != highRelatedFeatureDf.feature_y] highRelatedFeatureDf['feature_pair_key'] = highRelatedFeatureDf.loc[:,['feature_x', 'feature_y']].apply(lambda r:'#'.join(np.sort(r.values)), axis=1) highRelatedFeatureDf.drop_duplicates(subset=['feature_pair_key'],inplace=True) highRelatedFeatureDf.drop(['feature_pair_key'], axis=1, inplace=True) return highRelatedFeatureDf getHighRelatedFeatureDf(train.corr(),0.6) ''' feature_x feature_y corr 2 loanAmnt installment 0.953369 5 interestRate grade 0.953269 6 interestRate subGrade 0.970847 11 grade subGrade 0.993907 22 delinquency_2years n13 0.658946 24 ficoRangeLow ficoRangeHigh 1.000000 28 openAcc totalAcc 0.700796 29 openAcc n2 0.658807 30 openAcc n3 0.658807 31 openAcc n4 0.618207 32 openAcc n7 0.830624 33 openAcc n8 0.646342 34 openAcc n9 0.660917 35 openAcc n10 0.998717 37 pubRec pubRecBankruptcies 0.644402 44 totalAcc n5 0.623639 45 totalAcc n6 0.678482 46 totalAcc n8 0.761854 47 totalAcc n10 0.697192 53 n1 n2 0.807789 54 n1 n3 0.807789 55 n1 n4 0.829016 56 n1 n7 0.651852 57 n1 n9 0.800925 61 n2 n3 1.000000 62 n2 n4 0.663186 63 n2 n7 0.790337 64 n2 n9 0.982015 65 n2 n10 0.655296 70 n3 n4 0.663186 71 n3 n7 0.790337 72 n3 n9 0.982015 73 n3 n10 0.655296 79 n4 n5 0.717936 80 n4 n7 0.742157 81 n4 n9 0.639867 82 n4 n10 0.614658 86 n5 n7 0.618970 87 n5 n8 0.838066 97 n7 n8 0.774955 98 n7 n9 0.794465 99 n7 n10 0.829799 105 n8 n10 0.640729 113 n9 n10 0.660395 '''
结果分析:
1) "loanAmnt"贷款金额,"installment"分期付款金额两个特征间相关系数为0.95
2)"ficoRangeLow"fico所属的下限范围,"ficoRangeHigh"fico所属的上限范围两个特征间相关系数为1
3)"openAcc"未结信用额度的数量,“n10” 两个特征间相关系数为0.93
4)“n3”,"n2"两个特征间相关系数为1;“n3”,“n9” 两个特征间相关系数为0.98
根据高相关特征,综合考虑他们与目标的相关性,删除特征"installment",“ficoRangeHigh”,“openAcc”,“n3”,“n9”
col = ['installment','ficoRangeHigh','openAcc','n3','n9']
for data in [train,test]:
data.drop(col,axis=1,inplace=True)
train.shape # (800000, 54)
其余高相关的特征可以使用PCA进行降维处理
(参考:https://zhuanlan.zhihu.com/p/255105477?utm_source=wechat_session)
注:这里不处理了
4.低方差过滤
train.var().sort_values()
结合相关性过滤方差小于0.1的特征"applicationType"
col = ['applicationType']
for data in [train,test]:
data.drop(col,axis=1,inplace=True)
train.shape # (800000, 53)
总结
特征选择中对高相关性的特征进行删除、PCA降维,处理的可能不太合适,可尝试使用过滤法、包装法、嵌入法等特征选择方法进行特征的筛选
若分类问题中各类别样本数量差距太大,则会造成样本不均衡的问题。样本不均衡不利于建立与训练出正确的模型,且不能做出合理的评估。
label.value_counts()/len(label)
'''
0 0.800488
1 0.199513
Name: isDefault, dtype: float64
'''
1.上采样
过抽样(也叫上采样、over-sampling)方法通过增加分类中少数类样本的数量来实现样本均衡,最直接的方法是简单复制少数类样本形成多条记录
参考:https://zhuanlan.zhihu.com/p/255105477?utm_source=wechat_session
import imblearn
from imblearn.over_sampling import SMOTE
over_samples = SMOTE(random_state=1234)
train_over,label_over = over_samples.fit_sample(train, label)
train_over.to_csv('train_over.csv',index=False)
label_over.to_csv('label_over.csv',index=False)
print(label_over.value_counts()/len(label_over))
print(train_over.shape)
2.下采样
欠抽样(也叫下采样、under-sampling)方法通过减少分类中多数类样本的样本数量来实现样本均衡,最直接的方法是随机地去掉一些多数类样本来减小多数类的规模
参考:https://zhuanlan.zhihu.com/p/255105477?utm_source=wechat_session
from imblearn.under_sampling import RandomUnderSampler
under_samples = RandomUnderSampler(random_state=1234)
train_under, label_under = under_samples.fit_sample(train,label)
train_under.to_csv('train_under.csv',index=False)
label_under.to_csv('label_under.csv',index=False)
print(label_under.value_counts()/len(label_under))
print(train_under.shape)
注:这里没有进行采样,如果做了可以分别利用上采样后的数据跑模型和下采样后的数据跑模型
在完成相关的特征处理后,接下来进行建模分析,通过调节参数得到性能更强的模型
参考:https://zhuanlan.zhihu.com/p/256310383
X = train.drop(['isDefault'], axis=1)
y = train.loc[:,'isDefault']
kf = KFold(n_splits=5, shuffle=True, random_state=525)
X_train_split, X_val, y_train_split, y_val = train_test_split(X, y, test_size=0.2)
使用5折交叉验证法对数据进行验证和训练
import lightgbm as lgb cv_scores = [] for i, (train_index, val_index) in enumerate(kf.split(X, y)): X_train, y_train, X_val, y_val = X.iloc[train_index], y.iloc[train_index], X.iloc[val_index], y.iloc[val_index] train_matrix = lgb.Dataset(X_train, label=y_train) valid_matrix = lgb.Dataset(X_val, label=y_val) params = { 'boosting_type': 'gbdt', 'objective': 'binary', 'learning_rate': 0.1, 'metric': 'auc', 'min_child_weight': 1e-3, 'num_leaves': 31, 'max_depth': -1, 'seed': 525, 'nthread': 8, 'silent': True, } model = lgb.train(params, train_set=train_matrix, num_boost_round=20000, valid_sets=valid_matrix, verbose_eval=1000, early_stopping_rounds=200) val_pred = model.predict(X_val, num_iteration=model.best_iteration) cv_scores.append(roc_auc_score(y_val, val_pred)) print(cv_scores) print("lgb_scotrainre_list:{}".format(cv_scores)) print("lgb_score_mean:{}".format(np.mean(cv_scores))) print("lgb_score_std:{}".format(np.std(cv_scores)))
lgb_scotrainre_list:[0.7303837315833632, 0.7258692125145638, 0.7305149209921737, 0.7296117869375041, 0.7294438695369077]
lgb_score_mean:0.7291647043129024
lgb_score_std:0.0016998349834934656
ROC曲线
from sklearn import metrics from sklearn.metrics import roc_auc_score al_pre_lgb = model.predict(X_val, num_iteration=model.best_iteration) fpr, tpr, threshold = metrics.roc_curve(y_val, val_pred) roc_auc = metrics.auc(fpr, tpr) print('AUC:{}'.format(roc_auc)) plt.figure(figsize=(8, 8)) plt.title('Validation ROC') plt.plot(fpr, tpr, 'b', label = 'Val AUC = %0.4f' % roc_auc) plt.ylim(0,1) plt.xlim(0,1) plt.legend(loc='best') plt.title('ROC') plt.ylabel('True Positive Rate') plt.xlabel('False Positive Rate') # 画出对角线 plt.plot([0,1],[0,1],'r--') plt.show()
AUC得分为0.7338
X = train.drop(['isDefault'], axis=1)
y = train.loc[:,'isDefault']
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y,test_size=0.3)
用XGBClassifier模型跑一下(具体的xgboost 参数设置可以参考官网)
from xgboost.sklearn import XGBClassifier
clf1 = XGBClassifier(n_jobs=-1)
clf1.fit(Xtrain,Ytrain)
clf1.score(Xtest,Ytest)
0.8068791666666667
计算模型结构的AUC面积
from sklearn.metrics import roc_curve, auc
predict_proba = clf1.predict_proba(Xtest)
false_positive_rate, true_positive_rate, thresholds = roc_curve(Ytest, predict_proba[:,1])
auc(false_positive_rate, true_positive_rate)
0.7326304866618416
gra=GradientBoostingClassifier()
xgb=XGBClassifier()
lgb=LGBMClassifier()
models=[gra,xgb,lgb]
model_names=["gra","xgb","lgb"]
#交叉验证看看上述3个算法评分
for i,model in enumerate(models):
score=cross_val_score(model,X,y,cv=5,scoring="accuracy",n_jobs=-1)
print(model_names[i],np.array(score).round(3),round(score.mean(),3))
其他建模方法
参见:
金融风控-贷款违约预测
数据竞赛入门-金融风控(贷款违约预测)四、建模与调参
以及
尝试多种模型
(1)贪心调参
参考:https://www.jianshu.com/p/cdf0a9ffec6f
(2)网格搜索
参考:https://www.jianshu.com/p/cdf0a9ffec6f
sklearn 提供GridSearchCV用于进行网格搜索,只需要把模型的参数输进去,就能给出最优化的结果和参数。相比起贪心调参,网格搜索的结果会更优,但是网格搜索只适合于小数据集,一旦数据的量级上去了,很难得出结果。
(3)贝叶斯调参
参考:https://www.jianshu.com/p/cdf0a9ffec6f
贝叶斯调参的主要思想是:给定优化的目标函数(广义的函数,只需指定输入和输出即可,无需知道内部结构以及数学性质),通过不断地添加样本点来更新目标函数的后验分布(高斯过程,直到后验分布基本贴合于真实分布)。简单的说,就是考虑了上一次参数的信息,从而更好的调整当前的参数。
参考:https://zhuanlan.zhihu.com/p/255105477?utm_source=wechat_session
1.优化max_depth,min_child_weight
from xgboost.sklearn import XGBClassifier from sklearn.model_selection import GridSearchCV # 其余参数 other_params = {'learning_rate': 0.1, 'n_estimators': 100, 'max_depth': 5, 'min_child_weight': 1, 'seed': 0, 'subsample': 0.8, 'colsample_bytree': 0.8, 'gamma': 0, 'reg_alpha': 0, 'reg_lambda': 1} # 待调参数 param_test1 = { 'max_depth':list(range(4,9,2)), 'min_child_weight':list(range(1,6,2)) } xgb1 = XGBClassifier(**other_params) # 网格搜索 gs1 = GridSearchCV(xgb1,param_test1,cv = 5,scoring = 'roc_auc',n_jobs = -1,verbose=2) best_model1=gs1.fit(Xtrain,Ytrain) print('最优参数:',best_model1.best_params_) print('最佳模型得分:',best_model1.best_score_)
最优参数:{‘max_depth’:4,,‘min-childweight’:5}
最佳模型得分:0.7185495198261862
2.优化gamma参数
other_params = {'learning_rate': 0.1, 'n_estimators': 100, 'max_depth': 4, 'min_child_weight': 5, 'seed': 0, 'subsample': 0.8, 'colsample_bytree': 0.8, 'gamma': 0, 'reg_alpha': 0, 'reg_lambda': 1} param_test = { 'gaama':[0,0.05,0.1,0.2,0.3] } xgb = XGBClassifier(**other_params) gs = GridSearchCV(xgb,param_test,cv = 5,scoring = 'roc_auc',n_jobs = -1,verbose=2) best_model=gs.fit(Xtrain,Ytrain) print('最优参数:',best_model.best_params_) print('最佳模型得分:',best_model.best_score_)
最优参数:{‘gaama’:0}
最模得分:0.7185495198261862
3.subsample和colsample_bytree
other_params = {'learning_rate': 0.1, 'n_estimators': 100, 'max_depth': 4, 'min_child_weight': 5, 'seed': 0,
'subsample': 0.8, 'colsample_bytree': 0.8, 'gamma': 0, 'reg_alpha': 0, 'reg_lambda': 1}
param_test = {
'subsample':[0.6,0.7,0.8,0.9],
'colsample_bytree':[0.6,0.7,0.8,0.9]
}
xgb = XGBClassifier(**other_params)
gs = GridSearchCV(xgb,param_test,cv = 5,scoring = 'roc_auc',n_jobs = -1,verbose=2)
best_model=gs.fit(Xtrain,Ytrain)
print('最优参数:',best_model.best_params_)
print('最佳模型得分:',best_model.best_score_)
最优参数:{‘colsample-bytree’:0.7,‘subsample’:0.7}
最佳模得分:0.7187964885978947
4.reg_alpha和reg_lambda
other_params = {'learning_rate': 0.1, 'n_estimators': 100, 'max_depth': 4, 'min_child_weight': 5, 'seed': 0,
'subsample': 0.7, 'colsample_bytree': 0.7, 'gamma': 0, 'reg_alpha': 0, 'reg_lambda': 1}
param_test = {
'reg_alpha': [4,5,6,7],
'reg_lambda': [0,0.01,0.05, 0.1]
}
xgb = XGBClassifier(**other_params)
gs = GridSearchCV(xgb,param_test,cv = 5,scoring = 'roc_auc',n_jobs = -1,verbose=2)
best_model=gs.fit(Xtrain,Ytrain)
print('最优参数:',best_model.best_params_)
print('最佳模型得分:',best_model.best_score_)
最优参数:{‘reg-alpha’:5,‘reg-lambda’:0.01}
最佳模型得分:0.7194153615536154
5. learning_rate和n_estimators
other_params = {'learning_rate': 0.1, 'n_estimators': 100, 'max_depth': 4, 'min_child_weight': 5, 'seed': 0,
'subsample': 0.7, 'colsample_bytree': 0.7, 'gamma': 0, 'reg_alpha': 5, 'reg_lambda': 0.01}
param_test = {
'learning_rate': [0.01, 0.05, 0.07, 0.1, 0.2],
'n_estimators': [100,200,300,400,500]
}
xgb = XGBClassifier(**other_params)
gs = GridSearchCV(xgb,param_test,cv = 5,scoring = 'roc_auc',n_jobs = -1,verbose=2)
best_model=gs.fit(Xtrain,Ytrain)
print('最优参数:',best_model.best_params_)
print('最佳模型得分:',best_model.best_score_)
最优参数:{‘learning-rate’:0.05,‘n-estimators’:400}
最佳模型得分:0.7207082359918353
通过调参后的最终模型
from xgboost.sklearn import XGBClassifier clf = XGBClassifier( learning_rate= 0.05, n_estimators= 400, max_depth= 4, min_child_weight= 5, seed= 0, subsample= 0.7, colsample_bytree= 0.7, gamma= 0, reg_alpha= 5, reg_lambda=0.01, n_jobs = -1) clf.fit(Xtrain,Ytrain) clf.score(Xtest,Ytest)
0.80934521
AUC面积
from sklearn.metrics import roc_curve, auc
predict_proba = clf.predict_proba(Xtest)
false_positive_rate, true_positive_rate, thresholds = roc_curve(Ytest, predict_proba[:,1])
auc(false_positive_rate, true_positive_rate)
0.74512067
这里做完后,还可以得出特征重要性
from xgboost import plot_importance
plot_importance(clf)
plt.show()
总结
调参过程综合要点:
(1)"n_estimators"基分类器数量越大,偏差越小,但时间有限,这里初步可选30
(2)"max_depth"越大偏差越小,方差越大,需综合考虑时间及拟合性
(3)"learning_rate"学习速率一般越小越好,只是耗时会更长
(4)"subsample"采样比例一般在[0.5,0.8]之间比较好
模型融合是比赛后期上分的重要手段,模型融合后结果会有大幅提升,以下是模型融合的方式。
1)平均法(Averaging)-针对回归问题
2)投票法(Voting)- 针对分类问题
简单投票法
加权投票法
硬投票法
模型 1:A - 99%、B - 1%,表示模型 1 认为该样本是 A 类型的概率为 99%,为 B 类型的概率为 1%
python实现
from xgboost import XGBClassifier from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.ensemble import VotingClassifier from sklearn.model_selection import train_test_split,cross_val_score #划分数据 交叉验证 clf1 = XGBClassifier(learning_rate=0.1, n_estimators=150, max_depth=3, min_child_weight=2, subsample=0.7, colsample_bytree=0.6, objective='binary:logistic') clf2 = RandomForestClassifier(n_estimators=50, max_depth=1, min_samples_split=4, min_samples_leaf=63,oob_score=True) clf3 = SVC(C=0.1) # 硬投票 eclf = VotingClassifier(estimators=[ ('xgb', clf1), ('rf', clf2), ('svc', clf3)], voting='hard') # 比较模型融合效果 for clf, label in zip([clf1, clf2, clf3, eclf], ['XGBBoosting', 'Random Forest', 'SVM', 'Ensemble']): scores = cross_val_score(clf, x, y, cv=5, scoring='accuracy') print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))
将所有模型预测样本为某一类别的概率的平均值作为标准
from xgboost import XGBClassifier from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.ensemble import VotingClassifier from sklearn.model_selection import train_test_split,cross_val_score #划分数据 交叉验证 clf1 = XGBClassifier(learning_rate=0.1, n_estimators=150, max_depth=3, min_child_weight=2, subsample=0.7, colsample_bytree=0.6, objective='binary:logistic') clf2 = RandomForestClassifier(n_estimators=50, max_depth=1, min_samples_split=4, min_samples_leaf=63,oob_score=True) clf3 = SVC(C=0.1) # 软投票 eclf = VotingClassifier(estimators=[ ('xgb', clf1), ('rf', clf2), ('svc', clf3)], voting='soft', weights=[2, 1, 1]) # 比较模型融合效果 for clf, label in zip([clf1, clf2, clf3, eclf], ['XGBBoosting', 'Random Forest', 'SVM', 'Ensemble']): scores = cross_val_score(clf, x, y, cv=5, scoring='accuracy') print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))
3)综合法
4)stacking/blending:
5) boosting/bagging
多树的提升方法,在xgboost,Adaboost,GBDT中已经用到
介绍完上述方法之后,回到赛题
这里使用之前的训练的lgb和xgb模型作为基分类器,逻辑回归作为目标分类器做stacking
from mlxtend.classifier import StackingClassifier
gra=GradientBoostingClassifier()
xgb=XGBClassifier()
lgb=LGBMClassifier()
lr = LogisticRegression()
sclf = StackingClassifier(classifiers=[gra, xgb, lgb],
use_probas=True,
meta_classifier=lr)
sclf.fit(Xtrain,Ytrain)
pre =sclf.predict_proba(Xtest)[:,1]
fpr, tpr, thresholds = roc_curve(Ytest, pre)
score = auc(fpr, tpr)
print(score)
总结
a) 预测评估数据集(通过验证数据集来验证被优化过的模型)
b) 利用整个数据集生产模型(通过整个数据集来生成模型)
c) 序列化模型(将模型序列化,以便于预测新数据)
当有新数据产生时,就可以采用这个模型来预测新数据。
至此
2021.3.29
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。