0x00 TL;DR
Filter4J是一个基于深度学习与神经网络的文本内容安全库,可以被用于自动检查文本内容的合规安全性。它对于具备对拼音、谐音、拆字等违规变体具备识别能力。它由纯Java编写,不依赖任何第三方库,推论模块体积仅10kb(不含模型)且兼容Java8。本文介绍了它的使用方法、原理与微调方法。求Star
System.out.println(TextFilter.isIllegal(str) ?"异常":"正常");
0x01 编写背景
我们都知道,文本内容安全是互联网平台的重要痛点。对于用户生成的内容,进行恰当的检查是非常有必要的,以免文本中出现可能令人反感、不安全或不适宜的内容。传统上,我们可能会采用关键词法或调用第三方API。然而,这两者都存在很严重的问题:采用关键词法时,一方面,恶意用户可能通过增加干扰等方式绕过检查,有时哪怕检查没有被绕过,被替换成了*等字符的关键词仍然会使人不适。另一方面正如一个广为流传的笑话中提到的,j**a(java),关键词法可能会影响到用户体验。采用第三方API法时,一方面,用户内容作为企业核心资产,交给第三方平台事实上是一种资产外泄,带来不必要的风险。另一方面,第三方的API的稳定性、可用性、定价,甚至是否关停,都不可控,带来额外的成本。
与此同时,在某些细分领域中,用户可能有一些细分领域术语。对于通用的API而言,对这些术语的处理情况可能不甚好,或因为需要处理许多其他细分领域的术语而带来不必要的性能开支。自行微调可以解决以上问题。
0x02 代码架构
sequenceDiagram
participant 原始文本
participant Token
participant 模型
loop 训练阶段
原始文本 ->> Token: 词频
Token ->> 原始文本: 构造分词器
原始文本 ->> Token: 分词
Token ->> 模型: 训练
end
loop 预测阶段
原始文本 ->> Token: 分词
Token ->> 模型: 预测
模型 ->> 原始文本: 分数
end
本文聚焦于预测阶段进行详细的介绍。
0x03 分词
分词指的是将自然语句生成高维词向量。此处我们使用词频直接构造分词器。
传统的分词器体量较大,并且其实,因为内容检查是一个时序无关的应用。我们知道,文字的序顺往不往影响类人的读阅,而反而可能成为潜在的绕过点。因此,我们可以通过词频直接进行01分词,而不是调用传统的分词库。
具体代码摘选如下:
public DataEntry tokenize(int type, String text) {
double[] values = new double[vocab.length];
for (int i = 0; i < values.length; i++) {
if (text.contains(vocab[i])) {
values[i] = 1;
}
}
return new DataEntry(type, values);
}
0x04 模型预测
使用预训练的人工智能模型对词向量进行预测。
MinRT是一个极小的神经网络运行时,核心方法实在是不舍得删除哪怕是一行代码。
核心方法代码如下:
public static int doAi(double[] input, String[] script) {
double[] current = new double[input.length];
System.arraycopy(input, 0, current, 0, input.length);
for (String str : script) {
if (str.length() < 2) {
continue;
}
String[] tokens = str.split(" ");
switch (tokens[0]) {
case "D":
int ic = Integer.parseInt(tokens[1]);
int oc = Integer.parseInt(tokens[2]);
if (current.length != ic) {
throw new RuntimeException("Wrong input size for Dense layer (expected " + ic + ", got " + current.length + ")");
}
double[] tmp = new double[oc];
for (int i = 0; i < oc; i++) {
double sum = 0;
for (int j = 0; j < ic; j++) {
sum += current[j] * Double.parseDouble(tokens[3 + i + j * oc]);
}
tmp[i] = sum;
}
current = tmp;
break;
case "L":
int n = Integer.parseInt(tokens[1]);
if (current.length != n) {
throw new RuntimeException("Wrong input size for LeakyRelu layer (expected " + n + ", got " + current.length + ")");
}
for (int i = 0; i < n; i++) {
current[i] = current[i] > 0 ? current[i] : current[i] * 0.01;
}
break;
case "J":
int m = Integer.parseInt(tokens[1]);
if (current.length != m) {
throw new RuntimeException("Wrong input size for Judge layer (expected " + m + ", got " + current.length + ")");
}
int idx = 0;
for (int i = 1; i < m; i++) {
if (current[i] > current[idx]) {
idx = i;
}
}
return idx;
default:
throw new RuntimeException("Unknown layer type");
}
}
throw new RuntimeException("No output layer");
}
0x05 效果展示
声明:图中语句仅供技术演示,不代表本人观点。
图中语句构造规则为:
违规语句1 包含相同词语的正常语句1-1 包含相同词语的正常语句1-2 混淆了词语的变体违规1
违规语句2 两个能够包含违规语句2中全部词语的正常语句2-1 2-2
可以看到,本模型能够正常地处理违规与变体违规,且不会误伤正常语句。
0x06 致谢与声明
特别鸣谢
北京信息科学与技术国家研究中心 Jiawen Deng(清华大学) et,al. 提供的COLDataset。
此数据集为我们提供了无与伦比的帮助。
质量声明
基于机器的文本审核系统,无法完全替代人工审核。请在使用本库时,仍然保持对用户输入的警惕。
作者在此明示,本模型一定存在缺陷且会存在错误判断,其输出结果与实际情况一定存在偏差。
使用者不应该将其用于任何环境中,除非这种偏差不会对使用者造成任何损失。
0x07 相关链接
Filter4J 仅推理仓库。适合于只需要调用Filter4J的人。
Filter4Jx 推理与训练仓库。适合于需要训练或高速运行Filter4J的人。
COLDataset 最重要的训练集。