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 i tuoi utenti e crea un ambiente online più sicuro.

In questa serie in due parti, esploreremo come utilizzare l'AI per rilevare e mitigare la tossicità alla sua fonte: le tastiere degli utenti.

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

In questa seconda parte, approfondiamo l'implementazione, inclusi esempi di codice e suggerimenti per la UX.

Demo e codice

Prova la nostra demo ed esamina il codice su GitHub.

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

Supporto browser

La nostra demo viene eseguita 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 demo deriva da questo esempio di classificazione del testo.

Scegliamo il modello toxic-bert, un modello preaddestrato progettato per identificare i pattern linguistici tossici. Si tratta di una versione di unitary/toxic-bert compatibile con il web. Per maggiori dettagli sulle etichette del modello e sulla classificazione degli attacchi all'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 bastano meno di 500 millisecondi in Chrome in esecuzione 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

Di seguito sono riportati i passaggi chiave della nostra implementazione:

Impostare una soglia di tossicità

Il nostro classificatore di 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 tossico. Una soglia di utilizzo comune è 0.9. In questo modo puoi rilevare i commenti apertamente tossici, evitando un'eccessiva sensibilità che potrebbe portare a troppi falsi positivi (ovvero 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

Carichiamo 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 attività di inferenza.

La funzione della 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 demo fa un po' di più della semplice preparazione del modello, perché scarichiamo i passaggi di preparazione del modello computazionalmente costosi in un web worker. In questo modo, il thread principale rimane reattivo. Scopri di più su come scaricare 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 dei messaggi che abbiamo creato e che corrispondono a 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. Restituiamo 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 chiede al worker di farlo. Nella nostra demo, attiviamo il classificatore non appena l'utente smette di digitare (vedi TYPING_DELAY). Quando ciò accade, il nostro thread principale invia un messaggio al worker che contiene 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,
  });
};

Elaborare l'output

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

Se è presente una delle etichette di tossicità, il commento viene contrassegnato 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, mostriamo un suggerimento all'utente. Nella nostra demo non utilizziamo il tipo di tossicità più granulare, ma l'abbiamo reso disponibile al thread principale, se necessario (toxicityTypeList). Potresti trovarlo utile per il tuo caso d'uso.

Esperienza utente

Nella nostra demo, abbiamo effettuato le seguenti scelte:

  • Consenti sempre la pubblicazione. Il 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 pubblicato sul client, ma è stato segnalato sul server o durante l'ispezione umana.
  • Tieni conto dei falsi negativi. Quando un commento non viene classificato come tossico, la nostra demo non offre feedback (ad esempio, "Bel commento!"). Oltre a essere rumoroso, offrire feedback positivi può inviare un segnale sbagliato, perché il nostro classificatore a volte, ma inevitabilmente, non rileva alcuni commenti tossici.
Demo della pubblicazione di un commento.
Il pulsante Pubblica è sempre attivo: nella nostra demo, l'utente può comunque decidere di pubblicare il proprio commento, anche se è classificato come tossico. Anche se un commento non è classificato come tossico, non mostriamo feedback positivi.

Miglioramenti e alternative

Limitazioni e miglioramenti futuri

  • Lingue: il modello che utilizziamo supporta principalmente l'inglese. Per il supporto multilingue, è necessario il perfezionamento. 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.
  • Sfumature: sebbene toxic-bert rilevi efficacemente la tossicità palese, potrebbe avere difficoltà con casi più sottili o dipendenti dal contesto (ironia, sarcasmo). La tossicità può essere altamente soggettiva e sottile. Ad esempio, potresti voler che determinati termini o persino emoji vengano classificati come tossici. Il perfezionamento può contribuire a migliorare l'accuratezza in questi ambiti.

È in arrivo un articolo sul perfezionamento di un modello di tossicità.

Alternative

Conclusione

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

Sfruttando modelli di AI come Toxic-BERT che vengono eseguiti nel browser con Transformers.js, puoi implementare meccanismi di feedback in tempo reale che scoraggiano comportamenti tossici e riducono il carico di classificazione della tossicità sui server.

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

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