第二部分:构建客户端 AI 毒性检测

发布时间:2024 年 11 月 13 日

仇恨言论、骚扰和网络辱骂已成为普遍存在的在线问题。有害评论让重要的声音沉默,并赶走用户和客户。毒性检测可以保护您的用户,并创建更安全的在线环境。

在本系列的两部分中,我们将探讨如何使用 AI 在源头(用户的键盘)检测和减轻毒性。

第一部分中,我们讨论了这种方法的使用场景和优势。

在第二部分中,我们将深入探讨实现细节,包括代码示例和用户体验技巧。

演示和代码

体验我们的演示,并研究 GitHub 上的代码

Demo of comment posting.
当用户停止输入时,我们会分析其评论的毒性。如果评论被归类为有害,我们会实时显示警告。

浏览器支持

我们的演示在最新版本的 Safari、Chrome、Edge 和 Firefox 中运行。

选择模型和库

我们使用 Hugging Face 的 Transformers.js 库,该库提供用于在浏览器中使用机器学习模型的工具。我们的演示代码源自此文本分类示例

我们选择 toxic-bert 模型,这是一种预训练模型,旨在识别有害语言模式。它是 unitary/toxic-bert 的 Web 兼容版本。有关模型标签及其身份攻击分类的更多详细信息,请参阅 Hugging Face 模型页面

toxic-bert 的下载大小为 111MB。

模型下载后,推理速度很快。

例如,在我们测试过的中端 Android 设备(普通的 Pixel 7 手机,而不是性能更高的 Pro 型号)上运行 Chrome 时,通常不到 500 毫秒。运行您自己的基准测试,以代表您的用户群。

实现

以下是我们实现的关键步骤

设置毒性阈值

我们的毒性分类器提供 01 之间的毒性评分。在该范围内,我们需要设置一个阈值来确定什么是毒性评论。常用的阈值是 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,可简化运行 ML 模型的过程。它处理模型加载、分词和后处理等任务。

我们的演示代码不仅仅是准备模型,因为我们将计算量大的模型准备步骤卸载到 Web Worker。这使主线程保持响应。了解有关将昂贵的任务卸载到 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 执行分类时,我们会调用 classify 函数。在我们的演示中,一旦用户停止输入(请参阅 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),我们已将其提供给主线程。您可能会发现它对您的用例很有用。

用户体验

在我们的演示中,我们做出了以下选择

  • 始终允许发布。 我们的客户端毒性提示不会阻止用户发布。在我们的演示中,即使模型尚未加载(因此未提供毒性评估),甚至评论被检测为有害,用户也可以发布评论。正如建议的那样,您应该有第二个系统来检测有害评论。如果这对您的应用程序有意义,请考虑告知用户他们的评论已在客户端通过,但随后在服务器上或在人工检查期间被标记。
  • 注意误报。当评论未被归类为有害时,我们的演示不提供反馈(例如,“评论不错!”)。除了嘈杂之外,提供正面反馈可能会发出错误的信号,因为我们的分类器偶尔但不可避免地会遗漏一些有害评论。
Demo of comment posting.
发布按钮始终启用:在我们的演示中,即使评论被归类为有害,用户仍然可以决定发布他们的评论。即使评论未被归类为有害,我们也不会显示正面反馈。

增强功能和替代方案

局限性和未来改进

  • 语言:我们使用的模型主要支持英语。对于多语言支持,您需要进行微调。Hugging Face 上列出的多个 毒性模型确实支持非英语语言(俄语、荷兰语),但目前它们与 Transformers.js 不兼容。
  • 细微差别:虽然 toxic-bert 有效地检测到明显的毒性,但它可能难以处理更微妙或依赖上下文的情况(讽刺、嘲讽)。毒性可能非常主观和微妙。例如,您可能希望将某些术语甚至表情符号归类为有害。微调可以帮助提高这些领域的准确性。

我们即将发布一篇关于微调毒性模型的文章。

替代方案

结论

客户端毒性检测是增强在线社区的强大工具。

通过利用像 toxic-bert 这样的 AI 模型(在浏览器中与 Transformers.js 一起运行),您可以实现实时反馈机制,以阻止有害行为并减少服务器上的毒性分类负载。

这种客户端方法已经在各种浏览器中运行。但是,请记住其局限性,尤其是在模型服务成本和下载大小方面。应用客户端 AI 的性能最佳实践缓存模型

为了实现全面的毒性检测,请结合使用客户端和服务器端方法。