Фото П’єра Гуї на Unsplash
Мітки для текстових даних за допомогою ChatGPT стали невід’ємною частиною сучасних робочих процесів, але залежність від чорних ящиків (black-box) API часто обмежує прозорість і контроль. З'являється promptzl, інтегрований у екосистему 🤗, який перетворює великі мовні моделі (LLM) на класифікатори PyTorch — з усіма бажаними властивостями, яких очікують практики, всього за кілька рядків коду.
Все, що потрібно — це добре сформульований запит, щоб направити модель до отримання бажаних міток, і будь-яка модель з хабу Hugging Face. Promptzl перетворює потребу в супервізованому навчанні на просте проектування запитів. Отже, promptzl робить побудову класифікаторів швидшою та доступнішою, дозволяючи швидко та гнучко виконувати текстову класифікацію.
Коротка версія
Весь блокнот для цього підручника також доступний на Google Colab.
from promptzl import FnVbzPair, Vbz, CausalLM4Classification
from datasets import Dataset
dataset = Dataset.from_dict(
{'text': ['I feel horrible.', 'Best day ever!'], 'label': [0, 1]}
)
prompt = FnVbzPair(lambda e:\
f"""Sentiment Classification.
Classify the following text into 'good' or 'bad':
'{e['text']}'
Answer first with 'good' or bad and then elaborate!
Your answer:""",
Vbz({0: ["bad"], 1: ["good"]}))
model = CausalLM4Classification(
"HuggingFaceTB/SmolLM2-1.7B",
prompt)
output = model.classify(dataset)
output.predictions, output.distribution
# (tensor([0, 1]),
# tensor([[0.6391, 0.3609],
# [0.4029, 0.5971]]))
Фон
Використання мовних моделей як класифікаторів через керовані запити — не нова концепція. Вона бере початок з роботи Шика і Шютце у 2021 році в еру BERT. Цей метод перетворює моделювання мови на задачу класифікації шляхом визначення запиту, де певні токени представляють цільові мітки, що нас цікавлять. Залежно від типу моделі (масковані мовні моделі або авторегресивні мовні моделі), акцент при проектуванні запиту може бути зовсім різним. У цьому підручнику ми зосередимося на авторегресивних моделях, таких як GPT.
Вхідні дані покращуються шаблоном, що утворює запит, який направляє модель до прогнозування токена, який можна використати як проксі-мітку. Ці проксі-мітки визначаються заздалегідь і витягуються за допомогою так званого вербалізатора. Після отримання логітів для міток слів можна обчислити розподіл softmax, що дає фінальні прогнози.
Ось ідея: мовні моделі прогнозують токени, і ми можемо використовувати підмножину цих прогнозованих токенів як проксі-мітки для нашої задачі класифікації.
Для досягнення цього ми повинні направити модель на створення одного токена як проксі-мітки для наступного токена. Цей процес дуже схожий на проектування запитів у ChatGPT, але з важливою різницею: генерується не ціла послідовність, а лише наш проксі-токен мітки.
Прогнозування одного токена дає вектор з логітами для кожного токена у словнику. Оскільки наші проксі-мітки є словами природної мови, ми можемо витягти відповідні логіти та обчислити розподіл softmax.
While prompt engineering here requires more focus on condensing the essential information on predicting one token, it also brings the upside of much quicker inference in batches and an accessible distribution that can be further used for analysis or distillation.
To additionally exemplify how this concept works, we can go back to the sentiment classification task and enhance the 𝗱𝗮𝘁𝗮 with a 𝗉𝗋𝗈𝗆𝗉𝗍 so that the 𝚗𝚎𝚇𝚝 𝚝𝚘𝚔𝚎𝚗 the model predicts can be our class:
𝗧𝗵𝗲 𝘄𝗲𝗮𝘁𝗵𝗲𝗿 𝗶𝘀 𝗴𝗼𝗼𝗱, 𝗲𝘃𝗲𝗿𝘆𝘁𝗵𝗶𝗻𝗴 𝗶𝘀 𝗴𝗿𝗲𝗮𝘁. 𝖨𝗍 𝗐𝖺𝗌 [𝚙𝚘𝚜𝚒𝚝𝚒𝚟𝚎|𝚗𝚎𝚐𝚊𝚝𝚒𝚟𝚎]
As many models are further fine-tuned to act like assistants, it is also possible to instruct the model on the task:
𝖲𝖾𝗇𝗍𝗂𝗆𝖾𝗇𝗍 𝖢𝗅𝖺𝗌𝗌𝗂𝖿𝗂𝖼𝖺𝗍𝗂𝗈𝗇. 𝖢𝗅𝖺𝗌𝗌𝗂𝖿𝗒 𝗍𝗁𝖾 𝖿𝗈𝗅𝗅𝗈𝗐𝗂𝗇𝗀 𝗍𝖾𝗑𝗍, 𝗉𝗈𝗌𝗂𝗍𝗂𝗏𝖾 𝗈𝗋 𝗇𝖾𝗀𝖺𝗍𝗂𝗏𝖾:‘𝗧𝗵𝗲 𝘄𝗲𝗮𝘁𝗵𝗲𝗿 𝗶𝘀 𝗴𝗼𝗼𝗱, 𝗲𝘃𝗲𝗿𝘆𝘁𝗵𝗶𝗻𝗴 𝗶𝘀 𝗴𝗿𝗲𝗮𝘁’. 𝖱𝖾𝗍𝗎𝗋𝗇 𝗍𝗁𝖾 𝖼𝗅𝖺𝗌𝗌 ‘𝗉𝗈𝗌𝗂𝗍𝗂𝗏𝖾’ 𝗈𝗋 ‘𝗇𝖾𝗀𝖺𝗍𝗂𝗏𝖾’ 𝖿𝗂𝗋𝗌𝗍, 𝗍𝗁𝖾𝗇 𝖾𝗅𝖺𝖻𝗈𝗋𝖺𝗧𝗂𝗇𝗀. 𝖸𝗈𝗎𝗋 𝖺𝗇𝗌𝗐𝖾𝗋:[𝚙𝚘𝚜𝚒𝚝𝚒𝚟𝚎|𝚗𝚎𝚐𝚊𝚝𝚒𝚟𝚎]
In both prompts, we guided the model in predicting the next token with the labels we wanted to use for our prediction.
Hands-on-Tutorial
In this tutorial, we will use the DBpedia14 dataset, which is about ontology classification for Wikipedia articles, and we will use 𝚀𝚠𝚎𝚗/𝚀𝚠𝚎𝚗𝟸.𝟻–𝟽𝙱-𝙸𝚗𝚜𝚝𝚛𝚞𝚌𝚝 as our LLM.
Setup
First, we prepare the dataset and truncate it for brevity:
from datasets import load_dataset
import numpy as np
np.random.seed(42)
dataset = load_dataset("fancyzhx/dbpedia_14")['test']
random_indices = np.random.permutation(len(dataset))
ds = dataset.select(random_indices[:100])
Defining Function-Verbalizer-Pair
Now we need to import all necessary objects from the promptzl library. 𝙵𝚗𝚅𝚋𝚣𝙿𝚊𝚒𝚛 and 𝚅𝚋𝚣 are required to define the prompt object, consisting of a template function and a verbalizer (𝚅𝚋𝚣) that maps the label words to the vocabulary indices to extract the desired logits from the predictions. 𝙲𝚊𝚞𝚜𝚊𝚕𝙻𝙼𝟺𝙲𝚕𝚊𝚜𝚜𝚒𝚏𝚒𝚌𝚊𝚝𝚒𝚘𝚗 is then used to set up the model with the a priori set up prompt, the model identifier, and further arguments for initialization.
from promptzl import FnVbzPair, Vbz, CausalLM4Classification
In the following, we will define a prompt explaining to the model what to do, into which classes the data is to be categorized, and the format of the answer for correct extraction. Providing clear guidelines to achieve the desired format is crucial, as only the logits of the first token after the input sequence are used for classification. As fine-tuned assistant LLMs can receive instructions on the task, we will describe exactly how the model should proceed and in which format the result must be returned.
vbz = {
0: ["Company"],
1: ["School"],
2: ["Artist"],
3: ["Athlete"],
4: ["Politics"],
5: ["Transportation"],
6: ["Building"],
7: ["Mountain"],
8: ["Village"],
9: ["Animal"],
10: ["Plant"],
11: ["Album"],
12: ["Film"],
13: ["Book"],
}
prompt = FnVbzPair(lambda e:\
f"""Ontology Classification.
The data to be classified:
'{e['title']}', '{e['content']}'
Classify into the categories: 'Company', 'School', 'Artist', 'Athlete', 'Politics', 'Transportation', 'Building', 'Mountain', 'Village', 'Animal', 'Plant', 'Album', 'Film' or 'Book'. Only mention the class after 'Your answer' and then elaborate. Your answer: """,
Vbz(vbz))
The template function accepts a dictionary with the corresponding strings used in the final prompt. Thus, the keys (e.g., 𝚎[‘𝚝𝚒𝚝𝚕𝚎’]) must refer to column names in the 🤗-dataset that we use.
As we want to keep the hardware requirements down, we use quantization.
So before setting up the model, we need to set up our quantization configuration:
import torch
from transformers import BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
Now, we can set up the model object. We only need to pass the model identifier from the Hugging Face hub and the prompt. Further arguments for model initialization are passed as a dictionary using the 𝚖𝚘𝚍𝚎𝚕_𝚊𝚛𝚐𝚜 argument, like the quantization configuration for lower resource consumption.
model = CausalLM4Classification(
"Qwen/Qwen2.5-7B-Instruct",
prompt,
model_args={
"quantization_config": bnb_config})
model.to("cuda")
Now, we can classify the dataset. The 𝙲𝚊𝚞𝚜𝚊𝚕𝙻𝙼𝟺𝙲𝚕𝚊𝚜𝚜𝚒𝚏𝚒𝚌𝚊𝚝𝚒𝚘𝚗 offers the 𝚌𝚕𝚊𝚜𝚜𝚒𝚏𝚢 method that accepts a Hugging Face dataset object, where the text column must correspond to the keys we used in the template function in the 𝙵𝚗𝚅𝚋𝚣𝙿𝚊𝚒𝚛 class.
output = model.classify(ds, batch_size=2, show_progress_bar=True)
Evaluating the output using accuracy shows that the model already impresses with a strong accuracy of 94%, achieved without any supervised training!
from sklearn.metrics import accuracy_score
accuracy_score(ds['label'], output.predictions)
0.94
Additionally, we can also apply calibration, which sometimes improves performance (although not here):
accuracy_score(ds['label'], model.calibrate_output(output).predictions)
0.95
And furthermore, we can also access the distribution over the labels:
output.distribution
tensor([[4.7088e-06, 2.3842e-07, 3.3975e-06, ..., 3.5346e-05, 4.1723e-07,
1.4031e-04],
[3.7551e-04, 6.3181e-06, 4.6074e-05, ..., 1.9360e-04, 4.9472e-06,
5.1594e-04],
[1.5640e-02, 1.5808e-02, 1.0433e-03, ..., 3.9940e-03, 1.1873e-03,
1.9180e-02],
...,
[3.8147e-06, 3.5763e-07, 2.0862e-06, ..., 2.0802e-05, 4.1723e-07,
7.2122e-05],
[1.7285e-06, 4.1723e-07, 4.3058e-04, ..., 9.9951e-01, 4.6492e-06,
4.9651e-05],
[2.0157e-02, 1.8609e-04, 1.4937e-04, ..., 8.5735e-04, 6.1810e-05,
1.9150e-02]], dtype=torch.float16)
With just a few lines of code, we built a classifier with a very high accuracy that can efficiently be run on a Laptop GPU with complete transparency.
More information can be found in the official documentation. Feel free to submit an issue to the promptzl GitHub repository if you encounter any problems.
Отже, перед налаштуванням моделі потрібно налаштувати конфігурацію квантизації:
import torch
from transformers import BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
Тепер можемо налаштувати об'єкт моделі. Нам потрібно лише передати ідентифікатор моделі з хабу Hugging Face та запит. Додаткові параметри для ініціалізації моделі передаються у вигляді словника через аргумент 𝚖𝚘𝚍𝚎𝚕_𝚊𝚛𝚐𝚜, наприклад, конфігурація квантизації для зменшення споживання ресурсів.
model = CausalLM4Classification(
"Qwen/Qwen2.5-7B-Instruct",
prompt,
model_args={
"quantization_config": bnb_config})
model.to("cuda")
Тепер можемо класифікувати набір даних. 𝙲𝚊𝚞𝚜𝚊𝚕𝙻𝙼𝟺𝙲𝚕𝚊𝚜𝚜𝚒𝚏𝚒𝚌𝚊𝚝𝚒𝚘𝚗 надає метод 𝚌𝚕𝚊𝚜𝚜𝚒𝚏𝚢, який приймає об'єкт набору даних Hugging Face, де стовпець тексту повинен відповідати ключам, які ми використовували в шаблонній функції класу 𝙵𝚗𝚅𝚋𝚣𝙿𝚊𝚒𝚛.
output = model.classify(ds, batch_size=2, show_progress_bar=True)
Оцінка результатів за допомогою точності показує, що модель вражає своєю високою точністю 94%, досягнутою без будь-якого навчання з учителем!
from sklearn.metrics import accuracy_score
accuracy_score(ds['label'], output.predictions)
0.94
Крім того, ми можемо застосувати калібрування, яке іноді покращує продуктивність (хоча не в нашому випадку):
accuracy_score(ds['label'], model.calibrate_output(output).predictions)
0.95
А також ми можемо отримати розподіл по мітках:
output.distribution
tensor([[4.7088e-06, 2.3842e-07, 3.3975e-06, ..., 3.5346e-05, 4.1723e-07,
1.4031e-04],
[3.7551e-04, 6.3181e-06, 4.6074e-05, ..., 1.9360e-04, 4.9472e-06,
5.1594e-04],
[1.5640e-02, 1.5808e-02, 1.0433e-03, ..., 3.9940e-03, 1.1873e-03,
1.9180e-02],
...,
[3.8147e-06, 3.5763e-07, 2.0862e-06, ..., 2.0802e-05, 4.1723e-07,
7.2122e-05],
[1.7285e-06, 4.1723e-07, 4.3058e-04, ..., 9.9951e-01, 4.6492e-06,
4.9651e-05],
[2.0157e-02, 1.8609e-04, 1.4937e-04, ..., 8.5735e-04, 6.1810e-05,
1.9150e-02]], dtype=torch.float16)
За допомогою кількох рядків коду ми створили класифікатор з дуже високою точністю, який можна ефективно запускати на GPU ноутбука з повною прозорістю.
Більше інформації можна знайти в офіційній документації. Якщо
Перекладено з: Transforming LLMs into Zero⁺-Shot Classifiers with Promptzl