I.风控决策介绍
之前的文章实现了决策引擎中的规则集、决策流,并介绍了模型引擎和机器学习建模。具体参见之前文章:
决策树也称规则树,是由多个规则按一定分支及流程编排而成,直观显示如下:

对比规则集示例如下:

其相同之处在于都可以抽象成多个规则,而差异在于规则集的决策结果为所有规则触发策略的最优先策略,规则间无因果顺序;而决策树则会由多个规则触发结果再进行逻辑运算所得,规则间存在因果顺序。基于此决策树的DSL建模可抽象为规则部分和决策部分,规则部分可复用之前的DSL结构和代码逻辑。
决策树可以抽象为规则、决策两部分,其中规则的结果为中间状态D1、D2、D3、D4,决策部分为中间状态的组合,中间状态为D1和D3时,输出结果为A,依次类推可得到不同的结果。

决策树抽象成DSL语法:
decisiontrees:decisiontree:name: decisiontree_1depends: [feature_1, feature_2]rules:rule:rule_name: "rule_1"conditions:condition:feature: feature_1operator: GEvalue: 20logic: ANDdecision: D1rule:rule_name: "rule_2"conditions:condition:feature: feature_1operator: LTvalue: 20logic: ANDdecision: D2rule:rule_name: "rule_3"conditions:condition:feature: feature_2operator: EQvalue: truelogic: ANDdecision: D3rule:rule_name: "rule_4"conditions:condition:feature: feature_2operator: EQvalue: falselogic: ANDdecision: D4decisions:decision:depends: [D1, D3]logic: ANDoutput: Adecision:depends: [D1, D4]logic: ANDoutput: Bdecision:depends: [D2, D3]logic: ANDoutput: Cdecision:depends: [D2, D4]logic: ANDoutput: D
对决策树DSL进行解析:
//define decision tree structtype Decisiontree struct {Name string `yaml:"name"`Depends []string `yaml:"depends,flow"`Rules []Rule `yaml:"rules,flow"`Decisions []Decision `yaml:"decisions,flow"`}//parse decision tree output type is string,also can be func()func (dt *Decisiontree) parse() string {log.Printf("decisiontree %s parse ...\n", dt.Name)var result = make(map[string]bool, 0)//reuse rule parsefor _, rule := range dt.Rules {result[rule.Decision] = rule.parse()}for _, decision := range dt.Decisions {if parseDecision(result, decision) {return decision.Output}}return ""}//parse decisionfunc parseDecision(result map[string]bool, decision Decision) bool {var rs = make([]bool, 0)for _, depend := range decision.Depends {if data, ok := result[depend]; ok {rs = append(rs, data)}}final, _ := operator.Boolean(rs, decision.Logic)return final}
编写测试用例测试决策树解析:
func TestDecisionTree(t *testing.T) {internal.SetFeature("feature_1", 18)internal.SetFeature("feature_2", false)dsl := dslparser.LoadDslFromFile("decisiontree.yaml")rs := dsl.ParseDecisionTree(dsl.Decisiontrees[0])if rs == "D" {t.Log("result is ", rs)} else {t.Error("result error,expert D, result is ", rs)}}
执行后效果如下:

决策表是通过对多个条件交叉组合成的一张表格,形式如下图示例:

对决策表进行抽象建模发现与决策树的抽象如出一辙,拆分成规则以及规则组合后的决策两部分,而交叉组合的方式也与决策树一致,因此决策表和决策树展示形式不同,代码实现保持一致,这里不再重复实现。
决策矩阵也叫交叉决策表,它由横向X特征和纵向Y特征两个特征维度的不同条件组合决定,输出结果为满足条件对应的X&Y “交叉单元格”的值。

在风控场景中,可对两个决策模型的输出结果进行交叉决策,输出的结果作为用户风险等级(Rank Grade)。由于模型结果表示概率,一般是-1到1间的浮点数,所以需要对模型结果进行一定的转换,转成方便表达的正整数(模型分数),如1-200区间,再进行分箱和组合。
decisionmatrixs:- decisionmatrix:name: decisionmatrix_1depends: [model_1, model_2]rules:- rule:rule_name: X1conditions:- condition:feature: model_1operator: LTvalue: 80logic: ANdecision: D1- rule:rule_name: X2conditions:- condition:feature: model_1operator: GEvalue: 80logic: ANDdecision: D2- rule:rule_name: Y1conditions:- condition:feature: model_2operator: LTvalue: 100logic: ANDdecision: D3- rule:rule_name: Y2conditions:- condition:feature: model_2operator: GEvalue: 100- condition:feature: model_2operator: LTvalue: 150logic: ANDdecision: D4- rule:rule_name: Y3conditions:- condition:feature: model_2operator: GEvalue: 150logic: ANDdecision: D5decisions:- decision:depends: [D1, D3]logic: ANDoutput: A- decision:depends: [D1, D4]logic: ANDoutput: A- decision:depends: [D1, D5]logic: ANDoutput: B- decision:depends: [D2, D3]logic: ANDoutput: A- decision:depends: [D2, D4]logic: ANDoutput: B- decision:depends: [D2, D5]logic: ANDoutput: C
规则矩阵DSL解析如下:
//define decision matrixtype DecisionMatrix struct {Name string `yaml:"name"`Depends []string `yaml:"depends,flow"`Rules []Rule `yaml:"rules,flow"`Decisions []Decision `yaml:"decisions,flow"`}//parese decision matrixfunc (dm *DecisionMatrix) parse() string {log.Printf("decisionmatrix %s parse ...\n", dm.Name)depends := internal.GetFeatures(dm.Depends)var result = make([]string, 0)for _, rule := range dm.Rules {if rule.parse() { //true will be addedresult = append(result, rule.Decision)}}for _, decision := range dm.Decisions {//compare slice []{x,y}if compareSlice(decision.Depends, result) {return decision.Output}}return ""}//compare two slicesfunc compareSlice(s1, s2 []string) bool {s1Str := strings.Replace(strings.Trim(fmt.Sprint(s1), "[]"), " ", "", -1)s2Str := strings.Replace(strings.Trim(fmt.Sprint(s2), "[]"), " ", "", -1)return s1Str == s2Str}
编写测试用例测试决策矩阵解析:
func TestDecisionMatrix(t *testing.T) {internal.SetFeature("model_1", 85)internal.SetFeature("model_2", 180)dsl := dslparser.LoadDslFromFile("decisionmatrix.yaml")rs := dsl.ParseDecisionMatrix(dsl.DecisionMatrix[0])if rs == "C" {t.Log("result is ", rs)} else {t.Error("result error,expert C, result is ", rs)}}
执行结果如下:

评估授信额度,一般根据用户风险评级、历史借贷次数(新/老用户),组合映射不同额度值。

此时又看到了熟悉的界面,就是通过决策表或决策树来实现,最终额度输出可能更复杂一些,需要根据特征乘不同系数,或给定最大上限额或最小上限,可以组成一个表达式。表达式支持:加减乘除、平方开方、取大取消等运算。
const MAX_CREDIT_LIMIT = 12000 //max credit limitcoeff := internal.getFeature("coeff") //额度系数//modelCreditLimit 评级额度映射表finalCreditLimit := min((modelCreditLimit * coeff), MAX_CREDIT_LIMIT)
除了额度外,还会输出费率、期限、额度有效期等,这部分可以按用户风险评级或其他特征进行映射配置,组合成金融产品包,方便管理,映射配置方式也使用决策树、决策表形式。
V.决策节点融入决策流
决策节点一般作为决策流的最后一个执行节点(end节点不算执行节点),所以它一般会输出最后的决策结果。

VI.更多思考
比较运算:operator.Compare
布尔运算:operator.Boolean
基础DSL:
条件表达式dslparse.condition
规则 dslparse.rule
决策 dslparse.decision
节点DSL:
决策流dslparse.workflow
规则集dslparse.ruleset
决策树dslparse.decisiontree
决策矩阵dslparse.decisionmatrix
条件网关dslparse.conditional
AB网关dslparse.abtest
规则集、决策树、决策矩阵、条件网关都可以分解用规则+决策来实现。
构造rete网络
另一实现方式是规则引擎常用的rete算法,它提供了更高效的模式匹配,这里展开讨论一下rete实现的异同,首先涉及几个基础概念:
facts 事实,对应理解为数据挖掘的特征。 rule 规则,由and或or组成的多个条件conditions,由if...then表示,if部分也叫lhs(left-hand-side),then部分为rhs(right-hand-side)
module 模式,最小原子条件,对应理解为condition。
rate算法,主要是改进match处理过程,其处理流程如下:

(图片来自网络)
核心引擎部分即为pattern matcher 模式匹配和 agenda 议程 (处理冲突和执行决策)。它将所有规则最终编译成一个网络(rete拉丁语是网络的意思),包括alpha网络和beta网络。

(图片来自网络)
网络的构建始于根节点Root Node(黑色)
构建Alpha网络,根据rule_1获取执行条件(module模式)中参数类型,添加Type Node节点(Object Type Node),并将module作为AlphaNode加入网络(已添加忽略),重复执行所有rule下的module,构建Alpha内存表 (Alpha Memory 黄色节点)。
构建Beta网络,Beta Node节点,Beta(i)左输入节点为Beta(i-1),右输入节点为Alpha(i),连接节点Join Node(绿色节点)
规则执行部分封装成Terminal Node(灰色节点)
