本文最新版已更新至:http://thinkinside.tk/2012/12/05/algorithm_of_pattern_match.html
前面提到,規則引擎的核心是Pattern Matcher(模式匹配器)。不管是正向推理還是反向推理,首先要解決一個模式匹配的問題。
對於規則的模式匹配,可以定義為: 一個規則是一組模式的集合。如果事實/假設的狀態符合該規則的所有模式,則稱為該規則是可滿足的。 模式匹配的任務就是將事實/假設的狀態與規則庫中的規則一一匹配,找到所有可滿足的規則。
2.1 什么是模式匹配
對於模式匹配我們都應該不陌生,我們經常使用的正則表達式就是一種模式匹配。
- 正則表達式是一種“模式(pattern)”,
- 編程語言提供的“正則表達式引擎”就是Pattern Matcher。比如python中的re模塊。
- 首先輸入“知識”:re.compile(r'string'),
- 然后就可以讓其匹配(match)事實(字符串)。
- 最后通過正則表達式引擎可以得到匹配后的結果。
對於規則匹配,通常定義如下:
- 條件部分,也稱為LHS(left-hand side)
- 事實部分,也稱為RHS(right-hand side)
假設系統中有N條規則,平均每個規則的條件部分有P個模式,在某個時點有M個事實需要處理。則規則匹配要做的事情就是: 對每一個規則r,判斷當前的事實o是否滿足LHS(r)=True,如果滿足,則將規則r的實例r(o),即規則+滿足該規則的事實,加到沖突集中等待處理。 通常采取如下過程:
- 從N條規則中取出一條r;
- 從M個事實中取出P個事實的一個組合c;
- 用c測試LHS(r),如果LHS(r(c))=True,將RHS(r(c))加入隊列中;
- 如果M個事實還存在其他的組合c,goto 3;
- 取出下一條規則r,goto 2;
實際的問題可能更復雜,在規則的執行過程中可能會改變RHS的數據,從而使得已經匹配的規則實例失效或者產生新的滿足規則的匹配,形成一種“動態”的匹配鏈。
2.2 模式匹配算法
上面的處理由於涉及到組合,過程很復雜。有必要通過特定的算法優化匹配的效率。目前常見的模式匹配算法包括Rete、Treat、Leaps,HAL,Matchbox等。
為了方便,將前面的圖仍然放在這里:
2.2.1 Rete算法
Rete算法是目前使用最廣泛的規則匹配算法,由Charles L. Forgy博士在1979年發明。Rete算法是一種快速的Forward-Chaining推理算法,其匹配速度與規則的數量無關。 Rete的高效率主要來自兩個重要的假設:
- 時間冗余性。 facts在推理過程中的變化是緩慢的, 即在每個執行周期中,只有少數的facts發生變化,因此影響到的規則也只占很小的比例。所以可以只考慮每個執行周期中已經匹配的facts.
- 結構相似性。許多規則常常包含類似的模式和模式組。
Rete算法的基本思想是保存過去匹配過程中留下的全部信息,以空間代價來換取執行效率 。對每一個模式 ,附加一個匹配元素表來記錄WorkingMemory中所有能與之匹配的元素。當一個新元素加入到WorkingMemory時, 找出所有能與之匹配的模式, 並將該元素加入到匹配元素表中; 當一個無素從WorkingMemory中刪除時,同樣找出所有與該元素匹配的模式,並將元素從匹配元素表中刪除。 Rete算法接受對工作存儲器的修改操作描述 ,產生一個修改沖突集的動作 。
Rete算法的步驟如下:
- 將初始數據(fact)輸入Working Memory。
- 使用Pattern Matcher比較規則(rule)和數據(fact)。
- 如果執行規則存在沖突(conflict),即同時激活了多個規則,將沖突的規則放入沖突集合。
- 解決沖突,將激活的規則按順序放入Agenda。
- 使用規則引擎執行Agenda中的規則。重復步驟2至5,直到執行完畢所有Agenda中的規則。
2.2.2 Tread算法
在 Rete算法中 ,同一規則連接結點上的寄存器保留了大量的冗余結果。實際上, 寄存器中大部分信息已經體現在沖突集的規則實例中。因此 ,如果在部分匹配過程中直接使用沖突集來限制模式之間的變量約束,不僅可以減少寄存器的數量 ,而且能夠加快匹配處理效率 。這一思想稱為 沖突集支撐策略 。
考慮增刪事實對匹配過程的影響,當向工作存儲器增加一個事實時 ,沖突集中已有的規則實例仍然保留,只是將與該事實匹配的規則實例加入到沖突集中; 當從工作存儲器刪去一個事實時,不可能有新的規則實例產生, 只是將 包含該事實的規則實例從沖突集中刪去。
基於沖突集支撐策略和上述觀察, Treat算法放棄了Rete算法中利用寄存器保存模式之間變量約束中間結果的思想,對於每一個模式 ,除保留原有 a寄存器的外 ,增加兩個新鏈來記錄與該模式匹配的增刪事實,一個叫做增鏈 (addlist),另一個叫做刪鏈 (deletelist)。當修改描述的操作符為 “+”時,臨時執行部分連接任務;當修改描述的操作符為 “一”時,直接刪去沖突集中包含該事實的規則實例。
Treat算法的步驟如下:
- 行動 :根據點火規則的 RHS,生成修改描述表 CHANGES;
- 模式匹配:置每一模式的刪鏈和增鏈為空,對 CHANGES的每一個修改描述 ,執行模式匹配。對於與修改描述中的事實匹配成功的模式,若修改描述的操作符為 “+”, 將該事實加入這一模式的增鏈;若修改描述的操作符為 “一”,將該事實加入這一模式的 刪鏈。
- 刪去事實的處理:對於任一模式鏈中的每一個事實,找到沖突集中所有包含該事實 的規則實例,並將這一規則實例從沖突集中刪去。相應地修改該模式的 a寄存器 。
- 新增事實的處理:對 於 每 一 模 式 ,若 其 增 鏈 非 空 ,則 將 增 鏈 中 的 所 有 事 實 加 入 該 模 式的a寄存器 ,並對與新增事實相關的每一條規則臨時執行部分匹配,尋找該規則新的實 例。具體做法為:首先將第一個模式增鏈中的事實集合與后一模式的 a寄存器進行連接 , 再將部分連接結果與第三個模式的a寄存器進行連接 ,一直到所有模式均連接完成為止。 其中 ,a寄存器 的內容包括新增 事實。若連接結果非空 ,則將找到 的規則 實例插入到沖突 集中。
2.2.3 Leaps 算法
前向推理引擎,包括LEAPS,都包括了匹配-選擇-執行(match-select-action)循環。即,確定可以匹配的規則,選擇某個匹配的元 組,此元組相應的規則動作被執行。重復這一過程,直到某一狀態(如沒有更多的規則動作)。RETE和TREAT匹配算法速度慢的原因是,它們把滿足規則條 件的元組都實例化。Leaps算法的最大的改進就是使用一種"lazy"的方法來評估條件(conditions),即僅當必要時才進行元組的實例化。這 一改進極大的減少了前向推理引擎的時空復雜度,極大提高了規則執行速度。
Leaps算法將所有的 asserted 的 facts ,按照其被 asserted 在 Working Memory 中的順序( FIFO ),放在主堆棧中。它一個個的檢查 facts ,通過迭代匹配 data type 的 facts 集合來找出每一個相關規則的匹配。當一個匹配的數據被發現時,系統記住此時的迭代位置以備待會的繼續迭代,並且激發規則結果( consequence )。當結果( consequence )執行完成以后,系統就會繼續處理處於主堆棧頂部的 fact 。如此反復。
Leaps算法的效率可以比Rete算法和Tread算法高幾個數量級。
2.2.4 其他算法
對於HAL算法和Matchbox算法,使用的范圍不是很廣,這里不做過多的介紹。