敏感詞的檢測與替換,是一個很常見的需求,因此搜了下網上的大致實現方案,這裏簡單整理下。
- 簡單替換
- 正則替換
- DFA
- 基於樸素貝葉斯分類算法
簡單替換
string = "hello world"
string.replace("o wo", "***")
類似於上面的代碼,我們會使用一個敏感詞列表,來對目標字符串進行檢測與替換,比較適合於敏感詞列表和待檢測目標字符串都比較小的場景。
正則替換
re.sub("|".join(keywords), "***", text)
非要扒開來說,和上面的正則替換是類似的,只不過用了專門的 re
庫來做,效率會高一點。本質上沒什麼大的變化。
DFA
典型範例:Trie樹。我去年有幸用python寫了一個比較low的版本。不適用於中文場景,然後今日看了hutaishi的代碼,覺得不賴,拿過來改了改,以後或許會用得到。
import java.util.HashMap;
import java.util.Map;
/**
* 前綴樹實現
* 特點:
* 1. 根節點不包含字符,除根節點外每一個子節點都包含一個字符
* 2. 從根節點到某一個節點,路徑即爲對應的單詞
* 3. 每個節點的所有子節點包含的字符各不相同
* 4. 從第一字符開始有連續重複的字符只佔用一個節點,如to,ten,第一個節點都是t
*
* 應用:
* 1. 前綴匹配
* 2. 字符串檢索
* 3. 詞頻統計
* 4. 字符串排序?第一次知道還有這麼個功能。
*/
public class TrieTree {
// 根節點
private TrieNode rootNode = new TrieNode();
// 判斷是否爲一個符號
private boolean isSymbol(char c) {
int ic = (int)c;
// 0x2e80 - 0x9fff 東亞文字範圍 忽略用Apache的CharUtils的工具判斷
return !(ic>0x2e80 && ic<0x9fff);
}
private void addWord(String lineText) {
TrieNode temNode = rootNode;
for(int i=0; i<lineText.length(); i++) {
Character c = lineText.charAt(i);
if(isSymbol(c)) {
continue;
}
TrieNode node = temNode.getSubNode(c);
if (node == null) {
node = new TrieNode();
temNode.addSubNode(c, node);
}
temNode = node;
if(i == lineText.length()-1) {
temNode.setKeywordEnd(true);
}
}
}
public String filter(String text) {
if("".equals(text)) {
return text;
}
String replacement = "***";
StringBuilder result = new StringBuilder();
TrieNode temNode = rootNode;
int begin = 0; // 開始指針
int position = 0; // 位移指針
while(position < text.length()) {
char c = text.charAt(position);
// 空格或者非東亞文字並且不是字母字符直接跳過
if(isSymbol(c)) {
if (temNode == rootNode) {
result.append(c);
begin++;
}
position++;
continue;
}
temNode = temNode.getSubNode(c);
if(temNode == null) {
// 以begin開始的字符串不會存在敏感字符
result.append(text.charAt(begin));
// 跳到下一個字符進行測試
position = begin + 1;
begin = position;
// 回到前綴樹的根節點
temNode = rootNode;
} else if(temNode.isKeywordEnd()) {
// 發現敏感詞,從begin到position的位置進行替換
result.append(replacement);
position ++;
begin = position;
temNode = rootNode;
} else {
++position;
}
}
result.append(text.substring(begin));
return result.toString();
}
public static void main(String[] args) {
TrieTree tree = new TrieTree();
tree.addWord("賭博");
tree.addWord("春天來了");
System.out.println(tree.filter("春"));
System.out.println(tree.filter("春夏秋冬"));
System.out.println(tree.filter("黃賭博"));
System.out.println(tree.filter("春天"));
System.out.println(tree.filter("冬天來了,春天來了,夏天還會遠嗎"));
}
class TrieNode{
private boolean end = false;
private Map<Character, TrieNode> subNodes = new HashMap<Character, TrieNode>();
void addSubNode(Character key, TrieNode node) {
subNodes.put(key, node);
}
TrieNode getSubNode(Character key) {
return subNodes.get(key);
}
boolean isKeywordEnd() {
return end;
}
void setKeywordEnd(boolean end) {
this.end = end;
}
public int getSubNodeCount() {
return this.subNodes.size();
}
}
}
運行結果如下:
春
春夏秋冬
黃***
春天
冬天來了,***,夏天還會遠嗎
這個應該算是一個比較不錯的範例了,實操性比較強。
樸素貝葉斯分類算法
寫過一個使用樸素貝葉斯分類算法實現的一個敏感詞檢測的程序,沒有應用到線上環境,所以不敢保證準確度。更爲關鍵的是:
先驗概率很重要,也就是初識敏感詞列表要有很高的準確度纔可以。
小結
忘了是聽誰說的了,人不可能學會所有知識,但整理、總結會讓智慧得到昇華。這裏整理了網上常見的敏感詞檢測相關的內容,肯定還有沒寫進來的好的方案,到時候遇到了再來補充。
參考鏈接:
1 https://www.jianshu.com/p/c124b0d6ebb0
2 https://my.oschina.net/hutaishi/blog/885356