筛选样本_智能风控(梅⼦⾏)笔记—⽤XGBoost进⾏特征筛
选
本⽂参考下链 ,如果对数据集或书内更多更多讲解内容感兴趣,请直接购买
智能风控(原理,算法与⼯程实践)w
这篇⼿码了⼀遍梅⼦⾏在书中的递归筛选⽅案,梳理的框架上是这样的.
1. ⾸先定义了在风控场景下,特征迭代筛选和两个关键性指标:
KS: 模型的正负样本体现的关键指标,这与普通学习任务中的针对AUC,Precision等的优化⽅向不⼀样,所以基本⾃⼰定义了⼀个评KS:
价指标。
PSI:
PSI: 模型各个在各个检测分段上的检测稳定性.
也就是区分能⼒要强,模型结果要稳定.
需要注意的是,我们在使⽤XGBoost的时候其实有⾃带⼀个Featureimportance功能(上⼀篇写xgboost有提到,分为weight, gain和cover三种不同形式),如果我们在⼊模之前没有对模型特征做相关性的处理的话,直接使⽤XGBoost进⾏特征筛选是不合理的. 我下⾯举个例⼦:
如果有两个特征的分布是像上图这样的,可以发现他们的相关性⼀定是差不多的,在XGBOOST⽤于分裂时2个特征肯定会被随机⽤来分树,那么这两个本⾝如果重要性⽐较⾼的话,特征的真正重要性就会丢失。
回到书中的内容,这节先定义了2个评估模型效果的函数
SolveKS:
⽤于计算当前模型在某数据集上的KS,KS值对模型的评价不会受到样本不均衡问题的影响.
这些函数都继承⾃之前XGBoost⽗类
注意这个函数还加了个Weight参数,这是为了还原真实样本⽐例下的⽐例,举个例⼦,正样本有100,负样本有10000,对负样本按照0.4的⽐例进⾏下采样,就会得到4000的负样本,对100的正样本和4000的负样本进⾏建模,但是需要将负样本的权重设置为
2.5,才能还原到初始的正负样本的⽐例
def sloveKS(lf, model, X, Y, Weight):
Y_predict = [s[1] for s in model.predict_proba(X)]
nrows = X.shape[0]
#还原权重
lis = [(Y_predict[i], Y.values[i], Weight[i]) for i in range(nrows)]
#按照预测概率倒序排列
ks_lis = sorted(lis, key=lambda x: x[0], rever=True)
KS = list()
bad = sum([w for (p, y, w) in ks_lis if y > 0.5])
good = sum([w for (p, y, w) in ks_lis if y <= 0.5])
bad_cnt, good_cnt = 0, 0
for (p, y, w) in ks_lis:
if y > 0.5:
#1*w 即加权样本个数
bad_cnt += w
el:
#1*w 即加权样本个数
good_cnt += w
ks = math.fabs((bad_cnt/bad)-(good_cnt/good))
KS.append(ks)
return max(KS)卡通图
solvePSI
这个指标可以⽐较好的衡量模型或特征的稳定性,可以⽤于监控线上模型和线下的差异.
def slovePSI(lf, model, dev_x, val_x):
dev_predict_y = [s[1] for s in model.predict_proba(dev_x)]
dev_nrows = dev_x.shape[0]
dev_predict_y.sort()
#等频分箱成10份
cutpoint = [100] + [dev_predict_y[int(dev_nrows/10*i)]
for i in range(1, 10)] + [100]
cutpoint = list(t(cutpoint))
cutpoint.sort()
val_predict_y = [s[1] for s in list(model.predict_proba(val_x))]
val_nrows = val_x.shape[0]
PSI = 0
#每⼀箱之间分别计算PSI
for i in range(len(cutpoint)-1):
start_point, end_point = cutpoint[i], cutpoint[i+1]
dev_cnt = [p for p in dev_predict_y
if start_point <= p < end_point]
dev_ratio = len(dev_cnt) / dev_nrows + 1e-10
val_cnt = [p for p in val_predict_y
if start_point <= p < end_point]
val_ratio = len(val_cnt) / val_nrows + 1e-10
psi = (dev_ratio - val_ratio) * math.log(dev_ratio/val_ratio)
PSI += psi
return PSI
开始特征筛选.
这个函数我⼿码了⼀遍,⽤minscore来控制筛选的阈值,⽤maxdelvarnums来控制每次删除特征的个数,同时输出每⼀轮筛选后对应模型ks和psi指标的变化,真的很夯啊。另外我之前分析kaggle数据的时候⼀般会通过woe和iv组合来计算特征的有效性,但书中也提到了,这个指标是针对单变量的特征
效果,XGBoost本⾝是有特征交叉能⼒的,所以如果⽤IV 来分析的话,只可以⽤来做特征的粗筛选.
import xgboost as xgb
from xgboost import plot_importance
class xgBoost(object):
def __init__(lf, datats, uid, dep, weight,
var_names, params, max_del_var_nums=0):
lf.datats = datats
#样本唯⼀标识,不参与建模
lf.uid = uid
#⼆分类标签
lf.dep = dep
#样本权重
lf.weight = weight
长辈生日祝福#特征列表
lf.var_names = var_names
#参数字典,未指定字段使⽤默认值
lf.params = params
#单次迭代最多删除特征的个数豆芽怎么做
lf.max_del_var_nums = max_del_var_nums
def training(lf, min_score=0.0001, modelfile="", output_scores=list()):
lis = lf.var_names[:]
dev_data = ("dev", "") #训练集
val_data = ("val", "") #测试集
off_data = ("off", "") #跨时间验证集
#从字典中查找参数值,没有则使⽤第⼆项作为默认值
model = xgb.XGBClassifier(
learning_rate=("learning_rate", 0.1),
n_estimators=("n_estimators", 100),
max_depth=("max_depth", 3),
max_depth=("max_depth", 3),
min_child_weight=("min_child_weight", 1),subsample=("subsample", 1), objective=("objective",
"binary:logistic"),
nthread=("nthread", 10),
scale_pos_weight=("scale_pos_weight", 1),
random_state=0,
n_jobs=("n_jobs", 10),
reg_lambda=("reg_lambda", 1),
missing=("missing", None) )
while len(lis) > 0:
#模型训练
model.fit(X=dev_data[lf.var_names], y=dev_data[lf.dep])
#得到特征重要性
scores = model.feature_importances_
#清空字典
lis.clear()
'''
当特征重要性⼩于预设值时,
将特征放⼊待删除列表。十大品牌名表
当列表长度超过预设最⼤值时,跳出循环。
即⼀次只删除限定个数的特征。
'''吃芒果的好处
for (idx, var_name) in enumerate(lf.var_names):
#⼩于特征重要性预设值则放⼊列表
if scores[idx] < min_score:
lis.append(var_name)
#达到预设单次最⼤特征删除个数则停⽌本次循环
if len(lis) >= lf.max_del_var_nums:
break
#训练集KS
devks = lf.sloveKS(model, dev_data[lf.var_names],
dev_data[lf.dep], dev_data[lf.weight])
#初始化ks值和PSI
valks, offks, valpsi, offpsi = 0.0, 0.0, 0.0, 0.0
#测试集KS和PSI
if not isinstance(val_data, str):
valks = lf.sloveKS(model,
val_data[lf.var_names],
val_data[lf.dep],
val_data[lf.weight])
valpsi = lf.slovePSI(model,
dev_data[lf.var_names],
val_data[lf.var_names])
#跨时间验证集KS和PSI
汽车远光灯怎么开if not isinstance(off_data, str):
offks = lf.sloveKS(model,
off_data[lf.var_names],
off_data[lf.dep],
off_data[lf.weight])
春天钓鱼
offpsi = lf.slovePSI(model,
dev_data[lf.var_names],
off_data[lf.var_names])
#将三个数据集的KS和PSI放⼊字典
dic = {"devks": float(devks),
"valks": float(valks),
"offks": offks,
"valpsi": float(valpsi),
"offpsi": offpsi}
print("del var: ", len(lf.var_names),
"-->", len(lf.var_names) - len(lis),
"ks: ", dic, ",".join(lis))
plot_importance(model)
#重新训练,准备进⼊下⼀循环
model = xgb.XGBClassifier(
learning_rate=("learning_rate", 0.1),
learning_rate=("learning_rate", 0.1), n_estimators=("n_estimators", 100),
max_depth=("max_depth", 3),
min_child_weight=("min_child_weight",1), subsample=("subsample", 1),
objective=("objective",
"binary:logistic"),
妈妈不哭nthread=("nthread", 10),
scale_pos_weight=("scale_pos_weight",1), random_state=0,
n_jobs=("n_jobs", 10),
reg_lambda=("reg_lambda", 1),
missing=("missing", None))