Zero-shot классификация: почему это не магия, а математика
Вы когда-нибудь пытались обучить BERT на своем корпусе тикетов? Я — да. Это боль. Нужна разметка, GPU на неделю, валидация, переобучение, поиск гиперпараметров. И тут приходит LLM и делает то же самое без обучения — по промпту. В 2026 году zero-shot классификация с помощью локальной LLM прочно заняла нишу быстрых прототипов и даже продакшена с невысокой нагрузкой. Модели вроде Qwen3.5 (70B), Llama 4 (405B) или Mistral Large 3 (123B) выдают качество, близкое к fine-tuned решениям, при этом ваши данные не покидают локальную машину. Но дьявол, как всегда, в деталях.
Zero-shot означает, что модель видит названия классов только в промпте и не требует примеров (few-shot). Достаточно одного внятного описания.
Как LLM умудряется классифицировать без обучения?
Секрет в том, что большие языковые модели обучены предсказывать следующее слово. Когда вы пишете: «Классифицируй текст как 'спам' или 'не спам'. Текст: ...», модель вычисляет вероятности для всех токенов и выбирает наиболее правдоподобное продолжение. Классы — это просто последовательности токенов. Если модель понимает контекст, она выдаст нужный токен. Этот подход работает, потому что на этапе претрейнга LLM видела миллионы инструкций и научилась следовать шаблонам.
Но есть нюанс: модель не «думает», она вычисляет условную вероятность. Если промпт составлен коряво, она может выдать «спам, потому что» вместо просто «спам». Или начать рассуждать вслух. Ваша задача — заставить её выдать ровно один токен из разрешённого списка. Об этом дальше.
Главные грабли (и как их обойти)
Ошибка №1: Модель возвращает лишний мусор — «Категория: позитивный» вместо «позитивный». Решение: используйте параметр logprobs и берите метку по максимальной вероятности.
Ошибка №2: Модель путает синонимы классов (например, «нейтральный» и «нейтрально»). Решение: всегда используйте один канонический формат меток и просите вернуть строго одну из них.
Ошибка №3: Дрейф логики при большом количестве классов (10+). Решение: разбивайте на бинарные классификации или используйте иерархический подход.
Если вы сомневаетесь, стоит ли вообще использовать LLM для этой задачи, советую почитать чек-лист в статье «Delegation Filter: когда НЕ использовать LLM в продакшн-пайплайнах». Там разобраны сценарии, где классификация через LLM избыточна.
Рабочий рецепт за 30 минут
Давайте пройдём по шагам на реальном примере: нужно классифицировать отзывы на «позитивный», «негативный», «нейтральный». Задача простая, но показательная.
1 Выберите модель и инструмент
Для русского языка хорошо подходят Mistral Large 3 (123B) или Qwen3.5-72B. Если ресурсов мало, можно взять Llama 4 8B — он хуже, но дешевле. Запускать лучше через Ollama (последняя версия на апрель 2026 — Ollama 0.7) — это самый простой способ. Альтернативы: llama.cpp, LM Studio. Подробный разбор инструментов — в этом обзоре.
Скачать Ollama: ollama.com. Можно запустить и на облачном GPU, если нет своего — например, RunPod даёт доступ к GPU по минутам.
2 Составьте жёсткий промпт
Вот пример плохого промпта: «Классифицируй этот отзыв на категории: позитивный, негативный, нейтральный. Отзыв: ...» — модель может выдать «позитивный, потому что клиент доволен». Хороший промпт должен содержать:
- Чёткое указание вернуть только одну метку.
- Формат вывода:
{label}. - Инструкцию игнорировать всё, кроме метки.
Классифицируй отзыв на one из: позитивный, негативный, нейтральный. Выведи строго одну метку в формате {label}. Никаких пояснений. Отзыв: «Товар пришёл бракованный, не советую.»С таким промптом модель почти всегда вернёт {негативный} — и парсить просто.
3 Получите логиты (или хотя бы logprobs)
Если вы используете llama.cpp или Ollama через API, запросите logprobs. Тогда вместо обрезки строки вы можете взять вероятности для каждого из трёх токенов: «позитивный», «негативный», «нейтральный». Это даёт calibration — если вероятность меньше 0.5, лучше откинуть результат как неуверенный. Для Ollama это делается параметром options: { logprobs: true }.
4 Пакетная обработка
LLM медленнее традиционных моделей. Один запрос — от 50 мс до 2 секунд в зависимости от размера модели и GPU. Поэтому не отправляйте тексты по одному — используйте батчинг (batch = 4-8) через многопоточность. Ollama поддерживает параллельные запросы. Но не перегружайте: если ошибка памяти, убавьте параллель.
Для высоконагруженных продакшенов стоит подумать о компромиссе: можно сначала прогнать через лёгкий классификатор (типа fastText), а на LLM отправлять только пограничные случаи. Идея описана в статье про минимальный размер LLM для классификации — там предлагают метод графа для понимания, когда без большой модели не обойтись.
Пример: классификация тикетов поддержки
Допустим, у вас десятки категорий: «оплата», «доставка», «возврат», «брак», «другое». Zero-shot справится, но есть риск, что модель будет путать «возврат» и «брак». Вот что помогает:
- Добавьте в промпт краткие дефиниции: «возврат — инициатива клиента вернуть товар, брак — дефект производства».
- Ограничьте число классов до 7-8. Если больше — используйте дерево: сначала бинарное ветвление («проблема с деньгами» vs «проблема с товаром»), потом внутри.
- Тестируйте на размеченном датасете хотя бы из 100 примеров. Ошибки — нормально, но если accuracy падает ниже 70% — бейте классы дальше.
Что ещё почитать?
Коллекция проверенных промптов для тестирования локальных LLM есть в отдельной статье: «Коллекция промптов для тестирования и сравнительного анализа локальных LLM». Там много готовых инструкций под разные задачи.
А если захотите пойти дальше и соединить классификацию с поиском — посмотрите гайд по RAG: «RAG за 15 минут: создаем свою систему на Python с нуля». Там как раз используется локальная LLM для анализа документов.
Частые вопросы (FAQ)
Сколько текстов можно обработать за минуту на домашнем GPU?
На RTX 4090 с Qwen3.5-14B — около 200-400 запросов в минуту при батчинге 4. Mistral Large 3 на том же GPU — ~30-50 запросов. Если нужно больше — либо берите меньшую модель, либо используйте облачные GPU.
Обязательно ли использовать logprobs?
Не обязательно, но сильно повышает надёжность. Без logprobs вы можете получить мусор, который разобьёт пайплайн. Рекомендую: если вероятность метки меньше 0.4, помечайте текст как «uncertain».
Какую модель выбрать для русского языка?
Лучшие на апрель 2026: Mistral Large 3 (отлично понимает русский), Qwen3.5-72B (дешевле), Saiga (Russian Mistral) — она специально дообучена. Но zero-shot через Saiga чуть хуже из-за меньшего разнообразия претрейна.
А если я хочу классифицировать на 50 классов?
Zero-shot с 50 классами — зона риска. Используйте иерархию или fine-tuning. Или возьмите эмбеддинги от LLM и обучите линейный классификатор — это быстрее и дешевле.
В конечном счёте, zero-shot классификация локальной LLM — отличный инструмент для быстрого старта и задач с невысокой нагрузкой. Но не забывайте про сравнение с традиционными подходами — иногда старый добрый TF-IDF с логистической регрессией уделывает LLM по скорости и цене.