在风控策略迭代过程中,我们通常需要从高维变量中搜索组合得到规则集(RuleSet),但是单纯依靠画格子、CART决策树等常规手段具有很高的挑战。此时,我们需要一种更为智能、更自动化的方法,从大量变量(高维空间)里找到最优规则集,这就是规则发现。
本文主要介绍一种规则发现算法,即病人规则归纳方法(Patient Rule Induction Method -PRIM),并紧密结合信贷风控业务知识,详细介绍理论和实践应用。
Part 1. 规则发现的概念
很多机器学习二分类问题抽象为 f(y|x) ,根据输入特征空间 X,预测个体发生目标事件的概率 P(y=1|X) 。然而,很多时候我们的目的不在于训练一个全局模型,而更为关注 y 值浓度很高(或很低)的某个局部空间。
现有 N个样本 {xi,y}^N ,我们希望从 M 维变量空间寻找一个子空间,使得这个子空间的目标变量浓度尽可能高。这个问题被称为子群识别(Subgroup Identification)。
为更容易理解子群识别的概念,我们以削苹果 为例。如图1所示,我们在苹果这个三维空间里横竖切几刀,找出了目标区域(芯),予以剔除(Peeling)。
而随着规则可解释性越来越受到大家关注,我们迫切需要找寻一些搜索过程透明、易于理解的智能算法来帮助我们进行规则发现。
为便于后文理解,我们定义如下概念:
#支持度(support):子群样本量相对于总体样本量的比例,反映规则命中率(hit rate)。
#正样本浓度:子群里正样本量相对于子群样本量的比例,反映坏人浓度(bad rate)。
#提升度(lift):子群正样本浓度相对于总体正样本浓度的提升,反映规则的提升杠杆。通常越大越好。
Part 2. 单维变量空间规则发现:巧用分位数
在贷前授信风险策略中,一般都会设置内部准入规则、反欺诈规则、外部准入规则、定额定价等环节。从更为抽象的角度,我们可将风控规则分为两种:
1、硬规则(hard rule):严拒规则,一般阈值固定下来后不再改变。典型的规则,包括严重多头借贷、高危设备行为等。
2、软规则(soft rule):信用模型分可归类于此。为控制通过率稳定,我们一般可调整模型分数的阈值cutoff。
那么,我们如何快速发现一些硬规则呢?假设变量具有一定的排序性,那么通常在两端的人群是目标群体。暂不考虑某些变量呈现两端风险低,而中间取值段风险高的情况,我们将实际场景简化为两种情况:
1、取值越小,坏人浓度越高
2、取值越大,坏人浓度越高
如图2所示, seg1 和 seg2 是潜在的目标子群体,分别对应分位数 x1-α 和 xα 。目标群体的规模可通过分位数 α 进行控制。
我们将目标子群体圈定出来,并评估坏人浓度,以及相对于总体的提升度(Lift)等指标。
我们可以暴力枚举出所有的变量规则,并结合业务含义筛选出满足要求的规则。对于排黑硬规规则的制定,这是一种颇为有效的方法。
rule_discover(input_df=df, var='score', target='is_bad')
在图3中,我们同样需要兼顾hit_rate和hit_bad_rate的关系,当hit_size过小时,计算hit_bad_rate容易发生波动,从而导致结果不可靠。
实现图3统计结果的Python代码如下所示:
def rule_evaluate(selected_df, total_df, target):
"""规则评估"""
# 命中规则的子群体指标统计
hit_size = selected_df.shape[0]
hit_bad_size = selected_df[target].sum()
hit_bad_rate = selected_df[target].mean()
# 总体指标统计
total_size = total_df.shape[0]
total_bad_size = total_df[target].sum()
total_bad_rate = total_df[target].mean()
hit_rate = hit_size / total_size # 命中率(支持度)
lift = hit_bad_rate / total_bad_rate # 提升度
res = [total_size, total_bad_size, total_bad_rate,
hit_rate, hit_size, hit_bad_size, hit_bad_rate, lift]
return res
def rule_discover(input_df, var, target, q_list=[0.005, 0.01, 0.02, 0.98, 0.99, 0.995]):
"""规则生成by tang yangyang"""
sub_df = input_df[input_df[var].notnull()].reset_index(drop=True)
miss_df = input_df[input_df[var].isnull()].reset_index(drop=True)
res_list = []
rule = "is missing"
res = rule_evaluate(miss_df, input_df, target)
res_list.append([var, rule] + res)
# 分位数列表,用以控制目标群体规模
for q in q_list:
threshold = sub_df[var].quantile(q)
rule = ""
if q < 0.2:
temp = sub_df.query("{0} <= @threshold".format(var))
rule += "<= {0}".format(threshold)
else:
temp = sub_df.query("{0} >= @threshold".format(var))
rule += ">= {0}".format(threshold)
res = rule_evaluate(temp, input_df, target)
res_list.append([var, rule] + res)
result_df = pd.DataFrame(res_list, columns=['var', 'rule',
'total_size', 'total_bad_size', 'total_bad_rate',
'hit_rate', 'hit_size', 'hit_bad_size', 'hit_bad_rate', 'lift'])
return result_df
Part3.高维空间变量规则发现:PRIM算法
如果我们想利用多维变量来构建风控规则呢?在医疗中,我们更关心一群人为什么会生病,需要归纳病因,对症下药。同样的,在风控中,我们也需要从高维特征空间中提取风控排黑规则。
此时就可借助一种算法——病人规则归纳方法(Patient Rule Induction Method,PRIM)。在理解单变量规则发现的原理后,我们应该会很容易理解该算法。其只是利用了更多的变量维度,但操作过程就像是从顶向下削苹果 (Top-down peeling)。
我们先定义一个计算正样本浓度的目标函数:
式 (2) 中, nm+1 表示在箱子 Bm 里的样本量, yi∈{0,1} 。因此, f(y) 含义为箱子 Bm 里的正样本浓度。
我们从 Bm 剔除一块区域后,可以得到一个新的箱子 Bm+1 :
式 (4) 的含义为,从多个候选区域中选择一个,使得剔除该区域后,剩下区域的正样本浓度最大。其中,候选区域集合为:
xjm(α) 表示 图片 在当前箱子 Bm 里的 α 分位数。低于 α 分位数或高于 1-α 分位数的样本将会被剔除(Peeling)。α 为超参数,表示每次剔除的样本比例。我们通常会选用一个比较小的数值,比如0.05-0.1。这样的好处在于,每次局部调整不会对最终结果产生很大影响。
我们不断迭代执行Peeling操作,直到满足以下条件:
式 (7) 中,指示函数 I(.) 的含义为,如果样例 xi 在箱子 Bm 里,则取值为1,反之为0。 n 是总样本量。因此, βm 的含义是,属于这个箱子里的样本量相对于总样本量的占比。
最小支持度 β0 是另一个超参数,是目标子群体的样本占比。该参数不宜过小,否则将会让结果失去统计意义,且容易波动。
最后,我们总结上述操作流程如图4所示。
为了更为直观理解,现有一批有贷后风险表现 y 的样本和两个决策变量 x1 和 x2 ,我们希望找出坏样本浓度最高的一个子群体,生成风控排黑规则。
初始化时,设置超参数 α =0.25, β0=0.075 。在图5-1中,初始箱 B1 包含所有样本量,根据 α 参数,可以计算这两个决策变量的 P25 和 P75 分位数。由此可得到4块候选区域:
根据式 (3) 定义的目标,我们选择剔除区域 b11+ ,使得剩余区域的坏人浓度最高,从而得到图5-2,由此 B2<-B1-b1* 。
在图5-3中,我们继续计算 B2 中的两个变量的 P25 和 P75 分位数,剔除掉 b*2 ,得到 B3 。不断重复执行这一操作,直到满足箱子里的样本占比不超过 β0 。
最终得到的箱子 B9 为:
我们统计上述迭代过程中所产生的所有箱子 Bm,m∈{1,2,…,M} 的 βm 和 f(y) 指标,并以 βm 作为横轴, f(y) 作为纵轴,绘制得到二维轨迹,如图6所示。从中可知:
当 β=1.0 时,坏人浓度 f(y)=0.2 ,说明大盘总体的坏人浓度为0.2。
最小支持度 β0 取值越大,坏人浓度f(y) 越低。显然,这可以理解为召回率和准确率之间的矛盾(tradeoff)。横轴是召回率,纵轴是准确率。
当最小支持度 β0 较小时,坏人浓度发生震荡。这是因为样本量过小,会导致计算坏人浓度时出现波动,也更容易发生过拟合(overfitting)风险。为此,可以从训练集中预留出一部分样本(holdout set)作为交叉验证。
在图6中,超参数 α 表示每次剔除的样本比例,不同的超参数 α 将会产生不同的轨迹。我们定义,若满足条件:
则称 Bn 被 Bm 所支配。
在Peeling步骤中,决策边界是局部最优,依赖于上一步结果。也就是说,Peeling是贪婪的。贪婪算法在搜索过程中是局部最优,而非全局最优。
Pasting策略是一种自底向上(Bottom-up)的操作,是与Peeling相反的操作。由此,支持度将会上升,而目标函数也可能会增大。因此,Pasting操作试图让其跳出局部,使得全局最优。
通常,我们会同时使用这两种策略。为此,我们引入超参数 α paste ,用以控制Pasting策略。
如果我们想寻找多个不同的子群体,那就引入covering策略。如图7所示,操作方法其实很简单:先找到子群体 B^(1)后,将这部分样本剔除,重新寻找子群体,那就会继续得到 B^(2) 。
Part4.PRIM和CART之间的差异
CART和PRIM这两者之间有什么差异和联系呢?
文献《PRIM versus CART in subgroup discovery: When patience is harmful》设计了3种典型场景,系统比较了两种算法的差异性。在一些维度上比较,有以下区别:
缺失值处理:CART忽略缺失值,而PRIM则考虑缺失值。
生成规则机制:PRIM可以控制剔除样本的量,而CART树在分裂过程中,是以特定标准(如信息增益)来评估的,而不管生成群体的最小样本量。
人机交互:PRIM需要用户来定义参数 α (剔除比例)和 β0(最小支持度),并引入α paste 实现人机交互,更容易满足用户需要。
图8是是两者寻找目标子空间的过程差异。
我们以实际风控业务场景为例,现在根据4个变量维度制定规则,找出目标人群予以拒绝,即:在通过率一定的前提下,拦截足够多的坏人,期望规则捕捉坏人的Lift能足够高。
这4个变量的业务含义分别为:
x1 : 风险分,取值范围0-100,取值越高,风险越高
x2 : 信用分,取值范围350-950,取值越高,风险越低
x3 : 信用分,取值范围350-950,取值越高,风险越低
x4 : 风险分,取值范围0-100,取值越高,风险越高
import prim
p = prim.Prim(x=test_df[['x1','x2', 'x3', 'x4']],
y=test_df['is_overdue'],
threshold=None, threshold_type='>',
peel_alpha=0.05, paste_alpha=0.05,
mass_min=0.04, # 目标命中率
include=None, exclude=None, coi=None)
box = p.find_box()
box.show_details() # 查看细节
if
x1 >= 67.5 and x1 <= 97 and
x2 >= 350 and x2 <= 555 and
x3 >= 399 and x3 <= 665.5 and
x4 >= 5 and x4 <= 95.5
then 'reject' else: 'pass'
在图9中,各统计指标的含义分别为:
Coverage:坏人召回率 = 命中规则样本的坏样本数 / 所有样本的坏样本数
Density:坏人密度 = 命中规则的坏样本数 / 命中规则的总样本数
Mass:命中率 = 命中规则的总样本数 / 总样本数
Res Dim:限制维度,生成目前决策规则的变量数
Mean:坏人浓度 = 命中规则的坏样本数 / 命中规则的总样本数
执行以下语句,可以得到图10。绿色为召回率的变化曲线,橙色为命中率的变化曲线,红色为坏人密度。当执行41轮后,由于Mass达到了我们预设阈值,迭代结束。
# Plot of peeling and pasting trajectory statistics.
# Produces a plot of the peeling and pasting trajectory statistics,
# including the mean, mass, coverage, density, and number of restricted dimensions.
box.show_ppt()
我们再观察Density(坏人密度)和Coverage(坏人召回率)之间的变化关系,如图11所示。
# Plot the tradeoff between coverage and density.
box.show_tradeoff()
策略面向人工理解,模型面向统计理解。不同于传统画格子、CART树来指定规则,PRIM提供来一种可行的参考工具。但是,任何工具都只是辅助进行决策判断,而不能完全依赖于工具。我们需要在此基础上,结合业务知识对其进行修正。
参考资料
https://xueshu.baidu.com/usercenter/paper/show?paperid=7b7c106ab654136dc4696850ac6d8513&site=xueshu_se
https://xueshu.baidu.com/usercenter/paper/show?paperid=6b27e1949ed62a8deb0de6f63e3a8e9a&site=xueshu_se
https://xueshu.baidu.com/usercenter/paper/show?paperid=e29c3c55bc43d8df0d3a1b103ed95625&site=xueshu_se
https://github.com/mmathioudakis/bump_hunting
https://pydoc.net/PRIM/0.4/prim/
https://github.com/Project-Platypus/PRIM