发布时间:2024 年 11 月 13 日
仇恨言论、骚扰和网络欺凌已成为网络上普遍存在的问题。恶意评论会扼杀重要声音,并赶走用户和客户。毒性内容检测功能可保护您的用户,并营造更安全的在线环境。
在本系列的第 2 部分中,我们将探讨如何使用 AI 从源头(用户的键盘)检测和减少有害内容。
在第 1 部分中,我们讨论了此方法的用例和优势。
在第二部分,我们将深入探讨实现,包括代码示例和用户体验提示。
演示和代码
试用我们的演示版,并研究 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。
我们的 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;
}
当主线程请求 worker 执行分类时,我们会调用分类函数。在我们的演示中,我们会在用户停止输入后立即触发分类器(请参阅 TYPING_DELAY
)。发生这种情况时,我们的主线程会向 worker 发送一条消息,其中包含要分类的用户输入。
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 threshold, 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
// potentially toxic (isToxic true)
isToxic: toxicityTypes.length > 0,
toxicityTypeList: toxicityTypes.length > 0 ? toxicityTypes.join(', ') : '',
};
self.postMessage({
code: MESSAGE_CODE.RESPONSE_READY,
payload: toxicityAssessement,
});
};
显示提示
如果 isToxic
为 true,我们会向用户显示提示。在我们的演示中,我们没有使用更精细的毒性类型,但在需要时,我们会将其提供给主线程 (toxicityTypeList
)。对于您的用例,它可能很有用。
用户体验
在我们的演示中,我们做出了以下选择:
- 始终允许发布。我们的客户端毒性提示不会阻止用户发布内容。在我们的演示中,即使模型尚未加载(因此无法提供恶意评估),即使评论被检测为恶意,用户也可以发表评论。根据建议,您应使用第二个系统来检测恶意评论。如果对您的应用而言有意义,不妨考虑告知用户,其评论在客户端上通过了审核,但在服务器上或人工审核期间被标记为不当。
- 注意假负例。如果评论未被归类为恶意评论,我们的演示版不会提供反馈(例如“好评论!”)。除了噪声之外,提供正面反馈也可能会发出错误的信号,因为我们的分类器偶尔会错过一些恶意评论,这是不可避免的。
增强功能和替代方案
局限性及未来改进
- 语言:我们使用的模型主要支持英语。如需支持多种语言,您需要进行微调。Hugging Face 上列出的多个毒性模型确实支持非英语语言(俄语、荷兰语),但目前不兼容 Transformers.js。
- Nuance:虽然 toxic-bert 可以有效检测明显的恶意言论,但对于更细微或取决于上下文的情况(讽刺、挖苦),它可能无法胜任。毒性可能非常主观且细微。例如,您可能希望将某些字词甚至表情符号归类为有害内容。微调有助于提高这些方面的准确性。
我们即将推出一篇介绍如何微调毒性内容检测模型的文章。
替代方案
- 用于文本分类的 MediaPipe。 请务必使用与分类任务兼容的模型。
TensorFlow.js 毒性分类器。 它提供的模型体积更小,提取速度更快,但已经有一段时间没有进行优化,因此您可能会发现推理速度比使用 Transformers.js 时慢一些。
总结
客户端毒性检测是一款强大的工具,可用于改善在线社区。
通过利用 toxic-bert 等 AI 模型(这些模型可在浏览器中通过 Transformers.js 运行),您可以实现实时反馈机制,以遏制有害行为并减少服务器上的有害内容分类负担。
这种客户端方法已在各浏览器中运行。不过,请注意存在的限制,尤其是在模型服务费用和下载大小方面。应用客户端 AI 性能最佳实践并缓存模型。
如需进行全面的毒性检测,请结合使用客户端和服务器端方法。