天池——贷款违约预测0.7287

前言

本次是在阿里云天池的第一个竞赛《零基础入门金融风控-贷款违约预测》,赛题以金融风控中的个人信贷为背景,要求选手根据贷款申请人的数据信息预测其是否有违约的可能,以此判断是否通过此项贷款,这是一个典型的分类问题。
最后精度为0.7287。

这个比赛很有意义,主要是可以参考论坛中大牛写的文章,内容全面详细,跟着顺序做完会有很大收获。Datawhale零基础入门金融风控 Task1 赛题理解

1 数据分析+可视化

变量信息

以前可视化我是用Excel加python画图实现的,现在有个巨无敌的库,巨无敌的可视化工具——pandas_profiling!!

import pandas_profiling
def profiling(train,test):
    train_y0 = train[train['isDefault'] == 0]
    train_y1 = train[train['isDefault'] == 1]
    pfr_y1 = pandas_profiling.ProfileReport(train_y1)
    pfr_y1.to_file("./train_y1.html")
    pfr_y0 = pandas_profiling.ProfileReport(train_y0)
    pfr_y0.to_file("./train_y0.html")
    pfr = pandas_profiling.ProfileReport(train)
    pfr.to_file("./train.html")
    pfr_y = pandas_profiling.ProfileReport(test)
    pfr_y.to_file("./test.html")

1.1 Overview


从Overview中,我们可以得知训练集有800000个,其中缺失值占据1.8%;其变量(包含预测值)有47个,数值型为36个,类别型为7个,布尔值为4个。

1.2 Warnings

Constant表示只有一个变量值;High cardinality是指高数量类别特征;High correlation是指高相似特征


Missing表示缺失值

Skewed表示偏态分布;Unique表示唯一值;Zeros表示变量值大多为0

1.3 Variables


以loanAmnt为例,我们可以查看一个变量的各种统计,像最值、均值、中值、标准差等,当然还可以查看Common values和Extreme values这两类普遍值和极端值

2 特征工程

将特征人为区分开来(因为有些数值特征实际上是无顺序的变量特征,如postCode,难以用代码区分)

    numerrical = ['loanAmnt','interestRate','installment','annualIncome','dti',
                  'delinquency_2years','ficoRangeHigh','ficoRangeLow','openAcc',
                  'pubRec','pubRecBankruptcies','revolBal','revolUtil','totalAcc']
    nominal = ['term','employmentTitle','homeOwnership','verificationStatus',
               'purpose','postCode','regionCode','initialListStatus','applicationType',
               'title','n0','n1','n2','n3','n4','n5','n6','n7','n8','n9','n10','n11','n12',
               'n13','n14','id']
    ordinal = ['grade','subGrade','employmentLength','earliesCreditLine','issueDate']
    y = ['isDefault']

numerrical——表示数值特征
nominal——表示无顺序的类别特征
ordina——表示有顺序的类别特征
y——表示预测值。

2.1 提取新特征

①债权类——从annualIncome、installment、loanAmnt、annualIncome、dti几个财务类信息互相组合提取出新特征。

    x['Income_installment']=round(x.loc[:,'annualIncome']/x.loc[:,'installment'],2)
    x['loanAmnt_installment']=round(x.loc[:,'loanAmnt']/x.loc[:,'installment'],2)
    x['debt']=round(x.loc[:,'annualIncome']*x.loc[:,'dti'],2)
    x['loanAmnt_debt']=round(x.loc[:,'annualIncome']/x.loc[:,'debt'],2)

②fico——求fico的平均值。

    x['fico']=(x.loc[:,'ficoRangeHigh']+x.loc[:,'ficoRangeLow'])*0.5

③employmentLength——提取出就业年限的数字(转换为连续变量)。

    def employmentLength_to_int(s):
        if pd.isnull(s):
            return s
        else:
            return np.int8(s.split()[0])
    x["employmentLength"].replace(to_replace="10+ years", value="10 years", inplace=True)
    x["employmentLength"].replace(to_replace="< 1 year", value="0 years", inplace=True)
    x['employmentLength'] = x.loc[:,"employmentLength"].apply(employmentLength_to_int)

④CreditLine——计算信用开户到本次借贷的时间,即信用账户的年限。

    x['issueDate'] = x.loc[:,"issueDate"].apply(lambda s: int(s[:4]))
    x['earliesCreditLine'] = x.loc[:,'earliesCreditLine'].apply(lambda s: int(s[-4:]))
    x['CreditLine'] = x.loc[:,'earliesCreditLine'] - x.loc[:,'issueDate']

最后,增添新特征;根据之前Warnings,删除相似特征(High correlation)、唯一值(Unique)、单变量值(Constant),和以上用来生成新特征的旧特征。

    numerrical=list(set(numerrical) - {'ficoRangeHigh', 'ficoRangeLow'}) + 
    ['Income_installment','loanAmnt_installment','loanAmnt_debt','fico']
    nominal=list(set(nominal)-{'id','n10', 'n2'})
    ordinal=list(set(ordinal) - {'grade', 'earliesCreditLine', 'issueDate'}) + ['CreditLine']

2.3 特征编码

在决定编码前我选择了XGB模型,所以按照模型去查询编码方式。
根据XGBoost之类别特征的处理kaggle编码categorical feature总结两篇编码总结。

from category_encoders import WOEEncoder ,OneHotEncoder,CatBoostEncoder,TargetEncoder
    def Category_Encoders(train_x, train_y, test_x, vel_x):
        for col in nominal:
            distinct = train_x[col].nunique()
            if distinct < 4 and distinct >2:
                enc = OneHotEncoder(handle_missing='indicator').fit(train_x[col], train_y)
            elif distinct >= 4:
                # enc = WOEEncoder().fit(train_x[col], train_y)
                # enc = TargetEncoder().fit(train_x[col],train_y)
                enc = CatBoostEncoder().fit(train_x[col],train_y)

            train_x[col] = enc.transform(train_x[col])
            test_x[col] = enc.transform(test_x[col])
            vel_x[col] = enc.transform(vel_x[col])

        return train_x, test_x, vel_x

2.4 缺失值处理和分箱处理

暂不处理,将缺失值视为一种信息,交给xgb模型处理。
分箱暂且没想到好的分箱方法,也交给xgb模型处理。

3 训练模型

模型方面自然是选择了百试百灵的XGBClassifier模型

3.1 模型调参

当年GridSearchCV用的贼爽,但当我用到这个80w大数据集时候,简直不要太慢了!!
总之,当需要调很多参数或是数据集很大的时候,欢迎使用贝叶斯优化调参示例代码 (xgboost,lgbm)

def BO_xgb(x,y):
    t1=time.clock()

    def xgb_cv(max_depth,gamma,min_child_weight,max_delta_step,subsample,colsample_bytree):
        paramt={'booster': 'gbtree',
                'max_depth': int(max_depth),
                'gamma': gamma,
                'eta': 0.1,
                'objective': 'binary:logistic',
                'nthread': 4,
                'eval_metric': 'auc',
                'subsample': max(min(subsample, 1), 0),
                'colsample_bytree': max(min(colsample_bytree, 1), 0),
                'min_child_weight': min_child_weight,
                'max_delta_step': int(max_delta_step),
                'seed': 1001}
        model=XGBClassifier(**paramt)
        res = cross_val_score(model,x, y, scoring='roc_auc', cv=5).mean()
        return res
    cv_params ={'max_depth': (5, 12),
                'gamma': (0.001, 10.0),
                'min_child_weight': (0, 20),
                'max_delta_step': (0, 10),
                'subsample': (0.4, 1.0),
                'colsample_bytree': (0.4, 1.0)}
    xgb_op = BayesianOptimization(xgb_cv,cv_params)
    xgb_op.maximize(n_iter=20)
    print(xgb_op.max)

    t2=time.clock()
    print('耗时:',(t2-t1))

    return xgb_op.max

我们对'max_depth','gamma','min_child_weight','max_delta_step','subsample','colsample_bytree'六个参数进行调参,并最后赋予'n_estimators':1000,'learning_rate':0.02。
最终最佳参数为:

'booster': 'gbtree','eta': 0.1,'nthread': 4,'eval_metric': 'auc','objective': 'binary:logistic',
                    'colsample_bytree': 0.4354, 'gamma': 9.888, 'max_delta_step': 4,'n_estimators':1000,'learning_rate':0.02,
                    'max_depth': 10, 'min_child_weight': 3.268, 'subsample': 0.7157

3.2 ROC可视化

分别look下预测集和训练集的ROC。

def roc(m,x,y,name):
    y_pred = m.predict_proba(x)[:,1]
    """"预测并计算roc的相关指标"""
    fpr, tpr, threshold = metrics.roc_curve(y, y_pred)
    roc_auc = metrics.auc(fpr, tpr)
    print(name+'AUC:{}'.format(roc_auc))
    """画出roc曲线图"""
    plt.figure(figsize=(8, 8))
    plt.title(name)
    plt.plot(fpr, tpr, 'b', label = name + '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()

3 提交成绩

def prediction(m,x):
    submit=pd.read_csv('sample_submit.csv')
    y_pred = m.predict_proba(x)[:,1]
    submit['isDefault'] = y_pred
    submit.to_csv('prediction.csv', index=False)

最终成绩roc=0.7287,排名357。

小结

作为一名信用管理专业的学生,来到天池做这个贷款违约预测,也算和本专业结合了。
这次最大困难我觉得是调参的不利,对于这种大数据处理,模型调参实在是太慢了。我之前习惯于特征改进一步就尝试调参优化,现在即使是使用贝叶斯调参,也是需要1小时以上的时间,并且还是在参数n_estimators默认100的情况下(n_estimators越大耗时越长)。
最后,此模型应该还有调参优化的可能,需要我再去求学。

代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。