Parte 2: crea un sistema di rilevamento della tossicità basato sull'IA lato client

Maud Nalpas
Maud Nalpas

Data di pubblicazione: 13 novembre 2024

L'incitamento all'odio, le molestie e gli abusi online sono diventati un problema pervasivo online. I commenti tossici mettono a tacere voci importanti e allontanano utenti e clienti. Il rilevamento della tossicità protegge gli utenti e crea un ambiente online più sicuro.

In questa serie in due parti, esploriamo come utilizzare l'IA per rilevare e ridurre la tossicità alla fonte: le tastiere degli utenti.

Nella prima parte, abbiamo discusso i casi d'uso e i vantaggi di questo approccio.

In questa seconda parte, analizziamo l'implementazione, inclusi esempi di codice e suggerimenti per l'esperienza utente.

Demo e codice

Prova la nostra demo e esamina il codice su GitHub.

Demo della pubblicazione di commenti.
Quando l'utente smette di digitare, analizziamo la tossicità del suo commento. Viene visualizzato un avviso in tempo reale se il commento è classificato come tossico.

Supporto browser

La nostra demo funziona nelle versioni più recenti di Safari, Chrome, Edge e Firefox.

Seleziona un modello e una libreria

Utilizziamo la libreria Transformers.js di Hugging Face, che fornisce strumenti per lavorare con i modelli di machine learning nel browser. Il nostro codice di dimostrazione è derivato da questo esempio di classificazione del testo.

Abbiamo scelto il modello toxic-bert, un modello preaddestrato progettato per identificare schemi di linguaggio tossico. È una versione compatibile con il web di unitary/toxic-bert. Per ulteriori dettagli sulle etichette del modello e sulla sua classificazione degli attacchi di identità, consulta la pagina del modello Hugging Face.

Le dimensioni del download di toxic-bert sono 111 MB.

Una volta scaricato il modello, l'inferenza è rapida.

Ad esempio, in genere sono necessari meno di 500 millisecondi in Chrome su un dispositivo Android di fascia media che abbiamo testato (uno smartphone Pixel 7 normale, non il modello Pro più performante). Esegui i tuoi benchmark rappresentativi della tua base utenti.

Implementazione

Ecco i passaggi chiave della nostra implementazione:

Impostare una soglia di tossicità

Il nostro classificatore della tossicità fornisce punteggi di tossicità compresi tra 0 e 1. All'interno di questo intervallo, dobbiamo impostare una soglia per determinare cosa costituisce un commento velenoso. Una soglia di utilizzo comune è 0.9. In questo modo puoi rilevare i commenti apertamente tossici, evitando al contempo una sensibilità eccessiva che potrebbe portare a troppi falsi positivi (in altre parole, commenti innocui classificati come tossici).

export const TOXICITY_THRESHOLD = 0.9

Importa i componenti

Iniziamo importando i componenti necessari dalla libreria @xenova/transformers. Importiamo anche costanti e valori di configurazione, inclusa la nostra soglia di tossicità.

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

Carica il modello e comunica con il thread principale

Caricammo il modello di rilevamento della tossicità toxic-bert e lo utilizziamo per preparare il nostro classificatore. La versione meno complessa è const classifier = await pipeline('text-classification', MODEL_NAME);

La creazione di una pipeline, come nel codice di esempio, è il primo passaggio per eseguire le attività di inferenza.

La funzione pipeline accetta due argomenti: l'attività ('text-classification') e il modello (Xenova/toxic-bert).

Termine chiave: in Transformers.js, una pipeline è un'API di alto livello che semplifica il processo di esecuzione dei modelli ML. Gestisce attività come il caricamento del modello, la tokenizzazione e la post-elaborazione.

Il nostro codice di dimostrazione fa qualcosa in più rispetto alla semplice preparazione del modello, perché offloadiamo i passaggi di preparazione del modello dal costo computazionale elevato a un web worker. In questo modo, il thread principale rimane reattivo. Scopri di più su come offloadare attività costose su un web worker.

Il nostro worker deve comunicare con il thread principale utilizzando messaggi per indicare lo stato del modello e i risultati della valutazione della tossicità. Dai un'occhiata ai codici messaggio che abbiamo creato e che mappano i diversi stati del ciclo di vita della preparazione e dell'inferenza del modello.

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

Classificare l'input dell'utente

Nella nostra funzione classify, utilizziamo il classificatore creato in precedenza per analizzare un commento dell'utente. Restituisce l'output non elaborato del classificatore di tossicità: etichette e punteggi.

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

Chiamiamo la nostra funzione di classificazione quando il thread principale lo chiede al worker. Nella nostra demo, attiviamo il classificatore non appena l'utente smette di digitare (vedi TYPING_DELAY). In questo caso, il thread principale invia un messaggio al worker contenente l'input dell'utente da classificare.

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

Elabora l'output

Controlliamo se i punteggi di output dello strumento di classificazione superano la nostra soglia. In questo caso, prendiamo nota dell'etichetta in questione.

Se è presente una delle etichette di tossicità, il commento viene segnalato come potenzialmente tossico.

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

Mostrare un suggerimento

Se isToxic è true, viene mostrato un suggerimento all'utente. Nella nostra demo non utilizziamo il tipo di tossicità più granulare, ma lo abbiamo reso disponibile per il thread principale, se necessario (toxicityTypeList). Potresti trovarlo utile per il tuo caso d'uso.

Esperienza utente

Nella nostra demo abbiamo fatto le seguenti scelte:

  • Consenti sempre la pubblicazione. Il nostro suggerimento sulla tossicità lato client non impedisce all'utente di pubblicare. Nella nostra demo, l'utente può pubblicare un commento anche se il modello non è stato caricato (e quindi non offre una valutazione della tossicità) e anche se il commento viene rilevato come tossico. Come consigliato, dovresti avere un secondo sistema per rilevare i commenti tossici. Se ha senso per la tua applicazione, valuta la possibilità di informare l'utente che il suo commento è stato elaborato sul client, ma è stato segnalato sul server o durante l'ispezione da parte di persone fisiche.
  • Attenzione ai falsi negativi. Quando un commento non viene classificato come tossico, la nostra demo non offre un feedback (ad esempio, "Bel commento!"). Oltre a essere poco affidabile, l'offerta di feedback positivi potrebbe inviare il segnale sbagliato, perché il nostro classificatore occasionalmente, ma inevitabilmente, perde alcuni commenti tossici.
Demo della pubblicazione di commenti.
Il pulsante Pubblica è sempre attivo: nella nostra demo, l'utente può ancora decidere di pubblicare il suo commento, anche se è classificato come tossico. Anche se un commento non è classificato come tossico, non viene visualizzato alcun feedback positivo.

Miglioramenti e alternative

Limitazioni e miglioramenti futuri

  • Lingue: il modello che utilizziamo supporta principalmente l'inglese. Per l'assistenza in più lingue, è necessaria la messa a punto. Diversi modelli di tossicità elencati su Hugging Face supportano lingue diverse dall'inglese (russo, olandese), anche se al momento non sono compatibili con Transformers.js.
  • Nuance: sebbene toxic-bert riesca a rilevare efficacemente la tossicità palese, potrebbe avere difficoltà con casi più sottili o dipendenti dal contesto (ironia, sarcasmo). La tossicità può essere molto soggettiva e sottile. Ad esempio, potresti voler classificare come dannosi determinati termini o persino emoji. La messa a punto può contribuire a migliorare la precisione in queste aree.

A breve pubblicheremo un articolo sulla messa a punto di un modello di tossicità.

Alternative

Conclusione

Il rilevamento della tossicità lato client è uno strumento efficace per migliorare le community online.

Sfruttando modelli di IA come toxic-bert che vengono eseguiti nel browser con Transformers.js, puoi implementare meccanismi di feedback in tempo reale che scoraggiano il comportamento tossico e riducono il carico di classificazione della tossicità sui tuoi server.

Questo approccio lato client funziona già su tutti i browser. Tuttavia, tieni presente le limitazioni, in particolare in termini di costi di pubblicazione del modello e dimensioni del download. Applica le best practice per le prestazioni dell'IA lato client e memorizza nella cache il modello.

Per un rilevamento completo della tossicità, combina gli approcci lato client e lato server.