龙空技术网

信贷风控建模实战(三)——逻辑回归评分卡

辣么大的圈圈 145

前言:

如今你们对“逻辑回归的方程”大约比较讲究,同学们都想要学习一些“逻辑回归的方程”的相关文章。那么小编也在网络上汇集了一些关于“逻辑回归的方程””的相关内容,希望同学们能喜欢,各位老铁们快快来了解一下吧!

1. 逻辑回归的评分映射逻辑

  逻辑回归(Logistic Regression)是统计学习中的经典分类方法,是一种有监督的机器学习模型,属于广义线性模型的一种。主要用来解决二分类问题,也可以用来解决多分类问题。使用逻辑回归模型可以得到一个[0,1]区间的结果,在风控场景下可以理解为用户违约的概率,在进行评分卡建模时我们需要把违约的概率映射为用户的评分。业内主要采用一种比率缩放的评分映射方法。

  比如:假设现在期望每个用户的基础分为650分,当这个用户非预期的概率是预期概率的两倍时,加50分,非预期概率是预期概率的4倍时加100分,反之则扣分。因此可以得出业内标准的评分映射逻辑:

其中score时映射后的评分输出,是样本非预期的概率,是样本预期的概率。接下来我们看看逻辑回归中,正负样本是怎么对应起来的。首先在二项逻辑回归模型中,正负样本的条件概率分布如下:

故我们可以得到如下的逻辑回归方程

由换地公式我们可知

因此我们可以最终得到我们的评分映射公式:

其中可以由最终训练完的逻辑回归模型的coef_系数和intercept_得到。

2. 训练逻辑回归模型

  我们以某互联网信贷平台的脱敏数据为例,简单展示基于逻辑回归模型构建评分卡的全过程。其数据字段信息如下:

字段名称

中文描述

obs_mth

申请日期所在月份的最后一天

bad_ind

好坏标识

uid

用户id

td_score

同盾分

jxl_score

聚信立分

mj_score

摩羯分

rh_score

人行征信分

zzc_score

中智诚分

zcx_score

中诚信分

person_info

个人信息

finance_info

金融信息

credit_info

信用信息

act_info

行为信息

  通过观察这些字段我们可以知道,这个数据集是一部分依赖于该企业外部征信的评分,一部分是用户在平台上的基本信息的整合,因此这里简单跳过特征工程的环节,直接利用上述信息进行评分卡制作。

加载依赖包

# 依赖包加载import pandas as pdimport numpy as npfrom sklearn import metricsfrom sklearn.linear_model import LogisticRegressionimport math
数据载入
# 数据导入data = pd.read_csv('Acard.txt')data.head()

数据预览结果如下图所示

数据基本信息探查

# 查看数据基本情况data.info()
<class 'pandas.core.frame.DataFrame'>RangeIndex: 95806 entries, 0 to 95805Data columns (total 13 columns): #   Column        Non-Null Count  Dtype  ---  ------        --------------  -----   0   obs_mth       95806 non-null  object  1   bad_ind       95806 non-null  float64 2   uid           95806 non-null  object  3   td_score      95806 non-null  float64 4   jxl_score     95806 non-null  float64 5   mj_score      95806 non-null  float64 6   rh_score      95806 non-null  float64 7   zzc_score     95806 non-null  float64 8   zcx_score     95806 non-null  float64 9   person_info   95806 non-null  float64 10  finance_info  95806 non-null  float64 11  credit_info   95806 non-null  float64 12  act_info      95806 non-null  float64dtypes: float64(11), object(2)memory usage: 9.5+ MB
查看数据特征项
# 申请日期所在月份的最后一天、# 好坏标识、用户id、同盾分、聚信立分、# mj_score, 人行征信分、中智诚分、中诚信分,# 个人信息、金融信息、信用信息、行为信息data.columns
Index(['obs_mth', 'bad_ind', 'uid', 'td_score', 'jxl_score', 'mj_score',       'rh_score', 'zzc_score', 'zcx_score', 'person_info', 'finance_info',       'credit_info', 'act_info'],      dtype='object')
数据特征变量汇总信息
# 查看离散变量的统计信息data.describe()
    bad_ind td_score    jxl_score   mj_score    rh_score    zzc_score   zcx_score   person_info finance_info    credit_info act_infocount   95806.000000    95806.000000    95806.000000    95806.000000    95806.000000    95806.000000    95806.000000    95806.000000    95806.000000    95806.000000    95806.000000mean    0.018767    0.499739    0.499338    0.501640    0.498407    0.500627    0.499672    -0.078229   0.036763    0.063626    0.236197std 0.135702    0.288349    0.288850    0.288679    0.287797    0.289067    0.289137    0.156859    0.039687    0.143098    0.157132min 0.000000    0.000005    0.000013    0.000007    0.000005    0.000012    0.000010    -0.322581   0.023810    0.000000    0.07692325% 0.000000    0.250104    0.249045    0.250517    0.250115    0.249501    0.248318    -0.261014   0.023810    0.000000    0.07692350% 0.000000    0.500719    0.499795    0.503048    0.497466    0.501688    0.499130    -0.053718   0.023810    0.000000    0.20512875% 0.000000    0.747984    0.748646    0.752032    0.747188    0.750986    0.750683    0.078853    0.023810    0.060000    0.346154max 1.000000    0.999999    0.999985    0.999993    0.999986    0.999998    0.999987    0.078853    1.023810    1.000000    1.089744
数据探查
# 查看数据重复情况data.duplicated().sum()
0
# 查看数据缺失情况data.isnull().sum()
obs_mth         0bad_ind         0uid             0td_score        0jxl_score       0mj_score        0rh_score        0zzc_score       0zcx_score       0person_info     0finance_info    0credit_info     0act_info        0dtype: int64
# 查看好坏样本的分布data.bad_ind.value_counts()
0.0    940081.0     1798Name: bad_ind, dtype: int64
观察样本在时间上的分布
# 查看样本在月份上的分布data.groupby(data.obs_mth).agg({'obs_mth':np.size})
            obs_mthobs_mth 2018-06-30  205652018-07-31  340302018-09-30  107092018-10-31  145272018-11-30  15975
挑选训练样本和时间外样本(OOT)
# 选择'2018-11-30'之外的数据作为时间内样本,用于模型训练train = data[data.obs_mth != '2018-11-30'].reset_index().copy()# 选择'2018-11-30'的数据作为时间外样本,用于模型验证valid = data[data.obs_mth == '2018-11-30'].reset_index().copy()
构建训练集和验证集
# 挑选特征feature_list = ['td_score', 'jxl_score', 'mj_score','rh_score', 'zzc_score', 'zcx_score',                 'person_info', 'finance_info','credit_info', 'act_info']x_train = train[feature_list].copy()y_train = train['bad_ind'].copy()x_valid = valid[feature_list].copy()y_valid = valid['bad_ind'].copy()
训练逻辑回归模型
# 构建并训练逻辑回归模型lr = LogisticRegression(C = 0.11, class_weight='balanced')lr.fit(x_train, y_train)# 在训练集上的评价指标y_pred_train = lr.predict_proba(x_train)[:, 1]fpr_lr_train,tpr_lr_train, _ = metrics.roc_curve(y_train, y_pred_train)ks_train = abs(fpr_lr_train - tpr_lr_train).max()print("the KS on train set is:", ks_train)# 在验证集上的评价指标y_pred_valid = lr.predict_proba(x_valid)[:, 1]fpr_lr_valid,tpr_lr_valid, _ = metrics.roc_curve(y_valid, y_pred_valid)ks_valid = abs(fpr_lr_valid - tpr_lr_valid).max()print("the KS on valid set is:", ks_valid)
the KS on train set is: 0.45180401329378495the KS on valid set is: 0.41464427841696455
ROC曲线
# 训练集和验证集上的ROC对比from matplotlib import pyplot as pltplt.plot(fpr_lr_train, tpr_lr_train, label = 'LR_train')plt.plot(fpr_lr_valid, tpr_lr_valid, label = 'LR_valid')plt.plot([0,1], [0,1], 'k--')plt.xlabel('Flase positive rate')plt.ylabel('True positive rate')plt.title('ROC Curve')plt.legend(loc='best')plt.show()

  通常我们使用ROC曲线(Receiver Operating Characteristic Curve)来衡量模型的整体区分度,以及通过ROC曲线的平稳度来推断模型的鲁棒性和泛化能力。

生成模型报告

  通过分箱,分别对KS值,正负样本数、正负样本占比、捕获率等进行汇总,生成最终的模型报告。

# 生成模型报告model = lrrow_num, col_num = 0, 0bins = 20Y_predict = [s[1] for s in model.predict_proba(x_valid)]Y = y_validrows = Y.shape[0]lis = [(Y_predict[i], Y[i]) for i in range(rows)]ks_lis = sorted(lis, key=lambda x : x[0], reverse=True)bin_num = int(rows/bins + 1)good = sum([1 for (p,y) in ks_lis if y <= 0.5])bad = sum([1 for (p,y) in ks_lis if y > 0.5])good_cnt, bad_cnt = 0,0KS = []GOOD = []GOOD_CNT = []BAD = []BAD_CNT = []BAD_PCTG = []BAD_RATE = []DCT_REPORT = []report = {}for j in range(bins):    ds = ks_lis[j * bin_num : min(bin_num * (j + 1), rows)]    good1 = sum([1 for (p,y) in ds if y <= 0.5])    bad1 = sum([1 for (p,y) in ds if y > 0.5])    bad_cnt += bad1    good_cnt += good1    bad_pctg = round(bad_cnt / sum(y_valid), 3)    bad_rate = round(bad1 /(bad1 + good1), 3)    ks = round(math.fabs(bad_cnt / bad - good_cnt / good), 3)    KS.append(ks)    GOOD.append(good1)    GOOD_CNT.append(good_cnt)    BAD.append(bad1)    BAD_CNT.append(bad_cnt)    BAD_PCTG.append(bad_pctg)    BAD_RATE.append(bad_rate)        report['KS'] = KS    report['正样本个数'] = GOOD    report['正样本累计个数'] = GOOD_CNT    report['负样本个数'] = BAD    report['负样本累计个数'] = BAD_CNT    report['捕获率'] = BAD_PCTG    report['负样本占比'] = BAD_RATE    pd.DataFrame(report)

报告结果如下所示:

KS	正样本个数	正样本累计个数	负样本个数	负样本累计个数	捕获率	负样本占比0	0.220	712	712	87	87	0.265	0.1091	0.293	759	1471	40	127	0.387	0.0502	0.323	773	2244	26	153	0.466	0.0333	0.390	761	3005	38	191	0.582	0.0484	0.398	780	3785	19	210	0.640	0.0245	0.394	784	4569	15	225	0.686	0.0196	0.396	782	5351	17	242	0.738	0.0217	0.398	782	6133	17	259	0.790	0.0218	0.375	790	6923	9	268	0.817	0.0119	0.361	787	7710	12	280	0.854	0.01510	0.335	791	8501	8	288	0.878	0.01011	0.290	797	9298	2	290	0.884	0.00312	0.254	794	10092	5	295	0.899	0.00613	0.222	793	10885	6	301	0.918	0.00814	0.208	787	11672	12	313	0.954	0.01515	0.182	791	12463	8	321	0.979	0.01016	0.137	797	13260	2	323	0.985	0.00317	0.096	796	14056	3	326	0.994	0.00418	0.045	799	14855	0	326	0.994	0.00019	0.000	792	15647	2	328	1.000	0.003

通过观察上述报告,我们可以看出,模型的最大的KS值出现在编号为4的分箱中,在报告中,前4箱的样本总数为3006,其中负样本190个,捕获率达到57.9%,也就是说,如果在风控决策中拒绝掉分数最低的20%的人,就可以捕获到57.9%的负样本。

3. 评分映射

  根据我们在第一部分中推导出来的评分映射公式,我们可以在验证集上,映射算出每个样本的最终风险分。这里需要注意的是,逻辑回归的输出中正样本和负样本的定义与我们实际也业务中的正负样本的定义刚好相反,即数据集中的正样本(即bad_ind为1)是我们业务上的负样本,也就是欺诈样本。因此上述映射公式中的加号,在这里应该改为减号。

# 评分计算def score(x, coef, intercept):    xbeta = intercept    for i in range(len(x) - 1):        xbeta += x[i] * coef[i]    score = 650-50 * xbeta / math.log(2)      return scorex_valid['score'] = x_valid.apply(lambda x : score(x, lr.coef_[0],lr.intercept_[0]) ,axis=1)  x_valid.head()
# 计算KSfpr_lr,tpr_lr,_ = metrics.roc_curve(y_valid, x_valid['score'])  val_ks = abs(fpr_lr - tpr_lr).max()  print('val_ks : ',val_ks) 
val_ks :  0.415602928637454

  通过对模型进行映射评分,在时间外样本上的KS值与逻辑回归模型预测的KS值一致,因此通过这种方式可以验证映射函数的逻辑是否正确。因为在映射函数逻辑正确的情况下,是不会影响模型的排序能力的, 故映射后的KS值应当与模型映射前直接输出的KS值是一致的

4.划分评分等级

  在得到用每个用户的评分之后,还可以根据评分对用户进行等级划分。如果希望某个区间的逾期率变大或变小,可以通过调整阈值参数来实现

# 划分评级def level(score):    level = 0    if score <= 680:        level = "D"    elif score <= 730 and score > 680 :         level = "C"    elif score <= 780 and score > 730:        level = "B"    elif  score > 780 :        level = "A"    return levelx_valid['level'] = x_valid.score.map(lambda x : level(x) )x_valid['level'].groupby(x_valid['level']).count()/len(x_valid)
levelA    0.033239B    0.167074C    0.208951D    0.590736Name: level, dtype: float64

  好了,以上就是关于使用逻辑回归构建评分卡的全部内容,逻辑回归是信贷风控中解释性较强的模型,往往用到的场景比较多,且因为其效果一般都还不错,在银行和各大互联网金融公司往往会作为baseline来使用,至于评分卡建模,后期我们会介绍一种基于XGBoost的评分卡构建方法。

标签: #逻辑回归的方程