Rete算法是Charles Forgy在1979年的論文中首次提出的,針對基於規則知識表現的模式匹配算法。目前來說,大部分規則引擎還是基於rete算法作為核心,但都有所改進,比如drool,jess等等,下面介紹rete算法的概念
1.rete 算法
Rete算法是一種高效的模式匹配算法用來實現產生式規則系統
(空間換時間,用內存換取匹配速度)
它是高效的算法,它通過緩存避免了相同條件多次評估的情況,但是帶來了大量的內存使用
Rete 在拉丁語中是 ”net” ,有網絡的意思;Rete算法通過規則條件生成了一個網絡,每個規則條件是網絡中的一個節點
rete可以被分為兩部分:規則編譯和運行時執行。規則編譯是指根據規則集生成推理網絡的過程,運行時執行指將數據送入推理網絡進行篩選的過程。
2.規則編譯
規則編譯就是根據規則文件推理生成網絡的過程。下面展示一下推理后的網絡節點圖。(大家只用看一下,心里有個印象就好,下面我會分析講解)
圖一
2.1 Root Node:
(如圖一的黑色節點) 根節點(RootNode)是所有對象進入網絡的入口,然后進入到TypeNode
2.2 Type Node
(有的叫ObjectTypeNode,圖一的紅色節點): type Node 就是我們的fact 也就是我們規則所用到的pojo;每個fact 就是一個 type node。 type Node 就是類型檢查,引擎只讓匹配Object 類型的對象到達節點,它能夠傳播到 AlphaNodes、LeftInputAdapterNodes (作為betaNodes的左端輸入節點)和 BetaNodes
舉例:有兩個fact ,Person 和cheese 。那么節點圖就如下所示。
2.3 Alpha Node
:(圖一的藍色節點)用來評估字面條件,例如。person.age>10 這就是一個 alpha node 節點,當一條規則有多條字面條件,這些字面條件被鏈接到一起。
Drools 通過散列法優化了從 ObjectTypeNode 到 AlphaNode 的傳播。每次一個 AlphaNode 被加到一個 ObjectTypeNode 的時候,就以字面值( literal value )作為 key ,以 AlphaNode 作為 value 加入 HashMap 。當一個新的實例進入 ObjectTypeNode 的時候,不用傳遞到每一個 AlphaNode ,它可以直接從 HashMap 中獲得正確的 AlphaNode ,避免了不必要的字面檢查。
舉例 1:條件 Cheese (name=”cheddar” ,strengh==”strong”)
解釋:name 和 strengh 都是對象Cheese 的屬性,且name 和 strengh兩個條件的關系是且,必須同時滿足拿么節點圖如下所示:
舉例 2:在舉例1 的條件上添加另外一條規則 Cheese (name=”cheddar” ,age>10)
解釋:name 和 age 都是對象Cheese 的屬性,且name 和 age兩個條件的關系是且,必須同時滿足,拿么結合舉例1的節點圖,如下所示:
此時我們發現我門共享了(name==“cheddar“) 節點。
2.4 Bate Node:
(如圖一的綠色節點)用來對2個對象進行對比、檢查。約定BetaNode的2個輸入稱為左邊(Join Node)和右邊。左邊通常是一個a list of objects,右邊(NotNode)通常是 a single object。每個Bate節點都有自己的終端節點等組成
BetaNode 具有記憶功能。左邊的輸入被稱為 Beta Memory,會記住所有到達過的語義。右邊的輸入成為 Alpha Memory,會記住所有到達過的對象。
舉例:條件 Cheese (name=”cheddar” ) Person(favouriteiteCheese==”cheese.name”)。
解釋:這個兩個對象Cheese和Person 的關聯操作。Cheese的name=”cheddar” 且cheese.ame ==favouriteiteCheese。那么節點圖如下所示。
圖中黃色的node 我門稱為LeftInputAdapterNode,這個節點的作用是將一個single Object轉化為一個單對象數組(single Object Tuple),傳播到JoinNode節點。因為我們上面提到過左邊輸入通常是a list of objects。
舉例2:
rule
when Cheese( $cheddar : name == "cheddar" ) $person : Person( favouriteCheese == $cheddar ) then System.out.println( $person.getName() + " likes cheddar" ); end rule when Cheese( $cheddar : name == "cheddar" ) $person : Person( favouriteCheese != $cheddar ) then System.out.println( $person.getName() + " does not like cheddar" ); end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
網絡圖如下:
Drools 通過節點的共享來提高規則引擎的性能。因為很多的規則可能存在部分相同的模式,節點的共享允許我們對內存中的節點數量進行壓縮,以提供遍歷節點的過程
從圖上可以看到,編譯后的RETE網絡中,AlphaNode是共享的,而BetaNode不是共享的。上面說的相等和不相等就體現在BetaNode的不同。然后這兩條規則有各自的Terminal Node。
2.5 創建 rete 網絡
Rete 算法的編譯結果是創建了規則集對應的 Rete 網絡 , 它是一個事實可以在其中流動的圖。創建 rete 網絡的過程 [1]如下:
1) 創建根節點;
2) 加入一條規則
a. 取出規則中的一個模式 ,(模式就是規則中的最小一個匹配項例如(age>10,age<20)拿么age>10 就是一個模式,age<20 就是另一個模式。)檢查模式中的參數類型,如果是新類型(也就是新的fact類型),則加入一個類型節點;
b. 檢查模式 對應的 Alpha 節點是否已存在,如果存在則記錄下節點位置,如果沒有則將模式 作為一個 Alpha 節點加入到網絡中,同時根據 Alpha 節點的模式建立 Alpha 內存表;
c. 重復 b 直到所有的模式處理完畢;
d. 組合 Beta 節點,按照如下方式: Beta 左輸入節點為 Alpha(1),右輸入節點為 Alpha(2) 。Beta(i) 左輸入節點為 Beta(i-1),右輸入節點為 Alpha(i) i>2 並將兩個父節點的內存表內聯成為自己的內存表;
e. 重復 d 直到所有的 Beta 節點處理完畢;
f. 將動作(Then 部分)封裝成葉節點(Action 節點)作為 Beta(n) 的輸出節點;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
3) 重復 2) 直到所有規則處理完畢; 執行完上述步驟,建立的 rete 網絡圖
3. 運行時執行
WME :存儲區儲存的最小單位是工作存儲區元素(Working Memory Element,簡稱WME),WME是為事實建立的元素,是用於和非根結點代表的模式進行匹配的元素。
Token:是WME的列表,包含有多個WME,(在Forgy的論文中,把Token看成是WME的列表或者單個WME,為了闡述方便,本文將把Token只看成WME的列表)
(1)如果WME的類型和根節點的后繼結點TypeNode(alpha結點的一種)所指定的類型相同,則會將該事實保存在該TypeNode結點對應的alpha存儲區中,該WME被傳到后繼結點繼續匹配,否則會放棄該WME的后續匹配;
TypeNode存儲: 每次一個AlphaNode被加到一個 ObjectTypeNode的時候,就以字面值(literal value)也就是file 作為key,以AlphaNode作為value加入HashMap。當一個新的實例進入ObjectTypeNode的時候,不用傳遞到每 一個AlphaNode,它可以直接從HashMap中獲得正確的AlphaNode,避免了不必要的字面檢查。
- 1
- 2
(2)如果WME被傳遞到alpha結點,則會檢測WME是否和該結點對應的模式相匹配,若匹配,則會將該事實保存在該alpha結點對應的存儲區中,該WME被傳遞到后繼結點繼續匹配,否則會放棄該WME的后續匹配;
alpha 存儲:檢測WME是否和該結點對應的模式相匹配,若匹配,則會將該事實保存在該alpha結點對應的存儲區中,該WME被傳遞到后繼結點繼續匹配
- 1
- 2
(3)如果WME被傳遞到beta結點的右端,則會加入到該beta結點的right存儲區,並和left存儲區中的Token進行匹配(匹配動作根據beta結點的類型進行,例如:join,projection,selection),匹配成功,則會將該WME加入到Token中,然后將Token傳遞到下一個結點,否則會放棄該WME的后續匹配;
bate存儲區:
每個非根結點都有一個存儲區。其中1-input(alpha)結點有alpha存儲區和一個輸入口;
2-input(bate)結點有left存儲區和right存儲區和左右兩個輸入口,其中left存儲區是beta存儲區,right存儲區是alpha存儲區。存儲區儲存的最小單位是工作存儲區元素(Working Memory Element,簡稱WME),WME是為事實建立的元素,是用於和非根結點代表的模式進行匹配的元素。
- 1
- 2
- 3
- 4
- 5
- 6
(4)如果Token被傳遞到beta結點的左端,則會加入到該beta結點的left存儲區,並和right存儲區中的WME進行匹配(匹配動作根據beta結點的類型進行,例如:join,projection,selection),匹配成功,則該Token會封裝匹配到的WME形成新的Token,傳遞到下一個結點,否則會放棄該Token的后續匹配;
(5)如果WME被傳遞到beta結點的左端,將WME封裝成僅有一個WME元素的WME列表做為Token,然后按照(4)所示的方法進行匹配;
(6)如果Token傳遞到終結點,則和該根結點對應的規則被激活,建立相應的Activation,並存儲到Agenda當中,等待激發。
(7)如果WME被傳遞到終結點,將WME封裝成僅有一個WME元素的WME列表做為Token,然后按照(6)所示的方法進行匹配;
以上是RETE算法對於不同的結點,來進行WME或者token和結點對應模式的匹配的過程。
4. Rete 算法的特點:
a. Rete 算法是一種啟發式算法,不同規則之間往往含有相同的模式,因此在 beta-network 中可以共享 BetaMemory 和 betanode。如果某個 betanode 被 N 條規則共享,則算法在此節點上效率會提高 N 倍。
b. Rete 算法由於采用 AlphaMemory 和 BetaMemory 來存儲事實,當事實集合變化不大時,保存在 alpha 和 beta 節點中的狀態不需要太多變化,避免了大量的重復計算,提高了匹配效率。
c. 從 Rete 網絡可以看出,Rete 匹配速度與規則數目無關,這是因為事實只有滿足本節點才會繼續向下沿網絡傳遞。
5. Rete 算法的不足:
a. 事實的刪除與事實的添加順序相同, 除了要執行與事實添加相同的計算外, 還需要執行查找, 開銷很高 [3]。
b. RETE 算法使用了β存儲區存儲已計算的中間結果, 以犧牲空間換取時間, 從而加快系統的速度。然而β存儲區根據規則的條件與事實的數目而成指數級增長, 所以當規則與事實很多時, 會耗盡系統資源 [3]。
針對 Rete 算法的特點和不足,在應用或者開發基於 Rete 算法的規則引擎時,提出如下建議:
a. 容易變化的規則盡量置后匹配,可以減少規則的變化帶來規則庫的變化。
b. 約束性較為通用或較強的模式盡量置前匹配,可以避免不必要的匹配。
c. 針對 Rete 算法內存開銷大和事實增加刪除影響效率的問題,技術上應該在 alpha 內存和 beata 內存中,只存儲指向內存的指針,並對指針建里索引(可用 hash 表或者非平衡二叉樹)。
d. Rete 算法 JoinNode 可以擴展為 AndJoinNode 和 OrJoinNode,兩種節點可以再進行組合 [5]。
- 1
- 2
- 3
- 4
- 5