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

Maud Nalpas
Maud Nalpas

Opublikowano: 13 listopada 2024 r.

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

W tej 2-częściowej serii artykułów dowiesz się, jak używać AI do wykrywania i ograniczania toksyczności u źródła, czyli na klawiaturach użytkowników.

pierwszej części omówiliśmy przypadki użycia i korzyści wynikające z tego podejścia.

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

Wersja demonstracyjna i kod

Wypróbuj wersję demonstracyjną i zapoznaj się z kodem na GitHubie.

Prezentacja publikowania komentarzy.
Gdy użytkownik przestanie pisać, analizujemy toksyczność jego komentarza. Jeśli komentarz zostanie zaklasyfikowany jako toksyczny, wyświetlimy ostrzeżenie w czasie rzeczywistym.

Obsługa przeglądarek

Nasza wersja demonstracyjna działa w najnowszych wersjach przeglądarek Safari, Chrome, Edge i Firefox.

Wybierz model i bibliotekę

Korzystamy z biblioteki Transformers.js od Hugging Face, która udostępnia narzędzia do pracy z modelami uczenia maszynowego w przeglądarce. Nasz kod demonstracyjny pochodzi z tego przykładu klasyfikacji tekstu.

Wybieramy model toxic-bert, czyli wstępnie wytrenowany model zaprojektowany do identyfikowania toksycznych wzorców językowych. Jest to wersja unitary/toxic-bert zgodna z internetem. Więcej informacji o etykietach modelu i jego klasyfikacji ataków na tożsamość znajdziesz na stronie modelu w Hugging Face.

Rozmiar pobierania modelu toxic-bert wynosi 111 MB.

Po pobraniu modelu wnioskowanie jest szybkie.

Na przykład w przypadku Chrome na testowanym przez nas urządzeniu z Androidem średniej klasy (zwykłym telefonie Pixel 7, a nie wydajniejszym modelu Pro) trwa to zwykle mniej niż 500 milisekund. Przeprowadzaj własne testy porównawcze, które reprezentują Twoją bazę użytkowników.

Implementacja

Oto najważniejsze etapy naszej implementacji:

Ustawianie progu toksyczności

Nasz klasyfikator toksyczności podaje wyniki 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 wychwytywać jawnie toksyczne komentarze, unikając przy tym nadmiernej czułości, która mogłaby prowadzić do zbyt wielu wyników fałszywie pozytywnych (czyli nieszkodliwych komentarzy sklasyfikowanych jako toksyczne).

export const TOXICITY_THRESHOLD = 0.9

Importowanie komponentów

Zaczynamy od zaimportowania niezbędnych komponentów z @xenova/transformersbiblioteki. 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';

Wczytywanie modelu i komunikacja z głównym wątkiem

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

Utworzenie potoku, tak jak w przykładowym kodzie, jest pierwszym krokiem do uruchomienia zadań wnioskowania.

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

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

Nasz kod demonstracyjny robi coś więcej niż tylko przygotowuje model, ponieważ przenosimy kosztowne obliczeniowo etapy przygotowania modelu do procesu roboczego w internecie. Dzięki temu główny wątek pozostaje responsywny. Dowiedz się więcej o przekazywaniu kosztownych zadań do wątku roboczego.

Wątek roboczy musi komunikować się z wątkiem głównym, używając wiadomości do wskazywania stanu modelu i wyników oceny toksyczności. Zapoznaj się z utworzonymi przez nas kodami wiadomości, które są powiązane z różnymi stanami cyklu życia przygotowania modelu i 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 });
  }
})();

Klasyfikowanie danych wejściowych użytkownika

W naszej classify używamy utworzonego wcześniej klasyfikatora do analizy komentarza użytkownika. Zwracamy surowe dane wyjściowe 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ę klasyfikacji wywołujemy, gdy wątek główny prosi o to wątek roboczy. W naszym przykładzie uruchamiamy klasyfikator, gdy tylko użytkownik przestanie pisać (patrz TYPING_DELAY). W takiej sytuacji wątek główny wysyła do wątku roboczego wiadomość zawierającą dane wejściowe użytkownika do sklasyfikowania.

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. W takim przypadku zwracamy uwagę na daną etykietę.

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 podpowiedzi

Jeśli wartość isToxic to prawda, wyświetlamy użytkownikowi wskazówkę. W naszej wersji demonstracyjnej nie używamy bardziej szczegółowego typu toksyczności, ale w razie potrzeby udostępniliśmy go w głównym wątku (toxicityTypeList). Może się on przydać w Twoim przypadku.

Interfejs użytkownika

W naszej wersji demonstracyjnej wybraliśmy te opcje:

  • Zawsze zezwalaj na publikowanie. Wskazówka o toksyczności po stronie klienta nie uniemożliwia użytkownikowi opublikowania posta. W naszej wersji demonstracyjnej użytkownik może opublikować komentarz, nawet jeśli model nie został jeszcze wczytany (a tym samym nie oferuje oceny toksyczności) i nawet jeśli komentarz zostanie wykryty jako toksyczny. Zgodnie z zaleceniami powinieneś mieć drugi system do wykrywania toksycznych komentarzy. Jeśli ma to sens w przypadku Twojej aplikacji, możesz poinformować użytkownika, że jego komentarz został przesłany na urządzeniu klienta, ale potem został oznaczony na serwerze lub podczas weryfikacji przez człowieka.
  • Pamiętaj o wynikach fałszywie negatywnych. Gdy komentarz nie zostanie zaklasyfikowany jako toksyczny, nasza wersja demonstracyjna nie wyświetli opinii (np. „Fajny komentarz!”). Oprócz tego, że jest to uciążliwe, przekazywanie pozytywnych opinii może wysyłać niewłaściwy sygnał, ponieważ nasz klasyfikator czasami, ale nieuchronnie, pomija niektóre toksyczne komentarze.
Prezentacja publikowania komentarzy.
Przycisk Opublikuj jest zawsze włączony: w naszej wersji demonstracyjnej użytkownik może nadal opublikować komentarz, nawet jeśli został on zaklasyfikowany jako obraźliwy. Nawet jeśli komentarz nie zostanie zaklasyfikowany jako obraźliwy, nie wyświetlamy pozytywnych opinii.

Ulepszenia i alternatywy

Ograniczenia i przyszłe udoskonalenia

  • Języki: używany przez nas model obsługuje głównie język angielski. Aby uzyskać obsługę wielu języków, musisz przeprowadzić dostrajanie. Wiele modeli toksyczności wymienionych na platformie Hugging Face obsługuje języki inne niż angielski (rosyjski, holenderski), ale obecnie nie są one 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). Toksyczność może być bardzo subiektywna i trudna do wykrycia. Możesz na przykład chcieć, aby określone słowa lub nawet emotikony były klasyfikowane jako szkodliwe. Dostrajanie może pomóc zwiększyć dokładność w tych obszarach.

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

Alternatywy

Podsumowanie

Wykrywanie toksycznych treści po stronie klienta to zaawansowane 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 wdrożyć mechanizmy informacji zwrotnych w czasie rzeczywistym, które zniechęcają do toksycznych zachowań i zmniejszają obciążenie serwerów związane z klasyfikacją toksyczności.

To podejście po stronie klienta działa już w różnych przeglądarkach. Pamiętaj jednak o ograniczeniach, zwłaszcza w zakresie kosztów udostępniania modelu i rozmiaru pobierania. Zastosuj sprawdzone metody dotyczące wydajności w przypadku AI po stronie klientazapisz model w pamięci podręcznej.

Aby uzyskać kompleksowe wykrywanie toksyczności, połącz podejścia po stronie klienta i serwera.