Модуль 5. Датасет — 80% результата
Цель модуля: собрать пары «запрос → эталонный ответ» в правильном формате (JSONL, chat messages, классификатор), почистить их и разделить на train/val без утечки. После модуля вы знаете, сколько примеров нужно под вашу задачу и почему 300 чистых пар побеждают 3000 грязных.
Перед началом: модуль 4 — вы уже решили, что задача решается fine-tuning. Инструменты: песочница сайта (сниппет модуля), python3 локально, таблица или CSV для своих пар. Трудоёмкость: ~2 ч (теория 50 мин · практика 60 мин · квиз 10 мин).
🔥 Два тюнинга: 300 чистых против 3000 грязных
Команда А и команда Б дообучали одну и ту же базовую модель для поддержки. А выгрузила 3000 пар из сырых логов: дубли, противоречия («вернуть нельзя» и «вернём за три дня» на одинаковый вопрос), опечатки, перекос — 70% пар про оплату. Б вручную собрала 300: прошлась по логам, отобрала лучшие ответы, вычистила дубли, свела противоречия, сбалансировала темы. На честной проверке (golden set, м8) модель команды Б выиграла по всем вопросам, кроме редких вариантов про оплату — той темы, которую А перекосила. Модель А отвечала про оплату увереннее — и чаще невпопад.
Мораль не «данных мало не бывает». Мораль: модель учит частоты. Дубль — это «этот ответ важнее». Противоречие — «отвечай случайно». Перекос — «большинство вопросов про оплату». Грязный датасет — программа, которую вы написали случайно. Этот модуль — про то, как писать её осознанно.
📖 5.1. Форматы: JSONL и его разновидности
Пары для генерации в AI Studio лежат в JSONL — по одному JSON-объекту на строку, три поля: instruction (постоянная системная инструкция), request (запрос пользователя), response (эталонный ответ):
jsonl{"instruction": "Ты — оператор поддержки учебной платформы. Отвечай кратко.", "request": "Как вернуть деньги за курс?", "response": "Откройте страницу заказа и нажмите «Вернуть»."} {"instruction": "Ты — оператор поддержки учебной платформы. Отвечай кратко.", "request": "Куда пропал платёж?", "response": "Платёж идёт до 15 минут; обновите страницу заказов."}
(Для читаемости объекты перенесены на несколько строк; в настоящем файле каждый объект занимает ровно одну строку — это и есть формат JSONL.) instruction обычно один и тот же во всём файле: так в дообучение «зашивается» роль (тюнинг YandexGPT Lite в м10 ест именно этот формат). Альтернатива — chat messages, OpenAI-совместимый формат с ролями, как в chat template из м3:
jsonl{"messages": [{"role": "system", "content": "Ты — оператор поддержки."}, {"role": "user", "content": "Как вернуть деньги за курс?"}, {"role": "assistant", "content": "Откройте заказ и нажмите «Вернуть»."}]}
Для классификатора (разметка тикетов, маршрутизация) формат другой — текст и метки из фиксированного набора, без генерации ответа:
jsonl{"text": "Не пришло подтверждение оплаты", "labels": ["оплата"]} {"text": "Книга пришла повреждённой", "labels": ["доставка", "возврат"]}
Выбор формата — это выбор задачи: генерация (длинный response), диалог (messages с историей) или разметка (labels). Смешивать задачи в одном файле нельзя — валидация загрузки упадёт ещё до обучения (м10).
📖 5.2. Источники пар: где брать эталоны
- Логи поддержки. Реальные вопросы пользователей и лучшие ответы операторов. Плюс: живой язык, реальные боли. Минус: ответы разного качества — отбор делает человек, а PII (имена, телефоны, номера карт) маскируется ещё до попадания в датасет.
- Экспертные ответы. FAQ, регламенты, внутренние инструкции. Плюс: канонический ответ. Минус: язык эксперта — не язык пользователя; вопрос к экспертному тексту часто дописывают вручную.
- Синтетика. Модель генерирует пары по вашим документам, человек ревьюит и правит. Плюс: быстро масштабируется, закрывает редкие темы. Минус: шаблонность и галлюцинации модели-генератора — без ревью вы получите «стиль модели о вашей базе», а не вашу базу.
- Журнал вызовов работающей LLM-фичи. Если у сервиса уже есть LLM-эндпоинт, вы (надеемся) пишете журнал: промпт, ответ, модель, токены, исход — как в модуле 12. Такой журнал — готовый черновик датасета в реальном распределении: видно, с чем приходят чаще всего. Плюс: настоящие запросы, а не выдуманные. Минус: ответы модели — не эталоны; пары для обучения получаются только после разметки куратором. Это и есть тот самый вход петли из диаграммы цикла в модуле 12.
Рабочая пропорция для старта: ядро из реальных кейсов (60–80%) плюс экспертные и синтетические пары для тем, которых в логах мало.
📖 5.3. Гигиена: чистим до сплита, а не после
Порядок пайплайна важнее отдельных приёмов: сначала нормализация, дедупликация и балансировка, и только потом перемешивание и разбиение train/val. Нарушите порядок — копия val-пары окажется в train, и метрика будет врать. Это называется утечкой валидации, и врёт она тихо: loss красивый, модель хуже.
Запустите сниппет модуля в песочнице — он прогоняет 13 сырых пар (три темы: оплата, возврат, доставка) через весь пайплайн и печатает воронку:
textИсходно: 13 пар После точной дедупликации: 12 (убрано 1) near-dup (0.40): «Почему мне отказали в возврате?» ≈ «Почему отказали в возврате?» near-dup (1.00): «Когда придёт книга» ≈ «Когда придёт книга?» После near-dup: 10 Split: train=7, val=3 train: возврат:1, доставка:3, оплата:3 val: возврат:1, доставка:1, оплата:1
Шаг 1 — нормализация: strip().lower(), схлопывание повторных пробелов
и пробела перед знаком препинания — «курс ?» и «курс?» обязаны стать одним
ключом. Шаг 2 — точная дедупликация по ключу «request + response» убрала
одну пару: «Как вернуть деньги за курс?» и «как вернуть деньги за курс ?»
совпали после приведения регистра и чистки пробелов. Урок: решите до
чистки, что считать «одинаковым» (регистр, пробелы, пунктуацию), и
приведите всё сразу — иначе дубликаты просачиваются.
Шаг 3 — near-dup: шинглы (пары соседних слов, пунктуация вырезана) и коэффициент Жаккара. Порог 0.4 — «перефразированный дубль»; он подбирается под домен: 0.7 ловит почти копии, ниже — агрессивнее. Здесь пойманы две пары. «Почему мне отказали в возврате?» ≈ «Почему отказали в возврате?» (0.40) — перефразировка одного вопроса с тем же ответом. «Когда придёт книга» ≈ «Когда придёт книга?» (1.00) — копия, пережившая точную дедупликацию: в ключе вопросительный знак сохраняется, а шинглы пунктуацию вырезают, и вопросы совпали до последнего слова. Две ступени не избыточны: каждая ловит своё.
Шаг 4 — перемешивание с фиксированным seed (42): сплит обязан быть воспроизводимым, иначе завтра вы не сможете повторить эксперимент. Шаг 5 — стратифицированный split: ~25% каждой темы в val, но минимум одна пара на тему — итого train=7, val=3, и в val есть все три темы. Без стратификации случайный сплит маленького файла мог оставить в val, скажем, только «доставку» — и деградацию по «оплате» вы бы не увидели в принципе.
📖 5.4. Сколько нужно примеров
| Задача | Порядок примеров | Комментарий |
|---|---|---|
| Стиль и формат ответа | десятки–сотни (50–300) | нужен тон, а не факты |
| Доменный тон и жаргон | сотни–тысячи | чем шире словарь домена, тем больше пар |
| Классификатор | от пары десятков на класс | классы должны быть сбалансированы |
Это ориентиры, не гарантии. Качество важнее количества (крючок модуля), а редкие темы добираются точечно, а не «ещё десятью тысячами всего». Стартуйте с 50–200 пар, смотрите по ошибкам на val и golden set (м8), каких тем не хватает, — и добавляйте именно их.
📖 5.5. Компромиссы
- Много vs чисто: грязные пары учат шуму и случайности, чистые дороги ревью. Правило: не добавляйте пару, которую не готовы защитить как эталон.
- Реальные vs синтетические: реальные отражают живые формулировки, синтетика закрывает редкие темы, но тянет шаблонность. Ревью человека — обязательный фильтр для обоих.
- Высокий vs низкий порог near-dup: высокий (0.7) ловит почти копии и сохраняет перефразировки — модель учит вариативность формулировок; низкий (0.4) убирает и перефразированных двойников, экономя объём ревью. Для классификатора дубли опаснее — порог ниже.
- Val побольше vs train побольше: маленький val зашумлён, но каждый пример на счету для обучения. Рабочий минимум val — по 5–10 пар на тему или класс.
💻 Практика
- (10 мин) Запустите сниппет в песочнице. Сопоставьте каждую строку вывода с шагом пайплайна: нормализация → точная дедупликация → near-dup → shuffle с seed → стратифицированный split.
- (10 мин) Поднимите порог near-dup с 0.4 до 0.7 (строка
if score >= 0.4:) и перезапустите. Перефразировка «Почему мне отказали в возврате?» с Жаккаром 0.40 перестанет ловиться: останется только копия с 1.00, из 13 пар — 11, split train=8/val=3. Решите, что для вашего домена хуже: лишний двойник в train или потерянная перефразировка. - (25 мин) Соберите 20–50 пар своего домена (задачу вы выбрали в м4): вопрос из реального лога или FAQ плюс эталонный ответ. Формат — JSONL из 5.1. PII маскируйте сразу при сборке.
- (10 мин) Прогоните свои пары через гигиену локально: скопируйте сниппет, замените список RAW на свой. Посмотрите, сколько ушло дубликатов и есть ли тема, которой не оказалось в val.
- (5 мин) Ответьте письменно: сколько пар нужно вашей задаче по таблице 5.4 и каких тем вам сейчас не хватает?
🧩 Quiz "Проверь себя"
✅ Итоги модуля
- Назвать три формата (JSONL instruction/request/response, chat messages, классификатор) и задачи, для которых они нужны.
- Перечислить источники пар и главный риск каждого.
- Объяснить порядок «чистка → сплит» и что такое утечка валидации.
- Разобрать воронку сниппета: 13 → 12 → 10 и роль порога near-dup (0.4 подбирается под домен).
- Назвать порядок числа примеров для стиля, домена и классификатора.
- Собрать 20–50 своих пар и прогнать их через гигиену.
🔗 Мостик к Модулю 6
Датасет готов: пары чистые, val не течёт, объём посчитан. Остался вопрос механики: что именно происходит с весами, когда вы нажимаете «обучить», и почему «просто дообучить все 7 миллиардов параметров» не выйдет даже на серьёзной GPU. Следующий модуль — «Полное дообучение, LoRA и QLoRA: что происходит с весами»: бюджет видеопамяти, низкоранговые дельты и адаптеры размером в мегабайты.