怎麼設計高效的敏感詞過濾系統(一)

IM項目需要對上邊傳輸的消息進行必要的過濾。如果總是對着某人輸入f**k就顯得不太文明瞭。

一個通用且簡單的做法是,設定一批敏感詞,如果消息中出現這些詞,由系統進行必要的處理。怎麼實現這個功能呢?

一、能夠實現敏感詞過濾功能的方法有很多

方法有很多,我簡單羅列了幾個。

1、直接將敏感詞組織成String後,利用indexOf方法來查詢。

2、傳統的敏感詞入庫後SQL查詢。

3、利用Lucene建立分詞索引來查詢。

4、利用DFA算法來進行。

顯然,方法1和方法2在性能上基本無法滿足IM系統高效處理消息的需求,放棄。

方法3,採用Lucene建立本地分詞索引,將消息內容分詞後,在索引庫裏搜索。這個方法較複雜,且分詞效率也不會很高,放棄。

大多數的敏感詞過濾系統採用的是方法4,DFA算法。

二、DFA簡介

DFA是什麼?這裏有必要簡單介紹一下這個概念(這部分看不懂沒關係,可以跳過)。

1、DFA定義

DFA翻譯成中文是“確定有窮自動機 ”

定義:一個確定有窮自動機(DFA)M是一個五元組:M=(K,Σ,f,S,Z)其中

① K是一個有窮集,它的每個元素稱爲一個狀態;

② Σ是一個有窮字母表,它的每個元素稱爲一個輸入符號,所以也稱Σ爲輸入符號字母表;

③ f是轉換函數,是K×Σ→K上的映射(且可以是部分函數),即,如 f(ki,a)=kj,(ki∈K,kj∈K)就意味着,當前狀態爲ki,輸入符爲a時,將轉換爲下一個狀態kj,我們把kj稱作ki的一個後繼狀態;

④ S ∈ K是唯一的一個初態;

⑤ Z⊂K是一個終態集,終態也稱可接受狀態或結束狀態。

2、DFA例子

3、DFA狀態圖表示

假定DFA M含有m個狀態,n個輸入字符,那麼這個狀態圖含有m個節點,每個節點最多有n個弧射出,整個圖含有唯一一個初態點和若干個終態點,初態節點冠以雙箭頭“=>”,終態節點用雙圈表示,若f(ki ,a)=kj,則從狀態結點ki到狀態節點kj畫標記爲a的弧。

4、DFA所接受

對於Σ* 中的任何符號串t,若存在一條從初態到某一終態的道路,且這條道路上所有弧的標記連接成的字符串等於t,則稱t可爲DFA M所接受,若M的初態同時又是終態,則空字可爲M所識別(接受)。

即:若 t∈ Σ* , f(S, t)=P, 其中S爲M的開始狀態,P∈Z,Z爲 終態集。

則稱 t 爲 DFA M所接受(識別)。

如果看懂了DFA的介紹,我們可以這麼理解敏感詞過濾系統。用需要被過濾的敏感詞構建一個DFA(確定有窮自動機 ),然後遍歷需要過濾的文本,判斷文本中是否有DFA可接受(識別)的字符串即可。

如果沒有看懂DFA,看下邊一節也OK。

三、用Trie樹構建DFA

Trie樹,即字典樹,又稱單詞查找樹或鍵樹,是一種樹形結構,是一種哈希樹的變種。典型應用是用於統計和排序大量的字符串(但不僅限於字符串),所以經常被搜索引擎系統用於文本詞頻統計。它的優點是:最大限度地減少無謂的字符串比較,查詢效率比哈希表高。

假設有b,abc,abd,bcd,abcd,efg,hii 這7個單詞(實際使用中,這些單詞就是敏感詞),我們構建的樹如下圖

如上圖所示,對於每一個節點,從根遍歷到他的過程就是一個單詞,如果這個節點被標記爲紅色,就表示這個單詞存在,否則不存在。

過濾敏感詞,就是把需要過濾的文本,從第一個字開始,逐個字往後在Trie樹中查找。如果能走到樹的結束節點,則就能發現敏感詞!

四、防止回溯

1、回溯的場景

看一句話待過濾的文本(以下簡稱母串)“瓜子二手車成交量全國領先”,再看下圖模擬的幾個敏感詞。我們來看看檢索過程。

(1)第1個字“瓜”在Trie樹的第一層節點(第一層節點有“二”、“瓜”、“西”三個字);繼續(在中間的子樹)往後找“子”字,在樹枝的後續節點;繼續找“二”,繼續找“手”,繼續找“車”,"車"字無法找到,查找失敗。

(2)(這裏不能從“二”字開始找,需要回溯到“子”字,萬一有“子”字開始的敏感詞呢 )第2個字“子”不在Trie樹第一層節點,查找失敗。

(3)第3個字“二”字,在Trie樹第一層節點……

(4)後續文字按此方法逐字往後查找即可。

這個查找方法能夠求解,但是效率不高(注意第2步),我們讀到了後邊的文字,但是由於沒有命中,檢索發生了回退,導致效率下降。事實上,我們在第1步已經比較過“二手”這個詞,如果能利用第1步中比較的結果,直觀感覺是能夠加快匹配出“二手車”這個敏感詞的。

2、前綴指針

前面的場景很像字符串查找的KMP算法,KMP算法可以防止字符串查找過程中的指針回溯。那Trie樹的結構有沒有辦法也避免這種情況發生呢?

答案是肯定的。參考KMP算法(百度一下你就知道了) 的最長相同前後綴的方法,來避免回溯。

這裏首先需要理解“前綴”、“後綴”的思想。如果不瞭解這兩個概念,推薦一篇KMP算法講得特別清楚的博文http://www.cnblogs.com/c-cloud/p/3224788.html(一定要讀)。

爲了避免回溯,參考KMP的next數組,在Trie圖中定義“前綴指針 ”

“前綴指針 ”定義:從根節點到節點P可以得到一個字符串S,節點P的前綴指針定義爲 指向樹中出現過的S的最長後綴(不能等於S)

後續文章將詳細講解怎麼高效構建“前綴指針 ”,如何快速遍歷母串,以及工程上如何實現的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章