整理的敏感词解决思路

敏感词的检测与替换,是一个很常见的需求,因此搜了下网上的大致实现方案,这里简单整理下。

  • 简单替换
  • 正则替换
  • 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

你可能感兴趣的:(业界常识)