分类问题经常用逻辑回归,模型简单,容易训练,可解释性强。
逻辑回归既可以看作是分类算法,也可以看作是回归算法。通常作为分类算法用,只能解决二分类问题
LightGBM 概念:
LigthGBM是boosting集合模型中的新进成员,由微软提供,它和XGBoost一样是对GBDT的高效实现,原理上它和GBDT及XGBoost类似,都采用损失函数的负梯度作为当前决策树的残差近似值,去拟合新的决策树。
LightGBM相比XGBOOST在原理和性能上的差异?
1.速度和内存上的优化:
xgboost用的是预排序(pre-sorted)的方法, 空间消耗大
- 这样的算法需要保存数据的特征值,还保存了特征排序的结果(例如排序后的索引,为了后续快速的计算分割点),这里需要消耗训练数据两倍的内存。
- 其次,时间上也有较大的开销,在遍历每一个分割点的时候,都需要进行分裂增益的计算,消耗的代价大。
LightGBM用的是直方图(Histogram)的决策树算法,直方图算法的基本思想是先把连续的浮点特征值离散化成k个整数,同时构造一个宽度为k的直方图。在遍历数据的时候,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。
2.准确率上的优化:
- xgboost 通过level(depth)-wise策略生长树, Level-wise过一次数据可以同时分裂同一层的叶子,容易进行多线程优化,也好控制模型复杂度,不容易过拟合。但实际上Level-wise是一种低效的算法,因为它不加区分的对待同一层的叶子,带来了很多没必要的开销,因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。
- LightGBM通过leaf-wise(best-first)策略来生长树, Leaf-wise则是一种更为高效的策略,每次从当前所有叶子中,找到分裂增益最大的一个叶子,然后分裂,如此循环。因此同Level-wise相比,在分裂次数相同的情况下,Leaf-wise可以降低更多的误差,得到更好的精度。Leaf-wise的缺点是可能会长出比较深的决策树,产生过拟合。因此LightGBM在Leaf-wise之上增加了一个最大深度的限制,在保证高效率的同时防止过拟合。
3.对类别型特征的处理:
- xgboost不支持直接导入类别型变量,需要预先对类别型变量作亚编码等处理。如果类别型特征较多,会导致哑变量处理后衍生后的特征过多,学习树会生长的非常不平衡,并且需要非常深的深度才能来达到较好的准确率。
- LightGBM可以支持直接导入类别型变量(导入前需要将字符型转为整数型,并且需要声明类别型特征的字段名),它没有对类别型特征进行独热编码,因此速度比独热编码快得多。LightGBM使用了一个特殊的算法来确定属性特征的分割值。基本思想是对类别按照与目标标签的相关性进行重排序,具体一点是对于保存了类别特征的直方图根据其累计值(sum_gradient/sum_hessian)重排序,在排序好的直方图上选取最佳切分位置。
逻辑回归构建评分卡
import pandas as pd
from sklearn.metrics import roc_auc_score,roc_curve,auc
from sklearn.model_selection import train_test_split
from sklearn import metrics
from sklearn.linear_model import LogisticRegression
import numpy as np
import random
import math #import math : 导入这个math模块,就可以用模块里面的一些做数学运算的函数了。
此表是B卡的用户,属于放款中
data=pd.read_csv("bcard.txt")
data
| 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 |
0 | 2018-10-31 | 0.0 | A10000005 | 0.675349 | 0.144072 | 0.186899 | 0.483640 | 0.928328 | 0.369644 | -0.322581 | 0.023810 | 0.00 | 0.217949 |
1 | 2018-07-31 | 0.0 | A1000002 | 0.825269 | 0.398688 | 0.139396 | 0.843725 | 0.605194 | 0.406122 | -0.128677 | 0.023810 | 0.00 | 0.423077 |
2 | 2018-09-30 | 0.0 | A1000011 | 0.315406 | 0.629745 | 0.535854 | 0.197392 | 0.614416 | 0.320731 | 0.062660 | 0.023810 | 0.10 | 0.448718 |
3 | 2018-07-31 | 0.0 | A10000481 | 0.002386 | 0.609360 | 0.366081 | 0.342243 | 0.870006 | 0.288692 | 0.078853 | 0.071429 | 0.05 | 0.179487 |
4 | 2018-07-31 | 0.0 | A1000069 | 0.406310 | 0.405352 | 0.783015 | 0.563953 | 0.715454 | 0.512554 | -0.261014 | 0.023810 | 0.00 | 0.423077 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
95801 | 2018-11-30 | 0.0 | Ab99_96436391998107976 | 0.890233 | 0.442687 | 0.802687 | 0.776982 | 0.638971 | 0.605522 | 0.078853 | 0.142857 | 0.25 | 0.076923 |
95802 | 2018-11-30 | 0.0 | Ab99_96436391998176292 | 0.161840 | 0.495766 | 0.085750 | 0.536738 | 0.596144 | 0.132972 | 0.078853 | 0.023810 | 0.00 | 0.076923 |
95803 | 2018-11-30 | 0.0 | Ab99_96436391998322771 | 0.746522 | 0.732739 | 0.025475 | 0.831805 | 0.642904 | 0.029297 | 0.078853 | 0.023810 | 0.00 | 0.076923 |
95804 | 2018-11-30 | 0.0 | Ab99_96436391998973383 | 0.176846 | 0.749610 | 0.933879 | 0.506921 | 0.867099 | 0.751643 | 0.078853 | 0.023810 | 0.02 | 0.076923 |
95805 | 2018-11-30 | 0.0 | Ab99_96436392001380983 | 0.417920 | 0.650343 | 0.985863 | 0.374100 | 0.330634 | 0.596833 | 0.078853 | 0.071429 | 0.62 | 0.076923 |
95806 rows × 13 columns
person_info 用户相关的信息 身份特质
finance_info:财务相关的信息 履约能力
credit_info: 信用相关的信息 信用历史
act_info; 行为信息 行为偏好
score 结尾的是,其他信用机构的评分
身份特质: 稳定性,所在公司,职业类型,消费稳定度,近一年内使用手机号码数,手机号码稳定天数,地址稳定天数
履约能力:是否有车,是否有房,近一个月流动资产日均值,近三个月流动资产日均值,近六个月流动资产日均值,近一年流动资产日均值,近一个月理财产品总收益,近三个月理财产品总收益,近六个月理财产品总收益,近一年理财产品总收益,历史理财产品总收益,近一个月支付总金额,近三个月支付总金额,近六个月支付总金额,近一个月消费总金额,近三个月消费总金额,近六个月消费总金额,
信用历史:近一个月主动查询金融机构次数,近三个月主动查询金融机构数,近六个月主动查询金融机构数,近一个月信贷类还款总金额,近三个月信贷类还款总金额,近六个月信贷类还款总金额,近一年信贷类还款总金额,近一年信贷类还款月份数,近一年M1状态,近一年M3状态,近一年M6状态,近两年M1状态,近两年M3状态,近两年M6状态,近五年M1状态,近五年M3状态,近五年M6状态
人脉关系:近1年人脉圈稳定度,社交影响力指数,信用环境指数
行为偏好:消费区域个数,近一年支付活跃场景数,近一年母婴消费总金额,近一年母婴消费总笔数,近一年游戏消费总金额,近一年游戏消费总笔数,近三个月家居建材消费总金额,进三个月家具建材消费总笔数,近一年汽车消费总金额,近一年汽车消费总笔数,近一年航旅度假消费总金额,近一年航旅度假消费总笔数
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.obs_mth.unique() #数据经过脱敏,把一个月份的放到一天中
#array(['2018-10-31', '2018-07-31', '2018-09-30', '2018-06-30', '2018-11-30'],dtype=object)
#划分训练集和跨时间验证集
train = data[data.obs_mth != '2018-11-30'].reset_index().copy() #train.count() 查看总条数79831 #划分训练集
val = data[data.obs_mth == '2018-11-30'].reset_index().copy() #val。count() 15975 #跨时间验证集
#把最后一个月留出来,看模型是否稳定,迁移能力强不强,不能是在线下特别好,上线就垮掉
#.copy() 函数返回一个字典的浅复制。浅复制是指当对象的字段值被复制时,字段引用的对象不会被复制
train
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 | |
0 | 0 | 2018-10-31 | 0.0 | A10000005 | 0.675349 | 0.144072 | 0.186899 | 0.483640 | 0.928328 | 0.369644 | -0.322581 | 0.023810 | 0.00 | 0.217949 |
1 | 1 | 2018-07-31 | 0.0 | A1000002 | 0.825269 | 0.398688 | 0.139396 | 0.843725 | 0.605194 | 0.406122 | -0.128677 | 0.023810 | 0.00 | 0.423077 |
2 | 2 | 2018-09-30 | 0.0 | A1000011 | 0.315406 | 0.629745 | 0.535854 | 0.197392 | 0.614416 | 0.320731 | 0.062660 | 0.023810 | 0.10 | 0.448718 |
3 | 3 | 2018-07-31 | 0.0 | A10000481 | 0.002386 | 0.609360 | 0.366081 | 0.342243 | 0.870006 | 0.288692 | 0.078853 | 0.071429 | 0.05 | 0.179487 |
4 | 4 | 2018-07-31 | 0.0 | A1000069 | 0.406310 | 0.405352 | 0.783015 | 0.563953 | 0.715454 | 0.512554 | -0.261014 | 0.023810 | 0.00 | 0.423077 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
79826 | 79826 | 2018-09-30 | 0.0 | Ab99_96436392005147255 | 0.905578 | 0.927706 | 0.994447 | 0.315842 | 0.959443 | 0.042640 | 0.078853 | 0.071429 | 0.13 | 0.076923 |
79827 | 79827 | 2018-10-31 | 0.0 | Ab99_96436392005205003 | 0.639265 | 0.219267 | 0.845014 | 0.751332 | 0.275557 | 0.902642 | 0.078853 | 0.023810 | 0.00 | 0.076923 |
79828 | 79828 | 2018-10-31 | 0.0 | Ab99_96436392005412387 | 0.355061 | 0.837747 | 0.931882 | 0.442463 | 0.579277 | 0.740754 | 0.078853 | 0.023810 | 0.02 | 0.076923 |
79829 | 79829 | 2018-10-31 | 0.0 | Ab99_96436392006241624 | 0.797237 | 0.501238 | 0.490850 | 0.592068 | 0.479618 | 0.859870 | 0.078853 | 0.023810 | 0.00 | 0.076923 |
79830 | 79830 | 2018-10-31 | 0.0 | Ab99_96436392007495598 | 0.990920 | 0.833572 | 0.993425 | 0.732783 | 0.925975 | 0.395655 | 0.078853 | 0.023810 | 0.00 | 0.076923 |
79831 rows × 14 columns
#这是我们全部的变量,info结尾的是自己做的无监督系统输出的个人表现,score结尾的是收费的外部征信数据
feature_lst = ['person_info','finance_info','credit_info','act_info','td_score','jxl_score','mj_score','rh_score']
feature_lst
'''
['person_info',
'finance_info',
'credit_info',
'act_info',
'td_score',
'jxl_score',
'mj_score',
'rh_score']
'''
#测试集
x = train[feature_lst]
y = train['bad_ind']
#验证集
val_x = val[feature_lst]
val_y = val['bad_ind']
#创建逻辑回归模型,训练评分卡模型
lr_model = LogisticRegression(C=0.1) #c=0.1 学习率
lr_model.fit(x,y)
'''
LogisticRegression(C=0.1)
'''
data.groupby(['bad_ind']).count()
| obs_mth | uid | td_score | jxl_score | mj_score | rh_score | zzc_score | zcx_score | person_info | finance_info | credit_info | act_info |
bad_ind | | | | | | | | | | | | |
0.0 | 94008 | 94008 | 94008 | 94008 | 94008 | 94008 | 94008 | 94008 | 94008 | 94008 | 94008 | 94008 |
1.0 | 1798 | 1798 | 1798 | 1798 | 1798 | 1798 | 1798 | 1798 | 1798 | 1798 | 1798 | 1798 |
# 利用模型预测结果 predict_proba 得到是预测的概率值 [第一个是是0的概率,第二个是是1的概率]
lr_model.predict_proba(x)
lr_model.predict_proba(x)
'''
array([[0.9926959 , 0.0073041 ],
[0.99102314, 0.00897686],
[0.98376226, 0.01623774],
...,
[0.9757364 , 0.0242636 ],
[0.97646662, 0.02353338],
[0.97714918, 0.02285082]])
'''
#训练集的表现
y_pred = lr_model.predict_proba(x)[:,1] # 取出是坏人的概率
#评估模型利用 KS值 还要绘制ROC曲线
fpr_lr_train,tpr_lr_train,_ = roc_curve(y,y_pred)
#计算训练集的ks值
train_ks = abs(fpr_lr_train-tpr_lr_train).max() #abs(): 绝对值
train_ks
#0.4151676259891534
#验证集的表现
y_pred = lr_model.predict_proba(val_x)[:,1] # 取出是坏人的概率
#评估模型利用 KS值 还要绘制ROC曲线
fpr_lr,tpr_lr,_ = roc_curve(val_y,y_pred) #验证集跟预测集比
#计算训练集的ks值
val__ks = abs(fpr_lr-tpr_lr).max() #abs(): 绝对值
val__ks
#0.3856283523530577
0.4151676259891534-0.3856283523530577
#0.02953927363609571
# 在验证集的表现上 比训练集差7.1。 如果是差百分之十以上,是不可以接受,必须在百分之十以内才可以接着用
0.02953927363609571/0.4151676259891534
#0.07115023375369699
#图形展示
import matplotlib.pyplot as plt
plt.plot(fpr_lr_train,tpr_lr_train,label = 'train LR')
plt.plot(fpr_lr,tpr_lr,label='evl LR')
plt.plot([0,1],[0,1],'k--')
plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title('ROC Curve')
plt.legend(loc = 'best')
#分别将字符串1、字符串2、字符串3……标注到图中,每个字符串对应的图标为画图时的图标。
#loc = 'best'是选择最佳得位置放置图标
plt.show()
#图形显示,黄的在下面,黄色的抖动更厉害。理想的效果是比较平滑的线,
模型评价
- KS 0.6 最好是0~1 0.2可以用 0.8左右
- ROC曲线上的点 找到 Y-X 最大的
- AUC CTR预估(推荐,计算广告,搜索)
- auc>0.6 就是可以用的 0.8左右
- AUC 最小值 0.5
KS值的计算
通过roc 计算出fpr,tpr
ks = abs(fpr- tpr).max()
模型评价的时候,训练集和验证集 表现(ks值)相差不要超过10%
使用第一种方式,方差膨胀系数作为特征选择
#再做特征筛选 计算VIF值 方差膨胀系数
from statsmodels.stats.outliers_influence import variance_inflation_factor
X = np.array(x)
for i in range(X.shape[1]):
print(variance_inflation_factor(X,i))
'''
1.302139754557776
1.9579535743187257
1.2899442089163675
2.9681708673324065
3.287109972276014
3.286493284008914
3.317508798033784
3.2910065791107597
'''
使用第二种方式lightgbm,作为特征筛选
lightgbm的原理是把数据丢到模型中,最主要的是feature_importance是当前模型的特征重要程度
# lightgbm 进行特征筛选
import lightgbm as lgb
from sklearn.model_selection import train_test_split
train_x,test_x,train_y,test_y = train_test_split(x,y,random_state=0,test_size=0.2)
def lgb_test(train_x,train_y,test_x,test_y):
clf =lgb.LGBMClassifier(boosting_type = 'gbdt',
#LGBMClassifier分类器
#gbdt全称梯度下降树,GBDT GBDT(Gradient Boosting Decision Tree)(梯度提升决策树)
objective = 'binary',#binary:logistic: 二分类逻辑回归,输出为概率
metric = 'auc', #auc:曲线下的面积; etric评价函数
learning_rate = 0.1, #学习率,控制每次迭代更新权重时的步长,默认0.3,这里设置得步长是0.1
n_estimators = 24, #生成最大树的数目,也是最大的迭代次数。
max_depth = 5, #树的最大深度
num_leaves = 20,# 一棵树上的叶子数
max_bin = 45, #分段数量 bin的最大数 决定 特征的最大组数
min_data_in_leaf = 6, #min_data_in_leaf:一个叶子上数据的最小数量. 可以用来处理过拟合
bagging_fraction = 0.6, #不进行重采样的情况下随机选择部分数据
bagging_freq = 0, #bagging的次数。0表示禁用bagging,非零值表示执行k次bagging
feature_fraction = 0.8, # 建树的特征选择比例 #通过设置feature_fraction使用特征子采样
)
# 一般来说n_estimators太小,容易欠拟合,n_estimators太大,又容易过拟合,一般选择一个适中的数值。默认是100。
#降低学习率,增加迭代次数.学习率通常和迭代次数配合使用
#max_bin:建议使用较小的 max_bin (e.g. 63) 来获得更快的速度. 为直方图算法中特征值离散化的分段数量
#使用较大的直方图数目 max_bin,这样会牺牲训练速度
#min_data_in_leaf 是一个很重要的参数, 也叫min_child_samples,
#它的值取决于训练数据的样本个树和num_leaves. 将其设置的较大可以避免生成一个过深的树, 但有可能导致欠拟合。
# fit传入数据训练模型 eval_metric =‘auc’ 利用AUC来评价模型效果
clf.fit(train_x,train_y,eval_set = [(train_x,train_y),(test_x,test_y)],eval_metric = 'auc')
# 返回的是最好的验证集和训练集的 AUC. clf LGBMClassifier 模型
return clf,clf.best_score_['valid_1']['auc'],
lgb_model , lgb_auc = lgb_test(train_x,train_y,test_x,test_y)
#训练出来最好的是training's auc: 0.839688 valid_1's auc: 0.812833
'''
[LightGBM] [Warning] feature_fraction is set=0.8, colsample_bytree=1.0 will be ignored. Current value: feature_fraction=0.8
[LightGBM] [Warning] min_data_in_leaf is set=6, min_child_samples=20 will be ignored. Current value: min_data_in_leaf=6
[LightGBM] [Warning] bagging_fraction is set=0.6, subsample=1.0 will be ignored. Current value: bagging_fraction=0.6
[LightGBM] [Warning] bagging_freq is set=0, subsample_freq=0 will be ignored. Current value: bagging_freq=0
[1] training's auc: 0.790509 valid_1's auc: 0.785818
[2] training's auc: 0.801828 valid_1's auc: 0.791351
[3] training's auc: 0.804934 valid_1's auc: 0.79404
[4] training's auc: 0.811643 valid_1's auc: 0.80383
[5] training's auc: 0.812088 valid_1's auc: 0.804768
[6] training's auc: 0.819316 valid_1's auc: 0.805852
[7] training's auc: 0.822285 valid_1's auc: 0.809526
[8] training's auc: 0.825303 valid_1's auc: 0.811742
[9] training's auc: 0.825884 valid_1's auc: 0.811141
[10] training's auc: 0.827035 valid_1's auc: 0.811658
[11] training's auc: 0.827486 valid_1's auc: 0.810881
[12] training's auc: 0.828296 valid_1's auc: 0.810599
[13] training's auc: 0.828793 valid_1's auc: 0.809961
[14] training's auc: 0.830189 valid_1's auc: 0.810371
[15] training's auc: 0.830516 valid_1's auc: 0.810961
[16] training's auc: 0.832925 valid_1's auc: 0.810342
[17] training's auc: 0.832958 valid_1's auc: 0.809997
[18] training's auc: 0.833791 valid_1's auc: 0.810946
[19] training's auc: 0.835088 valid_1's auc: 0.811165
[20] training's auc: 0.835256 valid_1's auc: 0.812049
[21] training's auc: 0.836739 valid_1's auc: 0.81188
[22] training's auc: 0.838448 valid_1's auc: 0.812217
[23] training's auc: 0.838992 valid_1's auc: 0.812342
[24] training's auc: 0.839688 valid_1's auc: 0.812833
'''
#feature_importance特征重要性
feature_importance = pd.DataFrame({'name':lgb_model.booster_.feature_name(),'importance':lgb_model.feature_importances_})
feature_importance.sort_values(by=['importance'],ascending = False)
| name | importance |
2 | credit_info | 95 |
3 | act_info | 66 |
0 | person_info | 55 |
5 | jxl_score | 51 |
1 | finance_info | 50 |
4 | td_score | 50 |
7 | rh_score | 46 |
6 | mj_score | 43 |
#feature_name 返回特征名字
lgb_model.booster_.feature_name()
'''
['person_info',
'finance_info',
'credit_info',
'act_info',
'td_score',
'jxl_score',
'mj_score',
'rh_score']
'''
lgb_model.feature_importances_
'''
array([55, 50, 95, 66, 50, 51, 43, 46])
'''
#确立新的特征
feature_lst = ['person_info','finance_info','credit_info','act_info']
x = train[feature_lst]
y = train['bad_ind']
val_x = val[feature_lst]
val_y = val['bad_ind']
lr_model = LogisticRegression(C=0.1,class_weight='balanced')
lr_model.fit(x,y)
y_pred = lr_model.predict_proba(x)[:,1]
fpr_lr_train,tpr_lr_train,_ = roc_curve(y,y_pred)
train_ks = abs(fpr_lr_train - tpr_lr_train).max()
print('train_ks : ',train_ks)
y_pred = lr_model.predict_proba(val_x)[:,1]
fpr_lr,tpr_lr,_ = roc_curve(val_y,y_pred)
val_ks = abs(fpr_lr - tpr_lr).max()
print('val_ks : ',val_ks)
from matplotlib import pyplot as plt
plt.plot(fpr_lr_train,tpr_lr_train,label = 'train LR')
plt.plot(fpr_lr,tpr_lr,label = 'evl LR')
plt.plot([0,1],[0,1],'k--')
plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title('ROC Curve')
plt.legend(loc = 'best')
plt.show()
#train_ks : 0.4482325608488951
#val_ks : 0.4198642457760936
0.4482325608488951-0.4198642457760936
#0.02836831507280152
0.02836831507280152/0.4482325608488951
#0.06328927782282386
lightGBM特征选择(embeded)
- 训练lightgbm模型
- 通过模型的lgb_model.feature_importances_ 可以输出模型重要性的得分,通过这个得分可以确定哪个特征比较重要
生成模型评估报告
# 生成模型评估报告
model=lr_model
row_num,col_num = 0,0
#将所有的数据按照 预测的坏人的概率进行分箱
bins = 20 #分成二十箱
model.predict_proba(val_x)
'''
array([[0.11136699, 0.88863301],
[0.60217326, 0.39782674],
[0.63381413, 0.36618587],
...,
[0.48207382, 0.51792618],
[0.46982277, 0.53017723],
[0.1055019 , 0.8944981 ]])
'''
# 取出验证集中 所有样本预测的是坏人的概率
Y_predict = [s[1] for s in model.predict_proba(val_x)] #1代表是坏人,遍历出所有是坏人的概率值
# 验证数据中 真实情况(0,没有逾期,1,逾期)
Y = val_y #val_y验证集中的坏人
nrows = Y.shape[0] #15975 #验证集中11月份
lis=[(Y_predict[i],Y[i]) for i in range(nrows)] #[(0.88863300866552, 0.0), 验证集中预测坏人的概率,坏人,
ks_lis = sorted(lis,key = lambda x:x[0],reverse = True) #概率从高到低排序,降序排列
bin_num = int(nrows/bins+1) #799 #总共15975份,分成20份,每份多少值
bad = sum([1 for (p,y) in ks_lis if y>0.5]) #328# 计算逾期人数
good = sum([1 for (p,y) in ks_lis if y<0.5]) #15647 # 计算好人的人数
bad_cnt, good_cnt = 0, 0 # 累计坏人人数 ,累计好人的人数。声明行和列
KS = []
BAD = []
GOOD = []
BAD_CNT = []
GOOD_CNT = []
BAD_PCTG = []
BADRATE = []
dct_report = {}
for j in range(bins):
ds = ks_lis[j*bin_num: min((j+1)*bin_num, nrows)]
#计算每一组 有多少好人 多少坏人
bad1 = sum([1 for (p, y) in ds if y > 0.5])
good1 = sum([1 for (p, y) in ds if y <= 0.5])
# 到这一组位置 一共出现了多少好人 多少坏人
bad_cnt += bad1
good_cnt += good1
bad_pctg = round(bad_cnt/sum(val_y),3) # 一箱一箱累加 到这一箱一共有多少逾期 占所有逾期的比例
#sum(val_y)验证集坏人的总数,bad_cnt遍历过程中,目前出现的坏人数;3是BAD_PCTG出现的小数点位数
badrate = round(bad1/(bad1+good1),3) # 一箱一箱累加 计算当前这一箱坏人比例
ks = round(math.fabs((bad_cnt / bad) - (good_cnt / good)),3) # 计算KS值
#fabs函数是一个求绝对值的函数,求出x的绝对值
#ks值计算公式 TPR-FPR差值最大的就是ks值。(纵轴-横轴)
KS.append(ks)
BAD.append(bad1)
GOOD.append(good1)
BAD_CNT.append(bad_cnt)
GOOD_CNT.append(good_cnt)
BAD_PCTG.append(bad_pctg)
BADRATE.append(badrate)
dct_report['KS'] = KS
dct_report['BAD'] = BAD
dct_report['GOOD'] = GOOD
dct_report['BAD_CNT'] = BAD_CNT
dct_report['GOOD_CNT'] = GOOD_CNT
dct_report['BAD_PCTG'] = BAD_PCTG
dct_report['BADRATE'] = BADRATE
val_repot = pd.DataFrame(dct_report)
val_repot
#BADRATE 预测情况是从高到底的,说明还是有优化空间的。希望预测的情况和真实的情况是一致的
| KS | BAD | GOOD | BAD_CNT | GOOD_CNT | BAD_PCTG | BADRATE |
0 | 0.217 | 86 | 713 | 86 | 713 | 0.262 | 0.108 |
1 | 0.299 | 43 | 756 | 129 | 1469 | 0.393 | 0.054 |
2 | 0.339 | 29 | 770 | 158 | 2239 | 0.482 | 0.036 |
3 | 0.381 | 30 | 769 | 188 | 3008 | 0.573 | 0.038 |
4 | 0.398 | 22 | 777 | 210 | 3785 | 0.640 | 0.028 |
5 | 0.403 | 18 | 781 | 228 | 4566 | 0.695 | 0.023 |
6 | 0.408 | 18 | 781 | 246 | 5347 | 0.750 | 0.023 |
7 | 0.398 | 13 | 786 | 259 | 6133 | 0.790 | 0.016 |
8 | 0.396 | 16 | 783 | 275 | 6916 | 0.838 | 0.020 |
9 | 0.361 | 5 | 794 | 280 | 7710 | 0.854 | 0.006 |
10 | 0.332 | 7 | 792 | 287 | 8502 | 0.875 | 0.009 |
11 | 0.287 | 2 | 797 | 289 | 9299 | 0.881 | 0.003 |
12 | 0.258 | 7 | 792 | 296 | 10091 | 0.902 | 0.009 |
13 | 0.225 | 6 | 793 | 302 | 10884 | 0.921 | 0.008 |
14 | 0.208 | 11 | 788 | 313 | 11672 | 0.954 | 0.014 |
15 | 0.182 | 8 | 791 | 321 | 12463 | 0.979 | 0.010 |
16 | 0.137 | 2 | 797 | 323 | 13260 | 0.985 | 0.003 |
17 | 0.092 | 2 | 797 | 325 | 14057 | 0.991 | 0.003 |
18 | 0.045 | 1 | 798 | 326 | 14855 | 0.994 | 0.001 |
19 | 0.000 | 2 | 792 | 328 | 15647 | 1.000 | 0.003 |
val_repot.BADRATE#BADRATE 预测情况是从高到底的,说明还是有优化空间的。希望预测的情况和真实的情况是一致的
'''
0 0.108
1 0.054
2 0.036
3 0.038
4 0.028
5 0.023
6 0.023
7 0.016
8 0.020
9 0.006
10 0.009
11 0.003
12 0.009
13 0.008
14 0.014
15 0.010
16 0.003
17 0.003
18 0.001
19 0.003
Name: BADRATE, dtype: float64
'''
模型报告
- 根据预测的坏人概率,对验证数据进行排序(降序排列,预测是坏人的概率比较高的排在前面)
- 将排序后的验证数据进行分箱,计算每一箱中实际上有多少个坏人,计算每一箱坏人的概率
- 如果模型比较靠谱的话,每一箱坏人人数应该是单调递减的,预测坏人概率比较高的箱,抓坏人的能力要比预测坏人概率比较低的箱能力要强
- 如果 数据不是单调递减的 模型需要调整
from pyecharts.charts import *
from pyecharts import options as opts
from pylab import *
mpl.rcParams['font.sans-serif'] = ['SimHei']
np.set_printoptions(suppress=True)
pd.set_option('display.unicode.ambiguous_as_wide', True)
pd.set_option('display.unicode.east_asian_width', True)
line = (
Line()
.add_xaxis([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) #x轴坐标1-19
.add_yaxis(
"分组坏人占比",
list(val_repot.BADRATE),
yaxis_index=0,
color="red",
)
.set_global_opts(
title_opts=opts.TitleOpts(title="行为评分卡模型表现"),
)
.extend_axis(
yaxis=opts.AxisOpts(
name="累计坏人占比",
type_="value",
min_=0,
max_=0.5,
position="right",
axisline_opts=opts.AxisLineOpts(
linestyle_opts=opts.LineStyleOpts(color="red")
),
axislabel_opts=opts.LabelOpts(formatter="{value}"),
)
)
.add_xaxis([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
.add_yaxis(
"KS",
list(val_repot['KS']),
yaxis_index=1,
color="blue",
label_opts=opts.LabelOpts(is_show=False),
)
)
#这里不能使用render_notebook()导出文件,会是空白情况
line.render(path='行为评分卡模型表现.html') #相对路径输出html文件展示图片
model.coef_
#array([[ 3.4946237 , 11.40440098, 2.45601882, -1.6844742 ]])
model.intercept_
#array([-0.34578469])
feature_lst
#['person_info', 'finance_info', 'credit_info', 'act_info']
def score(person_info,finance_info,credit_info,act_info):
xbeta = person_info * ( 3.49460978) + finance_info * ( 11.40051582 ) + credit_info * (2.45541981) + act_info * ( -1.68676079) --0.34484897
score = 650-34* (xbeta)/math.log(2) # 基准分+ 系数* 2^(1-p/p)
return score
val['score'] = val.apply(lambda x : score(x.person_info,x.finance_info,x.credit_info,x.act_info) ,axis=1)
val['score'].describe()
'''
count 15975.000000
mean 624.928765
std 52.882132
min 174.693564
25% 604.620826
50% 623.758049
75% 662.174834
max 735.528217
Name: score, dtype: float64
'''
#对应评级区间
def level(score):
level = 0
if score <= 600:
level = "D"
elif score <= 640 and score > 600 :
level = "C"
elif score <= 680 and score > 640:
level = "B"
elif score > 680 :
level = "A"
return level
val['level'] = val.score.map(lambda x : level(x) )
val['level'].value_counts()
'''
C 6251
B 3837
D 3581
A 2306
Name: level, dtype: int64
'''
import seaborn as sns
# seaborn绘制直方图 查看评分的分布情况
sns.distplot(val.score,kde=True)
val = val.sort_values('score',ascending=True).reset_index(drop=True)
df2=val.bad_ind.groupby(val['level']).sum() # 每一组逾期的数量
df3=val.bad_ind.groupby(val['level']).count() # 每一组一共有多少条记录
df2/df3
'''
level
A 0.002168
B 0.008079
C 0.014878
D 0.055571
Name: bad_ind, dtype: float64
'''
评分卡模型过程流程
- 数据准备 特征筛选
- 创建逻辑回归模型
- 主要问题集中在模型评估上
- KS 0.2可以用 0.6以上
- 跨时间验证 训练集和验证集上 表现的差距(ks值) 不要超过10%
- 生成评估报告
- 考察模型按照坏人概率单调排序,抓坏人的能力是不是也是单调变化的
- 模型的使用
- 利用逻辑回归模型的 系数和截距给每一个预测结果
- xbeta = person_info * ( 3.49460978) + finance_info * ( 11.40051582 ) + credit_info * (2.45541981) + act_info * ( -1.68676079) --0.34484897
- score = 650-34* (xbeta)/math.log(2) 系数可以自己调
- 可以根据信用分 给一个信用评级