Parte 2: Crea una detección de toxicidad de IA del cliente

Maud Nalpas
Maud Nalpas

Publicado: 13 de noviembre de 2024

El discurso de odio, el hostigamiento y el abuso en línea se han convertido en un problema generalizado en Internet. Los comentarios tóxicos silencian voces importantes y alejan a los usuarios y clientes. La detección de toxicidad protege a los usuarios y crea un entorno en línea más seguro.

En esta serie de dos partes, exploramos cómo usar la IA para detectar y mitigar la toxicidad en su origen: los teclados de los usuarios.

En la primera parte, analizamos los casos de uso y los beneficios de este enfoque.

En esta segunda parte, profundizaremos en la implementación, incluidos ejemplos de código y sugerencias de UX.

Demostración y código

Prueba nuestra demostración y explora el código en GitHub.

Es una demostración de la publicación de comentarios.
Cuando el usuario deja de escribir, analizamos la toxicidad de su comentario. Mostramos una advertencia en tiempo real si el comentario se clasifica como tóxico.

Navegadores compatibles

Nuestra demostración se ejecuta en las versiones más recientes de Safari, Chrome, Edge y Firefox.

Selecciona un modelo y una biblioteca

Usamos la biblioteca Transformers.js de Hugging Face, que proporciona herramientas para trabajar con modelos de aprendizaje automático en el navegador. Nuestro código de demostración se deriva de este ejemplo de clasificación de texto.

Elegimos el modelo toxic-bert, un modelo entrenado previamente diseñado para identificar patrones de lenguaje tóxico. Es una versión compatible con la Web de unitary/toxic-bert. Para obtener más detalles sobre las etiquetas del modelo y su clasificación de los ataques de identidad, consulta la página del modelo de Hugging Face.

El tamaño de descarga de toxic-bert es de 111 MB.

Una vez que se descarga el modelo, la inferencia es rápida.

Por ejemplo, suele tardar menos de 500 milisegundos en Chrome ejecutándose en un dispositivo Android de gama media en el que realizamos pruebas (un teléfono Pixel 7 normal, no el modelo Pro más potente). Ejecuta tus propias comparativas que sean representativas de tu base de usuarios.

Implementación

Estos son los pasos clave de nuestra implementación:

Cómo establecer un umbral de toxicidad

Nuestro clasificador de toxicidad proporciona puntuaciones de toxicidad entre 0 y 1. Dentro de ese rango, debemos establecer un umbral para determinar qué constituye un comentario tóxico. Un umbral de uso común es 0.9. Esto te permite detectar comentarios abiertamente tóxicos y, al mismo tiempo, evitar una sensibilidad excesiva que podría generar demasiados falsos positivos (es decir, comentarios inofensivos categorizados como tóxicos).

export const TOXICITY_THRESHOLD = 0.9

Importa los componentes

Comenzamos importando los componentes necesarios de la biblioteca @xenova/transformers. También importamos constantes y valores de configuración, incluido nuestro umbral de toxicidad.

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';

Carga el modelo y comunícate con el subproceso principal

Cargamos el modelo de detección de toxicidad toxic-bert y lo usamos para preparar nuestro clasificador. La versión menos compleja es const classifier = await pipeline('text-classification', MODEL_NAME);.

Crear una canalización, como en el código de ejemplo, es el primer paso para ejecutar tareas de inferencia.

La función de la canalización toma dos argumentos: la tarea ('text-classification') y el modelo (Xenova/toxic-bert).

Término clave: En Transformers.js, una canalización es una API de alto nivel que simplifica el proceso de ejecución de modelos de AA. Controla tareas como la carga de modelos, la tokenización y el procesamiento posterior.

Nuestro código de demostración hace algo más que preparar el modelo, ya que descargamos los pasos de preparación del modelo que son costosos en términos de procesamiento a un trabajador web. Esto permite que el subproceso principal siga siendo responsivo. Obtén más información para descargar tareas costosas a un trabajador web.

Nuestro trabajador necesita comunicarse con el subproceso principal, usando mensajes para indicar el estado del modelo y los resultados de la evaluación de toxicidad. Consulta los códigos de mensaje que creamos y que se asignan a diferentes estados del ciclo de vida de la preparación y la inferencia del modelo.

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 });
  }
})();

Clasifica la entrada del usuario

En nuestra función classify, usamos el clasificador que creamos anteriormente para analizar un comentario del usuario. Devolvemos el resultado sin procesar del clasificador de toxicidad: etiquetas y puntuaciones.

// 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;
}

Llamamos a nuestra función de clasificación cuando el subproceso principal le pide al subproceso de trabajo que lo haga. En nuestra demostración, activamos el clasificador en cuanto el usuario deja de escribir (consulta TYPING_DELAY). Cuando esto sucede, nuestro subproceso principal envía un mensaje al subproceso de trabajo que contiene la entrada del usuario para clasificar.

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,
  });
};

Cómo procesar el resultado

Verificamos si las puntuaciones de salida del clasificador superan nuestro umbral. Si es así, tomamos nota de la etiqueta en cuestión.

Si se incluye alguna de las etiquetas de toxicidad, el comentario se marca como potencialmente tóxico.

// 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,
  });
};

Cómo mostrar una sugerencia

Si isToxic es verdadero, mostramos una sugerencia al usuario. En nuestra demostración, no usamos el tipo de toxicidad más detallado, pero lo pusimos a disposición del subproceso principal si es necesario (toxicityTypeList). Es posible que lo encuentres útil para tu caso de uso.

Experiencia del usuario

En nuestra demostración, tomamos las siguientes decisiones:

  • Permitir siempre la publicación Nuestra sugerencia de toxicidad del cliente no impide que el usuario publique contenido. En nuestra demostración, el usuario puede publicar un comentario incluso si el modelo no se cargó (y, por lo tanto, no ofrece una evaluación de toxicidad) y aunque el comentario se detecte como tóxico. Como se recomienda, debes tener un segundo sistema para detectar comentarios tóxicos. Si tiene sentido para tu aplicación, considera informar al usuario que su comentario se publicó en el cliente, pero que luego se marcó en el servidor o durante la inspección humana.
  • Ten en cuenta los falsos negativos. Cuando un comentario no se clasifica como tóxico, nuestra demostración no ofrece comentarios (por ejemplo, "¡Buen comentario!"). Además de ser ruidoso, ofrecer comentarios positivos puede enviar la señal incorrecta, ya que nuestro clasificador, en ocasiones, pero de forma inevitable, no detecta algunos comentarios tóxicos.
Es una demostración de la publicación de comentarios.
El botón Publicar siempre está habilitado: En nuestra demostración, el usuario puede decidir publicar su comentario, incluso si se clasifica como tóxico. Incluso si un comentario no se clasifica como tóxico, no mostramos comentarios positivos.

Mejoras y alternativas

Limitaciones y próximas mejoras

  • Idiomas: El modelo que usamos admite principalmente el inglés. Para la asistencia multilingüe, necesitas un ajuste. Varios modelos de toxicidad que se enumeran en Hugging Face admiten idiomas distintos del inglés (ruso y neerlandés), aunque, por el momento, no son compatibles con Transformers.js.
  • Matiz: Si bien toxic-bert detecta de manera eficaz la toxicidad evidente, puede tener dificultades con casos más sutiles o dependientes del contexto (ironía, sarcasmo). La toxicidad puede ser muy subjetiva y sutil. Por ejemplo, es posible que desees que ciertos términos o incluso emojis se clasifiquen como tóxicos. La optimización puede ayudar a mejorar la precisión en estas áreas.

Próximamente, publicaremos un artículo sobre el ajuste de un modelo de toxicidad.

Alternativas

Conclusión

La detección de toxicidad del cliente es una herramienta poderosa para mejorar las comunidades en línea.

Si aprovechas los modelos de IA, como toxic-bert, que se ejecutan en el navegador con Transformers.js, puedes implementar mecanismos de comentarios en tiempo real que desalienten el comportamiento tóxico y reduzcan la carga de clasificación de toxicidad en tus servidores.

Este enfoque del cliente ya funciona en todos los navegadores. Sin embargo, ten en cuenta las limitaciones, especialmente en términos de costos de entrega del modelo y tamaño de descarga. Aplica prácticas recomendadas de rendimiento para la IA del cliente y almacena el modelo en caché.

Para una detección integral de toxicidad, combina enfoques del cliente y del servidor.