【java規則引擎】之規則引擎解釋

Love Lenka發表於2017-01-08

轉載:http://www.open-open.com/lib/view/open1417528754230.html

現實生活中,規則無處不在。法律、法規和各種制度均是;對於企業級應用來說,在IT技術領域,很多地方也應用了規則,比如路由表,防火牆策略,乃至角色許可權控制(RBAC),或者Web框架中的URL匹配。不管是那種規則,都規定了一組確定的條件和此條件所產生的結果。

舉一個例子:

IF

汽車是紅色
車是運動型的
駕駛員是男性
駕駛員在16-25歲之間
THEN

保險費用增加20%

從這個例子可以看出:

每條規則都是一組條件決定的一系列結果
一條規則可能與其他規則共同決定最終結果。比如例子中的規則只產生了增量,還需要與確定基數的規則共同作用才能決定最終的費率
可能存在條件互相交叉的規則,此時有必要規定規則的優先順序
規則作為一種知識,其典型運用就是透過實際情況,根據給定的一組規則,得出結論。這個結論可能是某種靜態的結果,也可能是需要進行的一組操作。這種 規則的運用過程叫做推理。如果由程式來處理推理過程,那麼這個程式就叫做推理機/推理引擎。推理引擎根據知識表示的不同採取的控制策略也是不同的,常見的 型別包括基於神經網路、基於案例和基於規則的推理機。其中,基於規則的推理機易於理解、易於獲取、易於管理,被廣泛採用。這種推理引擎被稱為“規則引 擎”。


規則引擎起源於基於規則的專家系統(專家系統CLIPS: 源於1984年NASA的人工智慧專案,現已開源,由C編寫。),而基於規則的專家系統又是專家系統的其中一個分支。專家系統屬於人工智慧的範疇,它模仿 人類的推理方式,使用試探性的方法進行推理,並使用人類能理解的術語解釋和證明它的推理結論。基於規則的專家系統(RBES)包括三部分:Rule Base(knowledge base)、Working Memory(fact base)和Inference Engine。它們的結構如下系統所示:

推理引擎(Inference Engine)包括三部分:模式匹配器(Pattern Matcher)、議程(Agenda)和執行引擎(Execution Engine)。推理引擎透過決定哪些規則滿足事實或目標,並授予規則優先順序,滿足事實或目標的規則被加入議程。

模式匹配器決定選擇執行哪個規則,何時執行規則;
議程管理模式匹配器挑選出來的規則的執行次序;
執行引擎負責執行規則和其他動作。
和人類的思維相對應,規則引擎中也存在兩種推理方式:正向推理(Forward-Chaining)和反向推理(Backward-Chaining)。

正向推理也叫演繹法,由事實驅動,從 一個初始的事實出發,不斷地應用規則得出結論。首先在候選佇列中選擇一條規則作為啟用規則進行推理,記錄其結論作為下一步推理時的證據。如此重複這個過程,直到再無可用規則可被選用或者求得了所要求的解為止。
反向推理也叫歸納法,由目標驅動,首先提出某個假設,然後尋找支援該假設的證據,若所需的證據都能找到,說明原假設是正確的;若無論如何都找不到所需要的證據,則說明原假設不成立,此時需要另做新的假設。
將事實與規則進行匹配的演算法。常見的模式匹配演算法有RETE,LFA,TREAI,LEAPS。Rete演算法是目前效率最高的一個演繹法推理演算法,許多規則引擎都是基於Rete演算法來進行推理計算的。

推理引擎的推理步驟如下:模式匹配、衝突消解、執行引擎。

將初始資料(fact)輸入 Working Memory 。
使用 Pattern Matcher 比較規則庫(rule base)中的規則(rule)和資料(fact)。
如果執行規則存在衝突(conflict),即同時啟用了多個規則,將衝突的規則放入衝突集合。
解決衝突,將啟用的規則按順序放入Agenda。
使用執行引擎執行Agenda中的規則。重複步驟2至5,直到執行完畢所有Agenda中的規則。
規則引擎的作用:

規則外部化,即有利於規則知識的複用,也可避免改變規則時帶來的程式碼變更問題
由規則引擎使用某種演算法進行推理過程,不需要編寫複雜晦澀的邏輯判斷程式碼
開發人員的不需要過多關注邏輯判斷,可以專注於邏輯處理

RETE演算法

Rete在拉丁語中是“net”,有網路的意思。Rete演算法由Carnegie Mellon University的Dr Charles L. Forgy設計發明,是一個用來實現產生式規則系統(production/inference)的高效模式匹配演算法。

RETE演算法可以分為兩部分:規則編譯(rule compilation)和執行時執行(runtime execution)。

規則編譯===>是指根據規則集生成推理網路的過程。

執行時執行===>指將資料送入推理網路進行篩選的過程。

相關概念:

事實(Fact):物件之間及物件屬性之間的關係
規則(rule):是由條件和結論構成的推理語句,一般表示為if…Then。一個規則的if部分稱為LHS(left-hand-side),then部分稱為RHS(right hand side)。
模式(module):就是指IF語句的條件。這裡IF條件可能是有幾個更小的條件組成的大條件。模式就是指的不能在繼續分割下去的最小的原子條件。
RETE推理網路的生成過程:從規則集{規則1,規則2……..}中拿出一條來,根據一定演算法,變成RETE推理網路的節點。不斷迴圈將所有規則都處理完,RETE推理網路就生成了。RETE網路主要分為兩個部分,alpha網路和beta網路。如下圖所示。

 

alpha網路:過濾working memory,找出符合規則中每一個模式的集合,生成alpha memory(滿足該模式的集合)。有兩種型別的節點,過濾type的節點和其他條件過濾的節點(我覺得這兩種是依照需要設定的,也並不一定需要兩種節點)。
Beta網路:有兩種型別的節點Beta Memory和Join Node。前者主要儲存Join完成後的集合。後者包含兩個輸入口,分別輸入需要匹配的兩個集合,由Join節點做合併工作傳輸給下一個節點。
在一個產生式系統中,主要流程可以分為以下步驟:

Match:找出符合LHS部分的working memory集合
Confilict resolution:選出一個條件被滿足的規則
Act:執行RHS的內容
返回1
RETE演算法主要改進Match的處理過程,透過構建一個網路進行匹配。



具體過程如下:

建立root節點(根節點),推理網路的入口。
拿到規則1,從規則1中取出模式1(前面說了,模式就是最小的原子條件,所以規則模式的關係是1:n)。
a)   檢查模式1中的引數型別,如果是新型別,新增一個型別節點。

b)   檢查模式1對應的Alpha節點是否存在,如果存在記錄下節點的位置;如果沒有,將模式1作為一個Alpha節點加入到網路中。同時根據Alpha節點建立Alpah記憶體表。

c)   重複b,直到處理完所有模式。

d)   組合Beta節點:Beta(2)左輸入節點為Alpha(1),右輸入節點為Alpha(2);Beta(i)左輸入節點是Beta(i-1),右輸入節點為Alpha(i),並將兩個父節點的記憶體表內聯成為自己的記憶體表

e)   重複d,直到所有Beta節點處理完畢

f)   將動作Then部分封裝成最後節點做為Beta(n)

重複2,直到所有規則處理完畢
下面是一個從網上找得例子:


規則P1:

LHS:

C1:(年紀:研2)
C2:(性別:男)
C3:(身材:較瘦)
C4:(身高:大於175cm)
RHS:

標點符

Rete演算法優於傳統的模式匹配演算法的特點

狀態儲存。事實集合中的每次變化,其匹配後的狀態都被儲存再alpha和beta節點中。在下一次事實集合發生變化時,絕大多數的結果都不需要變 化,rete演算法透過儲存操作過程中的狀態,避免了大量的重複計算。Rete演算法主要是為那些事實集合變化不大的系統設計的,當每次事實集合的變化非常劇 烈時,rete的狀態儲存演算法效果並不理想。
節點共享


Drools中用到的RETE演算法

編譯演算法描述了規則如何在Production Memory中產生一個有效的辨別網路。用一個非技術性的詞來說,一個辨別網路就是用來過濾資料。方法是透過資料在網路中的傳播來過濾資料。在頂端節點將 會有很多匹配的資料。當我們順著網路向下走,匹配的資料將會越來越少。在網路的最底部是終端節點(terminal nodes)。在Dr Forgy的1982年的論文中,他描述了4種基本節點:root, 1-input, 2-input and terminal。

下圖是Drools中的RETE節點型別:

 

根節點(RootNode)是所有的物件進入網路的入口。然後,從根節點立即進入到ObjectTypeNode。ObjectTypeNode的 作用是使引擎只做它需要做的事情。例如,我們有兩個物件集:Account和Order。如果規則引擎需要對每個物件都進行一個週期的評估,那會浪費很多 的時間。為了提高效率,引擎將只讓匹配object type的物件透過到達節點。透過這種方法,如果一個應用assert一個新的account,它不會將Order物件傳遞到節點中。很多現代RETE實 現都有專門的ObjectTypeNode。在一些情況下,ObjectTypeNode被用雜湊法進一步最佳化。

ObjectTypeNode能夠傳播到AlphaNodes,LeftInputAdapterNodes和BetaNodes。

1-input節點通常被稱為AlphaNode。AlphaNodes被用來評估字面條件(literal conditions)。雖然,1982年的論文只提到了相等條件(指的字面上相等),很多RETE實現支援其他的操作。例如,Account.name == “Mr Trout”是一個字面條件。當一條規則對於一種object type有多條的字面條件,這些字面條件將被連結在一起。這是說,如果一個應用assert一個account物件,在它能到達下一個 AlphaNode 之前,它必須先滿足第一個字面條件。在Dr. Forgy的論文中,他用IntraElement conditions來表述。下面的圖說明了Cheese的AlphaNode組合(name == “cheddar”,strength == “strong”):

Drools透過雜湊法最佳化了從ObjectTypeNode到AlphaNode的傳播。每次一個AlphaNode被加到一個 ObjectTypeNode的時候,就以字面值(literal value)作為key,以AlphaNode作為value加入HashMap。當一個新的例項進入ObjectTypeNode的時候,不用傳遞到每 一個AlphaNode,它可以直接從HashMap中獲得正確的AlphaNode,避免了不必要的字面檢查。



2-input節點通常被稱為BetaNode。Drools中有兩種BetaNode:JoinNode和NotNode。BetaNodes被用來對2個物件進行對比。這兩個物件可以是同種型別,也可以是不同型別。



我們約定BetaNodes的2個輸入稱為左邊(left)和右邊(right)。一個BetaNode的左邊輸入通常是a list of objects。在Drools中,這是一個陣列。右邊輸入是a single object。兩個NotNode可以完成‘exists’檢查。Drools透過將索引應用在BetaNodes上擴充套件了RETE演算法。下圖展示了一個 JoinNode的使用:

 

注意到圖中的左邊輸入用到了一個LeftInputAdapterNode,這個節點的作用是將一個single Object轉化為一個單物件陣列(single Object Tuple),傳播到JoinNode節點。因為我們上面提到過左邊輸入通常是a list of objects。

Terminal nodes被用來表明一條規則已經匹配了它的所有條件(conditions)。 在這點,我們說這條規則有了一個完全匹配(full match)。在一些情況下,一條帶有“或”條件的規則可以有超過一個的terminal node。

Drools透過節點的共享來提高規則引擎的效能。因為很多的規則可能存在部分相同的模式,節點的共享允許我們對記憶體中的節點數量進行壓縮,以提供遍歷節點的過程。下面的兩個規則就共享了部分節點:

 1 rule
 2     when
 3         Cheese( $chedddar : name  ==  " cheddar "  )
 4         $person : Person( favouriteCheese  ==  $cheddar )
 5     then
 6         System.out.println( $person.getName()  +   "  likes cheddar "  );
 7 end
 8 
 9 rule
10     when
11         Cheese( $chedddar : name  ==   " cheddar "  )
12         $person : Person( favouriteCheese  !=  $cheddar )
13     then
14         System.out.println( $person.getName()  +   "  does likes cheddar "  );
15 end
View Code

從圖上可以看到,編譯後的RETE網路中,AlphaNode是共享的,而BetaNode不是共享的。上面說的相等和不相等就體現在BetaNode的不同。然後這兩條規則有各自的Terminal Node。


RETE演算法的第二個部分是執行時(runtime)。當一個應用assert一個物件,引擎將資料傳遞到root
node。從那裡,它進入ObjectTypeNode並沿著網路向下傳播。當資料匹配一個節點的條件,節點就將它記錄到相應的記憶體中。這樣做的原因有以
 
下幾點:主要的原因是可以帶來更快的效能。雖然記住完全或部分匹配的物件需要記憶體,它提供了速度和可伸縮性的特點。當一條規則的所有條件都滿足,這就是完
 
全匹配。而只有部分條件滿足,就是部分匹配。(我覺得引擎在每個節點都有其對應的記憶體來儲存滿足該節點條件的物件,這就造成了如果一個物件是完全匹配,那
 這個物件就會在每個節點的對應記憶體中都存有其映象。)

參考連結:

引用地址:http://www.biaodianfu.com/rules-engine.html

 

另一篇帶有案例的部落格:http://hhw3.blog.163.com/blog/static/2690966201301065929233/

Java規則引擎對提交給引擎的Java資料物件進行檢索,根據這些物件的當前屬性值和它們之間的關係,從載入到引擎的規則集中發現符合條件的規則,建立 這些規則的執行例項。這些例項將在引擎接到執行指令時、依照某種優先序依次執行。一般來講,Java規則引擎內部由下面幾個部分構成:工作記憶體 (Working Memory)即工作區,用於存放被引擎引用的資料物件集合;規則執行佇列,用於存放被啟用的規則執行例項;靜態規則區,用於存放所有被載入的業務規則, 這些規則將按照某種資料結構組織,當工作區中的資料發生改變後,引擎需要迅速根據工作區中的物件現狀,調整規則執行佇列中的規則執行例項。Java規則引 擎的結構示意圖如圖4所示。

當引擎執行時,會根據規則執行佇列中的優先順序逐條執行規則執行例項,由於規則的執行部分可能會改變工作區的資料物件,從而會使佇列中的某些規則執行例項 因為條件改變而失效,必須從佇列中撤銷,也可能會啟用原來不滿足條件的規則,生成新的規則執行例項進入佇列。於是就產生了一種“動態”的規則執行鏈,形成 規則的推理機制。這種規則的“鏈式”反應完全是由工作區中的資料驅動的。

   任何一個規則引擎都需要很好地解決規則的推理機制和規則條件匹配的效率問題。規則條件匹配的效率決定了引擎的效能,引擎需要迅速測試工作區中的資料對 象,從載入的規則集中發現符合條件的規則,生成規則執行例項。1982年美國卡耐基·梅隆大學的Charles L. Forgy發明了一種叫Rete演算法,很好地解決了這方面的問題。目前世界頂尖的商用業務規則引擎產品基本上都使用Rete演算法。

 

相關文章