敏感词的检测与替换,是一个很常见的需求,因此搜了下网上的大致实现方案,这里简单整理下。
- 简单替换
- 正则替换
- 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