风控策略的自动化生成-利用决策树分分钟生成上千条策略

SAMshare

共 12009字,需浏览 25分钟

 ·

2022-02-24 08:22


本文重点:风控策略挖掘、策略推荐,策略发现,风控策略自动化,决策树

风控策略同学在挖掘有效的风控规则的时候,经常需要基于业务经验,将那几个特征进行组合形成风控策略,会导致在特征组合的时候浪费大量的时间,我们有没有什么方法,替代人工的分析,直接得出策略组合呢,决策树就是其中的一个选择,可以实现自动化的挖掘大批量的策略组合。
在众多的算法中,决策树整体分类准确率不高,但是部分叶子节点的准确率却可以很高,因此我们可以提取决策树的叶子规则,并筛选准确率比较高的叶子节点,作为风控策略挖掘手段,并进行策略推荐,替代人工或者辅助人工,大大提高策略发现的效率于效果。
本文介绍了如何在风控策略中使用决策树算法来挖掘有效的规则,并会分享自己编写的提取函数,此套代码会在极短的时间挖掘上千条规则,快速且有效,目标就是:风控策略自动化,然后干掉自己。【文末有获取数据的方法】
策略节选
 

一、数据说明及读取

1、数据集信息

数据从真实场景和实际应用出发,利用个人的基本身份信息、个人的住房公积金缴存和贷款等数据信息,来建立准确的风险控制模型,来预测用户是否会逾期还款。一共提供了40000带标签训练集样本,数据仅有一张表,一共有19个基本特征,且均不包含任何缺失值。

2、数据属性信息

标签:label是否逾期(是 = 1,否 = 0)。
特征:包含以下19个变量,名称和含义如下。
序号
字段名
类型
说明
1
id
String
主键
2
XINGBIE
int
性别
3
CSNY
int
出生年月
4
HYZK
int
婚姻状况
5
ZHIYE
int
职业
6
ZHICHEN
int
职称
7
ZHIWU
int
职务
8
XUELI
int
学历
9
DWJJLX
int
单位经济类型
10
DWSSHY
int
单位所属行业
11
GRJCJS
float
个人缴存基数
12
GRZHZT
int
个人账户状态
13
GRZHYE
float
个人账户余额
14
GRZHSNJZYE
float
个人账户上年结转余额
15
GRZHDNGJYE
float
个人账户当年归集余额
16
GRYICE
float
个人月缴存额
17
DWYICE
float
单位月缴存额
18
DKFFE
int
贷款发放额
19
DKYE
float
贷款余额
20
DKLL
float
贷款利率
21
label
int
是否逾期 (0代表没逾期1代表逾期)

3、读取数据

#数据读取import pandas as pdimport numpy  as nppd.set_option('display.max_columns'None)#显示所有的列path  = '/Users/wuzhengxiang/Documents/DataSets/RizhaoGongJiJin/train.csv'train = pd.read_csv(path).fillna(-1)train.columnsIndex(['id''XINGBIE''CSNY''HYZK''ZHIYE''ZHICHEN''ZHIWU''XUELI',       'DWJJLX''DWSSHY''GRJCJS''GRZHZT''GRZHYE''GRZHSNJZYE',       'GRZHDNGJYE''GRYJCE''DWYJCE''DKFFE''DKYE''DKLL''label'],      dtype='object')

train.head()#查看前面的数据 id  XINGBIE        CSNY  HYZK  ZHIYE  ZHICHEN  ZHIWU  XUELI  DWJJLX  \0  train_0        1  1038672000    90     90      999      0     99     150   1  train_1        2   504892800    90     90      999      0     99     110   2  train_2        1   736185600    90     90      999      0     99     150   3  train_3        1   428515200    90     90      999      0     99     150   4  train_4        2   544204800    90     90      999      0     99     900   
DWSSHY GRJCJS GRZHZT GRZHYE GRZHSNJZYE GRZHDNGJYE GRYJCE \0 12 1737.0 1 3223.515 801.310 837.000 312.00 1 0 4894.0 1 18055.195 53213.220 1065.200 795.84 2 9 10297.0 1 27426.600 13963.140 7230.020 1444.20 3 7 10071.5 1 111871.130 99701.265 2271.295 1417.14 4 14 2007.0 1 237.000 11028.875 35.780 325.50
DWYJCE DKFFE DKYE DKLL label 0 312.00 175237 154112.935 2.708 0 1 795.84 300237 298252.945 2.979 0 2 1444.20 150237 147339.130 2.708 0 3 1417.14 350237 300653.780 2.708 0 4   325.50  150237  145185.010  2.708      0  #构建训练集X = train.loc[:,'XINGBIE':'DKLL']= train['label']
 

二、构建决策树

训练一个决策树,这里限制了最大深度和最小样本树
from sklearn import treeclf = tree.DecisionTreeClassifier(     max_depth=3,     min_samples_leaf=50     )clf = clf.fit(X, Y)

三、决策树的可视化

决策树可视化的方案比较多,都写出来给对比看看,推荐第二种和第三种。对决策树进行可视化,是非常有必要的,能够帮助我们自己充分理解决策树的生成过程,如果是风控,也有利于咱们给业务部门解释数据和结果。

1、plot_tree(太丑,不推荐)

#包里自带的,有点丑tree.plot_tree(clf)plt.show()

2、graphviz对决策树进行可视化

如果搜索“可视化决策树”,很快便能找到由scikit提供的基于Python语言的解决方案:sklearn.tree.export_graphviz,这个也是最常用的解决方案

import graphviz dot_data = tree.export_graphviz(                     clf,                      out_file=None,                      feature_names=X.columns,                       class_names=['good','bad'],                       filled=True, rounded=True,                       special_characters=True)  graph = graphviz.Source(dot_data)  graph

 
生成的决策树解释
1)samples:节点中观察的数量,比如根节点40000,表示数据集总共有4万个样本
2)有多少种类别,整棵树的叶子就有多少种颜色,比如我们这里有2个类别,颜色对应是黄、绿、Gini指数越小,该节点颜色越深,也就是纯度越高。
3)value表示当前节点2种类别的样本有多少,比如下面第一棵树的根节点,value = [37243,2757],表示有37243个好样本,2757坏样本
4)class表示当前那个类别的样本最多,比如下面最右边的一棵树的根节点,class = bad,可以看到当前节点它的坏样本数是最多的。
5)gini:节点的基尼不纯度。当沿着树向下移动时,平均加权的基尼不纯度必须降低。
 

3、dtreeviz对决策树进行可视化

dtreeviz是我认为非常完美的决策树可视化的包,非常好理解,也非常美观。下面我们看看这个包可视化的结果。
  from dtreeviz.trees import dtreeviz  testX = X.iloc[77,:]  viz = dtreeviz(clf,X,Y,                 feature_names=np.array(X.columns),                 class_names={0:'good',1:'bad'},                 X = testX)                viz.view()
 
 
我们把树的深度再加深到5看看,树更复杂了
from sklearn import treeclf = tree.DecisionTreeClassifier(     max_depth=5,     min_samples_leaf=50     )clf = clf.fit(X, Y)  from dtreeviz.trees import dtreeviz  testX = X.iloc[77,:]  viz = dtreeviz(clf,X,Y,                 feature_names=np.array(X.columns),                 class_names={0:'good',1:'bad'},                 X = testX)               viz.view()
 
也可以横向展示
viz = dtreeviz(clf,X,Y,               orientation ='LR',  # left-right orientation               feature_names=np.array(X.columns),               class_names={0:'good',1:'bad'},               X = testX)       viz.view()
#如果只想可视化预测路径,则需要设置参数 show_just_path=True
 
 
viz = dtreeviz(clf,X,Y,               feature_names=np.array(X.columns),               class_names={0:'good',1:'bad'},               orientation ='LR',  # left-right orientation               show_just_path=True,               X = testX) viz.view()

只展示了预测的路径
有了决策树的可视化,我们就能直接得到每条策略了,当时人为的看,效率还是比较低,我们需要更高效的方式,对数据决策树上的信息进行提取,直接得到规则。

四、决策规则提取

1、决策树的生成的结构探索

要提取出来其中的规则,我们需要探索决策树的存储结构,为了探究sklearn中决策树是如何设计和实现的,以分类决策树为例,首先看下决策树都内置了哪些属性和接口:通过dir属性查看一颗初始的决策树都包含了哪些属性(这里过滤掉了以"_"开头的属性,因为一般是内置私有属性),得到结果如下:
from sklearn.datasets import load_irisfrom sklearn import treeiris = load_iris()clf = tree.DecisionTreeClassifier()clf = clf.fit(iris.data, iris.target)clf.classes_[x for x in dir(clf) if not x.startswith('_')]
大致浏览上述结果,属性主要是决策树初始化时的参数,例如ccp_alpha:剪枝系数,class_weight:类的权重,criterion:分裂准则等;还有就是决策树实现的主要函数,例如fit:模型训练,predict:模型预测等等。
本文的重点是探究决策树中是如何保存训练后的"那颗树",所以我们进一步用鸢尾花数据集对决策树进行训练一下,而后再次调用dir函数,看看增加了哪些属性和接口:
本文的重点是探究决策树中是如何保存训练后的"那颗树",所以我们进一步用鸢尾花数据集对决策树进行训练一下,而后再次调用dir函数,看看增加了哪些属性和接口:
通过集合的差集,很明显看出训练前后的决策树主要是增加了6个属性(都是属性,而非函数功能),其中通过属性名字也很容易推断其含义:
  • classes_:分类标签的取值,即y的唯一值集合
  • max_features_:最大特征数
  • n_classes_:类别数,如2分类或多分类等,即classes_属性中的长度
  • n_features_in_:输入特征数量,等价于老版sklearn中的n_features_,现已弃用,并推荐n_features_in_
  • n_outputs:多输出的个数,即决策树不仅可以用于实现单一的分类问题,还可同时实现多个分类问题,例如给定一组人物特征,用于同时判断其是男/女、胖/瘦和高矮,这是3个分类问题,即3输出(需要区别理解多分类和多输出任务)
  • tree_:毫无疑问,这个tree_就是今天本文的重点,是在决策树训练之后新增的属性集,其中存储了决策树是如何存储的。

那我们对这个tree_属性做进一步探究,首先打印该tree_属性发现,这是一个Tree对象,并给出了在sklearn中的文件路径:
我们可以通过help方法查看Tree类的介绍:
通过上述doc文档,其中第一句就很明确的对决策树做了如下描述:
Array-based representation of a binary decision tree.
即:基于数组表示的二分类决策树,也就是二叉树!进一步地,在这个二叉树中,数组的第i个元素代表了决策树的第i个节点的信息,节点0表示决策树的根节点。那么每个节点又都蕴含了什么信息呢?我们注意到上述文档中列出了节点的文件名:_tree.pxd,查看其中,很容易发现节点的定义如下:
虽然是cython的定义语法,但也不难推断其各属性字段的类型和含义,例如:
  • left_child:size类型(无符号整型),代表了当前节点的左子节点的索引
  • right_child:类似于left_child
  • feature:size类型,代表了当前节点用于分裂的特征索引,即在训练集中用第几列特征进行分裂
  • threshold:double类型,代表了当前节点选用相应特征时的分裂阈值,一般是≤该阈值时进入左子节点,否则进入右子节点
  • n_node_samples:size类型,代表了训练时落入到该节点的样本总数。显然,父节点的n_node_samples将等于其左右子节点的n_node_samples之和。
至此,决策树中单个节点的属性定义和实现基本推断完毕,那么整个决策树又是如何将所有节点串起来的呢?我们再次诉诸于训练后决策树的tree_属性,看看它都哪些接口,仍然过滤掉内置私有属性,得到如下结果:
# 决策树结构探索
dir(clf.tree_)['apply','capacity''children_left','children_right', 'compute_feature_importances','compute_partial_dependence', 'decision_path','feature', 'impurity','max_depth', 'max_n_classes','n_classes', 'n_features','n_leaves', 'n_node_samples','n_outputs','node_count', 'predict','threshold', 'value''weighted_n_node_samples']
大概比较重要的就是这些了!为了进一步理解各属性中的数据是如何存储的,我们仍以鸢尾花数据集为例,训练一个max_depth=2的决策树(根节点对应depth=0),并查看如下取值:
clf.tree_.children_leftclf.tree_.children_rightclf.tree_.featureclf.tree_.capacityclf.tree_.thresholdclf.tree_.valueclf.tree_.impurityclf.tree_.decision_path

可知:
  • 训练后的决策树共包含5个节点,其中3个叶子节点
  • 通过children_left和children_right两个属性,可以知道第0个节点(也就是根节点)的左子节点索引为1,右子节点索引为2,;第1个节点的左右子节点均为-1,意味着该节点即为叶子节点;第2个节点的左右子节点分别为3和4,说明它是一个内部节点,并做了进一步分裂
  • 通过feature和threshold两个属性,可以知道第0个节点(根节点)使用索引为3的特征(对应第4列特征)进行分裂,且其最优分割阈值为0.8;第1个节点因为是叶子节点,所以不再分裂,其对应feature和threshold字段均为-2
  • 通过value属性,可以查看落入每个节点的各类样本数量,由于鸢尾花数据集是一个三分类问题,且该决策树共有5个节点,所以value的取值为一个5×3的二维数组,例如第一行代表落入根节点的样本计数为[50, 50, 50],第二行代表落入左子节点的样本计数为[50, 0, 0],由于已经是纯的了,所以不再继续分裂。
  • 另外,tree中实际上并未直接标出各叶节点所对应的标签值,但完全可通过value属性来得到,即各叶子节点中落入样本最多的类别即为相应标签。甚至说,不仅可知道对应标签,还可通过计算数量之比得到相应的概率!
拿鸢尾花数据集手动验证一下上述猜想,以根节点的分裂特征3和阈值0.8进行分裂,得到落入左子节点的样本计数结果如下,发现确实是分裂后只剩下50个第一类样本,也即样本计数为[50, 0, 0],完全一致。
另外,通过children_left和children_right两个属性的子节点对应关系,其实我们还可以推断出该二叉树的遍历方式为前序遍历,即按照根-左-右的顺序,对于上述决策树其分裂后对应二叉树示意图如下:

2、老方法提取决策树规则

通过上面的分析,我们知道了决策树的存储方式,下面就开始规则提取,在网上搜索,基本上只能收到下面的方法:
数据读取
import pandas as pdimport numpy  as nppd.set_option('display.max_columns', None)#显示所有的列path  = '/Users/wuzhengxiang/Documents/DataSets/RizhaoGongJiJin/train.csv'train = pd.read_csv(path).fillna(-1)train.columns
构建训练集

X = train.loc[:,'XINGBIE':'DKLL']Y = train['label']

训练一个决策树,这里限制了最大深度和最小样本树

from sklearn import treeclf = tree.DecisionTreeClassifier(     max_depth=3,     min_samples_leaf=50     )clf = clf.fit(X, Y)
决策树规则提取-老方法
from sklearn.tree import _treedef tree_to_code(tree, feature_names):    tree_ = tree.tree_    feature_name = [        feature_names[i] if i != _tree.TREE_UNDEFINED else "undefined!"        for i in tree_.feature    ]    print ("def tree({}):".format(", ".join(feature_names)))
def recurse(node, depth): indent = " " * depth if tree_.feature[node] != _tree.TREE_UNDEFINED: name = feature_name[node] threshold = tree_.threshold[node] print("{}if {} <= {}:".format(indent, name, threshold)) recurse(tree_.children_left[node], depth + 1) print("{}else: # if {} > {}".format(indent, name, threshold)) recurse(tree_.children_right[node], depth + 1) else:            print("{}return {}".format(indent, tree_.value[node]))
recurse(0, 1)tree_to_code(clf,X.columns)   return [[161.   0.]]
 
’可以看得出来,虽然提取出来了策略,但是并不是很完美,还是需要人为拆解策略,于是我继续研究。

3、新方法提取决策树规则

不仅仅对对二叉树的所有路径进行遍历,还需要进行回溯并组合成变量,根据决策树的输出,构建规则提取函数,需要用到二叉树遍历和回溯算法,本人数据结构不是很好,干了两个晚上,真是编程偷不了懒,出来混迟早要还的。代码比较混乱,大家将就看,还好这个部分对效率没啥要求。如果有更好的代码,不吝赐教。
 

def XiaoWuGe_Get_Rules(clf,X):    n_nodes = clf.tree_.node_count    children_left = clf.tree_.children_left    children_right = clf.tree_.children_right    feature = clf.tree_.feature    threshold = clf.tree_.threshold    value = clf.tree_.value
node_depth = np.zeros(shape=n_nodes, dtype=np.int64) is_leaves = np.zeros(shape=n_nodes, dtype=bool) stack = [(0, 0)]
while len(stack) > 0:
node_id, depth = stack.pop() node_depth[node_id] = depth
is_split_node = children_left[node_id] != children_right[node_id]
if is_split_node: stack.append((children_left[node_id], depth+1)) stack.append((children_right[node_id], depth+1)) else: is_leaves[node_id] = True feature_name = [ X.columns[i] if i != _tree.TREE_UNDEFINED else "undefined!" for i in clf.tree_.feature]
ways = [] depth = [] feat = [] nodes = [] rules = [] for i in range(n_nodes): if is_leaves[i]: while depth[-1] >= node_depth[i]: depth.pop() ways.pop() feat.pop() nodes.pop() if children_left[i-1]==i:#当前节点是上一个节点的左节点,则是小于 a='{f}<={th}'.format(f=feat[-1],th=round(threshold[nodes[-1]],4)) ways[-1]=a last =' & '.join(ways)+':'+str(value[i][0][0])+':'+str(value[i][0][1]) rules.append(last) else: a='{f}>{th}'.format(f=feat[-1],th=round(threshold[nodes[-1]],4)) ways[-1]=a last = ' & '.join(ways)+':'+str(value[i][0][0])+':'+str(value[i][0][1]) rules.append(last)
else: #不是叶子节点 入栈 if i==0: ways.append(round(threshold[i],4)) depth.append(node_depth[i]) feat.append(feature_name[i]) nodes.append(i) else: while depth[-1] >= node_depth[i]: depth.pop() ways.pop() feat.pop() nodes.pop() if i==children_left[nodes[-1]]: w='{f}<={th}'.format(f=feat[-1],th=round(threshold[nodes[-1]],4)) else: w='{f}>{th}'.format(f=feat[-1],th=round(threshold[nodes[-1]],4)) ways[-1] = w ways.append(round(threshold[i],4)) depth.append(node_depth[i]) feat.append(feature_name[i]) nodes.append(i) return rules
 

4、利用函数对规则进行提取

#训练一个决策树,对规则进行提取clf = tree.DecisionTreeClassifier(max_depth=10,min_samples_leaf=50)clf = clf.fit(X, Y)Rules = XiaoWuGe_Get_Rules(clf,X)
Rules[0:5] # 查看前5条规则['GRZHZT<=1.5 & DWSSHY<=14.5 & DWJJLX<=177.0 & DWJJLX<=115.0 & DKYE<=111236.2852 & DWSSHY<=4.5 & DWYJCE<=663.54 & DKYE<=67419.1094:45.0:8.0', 'GRZHZT<=1.5 & DWSSHY<=14.5 & DWJJLX<=177.0 & DWJJLX<=115.0 & DKYE<=111236.2852 & DWSSHY<=4.5 & DWYJCE<=663.54 & DKYE >67419.1094:61.0:3.0', 'GRZHZT<=1.5 & DWSSHY<=14.5 & DWJJLX<=177.0 & DWJJLX<=115.0 & DKYE<=111236.2852 & DWSSHY<=4.5 & DWYJCE >663.54 & GRZHYE<=45622.4883 & DKYE<=1825.5625:63.0:2.0', 'GRZHZT<=1.5 & DWSSHY<=14.5 & DWJJLX<=177.0 & DWJJLX<=115.0 & DKYE<=111236.2852 & DWSSHY<=4.5 & DWYJCE >663.54 & GRZHYE<=45622.4883 & DKYE >1825.5625:188.0:0.0', 'GRZHZT<=1.5 & DWSSHY<=14.5 & DWJJLX<=177.0 & DWJJLX<=115.0 & DKYE<=111236.2852 & DWSSHY<=4.5 & DWYJCE >663.54 & GRZHYE >45622.4883:46.0:4.0']len(Rules) # 查看规则总数182
我们可以导出成表格的形式,更加清晰
 
提高树的深度再看看,max_depth=15,可以看到规则数从182变成了521条,规模更大

clf = tree.DecisionTreeClassifier(max_depth=15,min_samples_leaf=20)clf = clf.fit(X, Y)Rules = Get_Rules(clf,X)Rules[0:5]['GRZHZT<=1.5 & DWSSHY<=14.5 & DWJJLX<=177.0 & DWJJLX<=115.0 & DKYE<=111236.2852 & DWSSHY<=4.5 & DWYJCE<=663.54 & GRZHSNJZYE<=19428.9082 & DKFFE<=142737.0 & CSNY<=600926400.0:54.0:0.0', 'GRZHZT<=1.5 & DWSSHY<=14.5 & DWJJLX<=177.0 & DWJJLX<=115.0 & DKYE<=111236.2852 & DWSSHY<=4.5 & DWYJCE<=663.54 & GRZHSNJZYE<=19428.9082 & DKFFE<=142737.0 & CSNY >600926400.0:18.0:2.0', 'GRZHZT<=1.5 & DWSSHY<=14.5 & DWJJLX<=177.0 & DWJJLX<=115.0 & DKYE<=111236.2852 & DWSSHY<=4.5 & DWYJCE<=663.54 & GRZHSNJZYE<=19428.9082 & DKFFE >142737.0:19.0:4.0', 'GRZHZT<=1.5 & DWSSHY<=14.5 & DWJJLX<=177.0 & DWJJLX<=115.0 & DKYE<=111236.2852 & DWSSHY<=4.5 & DWYJCE<=663.54 & GRZHSNJZYE >19428.9082:15.0:5.0', 'GRZHZT<=1.5 & DWSSHY<=14.5 & DWJJLX<=177.0 & DWJJLX<=115.0 & DKYE<=111236.2852 & DWSSHY<=4.5 & DWYJCE >663.54 & GRZHYE<=73608.0156 & DKYE<=1825.5625 & GRZHSNJZYE<=9524.7949:21.0:2.0']len(Rules)521
#可以遍历所有的规则for i in Rules: print(i)
 
到此为止,我们完成了基础规则的挖掘,面对实际业务,我们还需要更丰富,更灵活的挖掘方式。
 

五、挖掘更多的风控策略

上面仅仅进行了初步的挖掘,我们还可以对特征进行抽样,然后进行更大规模的抽样,最后从决策树变成了决策森林,感觉有种随机深林的感觉,大家可以试试。如果还有不懂得,可以咨询我。【关注后回复决策树策略获取数据集】
 

浏览 29
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报