Teil 2: Clientseitige KI-Toxizitätserkennung erstellen

Maud Nalpas
Maud Nalpas

Veröffentlicht am 13. November 2024

Hassrede, Belästigung und Online-Missbrauch sind zu einem weitverbreiteten Problem im Internet geworden. Schädliche Kommentare unterdrücken wichtige Stimmen und vertreiben Nutzer und Kunden. Die Erkennung von toxischen Inhalten schützt Ihre Nutzer und sorgt für eine sicherere Onlineumgebung.

In dieser zweiteiligen Reihe erfahren Sie, wie Sie KI einsetzen können, um toxische Inhalte direkt an der Quelle zu erkennen und zu minimieren – also auf den Tastaturen der Nutzer.

Im ersten Teil haben wir die Anwendungsfälle und Vorteile dieses Ansatzes erläutert.

Im zweiten Teil geht es um die Implementierung, einschließlich Codebeispielen und UX-Tipps.

Demo und Code

Demo ausprobieren und Code auf GitHub ansehen

Demo zum Posten von Kommentaren
Wenn der Nutzer aufhört zu tippen, analysieren wir die Toxizität seines Kommentars. Wenn der Kommentar als unangemessen eingestuft wird, wird in Echtzeit eine Warnung angezeigt.

Unterstützte Browser

Unsere Demo wird in den neuesten Versionen von Safari, Chrome, Edge und Firefox ausgeführt.

Modell und Bibliothek auswählen

Wir verwenden die Transformers.js-Bibliothek von Hugging Face, die Tools für die Arbeit mit Modellen für maschinelles Lernen im Browser bietet. Unser Democode basiert auf diesem Beispiel für die Textklassifizierung.

Wir wählen das Modell toxic-bert aus, ein vortrainiertes Modell, das darauf ausgelegt ist, toxische Sprachmuster zu erkennen. Es handelt sich um eine webkompatible Version von unitary/toxic-bert. Weitere Informationen zu den Labels des Modells und seiner Klassifizierung von Identitätsangriffen finden Sie auf der Hugging Face-Modellseite.

Die Downloadgröße von „toxic-bert“ beträgt 111 MB.

Sobald das Modell heruntergeladen wurde, ist die Inferenz schnell.

In Chrome auf einem von uns getesteten Android-Mittelklassegerät (einem regulären Pixel 7, nicht dem leistungsstärkeren Pro-Modell) dauert das in der Regel weniger als 500 Millisekunden. Führen Sie eigene Benchmarks aus, die für Ihre Nutzer repräsentativ sind.

Implementierung

Dies sind die wichtigsten Schritte bei der Implementierung:

Grenzwert für Toxizität festlegen

Unser Klassifikator für unangemessene Äußerungen liefert Werte zwischen 0 und 1. Innerhalb dieses Bereichs müssen wir einen Schwellenwert festlegen, um zu bestimmen, was einen toxischen Kommentar ausmacht. Ein häufig verwendeter Grenzwert ist 0.9. So können Sie eindeutig unangemessene Kommentare erkennen und gleichzeitig eine übermäßige Sensibilität vermeiden, die zu zu vielen Falsch-Positiv-Ergebnissen führen könnte (d. h. harmlose Kommentare, die als unangemessen eingestuft werden).

export const TOXICITY_THRESHOLD = 0.9

Komponenten importieren

Zuerst importieren wir die erforderlichen Komponenten aus der @xenova/transformers-Bibliothek. Außerdem importieren wir Konstanten und Konfigurationswerte, einschließlich unseres Grenzwerts für toxische Inhalte.

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

Modell laden und mit dem Haupt-Thread kommunizieren

Wir laden das Modell zur Erkennung von Toxizität toxic-bert und verwenden es, um unseren Klassifikator vorzubereiten. Die einfachste Version davon ist const classifier = await pipeline('text-classification', MODEL_NAME);

Das Erstellen einer Pipeline, wie im Beispielcode, ist der erste Schritt zum Ausführen von Inferenzaufgaben.

Die Pipeline-Funktion verwendet zwei Argumente: die Aufgabe ('text-classification') und das Modell (Xenova/toxic-bert).

Schlüsselbegriff: In Transformers.js ist eine Pipeline eine API auf hoher Ebene, die das Ausführen von ML-Modellen vereinfacht. Es übernimmt Aufgaben wie das Laden von Modellen, die Tokenisierung und die Nachbearbeitung.

Unser Democode bereitet das Modell nicht nur vor, sondern lagert die rechenintensiven Schritte der Modellvorbereitung an einen Web-Worker aus. So bleibt der Hauptthread reaktionsfähig. Weitere Informationen zum Auslagern rechenintensiver Aufgaben an einen Web-Worker

Der Worker muss mit dem Hauptthread kommunizieren und Nachrichten verwenden, um den Status des Modells und die Ergebnisse der Toxizitätsbewertung anzugeben. Sehen Sie sich die von uns erstellten Nachrichtencodes an, die verschiedenen Status des Modellvorbereitungs- und Inferenzlebenszyklus zugeordnet sind.

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

Nutzeranfrage klassifizieren

In unserer classify-Funktion verwenden wir den zuvor erstellten Klassifikator, um einen Nutzerkommentar zu analysieren. Wir geben die Rohausgabe des Toxizitätsklassifikators zurück: Labels und Bewertungen.

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

Die Klassifizierungsfunktion wird aufgerufen, wenn der Hauptthread den Worker dazu auffordert. In unserer Demo wird die Klassifizierung ausgelöst, sobald der Nutzer mit der Eingabe fertig ist (siehe TYPING_DELAY). In diesem Fall sendet der Hauptthread eine Nachricht an den Worker, die die zu klassifizierende Nutzereingabe enthält.

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

Ausgabe verarbeiten

Wir prüfen, ob die Ausgabewerte des Klassifikators unseren Schwellenwert überschreiten. In diesem Fall notieren wir uns das entsprechende Label.

Wenn eines der Labels für unangemessene Äußerungen aufgeführt ist, wird der Kommentar als potenziell unangemessen gekennzeichnet.

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

Hinweis anzeigen

Wenn isToxic „true“ ist, wird dem Nutzer ein Hinweis angezeigt. In unserer Demo verwenden wir den detaillierteren Typ für schädliche Inhalte nicht, aber wir haben ihn bei Bedarf für den Hauptthread verfügbar gemacht (toxicityTypeList). Er kann für Ihren Anwendungsfall nützlich sein.

Nutzererfahrung

In unserer Demo haben wir folgende Entscheidungen getroffen:

  • Beiträge immer zulassen: Unser clientseitiger Hinweis auf toxische Inhalte verhindert nicht, dass der Nutzer Inhalte postet. In unserer Demo kann der Nutzer einen Kommentar posten, auch wenn das Modell nicht geladen wurde (und daher keine Bewertung der Unangemessenheit bietet) und auch wenn der Kommentar als unangemessen erkannt wird. Wie empfohlen, sollten Sie ein zweites System zur Erkennung unangemessener Kommentare haben. Wenn es für Ihre Anwendung sinnvoll ist, können Sie den Nutzer darüber informieren, dass sein Kommentar auf dem Client durchgegangen, dann aber auf dem Server oder bei der manuellen Überprüfung gekennzeichnet wurde.
  • Falsch negative Ergebnisse berücksichtigen: Wenn ein Kommentar nicht als unangemessen eingestuft wird, gibt unsere Demo kein Feedback (z. B. „Guter Kommentar!“). Abgesehen davon, dass es störend ist, kann positives Feedback das falsche Signal senden, da unser Klassifikator gelegentlich, aber unvermeidlich einige unangemessene Kommentare übersieht.
Demo zum Posten von Kommentaren
Der Button Posten ist immer aktiviert: In unserer Demo kann der Nutzer seinen Kommentar trotzdem posten, auch wenn er als toxisch eingestuft wird. Selbst wenn ein Kommentar nicht als toxisch eingestuft wird, zeigen wir kein positives Feedback an.

Verbesserungen und Alternativen

Einschränkungen und künftige Optimierungen

  • Sprachen: Das von uns verwendete Modell unterstützt hauptsächlich Englisch. Für die mehrsprachige Unterstützung ist ein Fine-Tuning erforderlich. Mehrere Toxizitätsmodelle, die auf Hugging Face aufgeführt sind, unterstützen nicht englische Sprachen (Russisch, Niederländisch), sind aber derzeit nicht mit Transformers.js kompatibel.
  • Nuance: Toxic-bert erkennt zwar effektiv offenkundige Toxizität, kann aber bei subtileren oder kontextabhängigen Fällen (Ironie, Sarkasmus) Schwierigkeiten haben. Toxizität kann sehr subjektiv und subtil sein. Sie möchten beispielsweise, dass bestimmte Begriffe oder sogar Emojis als schädlich eingestuft werden. Durch das Fine-Tuning kann die Genauigkeit in diesen Bereichen verbessert werden.

Wir werden demnächst einen Artikel zur Feinabstimmung eines Modells für die Erkennung von toxischen Inhalten veröffentlichen.

Alternativen

Fazit

Die clientseitige Erkennung von toxischen Inhalten ist ein leistungsstarkes Tool zur Verbesserung von Online-Communities.

Durch die Nutzung von KI-Modellen wie toxic-bert, die mit Transformers.js im Browser ausgeführt werden, können Sie Feedbackmechanismen in Echtzeit implementieren, die toxisches Verhalten verhindern und die Belastung Ihrer Server durch die Klassifizierung von Toxizität reduzieren.

Dieser clientseitige Ansatz funktioniert bereits browserübergreifend. Beachten Sie jedoch die Einschränkungen, insbesondere in Bezug auf die Kosten für die Bereitstellung von Modellen und die Downloadgröße. Best Practices für die Leistung von clientseitiger KI anwenden und Modell im Cache speichern.

Für eine umfassende Erkennung von toxischen Inhalten sollten Sie clientseitige und serverseitige Ansätze kombinieren.