Пошаговый пример тонкой настройки модели классификации с помощью интерфейса командной строки OpenAI.

OpenAI стал предметом разговоров в городе из-за его впечатляющей производительности во многих задачах ИИ. Хотя существующие модели действительно сильны, всегда есть возможности для улучшения, и во многих случаях необходимо доработать модели, ориентируясь на конкретные задачи. В этом руководстве мы покажем вам, как настроить пользовательскую модель классификации NLP с помощью OpenAI.

Создайте среду Conda

Мы рекомендуем вам создать новую среду conda. В этом уроке мы создали новую среду conda под названием OpenAI. Кроме того, мы добавили ключ OpenAI в качестве переменной среды следующим образом:

Во-первых, мы активируем среду:

conda activate OpenAI

Затем мы устанавливаем библиотеку OpenAI:

pip install --upgrade openai

Затем мы передаем переменную:

conda env config vars set OPENAI_API_KEY=<OPENAI_API_KEY>

После того, как вы установили переменную среды, вам нужно будет повторно активировать среду, выполнив:

conda activate OpenAI

Чтобы убедиться, что переменная существует, вы можете запустить:

conda env config vars list

и вы увидите переменную окружения OPENAI_API_KEY с соответствующим значением.

Набор данных

В демонстрационных целях мы рассмотрим обычный случай, в котором мы построим классификационную модель, пытаясь предсказать, является ли электронное письмо ветчиной или спамом. В других руководствах мы создали Детектор почтового спама с использованием Scikit-Learn и TF-IDF, а также настроили модель классификации НЛП с преобразователями и HuggingFace. Не стесняйтесь взглянуть на учебные пособия, чтобы получить данные и сравнить различные подходы.

Давайте загрузим набор данных и получим первые строки:

import pandas as pd
 
df = pd.read_csv('spam.csv')
df

Согласно документации, обучающие данные должны быть документом JSONL, где каждая строка представляет собой пару подсказка-завершение, соответствующую обучающему примеру следующим образом:

{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
...

Давайте посмотрим, как мы можем создать документ JSONL различными способами.

Использование панд

Pandas позволяет нам легко создать файл этого формата. Например:

# first rename the columns using the column names prompt and completion
df.rename(columns={'text':'prompt', 'target':'completion'}, inplace=True)
df.to_json("spam_pandas.jsonl", orient='records', lines=True)

Первые строки «spam_pandas.jsonl»:

{"prompt":"Go until jurong point, crazy.. Available only in bugis n great world la e buffet... Cine there got amore wat...","completion":"ham"}
{"prompt":"Ok lar... Joking wif u oni...","completion":"ham"}
{"prompt":"Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question(std txt rate)T&amp;C's apply 08452810075over18's","completion":"spam"}

Использование списков

Если мы не хотим использовать pandas, мы можем работать со списками. Предполагая, что у нас есть два списка, один для подсказки и один для завершения, мы можем применить понимание списка для создания требуемого словаря. Затем мы можем сохранить файл JSONL, записывая по одной строке за раз. Например:

# create separate lists for prompt and completion
prompt = df.prompt.tolist()
completion = df.completion.tolist()
 
# create a dictionary using 
 
input_dict = [{"prompt":p, "completion":c} for p,c in zip(prompt,completion)]
 
import json
 
# https://stackoverflow.com/questions/38915183/python-conversion-from-json-to-jsonl
 
with open("spam_list_comprehension.jsonl", "w") as f:
    for entry in input_dict:
        f.write(json.dumps(entry))
        f.write('\n')

Файл «spam_list_comprehension.jsonl»:

{"prompt": "Go until jurong point, crazy.. Available only in bugis n great world la e buffet... Cine there got amore wat...", "completion": "ham"}
{"prompt": "Ok lar... Joking wif u oni...", "completion": "ham"}
{"prompt": "Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question(std txt rate)T&amp;C's apply 08452810075over18's", "completion": "spam"}

Использование инструмента подготовки данных CLI

OpenAI разработал инструмент, который проверяет, дает предложения и переформатирует данные:

 
openai tools fine_tunes.prepare_data -f <LOCAL_FILE>

Инструмент ожидает «приглашение» и «завершение» имен столбцов или ключей и поддерживает форматы файлов CSV, TSV, XLSX, JSON или JSONL. На выходе будет файл JSONL, готовый к тонкой настройке, после того как вы проведете вас через процесс предлагаемых изменений. Посмотрим на практике. Мы открываем conda CLI и запускаем:

openai tools fine_tunes.prepare_data -f spam_with_right_column_names.csv

Обратите внимание, что имена столбцов файла «spam_with_right_column_names.csv» — «приглашение» и «завершение». Инструмент настолько умен, что возвращает следующий текст:

- Based on your file extension, your file is formatted as a CSV file
- Your file contains 5572 prompt-completion pairs
- Based on your data it seems like you're trying to fine-tune a model for classification
- For classification, we recommend you try one of the faster and cheaper models, such as `ada`
- For classification, you can estimate the expected model performance by keeping a held out dataset, which is not used for training
- There are 403 duplicated prompt-completion sets. These are rows: [102, 153, 206, 222, 325, 338, 356, 443, 532, 654, 657, 701, 767, 768, 774, 780, 789, 824, 849, 899, 962, 964, 1001, 1002, 1042, 1131, 1132, 1133, 1139, 1151, 1162, 1163, 1197, 1224, 1235, 1249, 1250, 1303, 1318, 1355, 1379, 1402, 1412, 1426, 1458, 1466, 1482, 1484, 1507, 1568, 1584, 1654, 1679, 1690, 1699, 1720, 1737, 1778, 1779, 1784, 1825, 1828, 1875, 1876, 1893, 1901, 1948, 1956, 1963, 1973, 1980, 1983, 1987, 1988, 1995, 2043, 2094, 2108, 2123, 2124, 2134, 2145, 2162, 2169, 2175, 2215, 2233, 2264, 2265, 2276, 2299, 2307, 2321, 2326, 2343, 2344, 2350, 2362, 2384, 2412, 2446, 2472, 2476, 2508, 2517, 2518, 2521, 2523, 2525, 2563, 2564, 2595, 2610, 2617, 2643, 2645, 2659, 2680, 2687, 2711, 2718, 2720, 2727, 2739, 2741, 2760, 2761, 2763, 2795, 2797, 2811, 2825, 2827, 2829, 2841, 2847, 2858, 2864, 2868, 2879, 2897, 2910, 2942, 2957, 2966, 2970, 2980, 2989, 2990, 3002, 3003, 3007, 3034, 3038, 3049, 3054, 3088, 3099, 3121, 3123, 3134, 3151, 3153, 3154, 3163, 3165, 3166, 3174, 3186, 3200, 3226, 3227, 3241, 3248, 3270, 3278, 3298, 3309, 3314, 3322, 3347, 3357, 3364, 3390, 3392, 3399, 3402, 3406, 3414, 3444, 3453, 3456, 3466, 3469, 3474, 3484, 3487, 3490, 3532, 3547, 3583, 3584, 3592, 3608, 3623, 3626, 3627, 3647, 3673, 3678, 3679, 3691, 3707, 3708, 3728, 3731, 3739, 3753, 3755, 3756, 3761, 3768, 3774, 3785, 3797, 3831, 3832, 3846, 3880, 3881, 3897, 3899, 3911, 3913, 3920, 3942, 3964, 3976, 3985, 3990, 4002, 4009, 4010, 4012, 4038, 4040, 4074, 4081, 4101, 4102, 4115, 4126, 4127, 4138, 4152, 4160, 4167, 4171, 4182, 4189, 4194, 4196, 4197, 4199, 4220, 4231, 4233, 4235, 4242, 4257, 4258, 4279, 4294, 4297, 4298, 4309, 4323, 4346, 4350, 4354, 4370, 4389, 4390, 4412, 4419, 4435, 4448, 4454, 4463, 4466, 4496, 4502, 4515, 4535, 4547, 4554, 4564, 4582, 4585, 4589, 4590, 4626, 4631, 4636, 4643, 4648, 4653, 4658, 4683, 4692, 4697, 4699, 4717, 4733, 4741, 4742, 4744, 4757, 4771, 4774, 4801, 4813, 4846, 4857, 4862, 4867, 4877, 4882, 4886, 4888, 4893, 4896, 4916, 4921, 4928, 4946, 4960, 4961, 4976, 5029, 5035, 5044, 5048, 5053, 5073, 5091, 5104, 5105, 5108, 5129, 5141, 5164, 5171, 5175, 5176, 5182, 5188, 5191, 5201, 5203, 5206, 5214, 5215, 5216, 5225, 5226, 5232, 5236, 5241, 5247, 5257, 5264, 5279, 5284, 5285, 5301, 5314, 5315, 5346, 5357, 5365, 5374, 5375, 5386, 5389, 5423, 5425, 5457, 5458, 5460, 5467, 5469, 5471, 5477, 5488, 5490, 5497, 5510, 5524, 5535, 5539, 5553, 5558]
- Your data does not contain a common separator at the end of your prompts. Having a separator string appended to the end of the prompt makes it clearer to the fine-tuned model where the completion should begin. See https://platform.openai.com/docs/guides/fine-tuning/preparing-your-dataset for more detail and examples. If you intend to do open-ended generation, then you should leave the prompts empty
- The completion should start with a whitespace character (` `). This tends to produce better results due to the tokenization we use. See https://platform.openai.com/docs/guides/fine-tuning/preparing-your-dataset for more details
 
Based on the analysis we will perform the following actions:
- [Necessary] Your format `CSV` will be converted to `JSONL`
- [Recommended] Remove 403 duplicate rows [Y/n]: Y
- [Recommended] Add a suffix separator ` ->` to all prompts [Y/n]: Y
c:\users\gpipis\anaconda3\envs\openai\lib\site-packages\openai\validators.py:222: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
 
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  x["prompt"] += suffix
- [Recommended] Add a whitespace character to the beginning of the completion [Y/n]: Y
c:\users\gpipis\anaconda3\envs\openai\lib\site-packages\openai\validators.py:421: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
 
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  x["completion"] = x["completion"].apply(
- [Recommended] Would you like to split into training and validation set? [Y/n]: Y
 
 
Your data will be written to a new JSONL file. Proceed [Y/n]: Y
 
Wrote modified files to `spam_with_right_column_names_prepared_train.jsonl` and `spam_with_right_column_names_prepared_valid.jsonl`
Feel free to take a look!

Во всех вопросах мы вводили «Y», чтобы продолжить, и, наконец, он сгенерировал поезд и набор тестовых данных, а также необходимую команду для точной настройки модели.

Now use that file when fine-tuning:
> openai api fine_tunes.create -t "spam_with_right_column_names_prepared_train.jsonl" -v "spam_with_right_column_names_prepared_valid.jsonl" --compute_classification_metrics --classification_positive_class " ham"
 
After you’ve fine-tuned a model, remember that your prompt has to end with the indicator string ` ->` for the model to start generating completions, rather than continuing with the prompt. Make sure to include `stop=["am"]` so that the generated texts ends at the expected place.
Once your model starts training, it'll approximately take 2.11 hours to train a `curie` model, and less for `ada` and `babbage`. Queue will approximately take half an hour per job ahead of you.

Важная заметка

Как вы могли заметить, подготовка данных CLI изменила наши данные, добавив суффикс к подсказке (->) и префикс к завершению (пробел). Как правило, лучшие практики для набора входных данных:

Для точной настройки модели вам понадобится набор обучающих примеров, каждый из которых состоит из одного ввода («подсказка») и связанного с ним вывода («завершение»). Это заметно отличается от использования наших базовых моделей, где вы можете ввести подробные инструкции или несколько примеров в одном запросе.

  • Каждое приглашение должно заканчиваться фиксированным разделителем, чтобы сообщить модели, когда заканчивается приглашение и начинается завершение. Простой разделитель, который обычно хорошо работает, — это \n\n###\n\n. Разделитель не должен появляться где-либо еще в любом приглашении.
  • Каждое завершение должно начинаться с пробела из-за нашей разметки, которая размечает большинство слов предшествующим пробелом.
  • Каждое завершение должно заканчиваться фиксированной последовательностью остановок, чтобы информировать модель об окончании завершения. Последовательность остановки может быть \n, ### или любым другим токеном, который не появляется ни в одном завершении.
  • Для вывода вы должны отформатировать свои подсказки так же, как при создании набора обучающих данных, включая тот же разделитель. Также укажите ту же самую последовательность остановки, чтобы правильно обрезать завершение.

Сказав это, если мы не хотели работать с инструментом подготовки CLI, то мы должны были изменить наши данные, добавив суффикс к подсказке и префикс к завершению.

Тонкая настройка модели

Мы создали поезд и набор данных для проверки, которые можно использовать для обучения и оценки нашей модели. Поскольку мы имеем дело с задачей классификации, имеет смысл работать с моделью ada. Обратите внимание, что другими доступными моделями являются davinci, curie и babbage.

Используя openai CLI, для точной настройки модели «ada» мы можем запустить следующую команду:

openai api fine_tunes.create -t "spam_with_right_column_names_prepared_train.jsonl" -v "spam_with_right_column_names_prepared_valid.jsonl" --compute_classification_metrics --classification_positive_class " spam" -m ada

Где:

  • t — это путь к набору данных поезда.
  • v — это путь к набору данных проверки.
  • Мы устанавливаем compute_classification_metrics в true, чтобы получить отчет о классификации
  • classification_positive_class устанавливает «положительный» класс, где в нашем случае это «спам», так как мы создаем детектор спама.
  • m для модели, где в нашем случае ada

Существуют и другие доступные параметры, такие как n_epochs, для которого по умолчанию установлено значение 4, batch_size, learning_rate_multipler и так далее.

Когда мы запускаем эту команду, мы получаем:

Upload progress: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 485k/485k [00:00<00:00, 498Mit/s]
Uploaded file from spam_with_right_column_names_prepared_train.jsonl: file-1T5R0Rr4T562mhwXIU7prTvf
Upload progress: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 117k/117k [00:00<00:00, 58.9Mit/s]
Uploaded file from spam_with_right_column_names_prepared_valid.jsonl: file-ab2LnsDV6W4tTBUtq5EXEY41
Created fine-tune: ft-sEQZBANQSg4NTNEodwHH2hNz
Streaming events until fine-tuning is complete...
 
(Ctrl-C will interrupt the stream, but not cancel the fine-tune)
[2023-04-12 16:00:51] Created fine-tune: ft-sEQZBANQSg4NTNEodwHH2hNz
 
Stream interrupted (client disconnected).
To resume the stream, run:
 
  openai api fine_tunes.follow -i ft-sEQZBANQSg4NTNEodwHH2hNz

После того, как мы запустим приведенную выше команду, нам придется подождать около 1 часа, пока модель будет обучена. Чтобы проверить статус, мы можем запустить:

openai api fine_tunes.follow -i ft-sEQZBANQSg4NTNEodwHH2hNz

И мы получаем:

[2023-04-12 16:00:51] Created fine-tune: ft-sEQZBANQSg4NTNEodwHH2hNz
[2023-04-12 16:02:30] Fine-tune costs $0.16
[2023-04-12 16:02:30] Fine-tune enqueued. Queue number: 12
[2023-04-12 16:06:10] Fine-tune is in the queue. Queue number: 11
[2023-04-12 16:06:44] Fine-tune is in the queue. Queue number: 10
[2023-04-12 16:06:51] Fine-tune is in the queue. Queue number: 9
[2023-04-12 16:06:52] Fine-tune is in the queue. Queue number: 8
[2023-04-12 16:10:12] Fine-tune is in the queue. Queue number: 7
[2023-04-12 16:11:00] Fine-tune is in the queue. Queue number: 6
[2023-04-12 16:11:16] Fine-tune is in the queue. Queue number: 5
[2023-04-12 16:17:02] Fine-tune is in the queue. Queue number: 4
[2023-04-12 16:21:27] Fine-tune is in the queue. Queue number: 3
[2023-04-12 16:21:55] Fine-tune is in the queue. Queue number: 2
[2023-04-12 16:22:01] Fine-tune is in the queue. Queue number: 1
[2023-04-12 16:31:35] Fine-tune is in the queue. Queue number: 0
[2023-04-12 16:31:37] Fine-tune started
[2023-04-12 16:35:35] Completed epoch 1/4
[2023-04-12 16:38:59] Completed epoch 2/4
[2023-04-12 16:42:39] Completed epoch 3/4
[2023-04-12 16:46:19] Completed epoch 4/4
[2023-04-12 16:46:58] Uploaded model: ada:ft-persadonlp-2023-04-12-13-46-58
[2023-04-12 16:47:00] Uploaded result file: file-DZcArmsGPse796GhTWZgf59Y
[2023-04-12 16:47:00] Fine-tune succeeded
 
Job complete! Status: succeeded &#x1f389;
Try out your fine-tuned model:
 
openai api completions.create -m ada:ft-persadonlp-2023-04-12-13-46-58 -p <YOUR_PROMPT>

Как мы видим, обучение модели заняло около 57 минут и обошлось нам в 0,16 доллара. Название модели ada:ft-persadonlp-2023-04-12-13-46-58. Наконец, мы можем делать прогнозы, выполнив следующую команду в CLI.

openai api completions.create -m ada:ft-persadonlp-2023-04-12-13-46-58 -p <YOUR_PROMPT>

Оцените модель

Мы можем оценить модель, просмотрев отчет о классификации. Мы можем загрузить отчет о классификации в виде CSV-файла с именем «result.csv», выполнив:

openai api fine_tunes.results -i ft-sEQZBANQSg4NTNEodwHH2hNz > result.csv

Затем мы можем получить отчет о классификации, выполнив следующую команду Python в нашей записной книжке Jupyter.

results = pd.read_csv('result.csv')
results[results['classification/accuracy'].notnull()].tail(1)

Делать предсказания

Давайте посмотрим, как мы можем делать прогнозы.

import pandas as pd
import openai
import os
openai.api_key = os.getenv('OPENAI_API_KEY')


# load the validation dataset
test = pd.read_json('spam_with_right_column_names_prepared_valid.jsonl', lines=True)
test.head()

Сделаем прогноз для первой подсказки:

ft_model = 'ada:ft-persadonlp-2023-04-12-13-46-58'
res = openai.Completion.create(model=ft_model, prompt=test['prompt'][0] + ' ->', max_tokens=1, temperature=0)
res['choices'][0]['text']

Мы также можем создать функцию, которую можно использовать в качестве лямбда-функции для фрейма данных pandas.

ft_model = 'ada:ft-persadonlp-2023-04-12-13-46-58'

def ham_spam(text):
    
    # add the suffix ` ->` to the prompt
    input_prompt = text + ' ->'
    response = openai.Completion.create(model=ft_model, prompt=input_prompt, max_tokens=1, temperature=0)
    
    output = response['choices'][0]['text']
    return output


# get predictions for the test dataset
test['predictions'] = test['prompt'].apply(lambda x:ham_spam(x))

test

Точность на тестовом наборе данных равна 99,4%:

import numpy as np

np.mean(test.completion==test.predictions)

Мы получаем:

0.994

Если мы хотим вернуть логарифмические вероятности, мы можем запустить:

ft_model = 'ada:ft-persadonlp-2023-04-12-13-46-58'
res = openai.Completion.create(model=ft_model, prompt=test['prompt'][0] + ' ->', max_tokens=1, temperature=0, logprobs=2)
res['choices'][0]['logprobs']['top_logprobs'][0]

Как получить список всех задач тонкой настройки

Мы можем получить список всех задач тонкой настройки, выполнив в openai CLI следующую команду:

openai api fine_tunes.list

Как удалить доработанную модель

Чтобы удалить доработанную модель, вы должны быть назначены «владельцем» в вашей организации. Если у вас есть необходимые права, вы можете удалить модель следующим образом:

openai api models.delete -i <FINE_TUNED_MODEL>

Заключительные замечания

OpenAI — это не только мощный инструмент с продвинутыми большими языковыми моделями, он также позволяет нам настраивать существующие модели в соответствии с нашими потребностями. В этом уроке мы представили простую задачу классификации. Точно так же мы можем создавать собственные модели для анализа настроений и других задач классификации с более чем двумя классами. Наконец, мы можем применить ту же логику для построения других моделей, таких как NLG, Paraphrase, Question and Answers и так далее. Необходимо всегда иметь подсказку и завершение.



Первоначальное сообщение от Предсказательные хаки

Использованная литература:

Документация OpenAI

Повышение уровня кодирования

Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:

  • 👏 Хлопайте за историю и подписывайтесь на автора 👉
  • 📰 Смотрите больше контента в публикации Level Up Coding
  • 💰 Бесплатный курс собеседования по программированию ⇒ Просмотреть курс
  • 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"

🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу