rete可以被分为两部分:规则编译和运行执行。规则编译是指根据规则集生成推理网络的过程,运行时执行指将数据送入推理网络进行筛选的过程。
基本概念
Rete算法的初衷是:利用规则之间各个域的公用部分减少规则存储,同时保存匹配过程的临时结果以加快匹配速度。为了达到这种效果,算法将规则拆分,其中每个条件单元作为基本单位(节点)连接成一个数据辨别网络,然后将事实(facts)经过网络筛选并传播,最终所有条件都有事实匹配的规则被激活。
网络共有5类节点:root节点、type节点、alpha节点(也称单输入节点)、beta节点(也称双输入节点)、terminal节点等。
根节点
代表整个Rete网络的入口,它可以让所有的事实通过,并传递给ObjectType节点。作为根节点的后继,
类型节点
ObjectType Node就是我们的fact,也就是我们规则所用到的pojo,用于选择事实的类型,将符合本节点类型的事实向后继的alpha节点传播。
alpha节点
alpha节点主要进行同对象类型内属性的约束或常量测试,比如“name==zhang”,“age>15”等等。
一条简单的规则
rule "规则1:账户余额小于100"
when
$account:Account(balance<100)
then
print("账户余额小于100");
end
规则1对应的rete网络如下:
LeftInputAdapterNode节点
这个节点的作用是将一个事实对象转化为一个元组,主要为beta节点提供作用。
beta节点
beta节点主要根据不同对象之间的约束,如“p.name==c.friend”,“p.age>cat.age”等进行连接操作。beta节点又分为join节点、Not节点等。Join节点包括两种输入,左部输入事实列表,称为元组(Tuple),右部输入一个事实对象,对象与元组在Join节点按照类型间约束进行Join操作,将符合的事实加入元组中继续传入下一个beta节点。
在规则1基础上增加条件,形成另外一条规则
rule "规则2:账户余额小于100的姓张的学生卡"
when
$account:Account(balance<100,type=Account.Type.学生)
$customer:Customer(firstName = 'zhang',accounts contains $account)
then
print("账户余额小于100的姓张的学生卡");
end
规则2对应的rete网络如下:
Terminal节点
Terminal节点是规则的末尾节点,它代表一个规则匹配结束,当事实或元组传递到Terminal节点时,表示该Terminal节点对应的规则被激活。
RETE网络的构建
Rete算法的编译结果是创建了规则集对应的Rete网络,它是一个事实可以在其中流动的图。创建rete网络的过程如下:
- 创建根。
- 取出一个规则r。
a.取出一个模式p,检查参数类型,如果是新类型,则加入一个类型节点; b.检查模式p的条件约束, 对于单类型约束,检查对应的α节点是否已存在,如果不存在将该约束作为一个α节点加入链的后继; 若为多类型约束,则创建相应的β节点,其左输入为前一β节点(第一个β节点左部输入为LeftInputAdapterNode节点),右输入为当前链的α节点; d.重复b-c,直到模式p的所有约束处理完毕; e.重复a-d,直到所有的模式处理完毕,创建Terminal节点,每个模式链的末尾连到Terminal节点; f.将动作(Then部分)封装成叶节点(Action节点)作为输出节点。
- 重复步骤2直到所有规则处理完毕。
按照上面创建rete网络的算法,生成规则3
rule "规则3:账户余额小于100的北苑路姓张的学生"
when
$account:Account(balance<100,type=Account.Type.学生)
$customer:Customer(firstName = 'zhang',accounts contains $account)
$addr:Addr(street="北苑路",customers contains $customer)
then
print("账户余额小于100的北苑路姓张的学生");
end
加入规则1、规则2和规则3之后对应的rete网络如下:
运行时执行
- 从工作内存中取一工作存储区元素WME(Working Memory Element,简称WME)放入根节点进行匹配。WME是为事实建立的元素,是用于和非根结点代表的模式进行匹配的元素。
- 遍历每个alpha节点(含ObjectType节点),如果alpha节点约束条件与该WME一致,则将该WME存在该alpha节点的匹配内存中,并向其后继节点传播。
- 对alpha节点的后继结点继续(2)的过程,直到alpha内存所有通过匹配的事实保存在alpha内存中。
- 对每个beta节点进行匹配,如果单个事实进入beta节点左部,则转换成一个元素的元组存在节点左侧内存中。如果是一个元组进入左部,则将其存在左内存中。如果一个事实进入右侧,则将其与左内存中的元组按照节点约束进行匹配,符合条件则将该事实对象与左部元组合并,并传递到下一节点。bate结点有left存储区和right存储,其中left存储区是beta内存,right存储区是alpha内存。存储区储存的最小单位是WME。
- 重复(4)直到所有beta处理完毕,元组对象进入到Terminal节点。对应的规则被触活,将规则后件加入议程(Agenda)。
- 对Agenda里的规则进行冲突消解,选择合适的规则执行。
Rete算法的特点
rete算法通过共享规则节点和缓存匹配结果,获得产生式推理系统在时间和空间上的性能提升。
1、状态保存
事实集合中的每次变化,其匹配后的状态都被保存再alpha和beta节点中。在下一次事实集合发生变化时,绝大多数的结果都不需要变化,rete算法通过保存操作过程中的状态,避免了大量的重复计算。Rete算法主要是为那些事实集合变化不大的系统设计的,当每次事实集合的变化非常剧烈时,rete的状态保存算法效果并不理想。
2、节点共享
另一个特点就是不同规则之间含有相同的模式,从而可以共享同一个节点。Rete网络的各个部分包含各种不同的节点共享。
3、节点索引
索引方法是指对rete网络的节点建立当前节点对后继的索引,在事实断言时可以通过索引快速找到对应的后继节点而无需逐个查 找。drools在rete的面向对象版本rete-oo算法中对ObjectType节点增加后继alpha节 点的索引,以事实的属性为key,alpha节点为value,这样在事实通过类型节点验证后可以迅速找到对应的alpha节点进行断言。
同样,对beta节点也可以建立索引,beta节点的索引主要是针对节点左右内存的查询。当一个事实传递到beta节点的右内存中时,需要与该节点的左内存进行连接操作,即遍历左侧内存中的事实元组,找到符合节点约束的事实进行连接。该过程的遍历查找效率较低,将beta内存分成若干单元,每个单元分配一个id;对右侧的事实用哈希函数求索引,该索引就是某个单元的位置,通过索引快速找到相应单元进行匹配,如果不在该分区,则将该对象组成一个新的单元加入左内存。
Rete 算法的不足
- 存在状态重复保存的问题,比如匹配过模式1和模式2的事实要同时保存在模式1和模式2的节点缓存中,将 占用较多空间并影响匹配效率。
- 事实的删除与事实的添加顺序相同, 除了要执行与事实添加相同的计算外, 还需要执行查找, 开销很高。rete的一个主要缺点就是不适合处理快速变化的数据和规则。主要表现在:
1.数据变化引起节点保存的临时事实频繁变化,这将让rete失去增量匹配的优势。
2.数据的变化使得对规则网络的种种优化方法如索引、条件排序等失去效果。
- rete算法使用了alpha内存存储已计算的中间结果, 以牺牲空间换取时间, 从而加快系统的速度。然而当处理海量数据与规则时,beta内存根据规则的条件与事实的数目而成指数级增长, 所以当规则与事实很多时, 会耗尽系统资源。
- 规则引擎不能处理缺失的数据及模糊的逻辑。例如规则“如果年级大则容易患中风”。产生式推理系统将不能精确表达“年级大”及“容易”这样的概念,相应的推理也不能得到精确的结果。这种场合下,算法变得很脆弱。
参考: