目录

  • 1.思考题
  • 1.1逻辑回归的假设条件是怎样的?
  • 1.2逻辑回归的损失函数是怎样的?
  • 1.3逻辑回归如何进行分类?
  • 1.4为什么在训练中需要将高度相关的特征去掉?
  • 2.编程题
  • 2.1基于评分卡的风控模型开发
  • 2.1.1数据预处理
  • 2.1.1.1处理缺失值
  • 2.1.1.2处理异常值
  • 2.1.1.3数据切分
  • 2.1.2探索性分析(EDA)
  • 2.1.3变量选择
  • 2.1.3.1分箱处理
  • 2.1.3.2WOE转换
  • 2.1.4模型分析
  • 2.1.4.1模型建立
  • 2.1.4.2模型检验
  • 2.1.5建立评分卡模型
  • 2.1.5.1评分标准
  • 2.1.5.2建立评分卡
  • 2.1.5.3自动计算评分
  • 2.1.6总结
  • 参考资料


1.思考题

1.1逻辑回归的假设条件是怎样的?

简要说明逻辑回归的假设条件
任何的模型都有自己的假设,在假设条件下才是适用的

假设1:数据服从伯努利分布

典型例子:连续的掷n次硬币(每次实验结果不受其他实验结果的影响,即n次实验是相互独立的)

贝努力分布为离散型概率分布,如果成功,随机变量取值为1;如果失败,随机变量取值为0。成功概率记为p,失败概率为 q=1-p

逻辑回归终止条件代码 逻辑回归的假设条件_python


对应二分类问题,样本为正类的概率p,和样本为负类的概率q=1-p

逻辑回归终止条件代码 逻辑回归的假设条件_数据分析_02

假设2:正类的概率由sigmoid函数计算,即

逻辑回归终止条件代码 逻辑回归的假设条件_d3_03


预测样本为正的概率

逻辑回归终止条件代码 逻辑回归的假设条件_数据分析_04


预测样本为负的概率

逻辑回归终止条件代码 逻辑回归的假设条件_机器学习_05

1.2逻辑回归的损失函数是怎样的?

简要说明逻辑回归的损失函数

逻辑回归终止条件代码 逻辑回归的假设条件_机器学习_06


逻辑回归终止条件代码 逻辑回归的假设条件_机器学习_07

Cost函数与J函数是基于最大似然估计推导得到的

将公式1综合起来,得到下面式子

逻辑回归终止条件代码 逻辑回归的假设条件_机器学习_08


取似然函数为

逻辑回归终止条件代码 逻辑回归的假设条件_逻辑回归终止条件代码_09


对数似然函数为

逻辑回归终止条件代码 逻辑回归的假设条件_逻辑回归终止条件代码_10


最大似然估计就是求使逻辑回归终止条件代码 逻辑回归的假设条件_python_11,取最大值时的θ,这里可以使用梯度上升法求解,也即对逻辑回归终止条件代码 逻辑回归的假设条件_python_12,使用梯度下降法求最小值逻辑回归终止条件代码 逻辑回归的假设条件_逻辑回归终止条件代码_13

1.3逻辑回归如何进行分类?

简要说明逻辑回归是如何进行分类的

设定一个阈值,判断正类概率是否大于该阈值,一般阈值是0.5,所以只用判断正类概率是否大于0.5

1.4为什么在训练中需要将高度相关的特征去掉?

简要说明为什么要去掉相关度高的特征

  • 可解释性更好
  • 提高训练的速度,特征多了,会增大训练的时间

2.编程题

2.1基于评分卡的风控模型开发

数据集地址:https://www.kaggle.com/c/GiveMeSomeCredit/data

目标:

1.完成LR模型
2.对评分卡模型的规则进行说明

数据集说明:

  • 基本属性:包括了借款人当时的年龄
  • 偿债能力:包括了借款人的月收入、负债比率
  • 信用往来:两年内35-59天逾期次数、两年内60-89天逾期次数、两年内90天或高于90天逾期的次数
  • 财产状况:包括了开放式信贷和贷款数量、不动产贷款或额度数量。
  • 其他因素:包括了借款人的家属数量

字段

说明

类型

SeriousDlqin2yrs

90天以上逾期的人,未来2年违约

Y/N

Age

年龄

整数

RevolvingUtilizationOfUnsecuredLines

除房地产和汽车贷款等无分期付款债务外,信用卡和个人信用额度的总余额除以信贷限额

百分比

DebtRatio

债务比(每月偿还的债务,赡养费,生活费除以每月的总收入)

Y/N

MonthlyIncome

每月收入

Y/N

SeriousDlqin2yrs

90天以上逾期的人,未来2年违约

实数

NumberOfOpenCreditLinesAndLoans

公开贷款(如汽车贷款或抵押贷款)和信用额度(如信用卡)的数量

整数

NumberRealEstateLoansOrLines

抵押贷款和房地产贷款的额度(包括房屋净值信贷)

整数

NumberOfTime30-59DaysPastDueNotWorse

90天以上逾期的人,未来2年违约

实数

NumberOfTime60-89DaysPastDueNotWorse

借款人逾期30-59天的次数,但在过去两年没有更糟

整数

NumberOfTimes90DaysLate

借款人逾期90天(或以上)的次数

整数

NumberOfDependents

除自己(配偶、子女等)以外的家庭受养人人数

整数

如今在银行、消费金融公司等各种贷款业务机构,普遍使用信用评分,对客户实行打分制,以期对客户有一个优质与否的评判。评分卡分为三类分别为:

A卡(Application score card)申请评分卡

B卡(Behavior score card)行为评分卡

C卡(Collection score card)催收评分卡

评分机制的区别在于:
1.使用的时间不同。分别侧重贷前、贷中、贷后;

2.数据要求不同。A卡一般可做贷款0-1年的信用分析,B卡则是在申请人有了一定行为后,有了较大数据进行的分析,一般为3-5年,C卡则对数据要求更大,需加入催收后客户反应等属性数据。

3.每种评分卡的模型会不一样。在A卡中常用的有逻辑回归,AHP等,而在后面两种卡中,常使用多因素逻辑回归,精度等方面更好。

逻辑回归终止条件代码 逻辑回归的假设条件_机器学习_14

2.1.1数据预处理

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestRegressor
import seaborn as sns
from scipy import stats
import copy
 
%matplotlib inline
 
train_data = pd.read_csv('cs-training.csv')
train_data = train_data.iloc[:,1:]
train_data.info()

逻辑回归终止条件代码 逻辑回归的假设条件_逻辑回归终止条件代码_15

2.1.1.1处理缺失值

可以看到数据方面,对于缺失比较多的MonthlyIncome,在此建立随机森林模型进行填补,而缺失较少的NumberOfDependts,则直接删除缺样本。

mData = train_data.iloc[:,[5,0,1,2,3,4,6,7,8,9]]
train_known = mData[mData.MonthlyIncome.notnull()].as_matrix()
train_unknown = mData[mData.MonthlyIncome.isnull()].as_matrix()
train_X = train_known[:,1:]
train_y = train_known[:,0]
rfr = RandomForestRegressor(random_state=0,n_estimators=200,max_depth=3,n_jobs=-1)
rfr.fit(train_X,train_y)
predicted_y = rfr.predict(train_unknown[:,1:]).round(0)
train_data.loc[train_data.MonthlyIncome.isnull(),'MonthlyIncome'] = predicted_y
 
train_data = train_data.dropna()
train_data = train_data.drop_duplicates()
2.1.1.2处理异常值

缺失值处理后,来处理异常值。异常值一般是指偏离数据较大的值。例如在统计学中,常把低于 Q1-1.5IQR的值和高于Q3+1.5IQR的值作为异常值。通过绘制箱型图能很明显的看到异常值,例如:

train_box = train_data.iloc[:,[3,7,9]]
train_box.boxplot()

逻辑回归终止条件代码 逻辑回归的假设条件_d3_16


很明显可以看到,在这三个特征之中有两组样本偏离了其他样本的分布,可以将其去除,此外,我们发现在age为0的样本,这很明显是不符合常识的,应同样作为异常值舍弃。

train_data = train_data[train_data['NumberOfTime30-59DaysPastDueNotWorse']<90]
train_data = train_data[train_data.age>0]
train_data['SeriousDlqin2yrs'] = 1-train_data['SeriousDlqin2yrs'] #使好客户为1,违约客户为0
2.1.1.3数据切分

为了使得能够更好地检验模型效果,我们将数据切分化为训练集和测试集。测试集取原数据的30%

from sklearn.cross_validation import train_test_split
y = train_data.iloc[:,0]
X = train_data.iloc[:,1:]
train_X,test_X,train_y,test_y = train_test_split(X,y,test_size =0.3,random_state=0)
ntrain_data = pd.concat([train_y,train_X],axis=1)
ntest_data = pd.concat([test_y,test_X],axis=1)

2.1.2探索性分析(EDA)

在建立模型之前,我们一般会对现有的数据进行 探索性数据分析(Exploratory Data Analysis) 。 EDA是指对已有的数据(特别是调查或观察得来的原始数据)在尽量少的先验假定下进行探索。常用的探索性数据分析方法有:直方图、散点图和箱线图等。

age = ntrain_data['age']
sns.distplot(age)

逻辑回归终止条件代码 逻辑回归的假设条件_逻辑回归终止条件代码_17


可以看到,年龄的分布大致呈正态分布,符合统计分析假设。

mi = ntrain_data[['MonthlyIncome']]
sns.distplot(mi)

逻辑回归终止条件代码 逻辑回归的假设条件_d3_18


同样,收入的分布也大致呈正态分布。

2.1.3变量选择

2.1.3.1分箱处理

首先,需要将特征进行分箱处理。分箱是将连续特征离散化的一种方式,一般有等距,等频,卡方分箱的等多种方式,合理的分箱可以使模型更加精准。

def mono_bin(Y, X, n=10):
    r = 0
    good=Y.sum()
    bad=Y.count()-good
    while np.abs(r) < 1: 
        d1 = pd.DataFrame({"X": X, "Y": Y, "Bucket": pd.qcut(X, n)})
        d2 = d1.groupby('Bucket', as_index = True)
        r, p = stats.spearmanr(d2.mean().X, d2.mean().Y)  
        n = n - 1
    d3 = pd.DataFrame(d2.X.min(), columns = ['min'])
    d3['min']=d2.min().X
    d3['max'] = d2.max().X
    d3['sum'] = d2.sum().Y
    d3['total'] = d2.count().Y
    d3['rate'] = d2.mean().Y
    d3['woe']=np.log((d3['rate']/good)/((1-d3['rate'])/bad))
    d3['goodattribute']=d3['sum']/good
    d3['badattribute']=(d3['total']-d3['sum'])/bad
    iv=((d3['goodattribute']-d3['badattribute'])*d3['woe']).sum()
    d4 = (d3.sort_index(by = 'min')).reset_index(drop=True)
    woe=list(d4['woe'].round(3))
    cut=[]
    cut.append(float('-inf'))
    for i in range(1,n+1):
        qua=X.quantile(i/(n+1))
        cut.append(round(qua,4))
    cut.append(float('inf'))
    return d4,iv,cut,woe
x1_d,x1_iv,x1_cut,x1_woe = mono_bin(train_y,train_X.RevolvingUtilizationOfUnsecuredLines)
 
x2_d,x2_iv,x2_cut,x2_woe = mono_bin(train_y,train_X.age)
 
x4_d,x4_iv,x4_cut,x4_woe = mono_bin(train_y,train_X.DebtRatio)
 
x5_d,x5_iv,x5_cut,x5_woe = mono_bin(train_y,train_X.MonthlyIncome)

对于 RevolvingUtilizationOfUnsecuredLines、age、DebtRatio和MonthlyIncome我们使用这种方式进行分类。

然而,其他的变量无法通过这种方式分箱,故我们使用人工选择的方式进行:

cutx3 = [-inf, 0, 1, 3, 5, +inf]
cutx6 = [-inf, 1, 2, 3, 5, +inf]
cutx7 = [-inf, 0, 1, 3, 5, +inf]
cutx8 = [-inf, 0,1,2, 3, +inf]
cutx9 = [-inf, 0, 1, 3, +inf]
cutx10 = [-inf, 0, 1, 2, 3, 5, +inf]

以NumberOfTime30-59DaysPastDueNotWorse为例:

def woe_value(d1):
    d2 = d1.groupby('Bucket', as_index = True)
    good=train_y.sum()
    bad=train_y.count()-good
    d3 = pd.DataFrame(d2.X.min(), columns = ['min'])
    d3['min']=d2.min().X
    d3['max'] = d2.max().X
    d3['sum'] = d2.sum().Y
    d3['total'] = d2.count().Y
    d3['rate'] = d2.mean().Y
    d3['woe'] = np.log((d3['rate']/good)/((1-d3['rate'])/bad))
    d3['goodattribute']=d3['sum']/good
    d3['badattribute']=(d3['total']-d3['sum'])/bad
    iv=((d3['goodattribute']-d3['badattribute'])*d3['woe']).sum()
    d4 = (d3.sort_index(by = 'min')).reset_index(drop=True)
    woe=list(d4['woe'].round(3))
    return d4,iv,woe
d1 = pd.DataFrame({"X": train_X['NumberOfTime30-59DaysPastDueNotWorse'], "Y": train_y})
d1['Bucket'] = d1['X']
d1_x1 = d1.loc[(d1['Bucket']<=0)]
d1_x1.loc[:,'Bucket']="(-inf,0]"
 
 
d1_x2 = d1.loc[(d1['Bucket']>0) & (d1['Bucket']<= 1)]
d1_x2.loc[:,'Bucket'] = "(0,1]"
 
 
d1_x3 = d1.loc[(d1['Bucket']>1) & (d1['Bucket']<= 3)]
d1_x3.loc[:,'Bucket'] = "(1,3]"
 
 
d1_x4 = d1.loc[(d1['Bucket']>3) & (d1['Bucket']<= 5)]
d1_x4.loc[:,'Bucket'] = "(3,5]"
 
 
d1_x5 = d1.loc[(d1['Bucket']>5)]
d1_x5.loc[:,'Bucket']="(5,+inf)"
d1 = pd.concat([d1_x1,d1_x2,d1_x3,d1_x4,d1_x5])
 
 
x3_d,x3_iv,x3_woe= woe_value(d1)
x3_cut = [float('-inf'),0,1,3,5,float('+inf')]

在分箱的过程中,同时计算了WOE(Weight of Evidence)和IV(Information Value),前者在建立逻辑回归模型是需要将所有的变量转为WOE,而后者则可以很好的展示变量的预测能力。这两个值的计算方式如下:

逻辑回归终止条件代码 逻辑回归的假设条件_逻辑回归终止条件代码_19


逻辑回归终止条件代码 逻辑回归的假设条件_逻辑回归终止条件代码_20


在通过IV值判断之前可以先检查一下变量之间的相关性,对变量有个直观的了解:

corr = train_data.corr()
xticks = ['x0','x1','x2','x3','x4','x5','x6','x7','x8','x9','x10']
yticks = list(corr.index)
fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
sns.heatmap(corr, annot=True, cmap='rainbow', ax=ax1, annot_kws={'size': 5,  'color': 'blue'})
ax1.set_xticklabels(xticks, rotation=0, fontsize=10)
ax1.set_yticklabels(yticks, rotation=0, fontsize=10)
plt.show()

逻辑回归终止条件代码 逻辑回归的假设条件_python_21

可以看到 NumberOfTime30-59DaysPastDueNotWorse,NumberOfOpenCreditLinesAndLoans和NumberOfTime60-89DaysPastDueNotWorse这三个特征对于我们所要预测的值有较强的相关性。

接下来,看一下各个变量的IV值:

informationValue = []
informationValue.append(x1_iv)
informationValue.append(x2_iv)
informationValue.append(x3_iv)
informationValue.append(x4_iv)
informationValue.append(x5_iv)
informationValue.append(x6_iv)
informationValue.append(x7_iv)
informationValue.append(x8_iv)
informationValue.append(x9_iv)
informationValue.append(x10_iv)
informationValue
 
index=['x1','x2','x3','x4','x5','x6','x7','x8','x9','x10']
index_num = range(len(index))
ax=plt.bar(index_num,informationValue,tick_label=index)
plt.show()

逻辑回归终止条件代码 逻辑回归的假设条件_逻辑回归终止条件代码_22

通过IV值判断变量预测能力的标准是:

逻辑回归终止条件代码 逻辑回归的假设条件_机器学习_23

可以看到,对于X4,X5,X6,X8,以及X10而言,IV值都比较低,因此可以舍弃这些预言能力较差的特征

2.1.3.2WOE转换

接下来,将所有的需要的特征woe化,并将不需要的特征舍弃,仅保留WOE转码后的变量:

def trans_woe(var,var_name,x_woe,x_cut):
    woe_name = var_name + '_woe'
    for i in range(len(x_woe)):
        if i == 0:
            var.loc[(var[var_name]<=x_cut[i+1]),woe_name] = x_woe[i]
        elif (i>0) and (i<= len(x_woe)-2):
            var.loc[((var[var_name]>x_cut[i])&(var[var_name]<=x_cut[i+1])),woe_name] = x_woe[i]
        else:
            var.loc[(var[var_name]>x_cut[len(x_woe)-1]),woe_name] = x_woe[len(x_woe)-1]
    return var
 
x1_name = 'RevolvingUtilizationOfUnsecuredLines'
x2_name = 'age'
x3_name = 'NumberOfTime30-59DaysPastDueNotWorse'
x7_name = 'NumberOfTimes90DaysLate'
x9_name = 'NumberOfTime60-89DaysPastDueNotWorse'
 
train_X = trans_woe(train_X,x1_name,x1_woe,x1_cut)
train_X = trans_woe(train_X,x2_name,x2_woe,x2_cut)
train_X = trans_woe(train_X,x3_name,x3_woe,x3_cut)
train_X = trans_woe(train_X,x7_name,x7_woe,x7_cut)
train_X = trans_woe(train_X,x9_name,x9_woe,x9_cut)
train_X = train_X.iloc[:,-5:]

此时数据如下所示:

逻辑回归终止条件代码 逻辑回归的假设条件_python_24

2.1.4模型分析

2.1.4.1模型建立

通过调用STATSMODEL包来建立逻辑回归模型:

import statsmodels.api as sm
X1=sm.add_constant(train_X)
logit=sm.Logit(train_y,X1)
result=logit.fit()
print(result.summary())

结果如下:

逻辑回归终止条件代码 逻辑回归的假设条件_机器学习_25

2.1.4.2模型检验

模型建立后,可以通过导入测试集的数据,画出ROC曲线来判断模型的准确性:

1.对测试集进行woe转化

test_X = trans_woe(test_X,x1_name,x1_woe,x1_cut)
test_X = trans_woe(test_X,x2_name,x2_woe,x2_cut)
test_X = trans_woe(test_X,x3_name,x3_woe,x3_cut)
test_X = trans_woe(test_X,x7_name,x7_woe,x7_cut)
test_X = trans_woe(test_X,x9_name,x9_woe,x9_cut)
 
test_X = test_X.iloc[:,-5:]

2.拟合模型,画出ROC曲线得到AUC值

from sklearn import metrics
X3 = sm.add_constant(test_X)
resu = result.predict(X3)
fpr, tpr, threshold = metrics.roc_curve(test_y, resu)
rocauc = metrics.auc(fpr, tpr)
plt.plot(fpr, tpr, 'b', label='AUC = %0.2f' % rocauc)
plt.legend(loc='lower right')
plt.plot([0, 1], [0, 1], 'r--')
plt.xlim([0, 1])
plt.ylim([0, 1])
plt.ylabel('TPR')
plt.xlabel('FPR')
plt.show()

逻辑回归终止条件代码 逻辑回归的假设条件_python_26


可以看到,ACU=0.85,是可以接受的。

2.1.5建立评分卡模型

2.1.5.1评分标准

逻辑回归终止条件代码 逻辑回归的假设条件_python_27


依据以上论文资料得到:

a=log(p_good/P_bad)

Score = offset + factor * log(odds)

在建立标准评分卡之前,我们需要选取几个评分卡参数:基础分值、 PDO(比率翻倍的分值)和好坏比。 这里, 我们取600分为基础分值,PDO为20 (每高20分好坏比翻一倍),好坏比取20。

2.1.5.2建立评分卡
p = 20/np.log(2)
q = 600 - 20*np.log(20)/np.log(2)
 
def get_score(coe,woe,factor):
    scores=[]
    for w in woe:
        score=round(coe*w*factor,0)
        scores.append(score)
    return scores
 
x_coe = [2.6084,0.6327,0.5151,0.5520,0.5747,0.4074]
baseScore = round(q + p * x_coe[0], 0)x1_score = get_score(x_coe[1], x1_woe, p)
x1_score = get_score(x_coe[1], x1_woe, p)
x2_score = get_score(x_coe[2], x2_woe, p)
x3_score = get_score(x_coe[3], x3_woe, p)
x7_score = get_score(x_coe[4], x7_woe, p)
x9_score = get_score(x_coe[5], x9_woe, p)

x_coe是之前逻辑回归模型得到的系数。最后BaseScore等于589分。

通过get_score可以得到所有分段的分数,如下:

逻辑回归终止条件代码 逻辑回归的假设条件_数据分析_28


逻辑回归终止条件代码 逻辑回归的假设条件_逻辑回归终止条件代码_29

2.1.5.3自动计算评分

建立一个函数使得当输入x1,x2,x3,x7,x9的值时可以返回评分数

cut_t = [x1_cut,x2_cut,x3_cut,x7_cut,x9_cut]
def compute_score(x):        #x为数组,包含x1,x2,x3,x7和x9的取值
    tot_score = baseScore
    cut_d = copy.deepcopy(cut_t)
    for j in range(len(cut_d)):
        cut_d[j].append(x[j])
        cut_d[j].sort()
        for i in range(len(cut_d[j])):
            if cut_d[j][i] == x[j]:
                tot_score = score[j][i-1] +tot_score
    return tot_score

运行代码,得到以下结果:

逻辑回归终止条件代码 逻辑回归的假设条件_逻辑回归终止条件代码_30

2.1.6总结

至此此次基于python制作的行为评分卡就此完成。本文通过对于Kaggle上项目的数据进行分析,利用逻辑回归制作了一个简单的评分卡。在建立评分卡的过程中,首先进行了数据清洗,对缺失值和异常值进行了处理并对数据分布进行了宏观展示。然后对特征值进行了处理,将连续的变量分箱,同时计算了woe和iv值,并保留了iv值较高的变量对其woe转化。最后将woe转化后的数据进行逻辑回归分析,利用得到变量系数并自行拟定了评分标准建立了评分卡。

在整体过程中,并没有对数据进行过多的挖掘。例如:只舍弃了个别变量的异常值,亦或是对于不能自动分箱的变量采取了直观分箱的方式,并没有过多的去探究其可能对于模型的影响。这可以为后续的模型优化奠定方向。