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 pd
import numpy as np
from sklearn import metrics
from sklearn.linear_model import LogisticRegression
import math
数据载入
# 数据导入
data = pd.read_csv('Acard.txt')
data.head()
数据预览结果如下图所示

数据基本信息探查
# 查看数据基本情况
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 95806 entries, 0 to 95805
Data 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 float64
dtypes: float64(11), object(2)
memory usage: 9.5+ MB
数据变量汇总信息
# 查看离散变量的统计信息
data.describe()
bad_ind td_score jxl_score mj_score rh_score zzc_score zcx_score person_info finance_info credit_info act_info
count 95806.000000 95806.000000 95806.000000 95806.000000 95806.000000 95806.000000 95806.000000 95806.000000 95806.000000 95806.000000 95806.000000
mean 0.018767 0.499739 0.499338 0.501640 0.498407 0.500627 0.499672 -0.078229 0.036763 0.063626 0.236197
std 0.135702 0.288349 0.288850 0.288679 0.287797 0.289067 0.289137 0.156859 0.039687 0.143098 0.157132
min 0.000000 0.000005 0.000013 0.000007 0.000005 0.000012 0.000010 -0.322581 0.023810 0.000000 0.076923
25% 0.000000 0.250104 0.249045 0.250517 0.250115 0.249501 0.248318 -0.261014 0.023810 0.000000 0.076923
50% 0.000000 0.500719 0.499795 0.503048 0.497466 0.501688 0.499130 -0.053718 0.023810 0.000000 0.205128
75% 0.000000 0.747984 0.748646 0.752032 0.747188 0.750986 0.750683 0.078853 0.023810 0.060000 0.346154
max 1.000000 0.999999 0.999985 0.999993 0.999986 0.999998 0.999987 0.078853 1.023810 1.000000 1.089744
观察正负样本分布
# 查看好坏样本的分布
data.groupby(['bad_ind']).agg({'bad_ind':np.size})
bad_ind
bad_ind
0.0 94008
1.0 1798
观察样本在时间上的分布
# 查看样本在月份上的分布
data.groupby(data.obs_mth).agg({'obs_mth':np.size})
obs_mth
obs_mth
2018-06-30 20565
2018-07-31 34030
2018-09-30 10709
2018-10-31 14527
2018-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.1, 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)
the KS on train set is: 0.45006177149590326
# 在验证集上的评价指标
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("Train KS is:", ks_valid)
the KS on valid set is: 0.4156029286374541
# 训练集和验证集上的ROC对比
from matplotlib import pyplot as plt
plt.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 = lr
row_num, col_num = 0, 0
bins = 20
Y_predict = [s[1] for s in model.predict_proba(x_valid)]
Y = y_valid
rows = 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,0
KS = []
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值出现在编号为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 score
x_valid['score'] = x_valid.apply(lambda x : score(x, lr.coef_[0],lr.intercept_[0]) ,axis=1)
x_valid.head()

# 计算KS
fpr_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 level
x_valid['level'] = x_valid.score.map(lambda x : level(x) )
x_valid['level'].groupby(x_valid['level']).count()/len(x_valid)
level
A 0.033239
B 0.167074
C 0.208951
D 0.590736
Name: level, dtype: float64
好了,以上就是关于使用逻辑回归构建评分卡的全部内容,逻辑回归是信贷风控中解释性较强的模型,往往用到的场景比较多,且因为其效果一般都还不错,在银行和各大互联网金融公司往往会作为baseline来使用,至于评分卡建模,后期我们会介绍一种基于XGBoost的评分卡构建方法。
信贷风控建模实战系列
信贷风控建模实战(一)——建模流程总览
信贷风控建模实战(二)——策略生成及规则挖掘
信贷风控建模实战(三)——评分卡建模之逻辑回归
信贷风控建模实战(四)——评分卡建模之XGBoost
信贷风控建模实战(五)——特征工程
信贷风控建模实战(六)——异常检测
信贷风控建模实战(七)——群组划分or聚类
信贷风控建模实战(八)——风控基础概念
网友评论