نمایش طبقه‌بندی‌کننده‌های ایمنی چابک با Gemma

۱. مرور کلی

این آزمایشگاه کد، نحوه ایجاد یک طبقه‌بندی‌کننده متن سفارشی با استفاده از تنظیم پارامتر کارآمد (PET) را نشان می‌دهد. روش‌های PET به جای تنظیم دقیق کل مدل، تنها مقدار کمی از پارامترها را به‌روزرسانی می‌کنند که آموزش آن را نسبتاً آسان و سریع می‌کند. همچنین یادگیری رفتارهای جدید را با داده‌های آموزشی نسبتاً کم برای یک مدل آسان‌تر می‌کند. این روش به تفصیل در «به سوی طبقه‌بندی‌کننده‌های متن چابک برای همه» شرح داده شده است که نشان می‌دهد چگونه می‌توان این تکنیک‌ها را در انواع وظایف ایمنی به کار برد و با تنها چند صد مثال آموزشی به عملکرد پیشرفته دست یافت.

این آزمایشگاه کد از روش LoRA PET و مدل کوچک‌تر Gemma ( gemma_instruct_2b_en ) استفاده می‌کند، زیرا می‌تواند سریع‌تر و کارآمدتر اجرا شود. این آزمایشگاه کد مراحل دریافت داده‌ها، قالب‌بندی آن برای LLM، آموزش وزن‌های LoRA و سپس ارزیابی نتایج را پوشش می‌دهد. این آزمایشگاه کد بر روی مجموعه داده ETHOS ، یک مجموعه داده در دسترس عموم برای تشخیص سخنان نفرت‌انگیز، که از نظرات YouTube و Reddit ساخته شده است، آموزش می‌بیند. هنگامی که فقط روی 200 مثال (1/4 مجموعه داده) آموزش داده می‌شود، به F1: 0.80 و ROC-AUC: 0.78 دست می‌یابد، کمی بالاتر از SOTA که در حال حاضر در جدول امتیازات گزارش شده است (در زمان نگارش، 15 فوریه 2024). هنگامی که روی 800 مثال کامل آموزش داده می‌شود، به امتیاز F1 83.74 و امتیاز ROC-AUC 88.17 دست می‌یابد. مدل‌های بزرگ‌تر، مانند gemma_instruct_7b_en عموماً عملکرد بهتری خواهند داشت، اما هزینه‌های آموزش و اجرا نیز بیشتر است.

هشدار فعال‌سازی : از آنجا که این آزمایشگاه کد، یک طبقه‌بندی‌کننده‌ی ایمنی برای تشخیص گفتار نفرت‌انگیز توسعه می‌دهد، مثال‌ها و ارزیابی نتایج حاوی برخی زبان‌های وحشتناک است.

۲. نصب و راه‌اندازی

برای این آزمایشگاه کد، به یک نسخه جدید keras (3)، keras-nlp (0.8.0) و یک حساب کاگل برای دانلود مدل پایه نیاز دارید.

!pip install -q -U keras-nlp
!pip install -q -U keras

برای ورود به Kaggle، می‌توانید فایل اعتبارنامه‌های kaggle.json خود را در ~/.kaggle/kaggle.json ذخیره کنید یا دستور زیر را در محیط Colab اجرا کنید:

import kagglehub

kagglehub.login()

این آزمایشگاه کد با استفاده از Tensorflow به عنوان backend Keras آزمایش شده است، اما شما می‌توانید از Tensorflow، Pytorch یا JAX نیز استفاده کنید:

import os

os.environ["KERAS_BACKEND"] = "tensorflow"

۳. بارگذاری مجموعه داده ETHOS

در این بخش، مجموعه داده‌ای را که برای آموزش طبقه‌بندی‌کننده ما و پیش‌پردازش آن استفاده می‌شود، در یک مجموعه آموزش و آزمایش بارگذاری خواهید کرد. شما از مجموعه داده تحقیقاتی محبوب ETHOS که برای تشخیص سخنان نفرت‌پراکن در رسانه‌های اجتماعی جمع‌آوری شده است، استفاده خواهید کرد. می‌توانید اطلاعات بیشتر در مورد نحوه جمع‌آوری مجموعه داده را در مقاله ETHOS: یک مجموعه داده تشخیص سخنان نفرت‌پراکن آنلاین بیابید.

import pandas as pd

gh_root = 'https://raw.githubusercontent.com'
gh_repo = 'intelligence-csd-auth-gr/Ethos-Hate-Speech-Dataset'
gh_path = 'master/ethos/ethos_data/Ethos_Dataset_Binary.csv'
data_url = f'{gh_root}/{gh_repo}/{gh_path}'

df = pd.read_csv(data_url, delimiter=';')
df['hateful'] = (df['isHate'] >= df['isHate'].median()).astype(int)

# Shuffle the dataset.
df = df.sample(frac=1, random_state=32)

# Split into train and test.
df_train, df_test = df[:800],  df[800:]

# Display a sample of the data.
df.head(5)[['hateful', 'comment']]

چیزی شبیه به این خواهید دید:

برچسب

نظر

0

0

You said he but still not convinced this is a ...

۱

0

well, looks like its time to have another child.

۲

0

What if we send every men to mars to start a n...

۳

1

It doesn't matter if you're black or white, ...

۴

0

Who ever disliked this video should be ashamed...

۴. دانلود و نمونه‌سازی مدل

همانطور که در مستندات توضیح داده شده است، می‌توانید به راحتی از مدل Gemma به روش‌های مختلفی استفاده کنید. با Keras، این کاری است که باید انجام دهید:

import keras
import keras_nlp

# For reproducibility purposes.
keras.utils.set_random_seed(1234)

# Download the model from Kaggle using Keras.
model = keras_nlp.models.GemmaCausalLM.from_preset('gemma_instruct_2b_en')

# Set the sequence length to a small enough value to fit in memory in Colab.
model.preprocessor.sequence_length = 128

می‌توانید با تولید مقداری متن، عملکرد مدل را آزمایش کنید:

model.generate('Question: what is the capital of France? ', max_length=32)

۵. پیش‌پردازش متن و جداکننده‌های توکن

برای کمک به مدل در درک بهتر هدف ما، می‌توانید متن را پیش‌پردازش کرده و از نشانه‌های جداکننده استفاده کنید. این کار احتمال تولید متنی که با قالب مورد انتظار مطابقت ندارد را برای مدل کاهش می‌دهد. برای مثال، می‌توانید با نوشتن یک اعلان مانند این، از مدل درخواست طبقه‌بندی احساسات کنید:

Classify the following text into one of the following classes:[Positive,Negative]

Text: you look very nice today
Classification:

در این حالت، مدل ممکن است آنچه را که شما به دنبال آن هستید، خروجی دهد یا ندهد. برای مثال، اگر متن حاوی کاراکترهای خط جدید باشد، احتمالاً تأثیر منفی بر عملکرد مدل خواهد داشت. یک رویکرد قوی‌تر استفاده از توکن‌های جداکننده است. در این صورت، اعلان به صورت زیر خواهد بود:

Classify the following text into one of the following classes:[Positive,Negative]
<separator>
Text: you look very nice today
<separator>
Prediction:

این را می‌توان با استفاده از تابعی که متن را پیش‌پردازش می‌کند، خلاصه کرد:

def preprocess_text(
    text: str,
    labels: list[str],
    instructions: str,
    separator: str,
) -> str:
  prompt = f'{instructions}:[{",".join(labels)}]'
  return separator.join([prompt, f'Text:{text}', 'Prediction:'])

حال، اگر تابع را با استفاده از همان اعلان و متن قبلی اجرا کنید، باید همان خروجی را دریافت کنید:

text = 'you look very nice today'

prompt = preprocess_text(
    text=text,
    labels=['Positive', 'Negative'],
    instructions='Classify the following text into one of the following classes',
    separator='\n<separator>\n',
)

print(prompt)

که باید خروجی آن این باشد:

Classify the following text into one of the following classes:[Positive,Negative]
<separator>
Text:well, looks like its time to have another child
<separator>
Prediction:

۶. پس‌پردازش خروجی

خروجی‌های مدل، توکن‌هایی با احتمالات مختلف هستند. معمولاً برای تولید متن، شما از بین چند توکن محتمل‌تر، یکی را انتخاب می‌کنید و جملات، پاراگراف‌ها یا حتی اسناد کامل را می‌سازید. با این حال، برای هدف طبقه‌بندی، آنچه در واقع مهم است این است که آیا مدل معتقد است که Positive محتمل‌تر از Negative است یا برعکس.

با توجه به مدلی که قبلاً نمونه‌سازی کردید، می‌توانید خروجی آن را به صورت احتمالات مستقل Positive یا Negative بودن توکن بعدی پردازش کنید:

import numpy as np


def compute_output_probability(
    model: keras_nlp.models.GemmaCausalLM,
    prompt: str,
    target_classes: list[str],
) -> dict[str, float]:
  # Shorthands.
  preprocessor = model.preprocessor
  tokenizer = preprocessor.tokenizer

  # NOTE: If a token is not found, it will be considered same as "<unk>".
  token_unk = tokenizer.token_to_id('<unk>')

  # Identify the token indices, which is the same as the ID for this tokenizer.
  token_ids = [tokenizer.token_to_id(word) for word in target_classes]

  # Throw an error if one of the classes maps to a token outside the vocabulary.
  if any(token_id == token_unk for token_id in token_ids):
    raise ValueError('One of the target classes is not in the vocabulary.')

  # Preprocess the prompt in a single batch. This is done one sample at a time
  # for illustration purposes, but it would be more efficient to batch prompts.
  preprocessed = model.preprocessor.generate_preprocess([prompt])

  # Identify output token offset.
  padding_mask = preprocessed["padding_mask"]
  token_offset = keras.ops.sum(padding_mask) - 1

  # Score outputs, extract only the next token's logits.
  vocab_logits = model.score(
      token_ids=preprocessed["token_ids"],
      padding_mask=padding_mask,
  )[0][token_offset]

  # Compute the relative probability of each of the requested tokens.
  token_logits = [vocab_logits[ix] for ix in token_ids]
  logits_tensor = keras.ops.convert_to_tensor(token_logits)
  probabilities = keras.activations.softmax(logits_tensor)

  return dict(zip(target_classes, probabilities.numpy()))

می‌توانید آن تابع را با اجرای آن با اعلانی که قبلاً ایجاد کرده‌اید، آزمایش کنید:

compute_output_probability(
    model=model,
    prompt=prompt,
    target_classes=['Positive', 'Negative'],
)

که چیزی شبیه به موارد زیر را خروجی خواهد داد:

{'Positive': 0.99994016, 'Negative': 5.984089e-05}

۷. قرار دادن همه چیز به عنوان یک طبقه‌بندی‌کننده

برای سهولت استفاده، می‌توانید تمام توابعی که ایجاد کرده‌اید را در یک کلاسه‌بندی‌کننده واحد شبیه به sklearn با توابع آسان و آشنا مانند predict() و predict_score() قرار دهید.

import dataclasses


@dataclasses.dataclass(frozen=True)
class AgileClassifier:
  """Agile classifier to be wrapped around a LLM."""

  # The classes whose probability will be predicted.
  labels: tuple[str, ...]

  # Provide default instructions and control tokens, can be overridden by user.
  instructions: str = 'Classify the following text into one of the following classes'
  separator_token: str = '<separator>'
  end_of_text_token: str = '<eos>'

  def encode_for_prediction(self, x_text: str) -> str:
    return preprocess_text(
        text=x_text,
        labels=self.labels,
        instructions=self.instructions,
        separator=self.separator_token,
    )

  def encode_for_training(self, x_text: str, y: int) -> str:
    return ''.join([
        self.encode_for_prediction(x_text),
        self.labels[y],
        self.end_of_text_token,
    ])

  def predict_score(
      self,
      model: keras_nlp.models.GemmaCausalLM,
      x_text: str,
  ) -> list[float]:
    prompt = self.encode_for_prediction(x_text)
    token_probabilities = compute_output_probability(
        model=model,
        prompt=prompt,
        target_classes=self.labels,
    )
    return [token_probabilities[token] for token in self.labels]

  def predict(
      self,
      model: keras_nlp.models.GemmaCausalLM,
      x_eval: str,
  ) -> int:
    return np.argmax(self.predict_score(model, x_eval))


agile_classifier = AgileClassifier(labels=('Positive', 'Negative'))

۸. تنظیم دقیق مدل

LoRA مخفف Low-Rank Adaptation (انطباق رتبه پایین) است. این یک تکنیک تنظیم دقیق است که می‌تواند برای تنظیم دقیق مدل‌های زبانی بزرگ به طور موثر مورد استفاده قرار گیرد. می‌توانید اطلاعات بیشتر در مورد آن را در مقاله LoRA: Low-Rank Adaptation of Large Language Models مطالعه کنید.

پیاده‌سازی Keras از Gemma یک متد enable_lora() ارائه می‌دهد که می‌توانید برای تنظیم دقیق از آن استفاده کنید:

# Enable LoRA for the model and set the LoRA rank to 4.
model.backbone.enable_lora(rank=4)

پس از فعال کردن LoRA، می‌توانید فرآیند تنظیم دقیق را شروع کنید. این کار تقریباً 5 دقیقه در هر دوره در Colab طول می‌کشد:

import tensorflow as tf

# Create dataset with preprocessed text + labels.
map_fn = lambda xy: agile_classifier.encode_for_training(*xy)
x_train = list(map(map_fn, df_train[['comment', 'hateful']].values))
ds_train = tf.data.Dataset.from_tensor_slices(x_train).batch(2)

# Compile the model using the Adam optimizer and appropriate loss function.
model.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer=keras.optimizers.Adam(learning_rate=0.0005),
    weighted_metrics=[keras.metrics.SparseCategoricalAccuracy()],
)

# Begin training.
model.fit(ds_train, epochs=4)

آموزش برای دوره‌های بیشتر منجر به دقت بالاتر می‌شود، تا زمانی که بیش‌برازش رخ دهد.

۹. نتایج را بررسی کنید

اکنون می‌توانید خروجی طبقه‌بندی‌کننده چابکی که آموزش داده‌اید را بررسی کنید. این کد امتیاز پیش‌بینی‌شده کلاس را با توجه به یک متن ارائه می‌دهد:

text = 'you look really nice today'
scores = agile_classifier.predict_score(model, text)
dict(zip(agile_classifier.labels, scores))
{'Positive': 0.99899644, 'Negative': 0.0010035498}

۱۰. ارزیابی مدل

در نهایت، شما عملکرد مدل ما را با استفاده از دو معیار رایج، امتیاز F1 و AUC-ROC ، ارزیابی خواهید کرد. امتیاز F1 با ارزیابی میانگین هارمونیک دقت و فراخوانی در یک آستانه طبقه‌بندی خاص، خطاهای منفی کاذب و مثبت کاذب را ثبت می‌کند. از سوی دیگر، AUC-ROC، بده‌بستان بین نرخ مثبت واقعی و نرخ مثبت کاذب را در آستانه‌های مختلف ثبت کرده و مساحت زیر این منحنی را محاسبه می‌کند.

from sklearn.metrics import f1_score, roc_auc_score

y_true = df_test['hateful'].values
# Compute the scores (aka probabilities) for each of the labels.
y_score = [agile_classifier.predict_score(model, x) for x in df_test['comment']]
# The label with highest score is considered the predicted class.
y_pred = np.argmax(y_score, axis=1)
# Extract the probability of a comment being considered hateful.
y_prob = [x[agile_classifier.labels.index('Negative')] for x in y_score]

# Compute F1 and AUC-ROC scores.
print(f'F1: {f1_score(y_true, y_pred):.2f}')
print(f'AUC-ROC: {roc_auc_score(y_true, y_prob):.2f}')
F1: 0.84
AUC-ROC: = 0.88

یکی دیگر از روش‌های جالب برای ارزیابی پیش‌بینی‌های مدل، ماتریس‌های درهم‌ریختگی هستند. یک ماتریس درهم‌ریختگی، انواع مختلف خطاهای پیش‌بینی را به صورت بصری نشان می‌دهد.

from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay

cm = confusion_matrix(y_true, y_pred)
ConfusionMatrixDisplay(
    confusion_matrix=cm,
    display_labels=agile_classifier.labels,
).plot()

ماتریس سردرگمی

در نهایت، می‌توانید به منحنی ROC نیز نگاهی بیندازید تا از خطاهای پیش‌بینی بالقوه هنگام استفاده از آستانه‌های امتیازدهی مختلف مطلع شوید.

from sklearn.metrics import RocCurveDisplay, roc_curve

fpr, tpr, _ = roc_curve(y_true, y_prob, pos_label=1)
RocCurveDisplay(fpr=fpr, tpr=tpr).plot()

منحنی ROC