Publié le 13 novembre 2024
L'incitation à la haine, le harcèlement et les abus en ligne sont devenus un problème omniprésent sur Internet. Les commentaires toxiques étouffent des voix importantes et éloignent les utilisateurs et les clients. La détection de la toxicité protège vos utilisateurs et crée un environnement en ligne plus sûr.
Dans cette série en deux parties, nous verrons comment utiliser l'IA pour détecter et atténuer la toxicité à sa source: les claviers des utilisateurs.
Dans la première partie, nous avons abordé les cas d'utilisation et les avantages de cette approche.
Dans cette deuxième partie, nous nous penchons sur l'implémentation, y compris des exemples de code et des conseils d'UX.
Démo et code
Testez notre démo et examinez le code sur GitHub.
Prise en charge des navigateurs
Notre démonstration s'exécute dans les dernières versions de Safari, Chrome, Edge et Firefox.
Sélectionner un modèle et une bibliothèque
Nous utilisons la bibliothèque Transformers.js de Hugging Face, qui fournit des outils pour travailler avec des modèles de machine learning dans le navigateur. Notre code de démonstration est dérivé de cet exemple de classification du texte.
Nous avons choisi le modèle toxic-bert, un modèle pré-entraîné conçu pour identifier les schémas de langage toxiques. Il s'agit d'une version compatible avec le Web de unitary/toxic-bert. Pour en savoir plus sur les libellés du modèle et sa classification des attaques d'identité, consultez la page du modèle Hugging Face.
Une fois le modèle téléchargé, l'inférence est rapide.
Par exemple, il faut généralement moins de 500 millisecondes dans Chrome exécuté sur un appareil Android de milieu de gamme que nous avons testé (un téléphone Pixel 7 standard, et non le modèle Pro plus performant). Exécutez vos propres benchmarks représentatifs de votre base d'utilisateurs.
Implémentation
Voici les principales étapes de notre implémentation:
Définir un seuil de toxicité
Notre classificateur de toxicité fournit des scores de toxicité compris entre 0
et 1
. Dans cette plage, nous devons définir un seuil pour déterminer ce qui constitue un commentaire toxique. Le seuil couramment utilisé est 0.9
. Cela vous permet de détecter les commentaires ouvertement toxiques, tout en évitant une sensibilité excessive qui pourrait entraîner trop de faux positifs (en d'autres termes, des commentaires inoffensifs classés comme toxiques).
export const TOXICITY_THRESHOLD = 0.9
Importer les composants
Nous commençons par importer les composants nécessaires à partir de la bibliothèque @xenova/transformers
. Nous importons également des constantes et des valeurs de configuration, y compris notre seuil de toxicité.
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';
Charger le modèle et communiquer avec le thread principal
Nous chargeons le modèle de détection de toxicité toxic-bert et l'utilisons pour préparer notre classificateur. La version la moins complexe est const classifier = await pipeline('text-classification', MODEL_NAME);
.
La création d'un pipeline, comme dans l'exemple de code, est la première étape de l'exécution des tâches d'inférence.
La fonction de pipeline accepte deux arguments: la tâche ('text-classification'
) et le modèle (Xenova/toxic-bert
).
Terme clé: Dans Transformers.js, un pipeline est une API de haut niveau qui simplifie le processus d'exécution des modèles de ML. Il gère des tâches telles que le chargement du modèle, la tokenisation et le post-traitement.
Notre code de démonstration ne se contente pas de préparer le modèle, car nous transférons les étapes de préparation du modèle coûteuses en calcul à un nœud de travail Web. Cela permet au thread principal de rester réactif. Découvrez comment décharger des tâches coûteuses sur un worker Web.
Notre worker doit communiquer avec le thread principal, à l'aide de messages pour indiquer l'état du modèle et les résultats de l'évaluation de la toxicité. Examinez les codes de message que nous avons créés et qui correspondent à différents états du cycle de vie de la préparation et de l'inférence du modèle.
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 });
}
})();
Classer l'entrée utilisateur
Dans notre fonction classify
, nous utilisons notre classifieur créé précédemment pour analyser un commentaire utilisateur. Nous renvoyons la sortie brute du classificateur de toxicité: étiquettes et scores.
// 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;
}
Nous appelons notre fonction de classification lorsque le thread principal demande au worker de le faire. Dans notre démonstration, nous déclenchons le classificateur dès que l'utilisateur a cessé de taper (voir TYPING_DELAY
). Dans ce cas, notre thread principal envoie un message au worker contenant l'entrée utilisateur à classer.
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,
});
};
Traiter la sortie
Nous vérifions si les scores de sortie du classificateur dépassent notre seuil. Le cas échéant, nous prenons note de l'étiquette en question.
Si l'un des libellés de toxicité est listé, le commentaire est signalé comme potentiellement toxique.
// 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,
});
};
Afficher un indice
Si isToxic
est défini sur "True", nous affichons un indice à l'utilisateur. Dans notre démonstration, nous n'utilisons pas le type de toxicité plus précis, mais nous l'avons mis à la disposition du thread principal si nécessaire (toxicityTypeList
). Vous pouvez le trouver utile pour votre cas d'utilisation.
Expérience utilisateur
Dans notre démonstration, nous avons fait les choix suivants:
- Toujours autoriser la publication Notre indice de toxicité côté client n'empêche pas l'utilisateur de publier son message. Dans notre démonstration, l'utilisateur peut publier un commentaire même si le modèle n'a pas été chargé (et n'offre donc pas d'évaluation de la toxicité) et même si le commentaire est détecté comme étant toxique. Comme recommandé, vous devez disposer d'un deuxième système pour détecter les commentaires indésirables. Si cela est pertinent pour votre application, envisagez d'informer l'utilisateur que son commentaire a été accepté sur le client, mais qu'il a ensuite été signalé sur le serveur ou lors d'un examen manuel.
- Attention aux faux négatifs. Lorsqu'un commentaire n'est pas classé comme toxique, notre démonstration n'offre pas de commentaires (par exemple, "Bon commentaire !"). En plus d'être bruyant, envoyer des commentaires positifs peut envoyer le mauvais signal, car notre classificateur manque parfois, mais inévitablement, certains commentaires toxiques.
Améliorations et alternatives
Limitations et améliorations futures
- Langues: le modèle que nous utilisons est principalement compatible avec l'anglais. Pour une prise en charge multilingue, vous devez effectuer un ajustement. Plusieurs modèles de toxicité listés sur Hugging Face sont compatibles avec des langues autres que l'anglais (russe, néerlandais), mais ils ne sont pas compatibles avec Transformers.js pour le moment.
- Nuance: bien que toxic-bert détecte efficacement la toxicité manifeste, il peut avoir du mal à gérer les cas plus subtils ou dépendants du contexte (ironie, sarcasme). La toxicité peut être très subjective et subtile. Par exemple, vous pouvez souhaiter que certains termes ou même des emoji soient classés comme toxiques. Un ajustement fin peut contribuer à améliorer la précision dans ces domaines.
Nous publierons prochainement un article sur l'ajustement d'un modèle de toxicité.
Autres solutions
- MediaPipe pour la classification de texte. Assurez-vous d'utiliser un modèle compatible avec les tâches de classification.
Classificateur de toxicité TensorFlow.js Il propose un modèle plus petit et plus rapide à extraire. Toutefois, il n'a pas été optimisé depuis un certain temps. Vous constaterez peut-être que l'inférence est un peu plus lente qu'avec Transformers.js.
Conclusion
La détection de la toxicité côté client est un outil efficace pour améliorer les communautés en ligne.
En exploitant des modèles d'IA tels que toxic-bert qui s'exécutent dans le navigateur avec Transformers.js, vous pouvez implémenter des mécanismes de rétroaction en temps réel qui découragent les comportements toxiques et réduisent la charge de classification de la toxicité sur vos serveurs.
Cette approche côté client fonctionne déjà dans les différents navigateurs. Toutefois, gardez à l'esprit les limites, en particulier en termes de coûts de diffusion du modèle et de taille de téléchargement. Appliquez les bonnes pratiques de performances pour l'IA côté client et mettez en cache le modèle.
Pour une détection de la toxicité complète, combinez les approches côté client et côté serveur.