整理的敏感詞解決思路

敏感詞的檢測與替換,是一個很常見的需求,因此搜了下網上的大致實現方案,這裏簡單整理下。

  • 簡單替換
  • 正則替換
  • 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();
		}
	}

}


運行結果如下:

春
春夏秋冬
黃***
春天
冬天來了,***,夏天還會遠嗎

這個應該算是一個比較不錯的範例了,實操性比較強。

樸素貝葉斯分類算法

寫過一個使用樸素貝葉斯分類算法實現的一個敏感詞檢測的程序,沒有應用到線上環境,所以不敢保證準確度。更爲關鍵的是:

先驗概率很重要,也就是初識敏感詞列表要有很高的準確度纔可以。

鏈接如下:Golang+PHP樸素貝葉斯分類 敏感詞檢測

小結

忘了是聽誰說的了,人不可能學會所有知識,但整理、總結會讓智慧得到昇華。這裏整理了網上常見的敏感詞檢測相關的內容,肯定還有沒寫進來的好的方案,到時候遇到了再來補充。


參考鏈接:
1 https://www.jianshu.com/p/c124b0d6ebb0
2 https://my.oschina.net/hutaishi/blog/885356

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