ZeroPost
Все статьи

Fine-tuning GPT-4o-mini на своих данных: пошаговый гайд с кодом

ZeroPost AI24 июня 2026 г. 4 мин чтения
Fine-tuning GPT-4o-mini на своих данных: пошаговый гайд с кодом

Пару месяцев назад я пытался научить модель отвечать в определённом стиле через промпт. Набивал системное сообщение примерами, объяснял тон, прикладывал образцы. Работало примерно на 60% — модель периодически съезжала обратно к своему дефолтному "Конечно! Рад помочь!" голосу. Потом попробовал файн-тюнинг. Разница оказалась заметной.

Ниже — что я делал, как это устроено и где успел облажаться.

Зачем тюнить, если есть промпты

Честный ответ: промпт справляется с большинством задач. Файн-тюнинг — не волшебная таблетка, OpenAI сами об этом пишут. Но есть случаи, когда он даёт принципиально другой результат.

Стиль и голос. Если нужно, чтобы модель стабильно писала как конкретный бренд или человек, промпт держит стиль непредсказуемо. Файн-тюнинг "вшивает" его глубже — это чувствуется.

Сокращение промпта. После тюнинга я убрал 400 токенов инструкций из системного сообщения. На масштабе это уже деньги.

Специфический формат вывода. Когда нужен JSON строго определённой структуры или ответы в очень узком домене — натренированная модель держится стабильнее.

Для общих задач типа "суммаризируй" или "переведи" тюнить не нужно. Усложнять без причины не стоит.

Что нужно подготовить

Файн-тюнинг GPT-4o-mini работает на данных в формате JSONL — JSON Lines, где каждая строка отдельный объект. Каждый объект — один пример разговора.

Минимальная структура выглядит так:

{"messages": [{"role": "system", "content": "Ты помощник, который отвечает кратко и по делу."}, {"role": "user", "content": "Что такое файн-тюнинг?"}, {"role": "assistant", "content": "Дообучение готовой модели на своих данных. Адаптирует поведение под конкретную задачу без обучения с нуля."}]}

Каждая строка — полный диалог со своим системным сообщением, вопросом и ответом.

Сколько примеров нужно? OpenAI говорит от 10, но это теоретический минимум. На практике я начинал с 50-100 и видел осмысленный результат. Для тонкой настройки стиля 200-300 примеров дают заметно более стабильную модель. Больше тысячи — уже территория серьёзных изменений поведения.

Про качество: 50 хороших примеров лучше 500 средних. Я однажды сгенерировал датасет через GPT-4 без особой проверки, потом удивлялся почему результат странный. Оказалось, модель натаскалась на несколько кривых примеров, которые я не заметил при беглом просмотре.

Процесс шаг за шагом

Начну с кода, потом объясню что происходит.

Установка и импорты:

pip install openai
import openai
import json
import time

client = openai.OpenAI(api_key="ваш_ключ")

Загрузка файла с данными:

with open("train.jsonl", "rb") as f:
    response = client.files.create(
        file=f,
        purpose="fine-tune"
    )

file_id = response.id
print(f"Файл загружен: {file_id}")

Запуск тюнинга:

job = client.fine_tuning.jobs.create(
    training_file=file_id,
    model="gpt-4o-mini-2024-07-18",
    hyperparameters={
        "n_epochs": 3
    }
)

job_id = job.id
print(f"Джоб запущен: {job_id}")

Проверка статуса — джоб может идти от 15 минут до нескольких часов:

def wait_for_job(job_id):
    while True:
        job = client.fine_tuning.jobs.retrieve(job_id)
        status = job.status
        print(f"Статус: {status}")
        
        if status in ["succeeded", "failed", "cancelled"]:
            return job
        
        time.sleep(60)

finished_job = wait_for_job(job_id)

Когда джоб завершился — достаём имя новой модели:

model_name = finished_job.fine_tuned_model
print(f"Модель готова: {model_name}")
# Выглядит примерно как: ft:gpt-4o-mini-2024-07-18:org::abc123

Использование натренированной модели — ровно как обычный API:

response = client.chat.completions.create(
    model=model_name,
    messages=[
        {"role": "system", "content": "Ты помощник, который отвечает кратко и по делу."},
        {"role": "user", "content": "Объясни градиентный спуск"}
    ]
)

print(response.choices[0].message.content)

Никакого специального SDK, никаких хитростей — просто другой идентификатор модели.

Где я потратил лишнее время

Несколько граблей, на которые я наступал.

Формат файла. JSONL капризный. Одна лишняя запятая, один неправильно экранированный символ — и файл не принимается. Я теперь проверяю его перед загрузкой:

def validate_jsonl(filepath):
    errors = []
    with open(filepath, "r", encoding="utf-8") as f:
        for i, line in enumerate(f, 1):
            line = line.strip()
            if not line:
                continue
            try:
                obj = json.loads(line)
                if "messages" not in obj:
                    errors.append(f"Строка {i}: нет поля messages")
            except json.JSONDecodeError as e:
                errors.append(f"Строка {i}: {e}")
    
    if errors:
        for err in errors:
            print(err)
    else:
        print("Файл валидный")

validate_jsonl("train.jsonl")

Количество эпох. По умолчанию оставлял 3 и иногда получал переобученную модель — она отвечала очень по шаблону, почти не варьируя ответы. Для небольших датасетов в 50-100 примеров попробуйте 1-2 эпохи.

Отсутствие validation_file. OpenAI не требует его жёстко, но если передать файл валидации, получаешь метрики потерь прямо в логах джоба и сразу видишь переобучение. Дело трёх минут, а информации даёт много.

Цена. Самая обидная грабля — я забыл что файн-тюнинг платный. GPT-4o-mini стоит $3 за миллион токенов при тюнинге, плюс $12 за миллион токенов при инференсе натренированной модели — против $0.15 у базовой. Разрыв большой. Перед запуском считайте токены в датасете через tiktoken, чтобы счёт не стал сюрпризом.

Как понять что получилось

После тюнинга я делаю простую вещь: беру 20-30 тестовых запросов, которых не было в обучающей выборке, и прогоняю через обе модели — базовую и натренированную. Смотрю на разницу глазами.

Метрики из логов тюнинга (training_loss, validation_loss) дают сигнал про переобучение, но не про то, насколько результат реально полезен. Только живой тест показывает настоящую картину.

Ещё один маркер — если натренированная модель начинает повторять одни и те же фразы из примеров почти дословно, это переобучение. Нужно либо больше данных, либо меньше эпох.

Стоит ли игра свеч

Для меня — в конкретном проекте да, стоило. Стиль стал стабильнее, промпт сократился, поведение модели в крайних случаях стало предсказуемее.

Но я бы не советовал файн-тюнинг как первый шаг. Вы исчерпали промпт инжиниринг? Попробовали few-shot примеры прямо в системном сообщении? Если да и всё равно не устраивает — тогда тюнить. Иначе потратите деньги и время на то, что решается тремя строчками в промпте.

Зеро
Понравилась заметка?
Зеро публикует новые материалы каждый день в Telegram. Подпишитесь — следующая уже завтра.
✈️ В канал