Część 2. Tworzenie systemu wykrywania toksycznych treści na podstawie AI po stronie klienta

Maud Nalpas
Maud Nalpas

Data publikacji: 13 listopada 2024 r.

Szerzenie nienawiści, nękanie i przemoc w internecie stały się powszechnym problemem. Toksyczne komentarze uciszają ważne głosyodstraczają użytkowników i klientów. Wykrywanie toksycznych treści chroni użytkowników i tworzy bezpieczniejsze środowisko online.

W tej dwuczęściowej serii omawiamy, jak używać AI do wykrywania i łagodzenia toksycznych treści u źródła, czyli na klawiaturze użytkownika.

części 1 omawialiśmy przypadki użycia i korzyści płynące z tego podejścia.

W tej drugiej części omawiamy implementację, w tym przykłady kodu i wskazówki dotyczące UX.

Wersja demonstracyjna i kod

Zapoznaj się z naszym demo i przejrzyj kod na GitHubie.

Demonstracja publikowania komentarzy
Gdy użytkownik przestanie pisać, analizujemy toksyczne treści jego komentarza. Jeśli komentarz zostanie zakwalifikowany jako toksyczny, wyświetlimy ostrzeżenie w czasie rzeczywistym.

Obsługa przeglądarek

Nasz pokaz działa w najnowszych wersjach przeglądarek Safari, Chrome, Edge i Firefox.

Wybierz model i bibliotekę

Używamy biblioteki Transformers.js firmy Hugging Face, która zawiera narzędzia do pracy z modelami systemów uczących się w przeglądarce. Nasz kod demonstracyjny pochodzi z tego przykładu klasyfikacji tekstu.

Wybieramy model toxic-bert, czyli wstępnie przetrenowany model do identyfikowania toksycznych wzorców językowych. Jest to wersja unitary/toxic-bert zgodna z wymaganiami dotyczącymi stron internetowych. Więcej informacji o etykietach modelu i jego klasyfikacji ataków na tożsamość znajdziesz na stronie modelu huggingface.

Rozmiar pliku do pobrania z toxic-bert to 111 MB.

Po pobraniu modelu wnioskowanie jest szybkie.

Na przykład w Chrome na urządzeniu z Androidem klasy średniej, które testowaliśmy (zwykły telefon Pixel 7, a nie model Pro o większej wydajności), wczytywanie zajmuje zwykle mniej niż 500 ms. Przeprowadzanie własnych testów porównawczych, które odzwierciedlają grupę użytkowników.

Implementacja

Oto najważniejsze kroki w naszej implementacji:

Ustawianie progu toksyczności

Nasz klasyfikator toksyczności podaje wyniki toksyczności w zakresie od 0 do 1. W tym zakresie musimy ustawić próg, aby określić, co stanowi toksyczny komentarz. Często używany próg to 0.9. Dzięki temu możesz wykrywać jawnie toksyczne komentarze, unikając przy tym nadmiernej wrażliwości, która może prowadzić do zbyt dużej liczby wyników fałszywie pozytywnych (czyli nieszkodliwych komentarzy sklasyfikowanych jako toksyczne).

export const TOXICITY_THRESHOLD = 0.9

Importowanie komponentów

Zacznij od zaimportowania potrzebnych komponentów z biblioteki @xenova/transformers. Importujemy też stałe i wartości konfiguracji, w tym próg toksyczności.

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

Załaduj model i komunikuj się z głównym wątkiem

Wczytujemy model wykrywania toksyczności toxic-bert i używamy go do przygotowania naszego klasyfikatora. Najmniej skomplikowana wersja to:const classifier = await pipeline('text-classification', MODEL_NAME);

Tworzenie potoku, jak w przykładowym kodzie, jest pierwszym krokiem do wykonywania zadań wnioskowania.

Funkcja pipeline przyjmuje 2 argumenty: zadanie ('text-classification') i model (Xenova/toxic-bert).

Termin kluczowy: w Transformers.js potok to interfejs API wysokiego poziomu, który upraszcza proces uruchamiania modeli uczenia maszynowego. Obsługuje ona takie zadania jak wczytywanie modelu, tokenizacja i przetwarzanie końcowe.

Nasz kod demonstracyjny nie tylko przygotowuje model, ale też przekierowuje do web workera pracochłonne etapy przygotowywania modelu. Dzięki temu główny wątek będzie nadal reagować. Dowiedz się więcej o przekazaniu kosztownych zadań do wykonania przez robota internetowego.

Nasz pracownik musi komunikować się z głównym wątkiem, używając wiadomości, aby wskazać stan modelu i wyniki oceny toksyczności. Zapoznaj się z kodami wiadomości, które stworzyliśmy, aby odzwierciedlały różne stany cyklu życia przygotowania modelu i wykonywania wnioskowania.

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

Klasyfikacja danych wejściowych użytkownika

W funkcji classify używamy wcześniej utworzonego klasyfikatora do analizowania komentarzy użytkowników. Zwracamy surowe dane z klasyfikatora toksyczności: etykiety i wyniki.

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

Funkcję klasyfikowania wywołujemy, gdy wątek główny poprosi o to pracownika. W naszym demonstracyjnym przykładzie uruchamiamy klasyfikator, gdy użytkownik przestanie pisać (patrz TYPING_DELAY). W tej chwili nasz wątek główny wysyła do pracownika wiadomość z danymi wejściowymi użytkownika, które mają zostać zaklasyfikowane.

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

Przetwarzanie danych wyjściowych

Sprawdzamy, czy wyniki klasyfikatora przekraczają nasz próg. Jeśli tak, weźmiemy pod uwagę etykietę, której dotyczy problem.

Jeśli pojawi się któraś z etykiet toksyczności, komentarz zostanie oznaczony jako potencjalnie toksyczny.

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

Wyświetlanie wskazówki

Jeśli isToxic ma wartość true (prawda), wyświetlamy użytkownikowi podpowiedź. W naszym pokazie nie używamy bardziej szczegółowego typu toksyczności, ale udostępniliśmy go w głównym wątku na wypadek, gdyby był potrzebny (toxicityTypeList).

Interfejs użytkownika

W naszym pokazie użyliśmy tych ustawień:

  • Zawsze zezwalaj na publikowanie. Nasza podpowiedź dotycząca toksycznych treści po stronie klienta nie uniemożliwia użytkownikowi publikowania treści. W naszym pokazie użytkownik może opublikować komentarz nawet wtedy, gdy model nie został załadowany (a tym samym nie oferuje oceny toksyczności), a także wtedy, gdy komentarz został wykryty jako toksyczny. Zalecamy, aby mieć drugi system do wykrywania toksycznych komentarzy. Jeśli ma to sens w przypadku Twojej aplikacji, poinformuj użytkownika, że jego komentarz został przetworzony przez klienta, ale został zgłoszony na serwerze lub podczas weryfikacji manualnej.
  • Uważaj na wyniki fałszywie negatywne. Jeśli komentarz nie jest klasyfikowany jako toksyczny, nasza wersja demonstracyjna nie wyświetla informacji zwrotnej (np. „Miły komentarz”). Poza tym pozytywna opinia może wysłać niewłaściwy sygnał, ponieważ nasz klasyfikator czasami, ale nieuchronnie pomija niektóre toksyczne komentarze.
Demonstracja publikowania komentarzy
Przycisk Opublikuj jest zawsze włączony: w naszym demonstratorze użytkownik może nadal zdecydować się na opublikowanie komentarza, nawet jeśli zostanie on sklasyfikowany jako toksyczny. Nawet jeśli komentarz nie zostanie sklasyfikowany jako toksyczny, nie wyświetlamy pozytywnych opinii.

Ulepszenia i alternatywne rozwiązania

Ograniczenia i przyszłe udoskonalenia

  • Języki: model, którego używamy, obsługuje przede wszystkim język angielski. Aby uzyskać pomoc w wielu językach, musisz ją dostosować. Na stronie Hugging Face znajdziesz modele, które obsługują języki inne niż angielski (np. rosyjski i holenderski), ale nie są obecnie zgodne z Transformers.js.
  • Nuance model toxic-bert skutecznie wykrywa jawną toksyczność, ale może mieć problemy z bardziej subtelnymi lub zależnymi od kontekstu przypadkami (ironia, sarkazm). Toksyczne treści mogą być bardzo subiektywne i subtelne. Możesz na przykład określić, że pewne słowa lub emotikony mają być klasyfikowane jako toksyczne. Dokładne dostosowanie może pomóc w zwiększeniu dokładności w tych obszarach.

Wkrótce opublikujemy artykuł o dostrajaniu modelu toksyczności.

Alternatywy

Podsumowanie

Wykrywanie toksycznych treści po stronie klienta to potężne narzędzie do ulepszania społeczności online.

Korzystając z modeli AI, takich jak toxic-bert, które działają w przeglądarce z Transformers.js, możesz wdrażać mechanizmy informacji zwrotnych w czasie rzeczywistym, które zniechęcają do toksycznego zachowania i zmniejszają obciążenie serwerów związane z klasyfikacją toksycznych treści.

To podejście po stronie klienta działa już we wszystkich przeglądarkach. Pamiętaj jednak o ograniczeniach, zwłaszcza w odniesieniu do kosztów obsługi modelu i rozmiaru pobierania. Stosuj sprawdzone metody dotyczące wydajności AI po stronie klientazapisuj model w pamięci podręcznej.

Aby zapewnić kompleksowe wykrywanie toksyczności, łącz metody po stronie klienta i serwera.