发布时间:2024 年 11 月 13 日
仇恨言论、骚扰和网络滥用行为已成为普遍存在的网络问题。 有害评论会扼杀重要声音,并赶走用户和客户。 毒性检测功能可保护您的用户,并营造更安全的在线环境。
在本系列的两篇文章中,我们将探讨如何使用 AI 在源头(即用户键盘)检测和缓解有害内容。
在第一部分中,我们讨论了这种方法的应用场景和优势。
在第二部分中,我们将深入探讨实现,包括代码示例和用户体验提示。
演示和代码
试用我们的演示,并研究 GitHub 上的代码。
浏览器支持
我们的演示在最新版本的 Safari、Chrome、Edge 和 Firefox 中运行。
选择模型和库
我们使用 Hugging Face 的 Transformers.js 库,该库提供用于在浏览器中使用机器学习模型的工具。我们的演示代码源自此文本分类示例。
我们选择 toxic-bert 模型,这是一个旨在识别有害语言模式的预训练模型。它是 unitary/toxic-bert 的 Web 兼容版本。如需详细了解该模型的标签及其对身份攻击的分类,请参阅 Hugging Face 模型页面。
下载模型后,推理速度很快。
例如,在我们在中端 Android 设备(常规 Pixel 7 手机,而非性能更强的 Pro 型号)上测试的 Chrome 中,通常需要不到 500 毫秒。运行能代表您用户群的自有基准测试。
实现
以下是我们实施过程中的关键步骤:
设置毒性阈值
我们的恶意内容分类器提供的恶意评分介于 0 和 1 之间。在该范围内,我们需要设置一个阈值来确定哪些评论属于有害评论。常用的阈值为 0.9。这样一来,您就可以发现明显的恶意评论,同时避免过度敏感,以免导致过多的假正例(也就是说,将无害的评论归类为恶意评论)。
export const TOXICITY_THRESHOLD = 0.9
导入组件
我们首先从 @xenova/transformers 库中导入必要的组件。我们还导入了常量和配置值,包括毒性阈值。
import { env, pipeline } from '@xenova/transformers';
// Model name: 'Xenova/toxic-bert'
// Our threshold is set to 0.9
import { TOXICITY_THRESHOLD, MODEL_NAME } from './config.js';
加载模型并与主线程通信
我们加载了毒性检测模型 toxic-bert,并使用该模型来准备分类器。最简单的版本是 const classifier = await pipeline('text-classification', MODEL_NAME);
创建流水线(如示例代码中所示)是运行推理任务的第一步。
流水线函数接受两个实参:任务 ('text-classification') 和模型 (Xenova/toxic-bert)。
关键术语:在 Transformers.js 中,流水线是一种高级 API,可简化运行机器学习模型的过程。它可处理模型加载、分词和后处理等任务。
我们的演示代码不仅会准备模型,还会将计算量大的模型准备步骤分流到 Web 工作器。这样可确保主线程保持响应状态。详细了解如何将开销大的任务分流到 Web Worker。
工作线程需要与主线程通信,使用消息来指示模型的状态和毒性评估结果。不妨查看我们创建的消息代码,这些代码对应于模型准备和推理生命周期的不同状态。
let classifier = null;
(async function () {
// Signal to the main thread that model preparation has started
self.postMessage({ code: MESSAGE_CODE.PREPARING_MODEL, payload: null });
try {
// Prepare the model
classifier = await pipeline('text-classification', MODEL_NAME);
// Signal to the main thread that the model is ready
self.postMessage({ code: MESSAGE_CODE.MODEL_READY, payload: null });
} catch (error) {
console.error('[Worker] Error preparing model:', error);
self.postMessage({ code: MESSAGE_CODE.MODEL_ERROR, payload: null });
}
})();
对用户输入进行分类
在 classify 函数中,我们使用之前创建的分类器来分析用户评论。我们会返回毒性分类器的原始输出:标签和得分。
// Asynchronous function to classify user input
// output: [{ label: 'toxic', score: 0.9243140482902527 },
// ... { label: 'insult', score: 0.96187334060668945 }
// { label: 'obscene', score: 0.03452680632472038 }, ...etc]
async function classify(text) {
if (!classifier) {
throw new Error("Can't run inference, the model is not ready yet");
}
let results = await classifier(text, { topk: null });
return results;
}
当主线程要求工作线程执行分类任务时,我们会调用分类函数。在我们的演示中,一旦用户停止输入,我们就会触发分类器(请参阅 TYPING_DELAY)。发生这种情况时,主线程会向工作线程发送一条消息,其中包含要分类的用户输入内容。
self.onmessage = async function (message) {
// User input
const textToClassify = message.data;
if (!classifier) {
throw new Error("Can't run inference, the model is not ready yet");
}
self.postMessage({ code: MESSAGE_CODE.GENERATING_RESPONSE, payload: null });
// Inference: run the classifier
let classificationResults = null;
try {
classificationResults = await classify(textToClassify);
} catch (error) {
console.error('[Worker] Error: ', error);
self.postMessage({
code: MESSAGE_CODE.INFERENCE_ERROR,
});
return;
}
const toxicityTypes = getToxicityTypes(classificationResults);
const toxicityAssessement = {
isToxic:> toxicityTypes.length 0,
toxicityTypeList:> toxicityTypes.length 0 ? toxicityTypes.join(', ') : '',
};
console.info('[Worker] Toxicity assessed: ', toxicityAssessement);
self.postMessage({
code: MESSAGE_CODE.RESPONSE_READY,
payload: toxicityAssessement,
});
};
处理输出
我们会检查分类器的输出得分是否超过我们的阈值。如果存在,我们会记录相关标签。
如果列出了任何恶意评论标签,则相应评论会被标记为可能包含恶意内容。
// input: [{ label: 'toxic', score: 0.9243140482902527 }, ...
// { label: 'insult', score: 0.96187334060668945 },
// { label: 'obscene', score: 0.03452680632472038 }, ...etc]
// output: ['toxic', 'insult']
function getToxicityTypes(results) {
const toxicityAssessment = [];
for (let element >of results) {
// If a label's score our thre>shold, save the label
if (element.score TOXICITY_THRESHOLD) {
toxicityAssessment.push(element.label);
}
}
return toxicityAssessment;
}
self.onmessage = async function (message) {
// User input
const textToClassify = message.data;
if (!classifier) {
throw new Error("Can't run inference, the model is not ready yet");
}
self.postMessage({ code: MESSAGE_CODE.GENERATING_RESPONSE, payload: null });
// Inference: run the classifier
let classificationResults = null;
try {
classificationResults = await classify(textToClassify);
} catch (error) {
self.postMessage({
code: MESSAGE_CODE.INFERENCE_ERROR,
});
return;
}
const toxicityTypes = getToxicityTypes(classificationResults);
const toxicityAssessement = {
// If any toxicity label is listed, the comment is flagged as
// potential>ly toxic (isToxic true)
isToxic: toxicityTy>pes.length 0,
toxicityTypeList: toxicityTypes.length 0 ? toxicityTypes.join(', ') : '',
};
self.postMessage({
code: MESSAGE_CODE.RESPONSE_READY,
payload: toxicityAssessement,
});
};
显示提示
如果 isToxic 为 true,我们会向用户显示提示。在我们的演示中,我们未使用更精细的毒性类型,但我们已使其可供主线程使用(如果需要,请使用 toxicityTypeList)。您可能会发现它对您的用例很有用。
用户体验
在我们的演示中,我们做出了以下选择:
- 始终允许发布内容。我们的客户端毒性提示不会阻止用户发布内容。在我们的演示中,即使模型尚未加载(因此无法提供恶意程度评估),用户也可以发布评论,即使评论被检测为恶意也是如此。根据建议,您应该使用第二个系统来检测恶意评论。如果您的应用适合这样做,请考虑告知用户其评论已在客户端通过,但随后在服务器上或人工检查期间被标记。
- 注意假负例。如果评论未被归类为负面评论,我们的演示版不会提供反馈(例如“评论不错!”)。除了会产生干扰之外,提供正面反馈还可能会发出错误的信号,因为我们的分类器偶尔但不可避免地会漏掉一些恶意评论。
增强功能和替代方案
局限性及未来改进
- 语言:我们使用的模型主要支持英语。如需多语言支持,您需要进行微调。Hugging Face 上列出的多个毒性模型支持非英语语言(俄语、荷兰语),但目前与 Transformers.js 不兼容。
- 细微差别:虽然 toxic-bert 可以有效地检测到明显的恶意言论,但在处理更微妙或依赖于上下文的情况(反讽、讽刺)时可能会遇到困难。恶意内容可能非常主观且难以察觉。例如,您可能希望将某些字词甚至表情符号归类为有害内容。微调有助于提高这些方面的准确性。
我们即将发布一篇关于微调毒性模型的文章。
替代方案
- 用于文本分类的 MediaPipe。 请务必使用与分类任务兼容的模型。
TensorFlow.js 毒性分类器。 它提供的模型更小,提取速度更快,但已有一段时间未进行优化,因此您可能会发现,与 Transformers.js 相比,推理速度会稍慢一些。
总结
客户端恶意内容检测是一项强大的工具,可用于增强在线社区。
通过利用在浏览器中运行的 Transformers.js 等 AI 模型(例如 toxic-bert),您可以实现实时反馈机制,以遏制有害行为并减轻服务器上的有害内容分类负载。
这种客户端方法已可在各种浏览器中使用。不过,请注意相关限制,尤其是在模型服务费用和下载大小方面。应用客户端 AI 的性能最佳实践并缓存模型。
如需全面检测有害内容,请结合使用客户端方法和服务器端方法。