חלק 2: פיתוח זיהוי רעילות באמצעות AI בצד הלקוח

Maud Nalpas
Maud Nalpas

תאריך פרסום: 13 בנובמבר 2024

דברי שטנה, הטרדה והתעללות באינטרנט הפכו לבעיה נפוצה באינטרנט. תגובות רעילות משתקות קולות חשובים וגורמות למשתמשים וללקוחות להתרחק. זיהוי תוכן רעיל מגן על המשתמשים ויוצר סביבה בטוחה יותר באינטרנט.

בסדרה הזו, שמחולקת לשני חלקים, נסביר איך משתמשים ב-AI כדי לזהות רעילות ולצמצם אותה כבר במקור שלה: במקלדות של המשתמשים.

בחלק הראשון, דיברנו על התרחישים לדוגמה ועל היתרונות של הגישה הזו.

בחלק השני נתמקד בהטמעה, כולל דוגמאות קוד ועצות לממשק המשתמש.

הדגמה וקוד

תוכלו לנסות את הדמו שלנו ולעיין בקוד ב-GitHub.

הדגמה של פרסום תגובה.
כשהמשתמש מפסיק להקליד, אנחנו מנתחים את מידת הרעילות של התגובה שלו. אם התגובה מסווגת כרעילה, אנחנו מציגים אזהרה בזמן אמת.

תמיכה בדפדפנים

הדמו שלנו פועל בגרסאות האחרונות של Safari,‏ Chrome,‏ Edge ו-Firefox.

בחירת מודל וספרייה

אנחנו משתמשים בספרייה Transformers.js של Hugging Face, שמספקת כלים לעבודה עם מודלים של למידת מכונה בדפדפן. קוד ההדגמה שלנו נגזר מדוגמה לסיווג טקסט.

אנחנו בוחרים את המודל toxic-bert, מודל שהוכשרה מראש לזיהוי דפוסי שפה רעילים. זוהי גרסה תואמת לאינטרנט של unitary/toxic-bert. פרטים נוספים על התוויות של המודל ועל הסיווג שלו של התקפות על זהות זמינים בדף המודל של Hugging Face.

גודל ההורדה של toxic-bert הוא 111MB.

אחרי הורדת המודל, ההסקה מהירה.

לדוגמה, בדרך כלל נדרשות פחות מ-500 אלפיות השנייה כדי לבצע את הפעולה הזו ב-Chrome שפועל במכשיר Android מדור הביניים שבדקנו (טלפון Pixel 7 רגיל, ולא דגם Pro בעל הביצועים הגבוהים יותר). להריץ מדדי ביצועים משלכם שמייצגים את בסיס המשתמשים שלכם.

הטמעה

אלה השלבים העיקריים בתהליך ההטמעה שלנו:

הגדרת סף רעילות

סיווג הרעילות שלנו מספק ציונים של רעילות בין 0 ל-1. בתוך הטווח הזה, אנחנו צריכים להגדיר ערך סף כדי לקבוע מה נחשב לתגובה רעילה. ערך סף נפוץ הוא 0.9. כך תוכלו לזהות תגובות רעילות באופן מובהק, בלי להפעיל רגישות מוגזמת שעלולה להוביל לזיהוי יתר של תגובות לא מזיקות כרעילות.

export const TOXICITY_THRESHOLD = 0.9

מייבאים את הרכיבים

מתחילים בייבוא הרכיבים הנחוצים מהספרייה @xenova/transformers. אנחנו מייבאים גם ערכי קבועים וערכי תצורה, כולל ערך הסף של הרעילות.

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

טעינת המודל ותקשורת עם השרשור הראשי

אנחנו טוענים את מודל זיהוי הרעילות toxic-bert ומשתמשים בו כדי להכין את הסיווג שלנו. הגרסה הכי פחות מורכבת של הקוד היא const classifier = await pipeline('text-classification', MODEL_NAME);

יצירת צינור עיבוד נתונים, כמו בקוד לדוגמה, היא השלב הראשון להרצת משימות של הסקת מסקנות.

פונקציית צינור עיבוד הנתונים מקבלת שני ארגומנטים: המשימה ('text-classification') והמודל (Xenova/toxic-bert).

מונח מפתח: ב-Transformers.js, צינור עיבוד נתונים הוא ממשק API ברמה גבוהה שמפשט את תהליך ההרצה של מודלים של למידת מכונה. הוא מטפל במשימות כמו טעינת מודל, יצירת אסימונים ועיבוד לאחר העיבוד.

קוד ההדגמה שלנו עושה קצת יותר מאשר רק הכנת המודל, כי אנחנו מעבירים ל-web worker את שלבי הכנת המודל שהם יקרים מבחינה חישובית. כך השרשור הראשי ימשיך להגיב. מידע נוסף על העברת משימות יקרות ל-Web Worker

העובד שלנו צריך לתקשר עם השרשור הראשי באמצעות הודעות כדי לציין את סטטוס המודל ואת התוצאות של הערכת הרעילות. כדאי לעיין בקודי ההודעות שיצרנו, שמתאימים לסטטוסים שונים של מחזור החיים של הכנת המודל וההסקה.

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

סיווג הקלט של המשתמש

בפונקציה classify אנחנו משתמשים במסווג שיצרנו קודם כדי לנתח תגובה של משתמש. אנחנו מחזירים את הפלט הגולמי של הסיווג של התוכן הרעיל: תוויות ודירוגים.

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

אנחנו קוראים לפונקציית הסיווג כשהשרשור הראשי מבקש מהעובד לעשות זאת. בדמו שלנו, אנחנו מפעילים את הסיווג ברגע שהמשתמש מפסיק להקליד (ראו TYPING_DELAY). כשזה קורה, השרשור הראשי שולח הודעה לעובד שמכילה את הקלט של המשתמש לצורך סיווג.

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

עיבוד הפלט

אנחנו בודקים אם הציונים של הפלט של המסווג חורגים מהסף שלנו. במקרה כזה, אנחנו מתעדים את התווית הרלוונטית.

אם אחת מהתוויות של תוכן רעיל מופיעה, התגובה תסומן כתוכן שעלול להיות רעיל.

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

הצגת רמז

אם הערך של isToxic הוא true, אנחנו מציגים למשתמש רמז. בדמו שלנו לא משתמשים בסוג הרמת הטוהר המפורטת יותר, אבל הפכנו אותו לזמין לשרשור הראשי במקרה הצורך (toxicityTypeList). יכול להיות שהוא יעזור לכם בתרחיש לדוגמה.

חוויית משתמש

בדמו שלנו, בחרנו באפשרויות הבאות:

  • לאפשר תמיד פרסום. ההודעה שלנו על תוכן רעיל מצד הלקוח לא מונעת מהמשתמש לפרסם תוכן. בדמו שלנו, המשתמש יכול לפרסם תגובה גם אם המודל לא נטען (ולכן לא מוצגת הערכה של רמת הרעילות) וגם אם התגובה מזוהה כרעילה. כמומלץ, כדאי שתהיה לכם מערכת שנייה לזיהוי תגובות רעילות. אם זה הגיוני לאפליקציה שלכם, כדאי להודיע למשתמש שהתגובה שלו אושרה בצד הלקוח, אבל לאחר מכן סומנה בשרת או במהלך בדיקה אנושית.
  • חשוב לשים לב לתוצאות שליליות שגויות. כשתגובה לא מסווגת כרעילה, הדמו שלנו לא מציג משוב (לדוגמה, 'תגובה יפה!'). בנוסף לרעשי רקע, שליחת משוב חיובי עלולה לשלוח אות שגוי, כי לפעמים, אבל באופן בלתי נמנע, הסיווג שלנו מחמיץ תגובות רעילות.
הדגמה של פרסום תגובה.
הלחצן פרסום תמיד מופעל: בדמו שלנו, המשתמש עדיין יכול להחליט לפרסם את התגובה שלו, גם אם היא מסווגת כרעילה. גם אם תגובה לא מסווגת כרעילה, אנחנו לא מציגים משוב חיובי.

שיפורים וחלופות

מגבלות ושיפורים עתידיים

  • שפות: המודל שבו אנחנו משתמשים תומך בעיקר באנגלית. כדי לקבל תמיכה בכמה שפות, צריך לבצע שינויים קלים. יש כמה מודלים של זיהוי תוכן רעיל שמפורטים ב-Hugging Face שתומכים בשפות שאינן אנגלית (רוסית, הולנדית), אבל הם לא תואמים ל-Transformers.js כרגע.
  • Nuance: ה-toxic-bert מזהה ביעילות תכנים רעילים באופן מובהק, אבל יכול להיתקל בקשיים במקרים עדינים יותר או במקרים שתלויים בהקשר (אירוניה, סרקזם). התנהגות רעילה יכולה להיות סובייקטיבית ועדינה מאוד. לדוגמה, יכול להיות שתרצו לסווג מונחים מסוימים או אפילו אמוג'י מסוימים כתוכן רעיל. שיפורים קלים יכולים לשפר את הדיוק באזורים האלה.

בקרוב יפורסם מאמר בנושא כוונון עדין של מודל לזיהוי תוכן רעיל.

אפשרויות אחרות

סיכום

זיהוי תוכן רעיל בצד הלקוח הוא כלי יעיל לשיפור הקהילות באינטרנט.

בעזרת מודלים של AI כמו toxic-bert שפועלים בדפדפן באמצעות Transformers.js, אפשר להטמיע מנגנוני משוב בזמן אמת שמעודדים התנהגות לא רעילה ומפחיתים את העומס של סיווג הרעילות על השרתים.

הגישה הזו בצד הלקוח כבר פועלת בדפדפנים שונים. עם זאת, חשוב לזכור את המגבלות, במיוחד בנוגע לעלויות של הצגת המודל ולגודל ההורדה. ליישם שיטות מומלצות לשיפור הביצועים של AI מצד הלקוח ולשמור את המודל במטמון.

כדי לזהות תוכן רעיל באופן מקיף, כדאי לשלב בין גישות בצד הלקוח לבין גישות בצד השרת.