游戏和网站,都需要敏感词(屏蔽词)过滤,你懂的。而且敏感词库不断的增长,从十几年前的3000多词,已经增长到14000以上(各渠道获取的词库略有不同)。
这1万多词的扫描处理,开销的性能就有点客观了。所以今天把过去的算法,以及性能指标,重新整理了一下,给大家提供一些思路。
额外说说
从根上说,有两类办法去解决屏蔽词这个事儿。
1,接入第三方API。现在有很多处理敏感词的接口,各大平台都有,比如鹅厂、猪场、阿里、小度等等。目前最便宜的是小度,1块钱50W次。
好处:A,你不用管词库的更新问题。B,不浪费你的处理器资源
坏处:毕竟是第三方,即便我们认为它稳定性是100%,但是一旦网络出了问题(慢甚至断),那么我们自己的服务就跟着挂了。
2,自己写算法处理。好处坏处跟接入第三方正好相反。本文主要是写如何自己处理。
---- 词库处理 ----
词库是可以优化的。例如以下算法:
1,词库去重
如果是多个词库的,那么要去重复。如果是渠道给了最新的单文本文件,也可以略过去重步骤。
2,词库去包含
去包含的意思是:词库里经常有这样的词:【你好】【你好哇】,实际上过滤掉[你好],就破坏了[你好哇]的词义了。理论上讲,大多数的词,可以通估计去包含化,做精简处理。
注意,有些无法去包含化,因为语义的关系。您可以自行优化去包含化的算法,以达到语义层面的精简化。
算法也比较简单,贴一下python版的。
# 生成敏感词库, list格式
def generate_sensitive_list():
# 把不同版本的敏感词库合并, 去包含化
# ------ 利用字典key唯一性, 进行关键字[去重] ------
words_dic = {}
# 词库1
for word in civilise.cluckivilise:
words_dic[word] = {}
# 词库2
for word in sensitive_political.political_arr:
words_dic[word] = {}
# 词库3
for word in sensitive_porn.porn_arr:
words_dic[word] = {}
words_list = list(words_dic.keys())
print("去重:", len(words_list))
# ------ 关键字[去包含](效果不是很好) ------
# 比如: "luck"中包含了"luck", 那么,只保留fuck即可]
for A in words_list:
tmp_list = list(words_dic.keys())
for B in tmp_list:
# 在A比B短的情况下
if len(A) < len(B):
# A包含在B中, 那么删除B
if A in B:
del words_dic[B]
# 去包含化后的词库大小
words_list = list(words_dic.keys())
print("去包含化:", len(words_list))
# ---------- [生成敏感词文件] ----------
the_f = open("d:/sensitive_list.py", mode='w', encoding="utf8")
# 写文件头
the_f.write("#! /usr/bin/env python\n")
the_f.write("#coding=utf-8\n\n")
the_f.write("sensitive_list = [\n")
prefix = " " # 四空格
# 逐一写入敏感词
for word in words_list:
line_str = prefix + '"' + word + '",\n'
the_f.write(line_str)
the_f.write("]\n")
# 关闭文件
the_f.close()
---- 敏感词处理算法 ----
1,遍历,查找替换
最原始、最简单的算法。经过词库优化(去重,去包含)之后,新词库数量为2967个。运行一下查找替换算法,耗时0.0004秒。如图
2,利用正则
实际性能提高不了多少。所以干脆省略。
3,树状查找
把词重新进行数据结构上的分类,产生树状结构。然后,利用首字算法,进行查验。比如:【世】开头的敏感词有9个。那么,只要【世】不在文本里,那这9个敏感词,都不用查了。更不用说再往下的细分了。
以字典的数据结构作为示例:因为是敏感词,原谅我打码:
然后,我们来看一下算法示例(原理就是上面说的,很简单,算法可以您自行写,然后优化):
# 利用tree的算法
def text_legalization_2(raw_str):
from sensitive.sensitive_tree import sensitive_tree
x_list = list(sensitive_tree.keys())
print("树状1级节点:", len(x_list))
st_tm = time.time()
j = 0
new_str = raw_str
# 首字符列表
for x_1 in x_list:
# 首字符在输入的文本里
if x_1 in raw_str:
sort_list = list(sensitive_tree[x_1].keys())
# 优先处理文字更长的
sort_list.sort()
sort_list.reverse()
# 挨个对比
for word in sort_list:
new_str = new_str.replace(word, "**")
# print(word, new_str)
j += 1
j += 1
ed_tm = time.time()
print("处理结果:", new_str)
print("扫描次数:", j, "耗时:", ed_tm-st_tm)
最后看一下树状过滤算法的性能:
最终性能对比是:
原始字库:0.00081秒
原始字库:0.00044秒
原始字库:0.000077秒
结论
处理字库+利用树状扫描算法,整体性能,可以提高10倍以上。
这样,1核CPU,1秒也可以处理1.2万次的敏感词过滤。性能基本可以接受了。