Giới thiệu các giải pháp phân loại an toàn linh hoạt với Gemma

1. Tổng quan

Lớp học lập trình này minh hoạ cách tạo một bộ phân loại văn bản tuỳ chỉnh bằng cách sử dụng tính năng điều chỉnh thông số hiệu quả (PET). Thay vì tinh chỉnh toàn bộ mô hình, các phương pháp PET chỉ cập nhật một lượng nhỏ tham số, giúp việc huấn luyện tương đối dễ dàng và nhanh chóng. Điều này cũng giúp mô hình dễ dàng học các hành vi mới với dữ liệu huấn luyện tương đối ít. Phương pháp này được mô tả chi tiết trong bài viết Hướng tới bộ phân loại văn bản linh hoạt cho mọi người, trong đó cho thấy cách áp dụng các kỹ thuật này cho nhiều nhiệm vụ an toàn và đạt được hiệu suất hiện đại chỉ với vài trăm ví dụ huấn luyện.

Lớp học lập trình này sử dụng phương thức PET LoRA và mô hình Gemma nhỏ hơn (gemma_instruct_2b_en) vì có thể chạy nhanh và hiệu quả hơn. Dự án cộng tác này trình bày các bước nhập dữ liệu, định dạng dữ liệu cho LLM, huấn luyện trọng số LoRA, sau đó đánh giá kết quả. Lớp học lập trình này huấn luyện trên dữ liệu ETHOS, một tập dữ liệu công khai để phát hiện lời nói hận thù, được tạo từ các bình luận trên YouTube và Reddit. Khi chỉ được huấn luyện trên 200 ví dụ (1/4 của tập dữ liệu), mô hình này đạt được F1: 0,80 và ROC-AUC: 0,78, cao hơn một chút so với SOTA hiện được báo cáo trên bảng xếp hạng (tại thời điểm viết bài, ngày 15 tháng 2 năm 2024). Khi được huấn luyện trên 800 ví dụ đầy đủ, mô hình này đạt điểm F1 là 83,74 và điểm ROC-AUC là 88,17. Các mô hình lớn hơn, chẳng hạn như gemma_instruct_7b_en thường sẽ hoạt động hiệu quả hơn, nhưng chi phí huấn luyện và thực thi cũng lớn hơn.

Cảnh báo kích hoạt: vì lớp học lập trình này phát triển một thuật toán phân loại an toàn để phát hiện lời nói hận thù, nên các ví dụ và nội dung đánh giá kết quả có chứa một số ngôn từ phản cảm.

2. Cài đặt và thiết lập

Đối với lớp học lập trình này, bạn sẽ cần phiên bản keras (3), keras-nlp (0.8.0) gần đây và một tài khoản Kaggle để tải mô hình cơ sở xuống.

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

Để đăng nhập vào Kaggle, bạn có thể lưu trữ tệp thông tin xác thực kaggle.json tại ~/.kaggle/kaggle.json hoặc chạy đoạn mã sau trong môi trường Colab:

import kagglehub

kagglehub.login()

Lớp học lập trình này được kiểm thử bằng cách sử dụng Tensorflow làm phần phụ trợ Keras, nhưng bạn có thể sử dụng Tensorflow, Pytorch hoặc JAX:

import os

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

3. Tải tập dữ liệu ETHOS

Trong phần này, bạn sẽ tải tập dữ liệu để huấn luyện bộ phân loại và xử lý trước tập dữ liệu đó thành tập huấn luyện và tập kiểm thử. Bạn sẽ sử dụng tập dữ liệu nghiên cứu phổ biến ETHOS được thu thập để phát hiện lời nói hận thù trên mạng xã hội. Bạn có thể tìm thêm thông tin về cách thu thập tập dữ liệu này trong bài viết ETHOS: một tập dữ liệu phát hiện lời nói hận thù trên mạng.

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

Bạn sẽ thấy nội dung tương tự như sau:

nhãn

bình luận

0

0

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

1

0

well, looks like its time to have another child.

2

0

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

3

1

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

4

0

Who ever disliked this video should be ashamed...

4. Tải xuống và tạo bản sao mô hình

Như mô tả trong tài liệu, bạn có thể dễ dàng sử dụng mô hình Gemma theo nhiều cách. Với Keras, bạn cần làm như sau:

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

Bạn có thể kiểm thử xem mô hình có hoạt động hay không bằng cách tạo một số văn bản:

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

5. Tiền xử lý văn bản và mã thông báo dòng phân cách

Để giúp mô hình hiểu rõ hơn về ý định của chúng ta, bạn có thể xử lý trước văn bản và sử dụng mã thông báo phân cách. Điều này giúp giảm khả năng mô hình tạo ra văn bản không phù hợp với định dạng dự kiến. Ví dụ: bạn có thể thử yêu cầu mô hình phân loại cảm xúc bằng cách viết một câu lệnh như sau:

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

Text: you look very nice today
Classification:

Trong trường hợp này, mô hình có thể hoặc không thể đưa ra kết quả mà bạn đang tìm kiếm. Ví dụ: nếu văn bản chứa ký tự dòng mới, thì điều này có thể ảnh hưởng tiêu cực đến hiệu suất của mô hình. Một phương pháp hiệu quả hơn là sử dụng mã thông báo dòng phân cách. Sau đó, lời nhắc sẽ trở thành:

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

Bạn có thể tóm tắt điều này bằng cách sử dụng một hàm xử lý trước văn bản:

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:'])

Bây giờ, nếu chạy hàm bằng cùng một lời nhắc và văn bản như trước, bạn sẽ nhận được kết quả tương tự:

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)

Kết quả sẽ là:

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:

6. Xử lý hậu kỳ đầu ra

Kết quả của mô hình là các mã thông báo có nhiều xác suất. Thông thường, để tạo văn bản, bạn sẽ chọn trong số một vài mã thông báo có nhiều khả năng nhất và tạo các câu, đoạn văn hoặc thậm chí là toàn bộ tài liệu. Tuy nhiên, đối với mục đích phân loại, điều thực sự quan trọng là liệu mô hình có cho rằng Positive có nhiều khả năng xảy ra hơn Negative hay ngược lại.

Với mô hình mà bạn đã tạo bản sao trước đó, sau đây là cách bạn có thể xử lý đầu ra của mô hình đó thành các xác suất độc lập cho biết mã thông báo tiếp theo là Positive hay 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()))

Bạn có thể kiểm thử hàm đó bằng cách chạy hàm đó với lời nhắc mà bạn đã tạo trước đó:

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

Kết quả sẽ tương tự như sau:

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

7. Gói tất cả thành một Trình phân loại

Để dễ sử dụng, bạn có thể gói tất cả các hàm bạn vừa tạo vào một bộ phân loại giống như sklearn bằng các hàm dễ sử dụng và quen thuộc như 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'))

8. Tinh chỉnh mô hình

LoRA là viết tắt của Low-Rank Adaptation (Thích ứng theo thứ hạng thấp). Đây là một kỹ thuật tinh chỉnh có thể dùng để tinh chỉnh hiệu quả các mô hình ngôn ngữ lớn. Bạn có thể đọc thêm về vấn đề này trong bài viết LoRA: Điều chỉnh mô hình ngôn ngữ lớn theo thứ hạng thấp.

Phương thức triển khai Keras của Gemma cung cấp một phương thức enable_lora() mà bạn có thể sử dụng để tinh chỉnh:

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

Sau khi bật LoRA, bạn có thể bắt đầu quá trình tinh chỉnh. Quá trình này mất khoảng 5 phút cho mỗi epoch trên 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)

Việc huấn luyện cho nhiều epoch hơn sẽ giúp độ chính xác cao hơn, cho đến khi xảy ra hiện tượng nạp dữ liệu quá mức.

9. Kiểm tra kết quả

Giờ đây, bạn có thể kiểm tra kết quả của bộ phân loại nhanh nhẹn mà bạn vừa huấn luyện. Mã này sẽ xuất ra điểm số lớp được dự đoán cho một đoạn văn bản:

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}

10. Đánh giá mô hình

Cuối cùng, bạn sẽ đánh giá hiệu suất của mô hình bằng hai chỉ số phổ biến là điểm số F1AUC-ROC. Điểm F1 ghi nhận các lỗi âm tính giả và dương tính giả bằng cách đánh giá trung bình điều hoà của độ chính xác và tỷ lệ thu hồi ở một ngưỡng phân loại nhất định. Mặt khác, AUC-ROC cho biết sự đánh đổi giữa tỷ lệ dương tính thực và tỷ lệ dương tính giả trên nhiều ngưỡng và tính toán diện tích dưới đường cong này.

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

Một cách thú vị khác để đánh giá kết quả dự đoán của mô hình là ma trận nhầm lẫn. Ma trận nhầm lẫn sẽ mô tả trực quan các loại lỗi dự đoán khác nhau.

from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay

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

ma trận nhầm lẫn

Cuối cùng, bạn cũng có thể xem đường cong ROC để biết các lỗi dự đoán có thể xảy ra khi sử dụng các ngưỡng tính điểm khác nhau.

from sklearn.metrics import RocCurveDisplay, roc_curve

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

Đường cong ROC