特征筛选
在建模之前的一步就是将特征工程做好的变量进行筛选,在风控项目中筛选分为两步:初步筛选、逐步回归筛选以及稳定性筛选。
初步筛选
通常评分卡模型的特征筛选主要从以下4个角度出发: 缺失率(一般变量较少时可以可以相对放宽,使用一些方法,如插值等,来填充缺失值) 好坏区分能力(即IV值) 相关性(一般使用皮尔逊相关系数) 分箱后的单调性
信息量(IV)
信息量(Information Value,IV)定义为特征WOE编码的加权求和,woe的计算公式(前面章节有详细介绍:风控建模之特征转换):
其中pyi和pni的定义与WOE编码中完全相同。单个特征的IV等于该特征每个取值对应的ivi的累加。
从公式上看,IV衡量的是变量不同取值样本标签的分布差异,可以从一定程度上反映单特征对标签区分的贡献度。
对于特征的IV筛选标准:特征整体的IV值不高时,可以将筛选的阀值调低,如在项目中一般调低至0.01,去寻找有效特征;而当特征的IV值大于0.5时,说明该特征具有显著好坏区分能力,因而可以作为前置的策略规则,提前拒绝高风险人群,对于规则阀值的确定可以依据bad_rate以及业务需要进行设置。
python代码:
import toad
import pandas as pd
file_path = r'C:\Users\jiaxiong.he\Desktop\智能风控\book_data_code\ppd.xlsx'
data = pd.read_excel(file_path)
ex_list = ['Idx', 'ListingInfo']
# (1)empyt=0.9: 若变量的缺失值大于0.9被删除
# (2)iv=0.02: 若变量的iv值小于0.02被删除
# (3)corr=0.7: 若两个相关性高于0.7时,iv值低的变量被删除
# (4)return_drop=False: 若为True,function将返回被删去的变量列
# (5)exclude=None: 明确不被删去的列名,输入为list格式
df_select, drop_list = toad.selection.select(
data, data['target']
,empty=0.9,iv=0.01,corr=0.7
,return_drop=True,exclude=ex_list)
# df_select返回的被筛选数据,drop_list返回被各个条件删除的变量
逐步回归筛选
逐步回归(Stepwise Regression)是一种线性回归模型自变量选择方法,其基本思想是,贪心地遍历所有变量最优组合,以保证最终模型的变量组合为最优。该过程涉及多次F检验。分为:向前逐步回归、向后逐步回归、双向逐步回归。在风控建模项目中,是在特征转换成woe编码时,进行逐步回归筛选特征。
向前逐步回归
初始情况下,模型中只有一个F检验显著性最高的变量,之后尝试加入另一个F检验显著性最高的变量。上述过程不断迭代,直至没有变量满足放入模型的条件。
向后逐步回归
与前向选择相反。首先将所有变量同时放入模型,然后将其中F检验显著性最低的变量从模型中剔除。上述过程不断迭代,直至没有变量满足剔出模型的条件。
双向逐步回归
将前向选择与后向消除同时进行。模型中每加入一个自变量,可能使得某个已放入模型的变量显著性减小。当其显著性小于阈值时,可将该变量从模型中剔除。双向消除即每增加一个新的显著变量的同时,检验整个模型中所有变量的显著性,将不显著变量剔除,从而得到最优的变量组合。
在实际项目实践中,双向逐步回归的表现较好。
combiner = toad.transform.Combiner()
combiner.fit(df_select, data['target'], method='chi',
min_samples=0.05,empty_separate=True,exclude=ex_list+['target'])
transer = toad.transform.WOETransformer()
data_woe = transer.fit_transform(combiner.transform(df_select, labels=False), data['target'], exclude=ex_list+['target'])
import warnings
warnings.filterwarnings("ignore")
# - estimator: 用于拟合的模型,支持'ols', 'lr', 'lasso', 'ridge'
# - direction: 逐步回归的方向,支持'forward', 'backward', 'both' (推荐)
# - criterion: 评判标准,支持'aic', 'bic', 'ks', 'auc'
# - max_iter: 最大循环次数
# - return_drop: 是否返回被剔除的列名
# - exclude: 不需要被训练的列名,比如ID列和时间列
stepwise_rt = toad.selection.stepwise(data_woe, data['target'],estimator='lr'
,direction='both',criterion='aic'
,exclude=ex_list+['target'])
print(stepwise_rt.shape) # (5000, 47)从100多个变量中筛选出47个
经验证,direction = ‘both’效果最好。estimator = ‘ols’以及criterion = ‘aic’运行速度快且结果对逻辑回归建模有较好的代表性
stepwise_rt_list = [i for i in stepwise_rt.columns if i not in ex_list+['target']]
稳定性筛选(PSI)
在实际业务中,最后决定模型是否能够上线的标准是模型及变量的PSI(模型表现KS只要达到30以上就具备区分好坏能力),不然可能出现的情况就是在训练集里模型表现良好,而在测试集以及时间外样本验证中KS跌如狗,那么之前的工作前功尽弃,少侠请重新再来吧!所以特征稳定性筛选是十分必要的,一般而言特征整体稳定,那么模型就稳定,KS不会波动太大。
PSI介绍
群体稳定性指标(Population Stability Index,PSI)衡量特征及模型的稳定性。其基本含义为:计算同一指标在两个不同数据集上的分布差异,作为当前指标的稳定性指标。PSI公式定义为:
其中pitarget为目标分布上第i箱中样本占总比,pibase为基础分布上第i箱中样本占总比。首先将基础分布按照等频分箱策略进行10等分,然后对目标分布使用相同的阈值进行分箱。分别计算第i箱的pitarget和pibase,带入PSI公式即可得到两个分布的稳定性差异。
PSI一般而言越小越好,当其等于0时标明没有差异,而当其大于0.01时说明特征不稳定需要查明原因
python代码:
# 读取测试集数据
test_file_path = r'C:\Users\jiaxiong.he\Desktop\智能风控\book_data_code\ppd_test.xlsx'
data_test = pd.read_excel(test_file_path)
# 测试集数据转换
data_test_woe = transer.fit_transform(combiner.transform(data_test[df_select.columns], labels=False),
data_test['target'], exclude=ex_list+['target'])
# 计算逐步回归后训练集与测试集PSI
df_PSI = toad.metrics.PSI(data_woe[stepwise_rt_list], data_test_woe[stepwise_rt_list])
# 筛选PSI小于0.01的变量
psi_list = list(df_PSI[df_PSI<0.01].index)
建模
下面将使用传统评分卡的逻辑回归建模以及作为对比的XGBOOST建模
逻辑回归建模
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(data_woe[psi_list], data['target'])
# 预测训练集和以及测试集
pred_train = lr.predict_proba(data_woe[psi_list])[:,1]
pred_test = lr.predict_proba(data_test_woe[psi_list])[:,1]
# 使用KS,ROC评价模型好坏
from toad.metrics import KS, AUC
print('训练集结果')
print('train KS',KS(pred_train, data['target']))
print('train AUC',AUC(pred_train, data['target']))
print('测试集结果')
print('test KS',KS(pred_test, data_test['target']))
print('test AUC',AUC(pred_test, data_test['target']))
# 将预测等频分箱,观测每组的区别
toad.metrics.KS_bucket(pred_train, data['target'], bucket=10, method = 'quantile')
从bad_rate上看,模型排序性还行,只是在3,4行时出现波动
from sklearn.metrics import roc_auc_score,roc_curve,auc
from matplotlib import pyplot as plt
# 划roc曲线
fpr_train,tpr_train,_ = roc_curve(data['target'], pred_train)
fpr_test,tpr_test,_ = roc_curve(data_test['target'], pred_test)
plt.plot(fpr_train,tpr_train, label='train')
plt.plot(fpr_test,tpr_test, label='test')
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()
XGBOOST建模
import xgboost as xgb
model = xgb.XGBClassifier(learning_rate=0.05,
n_estimators=400,
max_depth=2,
class_weight='balanced',
min_child_weight=1,
subsample=1,
nthread=-1,
scale_pos_weight=1,
random_state=1,
n_jobs=-1,
reg_lambda=300)
model.fit(data_woe[psi_list], data['target'])
pred_train = model.predict_proba(data_woe[psi_list])[:,1]
pred_test = model.predict_proba(data_test_woe[psi_list])[:,1]
# 使用KS,ROC评价模型好坏
from toad.metrics import KS, AUC
print('训练集结果')
print('train KS',KS(pred_train, data['target']))
print('train AUC',AUC(pred_train, data['target']))
print('测试集结果')
print('test KS',KS(pred_test, data_test['target']))
print('test AUC',AUC(pred_test, data_test['target']))
# 训练集结果
# train KS 0.5071609424051782
# train AUC 0.8339465507977667
# 测试集结果
# test KS 0.2578754578754579
# test AUC 0.674981684981685
# 划roc曲线
fpr_train,tpr_train,_ = roc_curve(data['target'], pred_train)
fpr_test,tpr_test,_ = roc_curve(data_test['target'], pred_test)
plt.plot(fpr_train,tpr_train, label='train')
plt.plot(fpr_test,tpr_test, label='test')
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()
通过模型结果可以看出,XGBoost模型的效果并没有明显高于逻辑回归模型,但在测试集上表现较差,由于没有进行分箱调整,因此逻辑回归建模训练集合测试集的KS波动较大,挖个坑,下一章把模型调好吧!
评分卡模型输出
接下来就是评分卡离线建模的最后一步生成标准评分卡:
from toad.scorecard import ScoreCard
# 将之前分箱和编码对象输入,base_odds和pdo后面会专门写一章介绍
card = ScoreCard(combiner=combiner,
transer=transer, C=0.1,
class_weight='balanced',
base_score=600,
base_odds=35,
pdo=60,
rate=2)
card.fit(data_woe[psi_list],data['target'])
final_card = card.export(to_frame=True)
# final_card
留下两个坑,1)手动调整分箱使其符合业务要求以及KS在训练集合测试集相对稳定,2)关于base_odds和pdo写一章介绍