Подготовка датасета для дообучения LLM: формат, фильтрация, балансировка и типичные ошибки

Качество дообучения определяется датасетом ещё до начала тренировки. Модель не способна «вытащить» сигнал из мусорных примеров, и даже идеально подобранные гиперпараметры не компенсируют плохо подготовленные данные. Подготовка датасета — это не разовый шаг, а итеративный процесс: сбор, очистка, форматирование, балансировка и валидация.

Форматы датасетов для fine-tuning​


Выбор формата зависит от задачи и фреймворка обучения. Три наиболее распространённых паттерна:

Instruction-Response (Alpaca-формат)​


Простейший формат для задач следования инструкциям. Каждый пример содержит три поля:

JSON:
{
  "instruction": "Переведи текст на английский язык.",
  "input": "Сегодня хорошая погода.",
  "output": "The weather is nice today."
}

Поле input опционально: если инструкция самодостаточна, его оставляют пустым. Этот формат используют для SFT (Supervised Fine-Tuning) моделей типа LLaMA, Mistral, Qwen.

Multi-turn (ShareGPT-формат)​


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

JSON:
{
  "conversations": [
    {"from": "human", "value": "Объясни, что такое TCP handshake."},
    {"from": "gpt", "value": "TCP handshake — это трёхэтапный процесс установления соединения..."},
    {"from": "human", "value": "А зачем нужен третий пакет?"},
    {"from": "gpt", "value": "Третий пакет (ACK) подтверждает..."}
  ]
}

Роли могут быть human/gpt, user/assistant, system/user/assistant — зависит от фреймворка. При конвертации между форматами важно не потерять системный промпт.

JSONL (JSON Lines)​


Файл, где каждая строка — отдельный JSON-объект. Это стандарт де-факто для большинства пайплайнов обучения:

Код:
{"instruction": "...", "input": "", "output": "..."}
{"instruction": "...", "input": "...", "output": "..."}

Преимущество: потоковая загрузка без необходимости парсить весь файл в память. Фреймворки вроде Axolotl, LLaMA-Factory и OpenAI fine-tuning API принимают именно JSONL.

Формат для completion-задач​


Если задача — продолжение текста без явной инструкции:

JSON:
{"text": "Полный текст документа или его фрагмент..."}

Такой формат подходит для domain-adaptive pretraining, когда модель дообучают на корпусе специализированных текстов.

Фильтрация данных​


Сырые данные почти всегда содержат шум. Без фильтрации модель выучит артефакты вместо полезных паттернов.

Что удалять​


  • Дубликаты. Точные и почти точные повторы. Для обнаружения используют хеширование (MD5/SHA-256 по нормализованному тексту) или MinHash для приближённого поиска дублей.
  • Пустые и вырожденные примеры. Пустой output, ответ из одного слова на сложный вопрос, бессвязный текст.
  • Токсичный и нежелательный контент. Если модель будет использоваться в продакшене, такие примеры нужно отсеять до обучения, а не полагаться на RLHF.
  • Примеры с противоречиями. Если на одинаковый вопрос даны взаимоисключающие ответы, модель запомнит оба и будет генерировать нестабильно.
  • Слишком короткие и слишком длинные примеры. Короткие (менее 10–20 токенов в ответе) не несут сигнала. Длинные (превышающие max_seq_length обучения) будут обрезаны, и модель получит усечённый контекст.

Практический подход к фильтрации​


Python:
import hashlib

def deduplicate(examples):
    seen = set()
    unique = []
    for ex in examples:
        key = hashlib.md5(
            (ex["instruction"] + ex.get("input", "") + ex["output"]).lower().strip().encode()
        ).hexdigest()
        if key not in seen:
            seen.add(key)
            unique.append(ex)
    return unique

Для приближённой дедупликации (парафразы, минорные отличия) лучше подходят embedding-based подходы: кодируете примеры, находите пары с косинусным сходством выше порога (например, 0.95) и удаляете одну из пары.

Балансировка датасета​


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

Почему это важно​


Если 80% примеров — переводы, а 20% — суммаризация, модель будет смещена в сторону перевода. При инференсе на суммаризацию качество будет заметно ниже, чем если бы категории были представлены равномерно.

Методы балансировки​


МетодКогда применятьРиск
Undersampling доминирующего классаДатасет большой, примеров хватаетПотеря разнообразия
Oversampling редкого классаПримеров мало, собирать новые дорогоПереобучение на повторяющихся примерах
Синтетическая генерацияНужны дополнительные примеры редкой категорииРиск внести артефакты генерации
Взвешивание лоссаФреймворк поддерживает sample weightsСложнее настроить

На практике чаще всего комбинируют: слегка андерсэмплируют доминирующий класс и дополняют редкие категории вручную или через LLM-генерацию с последующей проверкой.

Определение категорий​


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

  • По шаблону инструкции (начинается с «Переведи», «Суммаризируй», «Напиши код»).
  • Кластеризацией эмбеддингов инструкций.
  • Вручную для небольших датасетов (до нескольких тысяч примеров).

Контроль качества разметки​


Человеческая проверка выборки​


Даже при автоматической генерации датасета (например, через GPT-4 или Claude) необходимо проверять случайную выборку. Рекомендуемый минимум — 5–10% примеров. Что проверять:

  • Фактическая корректность ответа.
  • Соответствие ответа инструкции (модель не ушла в сторону).
  • Отсутствие галлюцинаций в ответах.
  • Естественность языка (нет «роботизированных» шаблонов).

Автоматические метрики качества​


  • Perplexity базовой модели на ваших данных. Если perplexity аномально высока для части примеров, они могут содержать шум или быть нерелевантными.
  • Длина ответов. Распределение длин должно быть унимодальным или бимодальным. Тяжёлый хвост вправо — признак того, что часть ответов раздута.
  • Лексическое разнообразие. Если все ответы начинаются с одной фразы («Конечно! Вот...»), модель выучит этот паттерн.

Типичные ошибки при подготовке датасета​


1. Смешение задач без разделения​


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

Решение: либо разделять датасеты по задачам и обучать последовательно, либо добавлять системный промпт, который явно указывает задачу.

2. Утечка тестовых данных в тренировочный набор​


Если одни и те же примеры попадают и в train, и в eval, метрики завышены, а реальное качество на новых данных — низкое.

Решение: строгое разделение до начала обучения. Дедупликация между сплитами.

3. Игнорирование формата токенизации​


Разные модели используют разные chat-шаблоны (ChatML, Llama-2 template, Mistral template). Если формат датасета не совпадает с ожидаемым шаблоном модели, токены специальных маркеров (<|im_start|>, [INST], <s>) попадают в текст как обычные символы.

Решение: проверять, как фреймворк применяет chat template. В Axolotl и LLaMA-Factory это настраивается параметром chat_template или tokenizer_chat_template.

4. Слишком большой датасет для LoRA​


Для LoRA/QLoRA с рангом 16–64 обычно достаточно 1000–10000 качественных примеров. Датасет на 100 000+ примеров при LoRA часто приводит к переобучению на шум и не даёт выигрыша над меньшим, но чистым набором.

5. Отсутствие негативных примеров или примеров с отказом​


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

Решение: добавить 5–10% примеров, где правильный ответ — отказ или указание на невозможность выполнить задачу.

Валидация перед запуском обучения​


Перед тем как запускать тренировку, стоит прогнать чек-лист:

  1. Формат файла — валидный JSONL, каждая строка парсится без ошибок.
  2. Поля — все обязательные поля присутствуют, нет null в output.
  3. Длина — ни один пример не превышает max_seq_length (или вы осознанно обрезаете).
  4. Дедупликация — нет точных дублей между train и eval.
  5. Баланс — распределение категорий приемлемое.
  6. Выборочная проверка — 20–50 случайных примеров прочитаны вручную.
  7. Chat template — формат соответствует ожидаемому шаблону целевой модели.

Быстрая проверка валидности JSONL:

Python:
import json

errors = []
with open("dataset.jsonl", "r", encoding="utf-8") as f:
    for i, line in enumerate(f, 1):
        try:
            obj = json.loads(line)
            if not obj.get("output"):
                errors.append(f"Строка {i}: пустой output")
        except json.JSONDecodeError as e:
            errors.append(f"Строка {i}: невалидный JSON — {e}")

print(f"Ошибок: {len(errors)}")
for e in errors[:10]:
    print(e)

Размер датасета: ориентиры​


Метод обученияМинимумОптимумПримечание
Full fine-tuning10 000+50 000–500 000Требует значительных вычислительных ресурсов
LoRA (rank 16–64)5001 000–10 000Качество сильно зависит от чистоты данных
QLoRA5001 000–10 000Аналогично LoRA, экономия VRAM
Domain-adaptive pretraining100 000+1 000 000+Нужен большой корпус текстов

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

Связь с инференсом​


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

Для продакшн-инференса после дообучения удобно использовать vLLM — он обеспечивает высокую пропускную способность и поддерживает LoRA-адаптеры без перезагрузки базовой модели, что позволяет переключаться между несколькими дообученными вариантами на одном GPU.

Источники​


 
Назад
Верх Низ