cover_image

决策引擎系统设计原理与开源实践

贺鹏Kavin 技术岁月
2022年11月28日 00:38

前几天在 “开源小秀场” 分享了关于我自研的开源决策引擎系统的相关话题,主要介绍了 “天网决策引擎系统” 的开发设计原理以及如何将其部署到生产环境中使用。

图片


01

决策引擎应用场景

随着互联网金融的发展以及银行数字化转型的需求,金融风控、智能风控、智能营销等话题频频出现,而 “决策引擎” 作为支撑其业务场景的核心系统,也受到了更多的关注。一般来说,决策引擎可以用于包括金融风控、内容风控、推荐营销、物联网监控等各个领域,凡是涉及到使用规则或模型来做业务决策的场景都可以考虑使用决策引擎来实现。

图片

规则引擎(专家系统)被更多人所熟知,那么决策引擎又是什么?和规则引擎有什么区别和联系呢?我认为,决策引擎正是在规则引擎的基础上发展而来的。事实上,有不少决策引擎系统就是通过规则引擎改造实现的。而差别在于决策引擎在规则引擎的基础上实现了更多元的决策方式,并引入了流程编排过程,使其可以支持更复杂的决策场景。

图片

规则引擎的实现模式是规则清单,执行的主体是规则,简单的规则引擎就是罗列出所有规则并执行,有些复杂的规则引擎会引入规则的优先级和规则编排,但编排的主体仍是规则。

决策引擎的实现模式是决策流编排,执行的主体是决策流,在决策流中编排不同的规则节点、决策节点以及分支节点来实现复杂的决策流程。

图片(左侧为规则清单,右侧为决策流)


02

决策引擎开发原理

▌2.1 规则构建

简单介绍了决策引擎是什么,现在我们来看一下它是如何实现的?将决策引擎拆解开来,从基础构建元素 规则 开始,再将规则组合起来构成更复杂的决策集合。

图片
(风控规则案例)

要实现这条规则,首先想到可以通过代码 if / else 来实现。

图片
(规则模拟代码

此代码有明显的硬编码问题,规则调整要涉及到开发、测试、上线的流程,调整周期长。且规则逻辑维护到了代码中,难以传承,也容易造成生产中跑规则和业务预想的规则有差异。当有大量规则或决策场景接入时,工作量巨大难以维护。

图片(生产中的决策流包含几十个决策节点)

图片(生产中的规则集包含上百条规则)

为了实现规则调整不受系统制约,我们需要将 规则配置 与 程序代码 进行分离。我们可以通过在程序中嵌套脚本或代码来实现,这样每次调整规则,只需要重新加载即可,而不用修改和发布系统程序。

常见的嵌入式脚本有 Lua、Groovy、Javascript 等,市面上有不少引擎是基于此类脚本语言实现的。另一种方式可以通过自定义语言语法,也叫特定语言 Domain Specific Language(DSL),比如大名鼎鼎的开源规则引擎 Drools 就自己实现了一套 DSL语法(命名为 DRL)。DSL 可以更清晰地表达要实现的规则含义。

图片
(Drools 语法 demo)

我们也自定义实现了一套 DSL ,使用的语法结构是 Yaml。简单介绍下 Yaml 是一个轻量级的语法结构,比 xml / json 更小巧,可读性非常好,完全面向人类语言,也有很好的表达能力,支持多种数据结构。

图片

回到刚才的规则,我们看看如何用 Yaml 将该规则描述出来。对规则进行拆解发现,规则由三个条件表达式组成。

  • 条件表达式一:申请借款总金额  >  5000 

  • 条件表达式二:申请多头借贷数量 <  

  • 条件表达式三:月薪 <= 8000          


每个条件表达式又分别由
特征运算符阈值 构成。

然后将这三个条件表达式的计算结果,再进行逻辑(布尔)运算。

(表达式一  表达式二)  表达式三

逻辑运算后的结果,如果是 true,即代表命中,触发结果输出拒绝”

将上述过程用 Yaml 语法描述如下。

图片

构建出规则 DSL 后,我们对其进行语法解析,从上述分析来看,解析过程主要是分别完成条件表达式的比较运算以及针对表达式结果的逻辑运算,最后根据结果输出。

图片

实现比较运算的算子,要考虑不同类型特征需要支持不同的操作符和不同的实现。如数值型特征,支持大于、小于、等于、不等于、大于等于、小于等于、区间等;字符串特征支持等于、不等于、模糊查找等;数组特征的等于运算要考虑数组元素完全相同。实现时要根据特征的不同,来做不同的比较算子逻辑。

此外还要实现逻辑运算(or、and)和算数运算(+ - * / % 平方、开方等)。

对于复杂的表达式支持,要实现表达式运算。像 Python 类动态语言,支持 eval 执行字符串表达式。而像 Golang、Java 类静态语言,则需要自己实现字符串表达式的执行,其基本思路就是通过构建抽象语法树,将操作符、特征、常量区分出来,通过树的有序遍历来执行。有一些方便的三方包可以辅助实现表达式执行,如 govaluate(Golang)、SpEL(Java)、QLExpress(Java)。

图片

参考代码:https://github.com/skyhackvip/risk_engine/tree/master/internal/operator

▌2.2 规则组合

实现了基本规则和表达式,那么将规则和表达式以不同的方式组合起来,就构成了规则集、决策树、决策表等不同的组合形式。

规则集,就是多条规则组成的集合,规则集中的所有规则都是并列的,每条规则的执行都不影响其他规则,可以串行依次执行,也可以并行执行。

规则集的 Yaml 描述,就是在规则的基础上进行嵌套来实现。

图片

如果要让规则集中的规则并行执行,可以设置规则执行计划,增加 Yaml 描述。

exec_plan=parallel

执行时,将每条规则都放置到 goroutine 中执行,得益于 Golang 协程的轻量级特性,每条规则可以开一个协程,并行执行效率更高,通过 WaitGroup 汇总等待所有规则执行完成。

如果同时命中了规则集的多条规则,而规则的触发策略不同,有的规则触发输出通过,有的则输出拒绝,那么规则集要对结果进行冲突决策,可以通过设置触发结果的优先级,优先级高的结果作为规则集的输出结果。

图片

其他更多的决策类型,如决策树、决策矩阵、评分卡可以参阅之前文章。

图片

详情参阅:

决策节点实现

评分卡实现


▌2.3 决策流实现

有了不同类型的节点(规则集、决策树、决策矩阵、评分卡等),我们进一步将不同的节点编排起来即形成了决策流。

决策流可分无分支决策流和有分支决策流。无分支决策流即将所有节点顺序连接起来。所谓分支即决策流在执行到该节点时根据不同的条件进行分流选择,分流方式可以是条件分流(如根据性别条件分男、女两条分支),也可是随机分流(也叫冠军挑战者、Abtest,随机分55% 流量和 45% 流量两条分支)。

决策流实现遵循 BPMN 规范,包含开始节点、结束节点、活动节点(放置不同的规则集、决策树、决策矩阵等)、分流节点(条件分流、随机分流)、带箭头的线代表流向。

图片

要实现决策流,首先要对决策流的数据结构进行技术选型,考虑是基于树、图还是单向链表或其他结构。

图片

首先决策流是有流向(方向)的,也就是说前后节点存在依赖关系,后继节点依赖于前继节点的结果。比如决策树节点输出了评级特征,后继分流节点根据评级不同走不同的分支。对于有方向的特性,树、无向图结构无法采用。

有向图如果存在环,会造成执行死循环,无法终止,所以不满足。剩下有向无环图和单向链表可以放入备选方案考虑。

先来看看有向无环图(DAG)的方案:

图片
(DAG:a-e分别代表不同任务节点)

有向无环图实现了有向的依赖关系,计算过程沿着依赖关系方向进行,后继节点 b 是依赖于前继节点 a 执行完成;同时 (c->d)节点执行和 (f->g)节点执行可以并行,提高执行效率。因此有向无环图方案是可行的,DAG 方案可用于检测配置的决策流的正确性,验证决策流“不成环”、“全连通”等。

此外介绍一下网络图(Rete)的方案:

图片
(Rete:斜线左上方为alpha 网络,右下方为 beta 网络)

Rete 网络方案是一种高效的规则解析方案,Drools 即选择该方案来进行规则引擎的执行。其优点是通过构建的 beta 网络缓存记忆了推理数据,利用空间换时间加速了结果的推理。缺点是算法比较复杂。

介绍了两种可行的方案后,我们引入新的需求,进一步评估下新需求下方案的选择。

需求一:分支节点实现的是排它网关,无论是做随机分流还是条件分流,都是排它执行。即通过上图节点 b 后会选择 (c->d) 或 (f->g)其中一条路径执行,而不是全部节点执行。

需求二:决策流支持熔断。即执行到某节点时,由于其输出或命中满足特定条件,就会终止不继续执行。比如执行到节点 c,触发了黑名单规则,要做熔断处理,那么就会返回结果,而放弃继续执行 d、e 节点。

带着这两个需求来评估,对于 DAG 来说,其图遍历过程会执行所有节点,而 Rete 也无法简单的支持分流和熔断。这里考虑使用另一种解决方案,改进型单向链表方案。

图片

改进型单向链表,对于无分支的决策流,按指针顺序执行即可。对于有分支的决策流,可以通过剪枝的方式将流拉平为单向链表。而剪枝的方法就是在执行到分支节点的时候,根据执行结果动态的输出下一个节点是什么,进而控制决策流走向。

同时,每个节点执行完毕后,都可以查看是否满足熔断策略,如果满足熔断策略,可以控制后继节点为空,终止决策流的执行,进而实现决策流熔断能力。

执行过程采用了改进 Pipeline 模型,执行依赖通过全局上下文 Context 来控制。执行时,节点所依赖特征可以通过全局上下文来获取,其输出结果也将存储到全局上下文中,后继节点即可通过全局上下文获取到上一节点已处理的特征和结果来完成节点计算。节点之间通过全局上下文交换数据,可保障节点间无直接依赖关系(解耦),实现灵活编排,同时全局上下文也掌握全部的依赖特征、执行过程数据、执行结果数据和执行路径信息等,用于最终输出。这种模式也称为“全局工作台模式”

图片

确定了数据结构方案,我们还是通过 Yaml 来描述出一个简单的决策流,然后通过代码解析 Yaml(DSL) 来实现决策流的执行。

图片

左侧 Yaml 描述决策流:

  • 第一个节点为开始节点(start/start_1),有且只有一个;

  • 其后继节点类型和名称为任务节点规则集(ruleset/ruleset_1);

  • 再后继的节点是另一规则集(ruleset/ruleset_2);

  • 最后为结束节点(end/end_1),无后继节点。

右侧解析决策流代码:

  • 定义了节点接口 INode,核心为 Parse() 解析函数,每个挂载到决策流上的节点,都要实现 INode (实现节点解析);

  • 扩展决策流支持节点类型,如增加复杂评分卡、复杂决策矩阵等,可以通过实现核心接口 INode ,即可顺利接入决策流编排;

  • 决策流执行 Run() 通过遍历单向链表,一直执行到结束或退出。其中每个节点执行都会输出下一节点(flowNode)和熔断标识(gotoNext),用于控制决策流分流和熔断。

  • PipelineContext (全局工作台)用于为每个节点提供特征依赖,也接收来自每个节点的结果和缓存数据,并记录执行流程。


参考代码:https://github.com/skyhackvip/risk_engine/blob/master/core/flow.go


03

开源决策引擎实践

▌3.1 天网决策引擎介绍

图片

天网决策引擎系统是完全免费开源的决策引擎系统,基于 Golang 语言开发,实现自定义 DSL 语言,实现决策流编排和复杂规则决策。

开源决策引擎介绍

图片

天网决策引擎通过启动 WEB 服务,对外提供 HTTP 接口,实现了开箱即用。其输出结果包含决策过程和决策结果的完整信息,可以据此进行业务逻辑的组装。

图片

天网决策引擎代码结构简单,有完整语法文档可参考,可以快速实现业务接入。

▌3.2 生产环境应用

如果你打算尝试使用天网决策引擎系统,后期生产迭代方向和上下游系统架构可以考虑如下两方面。

1) 构建可视化的管理后台

图片

虽然 Yaml 语法简单易懂,但它仍偏向于技术人员,如果要交付给风控业务团队直接使用,需要构建一个可视化管理后台。通过可视化后台可以快速实现规则编辑,决策流绘制等工作,调整完成后可以一键发布生成 DSL,决策引擎发现决策流变更,重新加载最新数据,这个过程可以借助数据库来实现。

在决策流发布时,还可以增加审批、完整性校验等,保障发布安全可靠。

2) 上下游系统架构

图片

决策引擎一般是作为中台提供服务的,其上游对接业务后端系统,根据接入场景对接不同上游系统。如风控场景中根据贷前、贷中、贷后分别对接申请授信、贷中监测、贷后催收等系统。

决策引擎下游主要依赖于特征指标平台,根据特征指标的来源和加工方式的不同,又可能包括数据 API 平台,对接一些三方征信数据接口或其他内部接口。实时计算引擎,实现特征指标的实时加工。基于图关系的图特征也可以通过图数据库来实现。

此外,一些需要使用机器学习模型的场景,还会包含建模相关平台(AI 中台),模型执行平台(模型引擎)实现在线实时打分,模型分也作为特征指标提供给决策引擎做决策支持。

决策引擎配套可视化管理后台,提供给业务人员进行配置,其结果可以输出到风险大盘中进行实时监控分析和风险预警。

调用时序图中,决策引擎在执行时依赖的特征指标可由上游系统推送(推模式),也可以按需实时从特征引擎中加载(拉模式),实现推拉结合的模式。实际场景中一些三方数据因为考虑费用问题,可以采用拉模式延时加载,配置决策流先执行内部特征和黑名单规则,命中熔断即不会产生数据调用成本。

最后欢迎大家下载试用起来,如有任何问题也可以通过“技术岁月”公众号与我联系。

开源决策引擎下载地址:https://github.com/skyhackvip/risk_engine

以上就是分享的全部内容,如要下载分享 PPT 可以关注公众号“技术岁月”,发送 开源小秀场 即可。

直播回放地址:https://z.itpub.net/article/detail/71BA0EB721592F5782F8F17001E66E68


智能风控系统决策引擎 · 目录
上一篇一款开源又好用的决策引擎下一篇一文读懂金融风控领域的人脸反欺诈技术
继续滑动看下一个
技术岁月
向上滑动看下一个