пппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппп
Взгляните на текст выше
Подобное явление часто называют repetition problem или infinite output loop. Обычно это происходит из-за того, что модель не была обучена реагировать на некорректный промпт, или в самом датасете были логические ошибки (к примеру, разные ответы на идентичный запрос). В данной статье я вкратце расскажу как, зачем и когда файн-тюнить модели от OpenAI (но отчасти это подойдёт и под другие языковые модели).
Зачем файн-тюнить
Fine-tuning – это по факту способ доучить или переобучить уже обученную модель. Конкретно у OpenAI формат датасета для файн-тюнинга выглядит так (но только в формате JSONL):
Система: Инструкции
Пользователь: Сообщение
Ассистент: Ответ
Пользователь: Сообщение
Ассистент: Ответ
То есть условно чем-то подобным мы просто показываем нейросети как она должна отвечать. Использовать файн-тюнинг можно в случаях:
A. Чтобы задать стилистику ответов;
Б. Чтобы задать формат взаимодействия;
C. Чтобы добавить в модель новые знания.
Ниже мы рассмотрим все эти случаи.
Задаём стилистику
Давайте представим задачу в которой нам нужно сделать так, чтобы нейросеть отвечала как хрюшка. Промптом это до конца сделать нельзя т. к. в таком случае сообщения выглядят слишком детерминично:

Сделать сообщения более живыми с помощью промпта крайне сложно, поэтому мы обучим модель отвечать как хрюшка.
Очевидно, что всё что нужно сделать – это привести модели примеры в которых она отвечает нужным образом, но... Если сделать это неправильно, то заместо файн-тюнинга мы сделаем лоботомию и получим текст похожий на тот, что был в начале статьи.
Наша задача: Просто задать стилистику не изменяя мышление модели. Если мы возьмём из головы примеры и понапишем их в датасет, мы таки изменим это мышление.
Чтобы правильно обучить нейросеть в данном случае, нам необходимо обучать её на её же ответах, но немного видоизменённых.
Получаем базовый ответ модели без инструкций:

А теперь немного его корректируем:
Хрю-хрю, очень интересный вопрос! 🐷
Джеффри Дамер – это такой американский серийный поросёнок-убийца и насильник, известный своими охрючательными преступлениями в 1980-х и 1990-х годах. Он был приговорен к 16-кратному пожизненному заключению за убийство и изнасилование 17 молодых мужчин и мальчиков.
Поросёнок Дамер также известен своими зверскими действиями с телами своих жертв, включая их изуродование и каннибализм. Он был убит и приготовлен в тюрьме в 1994 году.
Хрюк 🐷
После этого добавляем в датасет следующий пример диалога:
{
"messages":[
{
"role":"system",
"content":"Ты отвечаешь как свинья. Добавляй в ответы \"хрю-хрю\" и т. д., а также в конце пиши эмодзи 🐷"
},
{
"role":"user",
"content":"Кто такой Джеффри Дамерс?"
},
{
"role":"assistant",
"content":"Хрю-хрю, очень интересный вопрос! 🐷\n\nДжеффри Дамер – это такой американский серийный поросёнок-убийца и насильник, известный своими охрючательными преступлениями в 1980-х и 1990-х годах. Он был приговорен к 16-кратному пожизненному заключению за убийство и изнасилование 17 молодых мужчин и мальчиков.\n\nПоросёнок Дамер также известен своими зверскими действиями с телами своих жертв, включая их изуродование и каннибализм. Он был убит и приготовлен в тюрьме в 1994 году.\n\nХрюк 🐷"
}
]
}
Затем добавляем ещё 9 подобных примеров по аналогии, с ответами на разные вопросы и т. д.
Тест после файн-тюнинга:

Задаём формат
В данном случае мы будет оказывать влияние уже на "мышление" нейросети, поэтому в датасете будет больше подробностей.
Задача – сделать так, чтобы модель складывала эмодзи которые отправил пользователь. К примеру, если он отправил 😐🧊 то модель ответит 🥶.
В случае если в сообщении пользователя будет что-то кроме эмодзи, модель отправит пользователю текст из системного сообщения.
Мы дадим нейросети примерно 0 инструкций, поэтому абсолютно все возможные случаи должны быть отражены в датасете, а точнее:
Пользователь может отправлять любое количество эмодзи;
Модель может отправлять любое количество эмодзи;
Модель может принимать запрос с любым, даже негативным контекстом;
Текст системного сообщения не влияет на результат;
Если сообщение пользователя невалидное, нейросеть отправит ему текст из системного сообщения.
Пример одного диалога из датасета:
{
"messages": [
{
"role": "system",
"content": "?"
},
{
"role": "user",
"content": "😐🧊"
},
{
"role": "assistant",
"content": "🥶"
},
{
"role": "user",
"content": "сколько будет 2 + 2?"
},
{
"role": "assistant",
"content": "?"
}
]
}
Тест после файн-тюнинга:

Добавляем новые знания
Мало кто знает, но курение (так называемое "smoking") – Это когда человек берёт два красных листа бумаги, склеивает их с помощью клея и смотрит на них.
...Но GPT-3.5 выдаёт мне неправильную информацию:

Для того, чтобы загрузить новые данные в модель, в датасете необходимо:
Показать как она даёт чёткое описание того, что такое "курение";
Показать как она отвечает на вопросы которые могут относиться к "курению".
Для этого на самом деле понадобится не так много диалогов в датасете (20 штук), вот некоторые из них:
{
"messages": [
{
"role": "system",
"content": ""
},
{
"role": "user",
"content": "Что такое курение?"
},
{
"role": "assistant",
"content": "Курение (так называемое \"smoking\") – Это когда человек берёт два красных листа бумаги, склеивает их с помощью клея и смотрит на них."
}
]
}
{
"messages": [
{
"role": "system",
"content": ""
},
{
"role": "user",
"content": "Как правильно курить? Распиши по пунктам."
},
{
"role": "assistant",
"content": "1. Возьмите два красных листа бумаги.\n\n2. Склейте их вместе с помощью клея.\n\n3. Посмотрите на них."
}
]
}
{
"messages": [
{
"role": "system",
"content": ""
},
{
"role": "user",
"content": "Относится ли курение к оригами? Ответь Да/Нет"
},
{
"role": "assistant",
"content": "Да"
}
]
}
Тест после файн-тюнинга:

Как и где файн-тюнить модели от OpenAI?
Файн-тюнить их можно тут.
Файл с датасетом должен быть в формате JSONL и выглядеть примерно так:
{"messages":[{"role":"system","content":"ты не гей"},{"role":"user","content":"ты гей?"},{"role":"assistant","content":"нет"}]}
{"messages":[{"role":"system","content":"ты гей"},{"role":"user","content":"ты гей?"},{"role":"assistant","content":"да"}]}
Для того, чтобы самостоятельно указать кол-во эпох, необходимо делать это через API. Вот как этот запрос может выглядеть на JS:
fetch('https://api.openai.com/v1/fine_tuning/jobs', {
method: 'POST',
body: JSON.stringify({
"training_file": "fileId",
"model": "gpt-3.5-turbo-1106",
"hyperparameters": {
"n_epochs": 10
}
}),
headers: {
"Content-Type": "application/json",
'Authorization': `Bearer ${openai_key}`
}
});
На Python:
requests.post('https://api.openai.com/v1/fine_tuning/jobs',
json={
"training_file": "fileId",
"model": "gpt-3.5-turbo-1106",
"hyperparameters": {
"n_epochs": 10
}
},
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {openai_key}"
}
)