引言
一、逻辑回归概述
从概率的角度来看:“逾期”是一个随机事件,可以用伯努利分布来刻画它的随机性。伯努利分布是一种离散的分布,用于表示0-1型事件发生的概率。
在上面的对数似然函数估计中,默认每一个样本的p是相同的,但是在申请评分卡模型中,不同申请人,逾期的概率是不同的。我们需要做的是针对不同的逾期概率区分出好样本与坏样本。
p = f ( p=f( p=f(x1 , , ,x2 , . . . , ,..., ,...,xk ) ) )
其中{x1,x2,…,xk}是申请人的个人资质。
p p p是有界的,但不可直接观测。
可以用线性回归来表示 f ( ) f() f()?
不能,因为线性回归的 p p p是无界的,而在申请评分卡模型中要求 p p p是(0,1)。同时,也不利于通过对数似然函数来求解参数
可以用逻辑回归来表示 f ( ) f() f()?
可以。
逻辑回归函数的特点:
x取值于负无穷到正无穷, p p p取值于(0,1),是有界的, f ( x ) f(x) f(x)处处可导
其函数图像为:
对数似然函数进行参数估计:
随机梯度上升法 SGD
批量梯度上升法 min batch(常用)
针对步长,可以选用自适应步长法,根据梯度对步长进行调整
梯度上升法是逼近最大值
梯度下降法是逼近最小值
二、逻辑回归中的变量选择
变量挑选的作用和目的:
- 剔除掉跟目标变量不太相关的特征
- 消除多重共线性的影响
- 增加解释性
变量挑选与降维:
变量挑选是降维的一种手段,反之,降维并不代表着变量挑选。比如:主成分分析法(PCA):虽然降维,但是并没有剔除变量
变量挑选的常用手段:
- LASSO回归
- 逐步回归法
- 随机森林法
1.LASSO回归
LASSO全称为Least absolute sgrinkage and selection operator,对回归模型特征进行压缩估计。LASSO计算量不大,并且还可以估计出变量的重要性。
原理:
LASSO回归的几何解释:
详细见:L1和L2正则几何解释
假如有两个变量,其对应权重为 w w w 1 1 1、 w w w 2 2 2,假如| w w w 1 1 1|+| w w w 2 2 2|=1(l1正则化),也就是w1和w2的绝对值之和为1,则正则化等高线为正方形,红色线是损失函数的等高线
无论是L1正则还是L2正则,最后的最优解一定是出现在损失函数和正则等高线的焦点上。
为什么L1正则更容易导致某些W变为零,本质上是因为它在空间里面形成的等高线是尖的,在轴上它会扎到loss的等高线上,如图,β1=0,β2不为0,就挑选了β2所对应的变量
超参数λ:
LASSO回归通过控制λ值来控制选择模型的特征
λ -> 0:没有正则化约束,不会剔除特征
λ ->正无穷:所有特征都不会挑选进模型
λ参数的选择非常重要,可以用交叉验证法选择最合适的λ
Group LASSO方法
- 可以指定一组变量同时被选进或者选出
适用于dummy encoding 和 one hot encoding比如:针对onehot编码,只有都被选入才有意义
2.逐步回归法
逐步挑选法分为向前挑选、向后挑选与双向挑选。逐步回归法计算量大,用的不多。最常用的还是LASSO,并且LASSO还可以估计出变量的重要性。python中也没有逐步回归法的包
评价模型的指标有:R2,precision(精确率),AIC,BIC
双向挑选用的较多,能够兼顾模型复杂度与模型精度的要求。
描述为:先两步向前挑选,再向后挑选,再反复向前向后
3.随机森林法(RF)
RF是一种集成机器学习方法,利用bootstrap和节点随机分裂技术构建多颗决策树,通过投票得到最终分类结果。RF的变量重要性度量可以作为高维数据的特征选择工具。
生成步骤:
变量的重要性:
变量的重要性,即OOB数据特征发生轻微扰动后分类正确率与扰动前分类正确率的平均减少量
计算步骤为:
- 对于每颗决策树,利用袋外数据进行预测,将袋外数据的预测误差记录下来。其每棵树的误差是{ e r r o r error error i i i}
- 随机重排每个特征(打乱特征变量的顺序),从而形成新的袋外数据,再利用袋外数据进行验证,其每个变量的误差是{
e r r o r error error i i i`}
随机重排特征,比如:原来性别特征是男、女、男,现在变成女、女、 女
- 对于某特征来说,计算其重要性是变换后的预测误差与原来相比的差的均值{ e r r o r error error i i i` - e r r o r error error i i i}
将特征按重要性从高到低排列,选出前N个特征
GBDT模型,AdaBoost模型都有特征重要性的属性
4.挑选变量总结
LASSO法是根据超参数λ来挑选变量的,是不可控的。逐步回归法计算代价大,并且python中还没有现成的包,不建议使用。在单因子分析与多因子分析后,如果变量还多的话,可以采用随机森林法来挑选变量。
三、带权重的逻辑回归模型
在违约预测模型中,常犯两种错误:
- 第一类错误:将逾期人群预测成非逾期
- 第二类错误:将非逾期人群预测成逾期
两种误判的代价是不一样的。通过加权的方式,改善模型对于两类样本的区分。
设{
y y yi}对应的权重向量是{
w w wi},则带权重的对数似然函数是:
用梯度上升法求出带权重的参数估计。
评分卡模型中:
- 逾期样本的权重总是高于非逾期样本的权重
- 可以用交叉验证法选择合适的权重
- 也可以跟业务相结合:权重通常跟利率有关。利率高,逾期样本的权重相对低。
四、代码实现
自定义函数部分
#!usr/bin/env python
# -*- coding:utf-8 -*-
"""
@author: admin
@file: scorecardfunction.py
@time: 2021/03/12
@desc:
"""
import random
import pandas as pd
import numpy as np
def timeWindowSelection(df, daysCol, time_windows):
"""
计算每一个时间切片内的事件的累积频率
:param df: 数据集
:param daysCol:时间间隔
:param time_windows:时间窗口列表
:return:返回覆盖度
"""
freq_tw = {
}
for tw in time_windows:
freq = sum(df[daysCol].apply(lambda x: int(x <= tw)))
freq_tw[tw] = freq / df[daysCol].shape[0]
return freq_tw
def ChangeContent(x):
"""
数据预处理:统一大小写、统一_PHONE与_MOBILEPHONE
:param x: UserupdateInfo1列字符
:return:返回处理后的字符
"""
y = x.upper()
if y == '_MOBILEPHONE':
y = '_PHONE'
return y
def missingCategorical(df, x):
"""
计算类别型变量的缺失比例
:param df: 数据集
:param x: 类别型变量
:return: 返回缺失比例
"""
missing_vals = df[x].map(lambda x: int(x != x))
return sum(missing_vals) * 1.0 / df.shape[0]
def missingContinuous(df, x):
"""
计算连续型变量的缺失比例
:param df:
:param x:
:return:
"""
missing_vals = df[x].map(lambda x: int(np.isnan(x)))
return sum(missing_vals) * 1.0 / df.shape[0]
def makeUpRandom(x, sampledList):
"""
对于连续型变量,利用随机抽样法补充缺失值
:param x:连续型变量的值
:param sampledList:随机抽样的列表
:return:补缺后的值
"""
# 非缺失,直接返回;缺失,填充后返回
if x == x:
return x
else:
return random.sample(sampledList, 1)
def AssignBin(x, cutOffPoints, special_attribute=[]):
'''
设置使得分箱覆盖所有训练样本外可能存在的值
:param x: the value of variable
:param cutOffPoints: the ChiMerge result for continous variable连续变量的卡方分箱结果
:param special_attribute :具有特殊含义的特殊值
:return: bin number, indexing from 0
for example, if cutOffPoints = [10,20,30], if x = 7, return Bin 0. If x = 35, return Bin 3
即将cutOffPoints = [10,20,30]分为4段,[0,10],(10,20],(20,30],(30,30+]
'''
numBin = len(cutOffPoints) + 1 + len(special_attribute)
if x in special_attribute:
i = special_attribute.index(x) + 1
return 'Bin {}'.format(0 - i)
if x <= cutOffPoints[0]:
return 'Bin 0'
elif x > cutOffPoints[-1]:
return 'Bin {}'.format(numBin - 1)
else:
for i in range(0, numBin - 1):
if cutOffPoints[i] < x <= cutOffPoints[i + 1]:
return 'Bin {}'.format(i + 1)
def MaximumBinPcnt(df, col):
"""
:param df:
:param col:
:return:
"""
N = df.shape[0]
total = df.groupby([col])[col].count()
pcnt = total * 1.0 / N
return max(pcnt)
def CalcWOE(df, col, target):
'''
计算WOE
:param df: dataframe containing feature and target
:param col: 需要计算WOE与IV的特征变量,通常是类别型变量
:param target: 目标变量
:return: WOE and IV in a dictionary
'''
total = df.groupby([col])[target].count()
total = pd.DataFrame({
'total': total})
bad = df.groupby([col])[target].sum()
bad = pd.DataFrame({
'bad': bad})
regroup = total.merge(bad, left_index=True, right_index=True, how='left')
regroup.reset_index(level=0, inplace=True)
# 总数量
N = sum(regroup['total'])
# 坏的数量
B = sum(regroup['bad'])
regroup['good'] = regroup['total'] - regroup['bad']
# 好的数量
G = N - B
regroup['bad_pcnt'] = regroup['bad'].map(lambda x: x * 1.0 / B)
regroup['good_pcnt'] = regroup['good'].map(lambda x: x * 1.0 / G)
regroup['WOE'] = regroup.apply(lambda x: np.log(x.good_pcnt * 1.0 / x.bad_pcnt), axis=1)
# 计算WOE
WOE_dict = regroup[[col, 'WOE']].set_index(col).to_dict(orient='index')
# 计算IV
IV = regroup.apply(lambda x: (x.good_pcnt - x.bad_pcnt) * np.log(x.good_pcnt * 1.0 / x.bad_pcnt), axis=1)
IV = sum(IV)
return {
"WOE": WOE_dict, 'IV': IV}
def BadRateEncoding(df, col, target):
'''
bad rate编码
:param df: dataframe containing feature and target
:param col: 需要以bad rate进行编码的特征变量,通常是类别型变量
:param target: good/bad indicator
:return: 返回被bad rate编码的类别型变量
'''
total = df.groupby([col])[target].count()
total = pd.DataFrame({
'total': total})
bad = df.groupby([col])[target].sum()
bad = pd.DataFrame({
'bad': bad})
regroup = total.merge(bad, left_index=True, right_index=True, how=