零基础入门金融风控之贷款违约预测Task4:建模和调参

零基础入门金融风控之贷款违约预测Task4:建模和调参

4.1 学习目标

  • 学习在金融分控领域常用的机器学习模型
  • 学习机器学习模型的建模过程与调参流程

4.2 理论基础

1.逻辑回归
sigmoid函数:
在这里插入图片描述

sigmoid是一种分类方法,主要用于两分类问题(即输出只有两种,分别代表两个类别)

sigmoid函数的特点:[-5,5]之间快速变化由-1到1,;奇函数;当x≥0 时,y≥0.5,分类为1,当 x<0时,y<0.5,分类为0

代价函数:适合于逻辑回归的代价函数是对hx取负对数
梯度下降法:调整参数θ使得代价函数J(θ)取得最小值,形像的说也就是从山顶一步一步的向下挪动到极小值处。这其中挪动的步长被称为学习率;越接近极值点处越小的步长,也就是使用可变学习率的原因。
2、树回归
在这里插入图片描述
3、集成模型
在这里插入图片描述

4.3、模型对比与性能评估

4.3.1 逻辑回归
  • 优点
    1、训练速度较快,分类的时候,计算量仅仅只和特征的数目相关;
    2、简单易理解,模型的可解释性非常好,从特征的权重可以看到不同的特征对最后结果的影响;
    3、适合二分类问题,不需要缩放输入特征;
    4、内存资源占用小,只需要存储各个维度的特征值;
  • 缺点
    1、逻辑回归需要预先处理缺失值和异常值
    2、不能用Logistic回归去解决非线性问题,因为Logistic的决策面是线性的;
    3、对多重共线性数据较为敏感,且很难处理数据不平衡的问题;
    4、准确率并不是很高,因为形式非常简单,很难去拟合数据的真实分布;
4.3.2 决策树模型
  • 优点
    1、简单直观,生成的决策树可以可视化展示
    2、数据不需要预处理,不需要归一化,不需要处理缺失数据
    3、既可以处理离散值,也可以处理连续值
  • 缺点
    1、决策树算法非常容易过拟合,导致泛化能力不强(可进行适当的剪枝)
    2、采用的是贪心算法,容易得到局部最优解
4.3.3 集成模型集成方法(ensemble method)

通过组合多个学习器来完成学习任务,通过集成方法,可以将多个弱学习器组合成一个强分类器,因此集成学习的泛化能力一般比单一分类器要好。集成方法主要包括Bagging和Boosting,Bagging和Boosting都是将已有的分类或回归算法通过一定方式组合起来,形成一个更加强大的分类。两种方法都是把若干个分类器整合为一个分类器的方法,只是整合的方式不一样,最终得到不一样的效果。常见的基于Baggin思想的集成模型有:随机森林、基于Boosting思想的集成模型有:Adaboost、GBDT、XgBoost、LightGBM等。

  • Baggin和Boosting的区别总结如下:
    1、样本选择上: Bagging方法的训练集是从原始集中有放回的选取,所以从原始集中选出的各轮训练集之间是独立的;而Boosting方法需要每一轮的训练集不变,只是训练集中每个样本在分类器中的权重发生变化。而权值是根据上一轮的分类结果进行调整
    2、样例权重上: Bagging方法使用均匀取样,所以每个样本的权重相等;而Boosting方法根据错误率不断调整样本的权值,错误率越大则权重越大
    3、预测函数上: Bagging方法中所有预测函数的权重相等;而Boosting方法中每个弱分类器都有相应的权重,对于分类误差小的分类器会有更大的权重
    4、并行计算上: Bagging方法中各个预测函数可以并行生成;而Boostin方法各个预测函数只能顺序生成,因为后一个模型参数需要前一轮模型的结果。
4.3.4 模型评估方法

在训练集上面的误差我们称之为训练误差或者经验误差,而在测试集上的误差称之为测试误差。
对于数据集的划分,我们通常要保证满足以下两个条件:

  • 训练集和测试集的分布要与样本真实分布一致,即训练集和测试集都要保证是从样本真实分布中独立同分布采样而得;
  • 训练集和测试集要互斥

对于数据集的划分有三种方法:

  • 留出法:大约2/3~4/5的样本作为训练集,其余的作为测试集。
  • 交叉验证法
  • 自助法,

数据集划分总结

  • 对于数据量充足的时候,通常采用留出法或者k折交叉验证法来进行训练/测试集的划分;
  • 对于数据集小且难以有效划分训练/测试集时使用自助法;
  • 对于数据集小且可有效划分的时候最好使用留一法来进行划分,因为这种方法最为准确
4.3.5 模型评价标准

一般使用roc曲线,这一部分在task1中已经有所介绍。在这里就不加已介绍了,希望可以往回看,模型的评价还是挺重要的。

4.4、调参方式

  • 贪心调参方法:
    先使用当前对模型影响最大的参数进行调优,达到当前参数下的模型最优化,再使用对模型影响次之的参数进行调优,如此下去,直到所有的参数调整完毕。这个方法的缺点就是可能会调到局部最优而不是全局最优,但是只需要一步一步的进行参数最优化调试即可,容易理解。需要注意的是在树模型中参数调整的顺序,也就是各个参数对模型的影响程度,这里列举一下日常调参过程中常用的参数和调参顺序:
    ①:max_depth、num_leaves
    ②:min_data_in_leaf、min_child_weight
    ③:bagging_fraction、 feature_fraction、bagging_freq
    ④:reg_lambda、reg_alpha
    ⑤:min_split_gain
  • 网格搜索
    sklearn 提供GridSearchCV用于进行网格搜索,只需要把模型的参数输进去,就能给出最优化的结果和参数。相比起贪心调参,网格搜索的结果会更优,但是网格搜索只适合于小数据集,一旦数据的量级上去了,很难得出结果。
  • 贝叶斯调参
    在使用之前需要先安装包bayesian-optimization,运行如下命令即可:
pip install bayesian-optimization

贝叶斯调参的主要思想是:给定优化的目标函数(广义的函数,只需指定输入和输出即可,无需知道内部结构以及数学性质),通过不断地添加样本点来更新目标函数的后验分布(高斯过程,直到后验分布基本贴合于真实分布)。简单的说,就是考虑了上一次参数的信息,从而更好的调整当前的参数。

贝叶斯调参的步骤如下:

  1. 定义优化函数(rf_cv)
  2. 建立模型
  3. 定义待优化参数
  4. 得到优化结果并返回要优化的分数指标

4.5、代码实战

4.5.1建立模型

导入所需要的包

import pandas as pd
import numpy as np
import lightgbm as lgb
import warnings
import matplotlib.pyplot as plt
from sklearn import metrics
from sklearn.model_selection import KFold, train_test_split, cross_val_score, GridSearchCV, StratifiedKFold
from sklearn.metrics import roc_auc_score
from bayes_opt import BayesianOptimization
import datetime
import pickle
import seaborn as sns
'''
sns 相关设置
@return:
"""
# 声明使用 Seaborn 样式
sns.set()
# 有五种seaborn的绘图风格,它们分别是:darkgrid, whitegrid, dark, white, ticks。默认的主题是darkgrid。
sns.set_style("whitegrid")
# 有四个预置的环境,按大小从小到大排列分别为:paper, notebook, talk, poster。其中,notebook是默认的。
sns.set_context('talk')
# 中文字体设置-黑体
plt.rcParams['font.sans-serif'] = ['SimHei']
# 解决保存图像是负号'-'显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
# 解决Seaborn中文显示问题并调整字体大小
sns.set(font='SimHei')
'''
warnings.filterwarnings('ignore')
pd.options.display.max_columns = None
pd.set_option('display.float_format', lambda x: '%.2f' % x)

读取数据

train = pd.read_csv("./train.csv")
testA = pd.read_csv( "./testA.csv")

压缩数据

def reduce_mem_usage(df):
    '''
    遍历DataFrame的所有列并修改它们的数据类型以减少内存使用
    :param df: 需要处理的数据集
    :return:
    '''
    start_mem = df.memory_usage().sum() / 1024 ** 2  # 记录原数据的内存大小
    print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))
    for col in df.columns:
        col_type = df[col].dtypes
        if col_type != object:  # 这里只过滤了object格式,如果代码中还包含其他类型,要一并过滤
            c_min = df[col].min()
            c_max = df[col].max()
            if str(col_type)[:3] == 'int':  # 如果是int类型的话,不管是int64还是int32,都加入判断
                # 依次尝试转化成in8,in16,in32,in64类型,如果数据大小没溢出,那么转化
                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
                    df[col] = df[col].astype(np.int8)
                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
                    df[col] = df[col].astype(np.int16)
                elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
                    df[col] = df[col].astype(np.int32)
                elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
                    df[col] = df[col].astype(np.int64)
            else:  # 不是整形的话,那就是浮点型
                if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
                    df[col] = df[col].astype(np.float16)
                elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
                    df[col] = df[col].astype(np.float32)
                else:
                    df[col] = df[col].astype(np.float64)
        else:  # 如果不是数值型的话,转化成category类型
            df[col] = df[col].astype('category')

    end_mem = df.memory_usage().sum() / 1024 ** 2    # 看一下转化后的数据的内存大小
    print('Memory usage after optimization is {:.2f} MB'.format(end_mem))
    print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))  # 看一下压缩比例
    return df


train = reduce_mem_usage(train)
testA = reduce_mem_usage(testA)
del testA['n2.2']
del testA['n2.3']

合并数据

data = pd.concat([train, testA], axis=0, ignore_index=True) 

由于数据又是重新开始的,所以这里我们将需要对数据进行简单数据预处理

data.groupby('employmentLength')['id'].count()
'''10年以上算10年,1年一下算0年'''
data['employmentLength'].replace(to_replace='10+ years', value='10 years', inplace=True)
data['employmentLength'].replace(to_replace='< 1 year', value='0 year', inplace=True)

def employmentLength_to_int(s):
    if pd.isnull(s):
        return s
    else:
        return np.int8(s.split()[0])

data['employmentLength'] = data['employmentLength'].apply(employmentLength_to_int)

data['earliesCreditLine_year'] = data['earliesCreditLine'].apply(lambda x: x[-4:])
data['earliesCreditLine_month'] = data['earliesCreditLine'].apply(lambda x: x[0:3])




def month_re(x):
    if x == 'Jan':
        return '01'
    elif x == 'Feb':
        return '02'
    elif x == 'Mar':
        return '03'
    elif x == 'Apr':
        return '04'
    elif x == 'May':
        return '05'
    elif x == 'Jun':
        return '06'
    elif x == 'Jul':
        return '07'
    elif x == 'Aug':
        return '08'
    elif x == 'Sep':
        return '09'
    elif x == 'Oct':
        return '10'
    elif x == 'Nov':
        return '11'
    else:
        return '12'
data['earliesCreditLine_month'] = data['earliesCreditLine_month'].apply(lambda x: month_re(x))
data['earliesCreditLine_date'] = data['earliesCreditLine_year'] + data['earliesCreditLine_month']
data['earliesCreditLine_date'] = data['earliesCreditLine_date'].astype('int')
del data['earliesCreditLine']
del data['earliesCreditLine_year']
del data['earliesCreditLine_month']

data['issueDate'] = pd.to_datetime(data['issueDate'], format='%Y-%m-%d')
startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')
data['issueDateDt'] = data['issueDate'].apply(lambda x: x - startdate).dt.days
del data['issueDate']


'''将1类以上且非高维稀疏的特征进行one-hot编码'''
cate_features = ['grade', 'subGrade', 'employmentTitle', 'homeOwnership', 'verificationStatus', 'purpose',
                 'postCode', 'regionCode', 'applicationType', 'initialListStatus', 'title', 'policyCode']

for cate in cate_features:
    print(cate, '类型数', data[cate].nunique())
del data['policyCode']

data = pd.get_dummies(data, columns=['grade', 'subGrade', 'homeOwnership', 'verificationStatus',
                                     'purpose', 'applicationType', 'initialListStatus'], drop_first=True)
for f in ['employmentTitle', 'postCode', 'regionCode', 'title']:
    data[f + '_counts'] = data.groupby([f])['id'].transform('count')
    data[f + '_rank'] = data.groupby([f])['id'].rank(ascending=False).astype(int)
    del data[f]


features = [f for f in data.columns if f not in ['id', 'isDefault']]
train = data[data.isDefault.notnull()].reset_index(drop=True)
testA = data[data.isDefault.isnull()].reset_index(drop=True)

得到数据

x_train = train[features]
y_train = train['isDefault']
x_testA = testA[features]

5次交叉验证

folds = 5
seed = 2020
kf = KFold(n_splits=folds, shuffle=True, random_state=seed)

建立模型

X_train_split, X_val, y_train_split, y_val = train_test_split(x_train, y_train, test_size=0.2)
train_split_matrix = lgb.Dataset(X_train_split, label=y_train_split)
val_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, 'reg_lambda': 0, 'reg_alpha': 0, 'feature_fraction': 1, 'bagging_fraction': 1,
    'bagging_freq': 0, 'seed': 2020, 'nthread': 8, 'verbose': -1
}
model = lgb.train(params, train_set=train_split_matrix, valid_sets=val_matrix, num_boost_round=20000,
                  verbose_eval=1000, early_stopping_rounds=200)

画出图像

 al_pre_lgb = model.predict(X_val, num_iteration=model.best_iteration)
fpr, tpr, threshold = metrics.roc_curve(y_val, val_pre_lgb)
roc_auc = metrics.auc(fpr, tpr)
`plt.figure(figsize=(8, 8))
plt.title('Val 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.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
plt.plot([0, 1], [0, 1], 'r--')     # 对角线

在这里插入图片描述
使用5折交叉验证进行模型性能评估

cv_scores = []  # 用于存放每次验证的得分

# ## 五折交叉验证评估模型
for i, (train_index, val_index) in enumerate(kf.split(x_train, y_train)):
    print('*** {} ***'.format(str(i+1)))

    X_train_split, y_train_split, X_val, y_val = x_train.iloc[train_index], y_train[train_index], \
                                                 x_train.iloc[val_index], y_train[val_index]
    '''划分训练集和验证集'''

    train_matrix = lgb.Dataset(X_train_split, label=y_train_split)
    val_matrix = lgb.Dataset(X_val, label=y_val)
    '''转换成lgb训练的数据形式'''

    params = {
        'boosting_type': 'gbdt', 'objective': 'binary', 'learning_rate': 0.1, 'metric': 'auc', 'min_child_weight': 1e-3,
        'num_leaves': 31, 'max_depth': -1, 'reg_lambda': 0, 'reg_alpha': 0, 'feature_fraction': 1,
        'bagging_fraction': 1,
        'bagging_freq': 0, 'seed': 2020, 'nthread': 8, 'verbose': -1
    }

    model = lgb.train(params, train_set=train_matrix, num_boost_round=20000, valid_sets=val_matrix, verbose_eval=1000,
                      early_stopping_rounds=200)
    val_pre_lgb = model.predict(X_val, num_iteration=model.best_iteration)
    cv_scores.append(roc_auc_score(y_val, val_pre_lgb))
    print(cv_scores)
4.5.2、模型调参
  • 贪心调参
  • 网格调参
  • 贝叶斯调参

后序调参代码将会继续补上,希望读者体谅,本次难度较大,吸收不易,望先把理论搞懂

4.6、结束总结

在本次学习中,各种模型都有了一个统称介绍,可能比较粗糙,毕竟篇幅有限,但对此用来复习总结还是不错的。这次task4中。各种模型再一次的简单学习一遍,将会有更加简单深刻认识。希望读者谅解
最后。如果文章中有不足之处,请务必指出,一定迅速改正。谢谢

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值