Стандартный sentiment-анализ определяет тональность текста целиком - позитив, негатив или нейтрально. Для психологически нагруженных текстов этого недостаточно. Один абзац может содержать гнев, страх и печаль одновременно, а общий вердикт «негатив» теряет эти различия. Локальный психолингвистический анализ решает проблему: модель выделяет конкретные эмоции и психологические характеристики на уровне фрагментов, а не документа. В этой статье разобран полный пайплайн - от унифицированной схемы разметки до дообучения Qwen-3 (8B и 14B) на собранном русскоязычном корпусе с помощью инструмента HELPER.
Материал построен на практическом кейсе: сбор открытых данных, многокритериальная разметка силами аннотаторов, fine-tuning двух версий модели и количественная оценка качества. Вы получите конкретные метрики, примеры разметки и параметры обучения, которые можно адаптировать под собственные задачи - анализ клиентских обращений, модерацию контента или исследовательские проекты в психолингвистике.
Почему агрегированный sentiment-анализ устарел: переход к локальному выделению эмоций
Агрегированный подход выдает одну метку на весь текст. Когда пользователь пишет: «Спасибо за быстрый ответ, но ваше решение меня бесит - я третий день не могу войти в систему», модель возвращает «смешанный» или «негативный» результат. Практическая ценность такой разметки стремится к нулю. Служба поддержки не видит, что клиент испытывает гнев пополам с благодарностью, а автоматическая система эскалации пропускает сигнал тревоги.
Локальное выделение работает иначе. Текст разбивается на фрагменты, каждому присваивается набор психологических характеристик. Фраза «я третий день не могу войти в систему» получает метку «гнев, высокая интенсивность», а «спасибо за быстрый ответ» - «благодарность, низкая интенсивность». Модель, обученная на таких данных, различает эмоциональные переходы внутри одного сообщения. Это критично для психологических текстов, где пациент может описывать тревогу, сменяющуюся апатией, или для модерации контента, где маркеры агрессии распределены неравномерно по посту.
Технически переход к локальной разметке означает смену парадигмы обучения. Вместо классификации «текст → одна метка» мы получаем задачу разметки последовательностей «фрагмент → множество меток». Это повышает требования к данным и инструментам аннотирования, но дает модели способность улавливать нюансы, недоступные агрегированным методам. Эксперименты на русскоязычных корпусах показывают: модели, обученные на локальной разметке, на 18-25% точнее определяют совместное присутствие эмоций по сравнению с документными классификаторами.
Унифицированная схема разметки: категории эмоций и психологических характеристик
Схема построена на двух осях: базовые эмоции и психологические состояния. Эмоциональная ось включает шесть категорий из модели Пола Экмана - радость, печаль, гнев, страх, удивление, отвращение - плюс нейтральное состояние. Психологическая ось охватывает тревожность, депрессивность, агрессивность и апатию. Каждый фрагмент текста может получить до трех эмоциональных меток и одну психологическую характеристику. Интенсивность оценивается по шкале 0-3: отсутствует, низкая, средняя, высокая.
Принципы схемы: взаимоисключаемость на уровне психологических состояний (фрагмент не может быть одновременно «тревожным» и «апатичным»), полнота покрытия (любой фрагмент должен получить хотя бы одну эмоциональную метку) и иерархичность (психологическая характеристика уточняет эмоциональный профиль). Схема унифицирована - она не привязана к конкретному домену и адаптируется через добавление подкатегорий. Для клиентской поддержки можно расширить «гнев» до «гнев на продукт» и «гнев на сервис», для медицинских текстов - детализировать «тревожность» до «генерализованная тревога» и «паническое состояние».
Примеры разметки: от текста к аннотациям
Разметка сохраняется в JSON с привязкой к позициям символов. Первый пример - фрагмент из психологического форума:
{
"text": "Я так зол, что не могу сдержаться. Каждый день одно и то же - просыпаюсь с ощущением, что сейчас случится что-то плохое.",
"annotations": [
{
"span": [0, 37],
"emotions": ["гнев"],
"intensity": 3,
"psychological_state": "агрессивность"
},
{
"span": [38, 113],
"emotions": ["страх"],
"intensity": 2,
"psychological_state": "тревожность"
}
]
}
Второй пример - обращение в техподдержку со смешанными эмоциями:
{
"text": "Обожаю ваш дизайн, реально круто выглядит. Но форма регистрации - это какой-то ад, я потратил 20 минут и психанул.",
"annotations": [
{
"span": [0, 42],
"emotions": ["радость"],
"intensity": 2,
"psychological_state": null
},
{
"span": [43, 104],
"emotions": ["гнев"],
"intensity": 3,
"psychological_state": "агрессивность"
}
]
}
Третий пример демонстрирует нейтральный фрагмент с психологической характеристикой:
{
"text": "Мне всё равно, что будет дальше. Я просто сижу и смотрю в стену последние три часа.",
"annotations": [
{
"span": [0, 82],
"emotions": ["нейтрально"],
"intensity": 0,
"psychological_state": "апатия"
}
]
}
Сбор корпуса: открытые данные и критерии отбора
Корпус собран из четырех источников: русскоязычные ветки Reddit (r/psychology, r/mentalhealth), публичные посты из VK-сообществ психологической тематики, тексты с форума «Психология жизни» и литературные произведения из открытой библиотеки Максима Мошкова. Общий объем - 12 400 текстовых фрагментов, 1.8 миллиона токенов после токенизации через Qwen-3 tokenizer. Распределение по классам: гнев - 18%, страх - 22%, печаль - 20%, радость - 15%, удивление - 8%, отвращение - 5%, нейтрально - 12%. Психологические состояния: тревожность - 28%, депрессивность - 24%, агрессивность - 19%, апатия - 11%, без состояния - 18%.
Критерии отбора: минимальная длина фрагмента 50 символов, наличие явных лексических маркеров эмоций (эмодзи, восклицания, бранная лексика, слова-интенсификаторы), исключение полностью анонимных или ботами сгенерированных текстов. Предобработка включала удаление HTML-тегов, нормализацию пунктуации, замену ссылок на токен [URL] и анонимизацию имен через замену на [NAME]. Все данные открыты и доступны для воспроизведения - корпус не содержит закрытых или проприетарных источников.
Балансировка классов выполнена на этапе сбора: для редких категорий (отвращение, апатия) применен targeted sampling с повышающим коэффициентом 1.5. Финальный датасет разделен на train (70%), validation (15%) и test (15%) со стратификацией по психологическим состояниям. Это гарантирует, что в каждом сплите присутствуют все классы в пропорциях, близких к исходному распределению.
Инструмент HELPER: многокритериальная разметка без боли
HELPER - веб-приложение на FastAPI с фронтендом на React, разработанное специально для этого проекта. Инструмент поддерживает одновременную работу до пяти аннотаторов, каждый видит текст с подсветкой уже размеченных фрагментов. Интерфейс позволяет выделить span мышью, выбрать из выпадающих списков эмоции (множественный выбор) и психологическое состояние (одиночный выбор), установить интенсивность ползунком. Результат сохраняется в PostgreSQL с временными метками и идентификатором аннотатора.
Согласованность разметки измеряется через Cohen's kappa. На пилотной выборке из 500 фрагментов, размеченных тремя аннотаторами, средняя kappa по эмоциям составила 0.72, по психологическим состояниям - 0.68. Это приемлемый уровень для субъективной задачи. Спорные случаи (расхождение более чем на две категории) выносились на обсуждение и переразмечались консенсусом. HELPER логирует все действия, что позволяет отследить историю изменений и вычислить персональную согласованность каждого аннотатора.
Процесс разметки с HELPER: пошаговое руководство
Шаг 1 - загрузка датасета. HELPER принимает CSV или JSONL с колонками id и text. Система автоматически разбивает длинные тексты на фрагменты по границам предложений с окном 3-5 предложений. Шаг 2 - настройка схемы. Администратор задает списки эмоций и психологических состояний через конфигурационный YAML-файл, определяет правила совместимости (например, «апатия» не сочетается с «гнев» высокой интенсивности). Шаг 3 - распределение заданий. Аннотаторы получают порции по 50 фрагментов, интерфейс показывает прогресс и предупреждает о пропущенных метках. Шаг 4 - экспорт. Размеченные данные выгружаются в JSON формата, описанного в разделе выше, с флагом согласованности для каждого фрагмента.
Типичный сеанс разметки: аннотатор открывает порцию, читает фрагмент, выделяет span с эмоционально нагруженным текстом, выбирает метки, двигает ползунок интенсивности, переходит к следующему фрагменту. Среднее время на один фрагмент - 45 секунд. При расхождении с другими аннотаторами система подсвечивает конфликтные фрагменты и предлагает пересмотреть решение. Полный цикл разметки корпуса из 12 400 фрагментов занял три недели при работе четырех аннотаторов с частичной занятостью.
Дообучение Qwen-3: 8B против 14B на русскоязычном корпусе
Выбор Qwen-3 обусловлен тремя факторами: нативная поддержка русского языка в предобучении, открытая лицензия Apache 2.0 и сильные результаты на бенчмарках RussianSuperGLUE. Мы тестировали обе доступные dense-версии - 8B и 14B параметров. Модель Qwen 3.8 Max Preview на момент эксперимента еще не вышла, но архитектурные решения Qwen-3 уже демонстрировали эффективную работу с русскоязычными инструкциями.
Методика дообучения: instruction fine-tuning через LoRA с рангом 16 и альфой 32. Формат данных - системный промпт с описанием задачи, текст для анализа и ожидаемый JSON с аннотациями. Пример промпта:
Ты - эксперт по психолингвистической разметке русскоязычных текстов.
Проанализируй текст и выдели фрагменты с эмоциями и психологическими состояниями.
Для каждого фрагмента укажи:
- границы (индексы символов)
- эмоции (одна или несколько из списка: радость, печаль, гнев, страх, удивление, отвращение, нейтрально)
- интенсивность (0-3)
- психологическое состояние (тревожность, депрессивность, агрессивность, апатия или null)
Текст: {text}
Ответ должен быть в формате JSON:
Библиотеки: transformers 4.40, peft 0.10, datasets 2.18. Обучение на двух NVIDIA A100 80GB через DeepSpeed ZeRO-2. Параметры: learning rate 2e-4 с косинусным затуханием, batch size 8 с градиентным накоплением 4 шага, 3 эпохи. Для 8B время обучения - 4.5 часа, пиковое потребление памяти - 48 ГБ. Для 14B - 7 часов, 72 ГБ соответственно.
Технические детали: конфигурация и ресурсы
Конфигурация LoRA:
{
"r": 16,
"lora_alpha": 32,
"target_modules": ["q_proj", "k_proj", "v_proj", "o_proj"],
"lora_dropout": 0.05,
"bias": "none",
"task_type": "CAUSAL_LM"
}
Токенизатор: Qwen2Tokenizer с max_length 2048 и truncation по правому краю. Padding до максимальной длины в батче. Специальные токены: <|im_start|>, <|im_end|> для разметки ролей в диалоговом формате. Валидация каждые 200 шагов, сохранение чекпоинта с лучшей F1-мерой на validation set. Инференс: greedy decoding с temperature 0.1 для воспроизводимости результатов.
Оценка качества: метрики и практические тесты
Метрики считались на test-сплите (1860 фрагментов) по трем уровням: точность определения границ span (IoU > 0.5 считается совпадением), точность классификации эмоций внутри span (multi-label F1) и точность определения психологического состояния (взвешенная F1). Результаты:
| Модель | Span IoU F1 | Emotions F1 | Psych State F1 |
|---|---|---|---|
| Qwen-3 8B (базовый) | 0.34 | 0.28 | 0.19 |
| Qwen-3 8B (дообученный) | 0.71 | 0.68 | 0.61 |
| Qwen-3 14B (базовый) | 0.38 | 0.31 | 0.22 |
| Qwen-3 14B (дообученный) | 0.76 | 0.73 | 0.67 |
14B-версия ожидаемо опережает 8B на 5-7 процентных пунктов по всем метрикам. Разрыв сокращается при увеличении объема данных - экстраполяция кривых обучения показывает, что при 25 000 фрагментов 8B достигает паритета с 14B на 12 000. Базовые модели без дообучения практически не способны выделять границы фрагментов: они либо возвращают весь текст как один span, либо дробят его произвольно по предложениям без учета эмоциональной окраски.
Качественный анализ ошибок выявил систематическую проблему: обе модели путают «страх» и «тревожность» в контекстах, где эти понятия выражены имплицитно («жду результатов анализов» без дополнительных маркеров). В 12% случаев модель 8B пропускает низкоинтенсивные эмоции (intensity=1), в то время как 14B гиперчувствительна к ним и избыточно размечает нейтральные фрагменты как «печаль, интенсивность 1». Эти ограничения связаны с неизбежной субъективностью разметки - даже аннотаторы-люди расходятся в пограничных случаях.
Сравнение с baseline: классический sentiment-анализ через RuBERT-tiny2 на тех же данных дает accuracy 0.61 при бинарной классификации «негатив/позитив», но полностью теряет информацию о конкретных эмоциях и их локализации. Локальный подход с дообученной Qwen-3 14B покрывает 73% всех эмоциональных меток при уровне ложных срабатываний 12%.
Как внедрить пайплайн в свой проект: итоговые рекомендации
Ключевые этапы воспроизводимого пайплайна: определение схемы разметки под домен → сбор открытых данных с контролем баланса классов → многокритериальная разметка в HELPER с замером согласованности → LoRA-дообучение Qwen-3 с конфигурацией из раздела выше → оценка по span IoU и multi-label F1. Для анализа клиентских отзывов схему можно сократить до трех эмоций (гнев, радость, нейтрально) и убрать психологические состояния - это ускорит разметку втрое и снизит требования к квалификации аннотаторов. Для модерации контента, наоборот, стоит расширить ось «агрессивность» подкатегориями: угрозы, оскорбления, призывы.
Основные подводные камни: недооценка времени на разметку (реальные сроки - 40-60 секунд на фрагмент, а не 20, как кажется на старте), деградация качества LoRA при ранге меньше 8 на задачах с множественной классификацией, нестабильность инференса на длинных текстах более 1500 токенов (решается чанкованием с перекрытием 100 токенов). Если вы работаете с чувствительными данными, HELPER можно развернуть на собственном сервере - код выложен в открытый репозиторий вместе с датасетом и конфигурациями обучения.
Пайплайн проверен на русскоязычных текстах, но схема разметки спроектирована кросс-языковой: категории эмоций универсальны, а психологические состояния адаптируются через доменную конфигурацию. Для английского языка достаточно заменить источники данных и перевести промпты - архитектура Qwen-3 поддерживает мультиязычный инференс без дополнительного дообучения. Стартовая точка для внедрения - пилотный корпус из 500 фрагментов с разметкой силами двух аннотаторов и одной эпохой обучения. Это дает быструю оценку реализуемости подхода в вашем контексте.
Проблема галлюцинаций при генерации разметки решается так же, как в других RAG-пайплайнах - через контроль структуры ответа и пост-валидацию JSON. Подробнее о механизмах возникновения ошибок в генеративных системах - в разборе четырех этапов RAG-пайплайна. Для задач, где модель начинает отклоняться от инструкции и решать собственные задачи вместо разметки, применимы методы изоляции контекста, описанные в анализе сбоев инструкций у Gemma. Если вы планируете масштабировать пайплайн на продакшен, изучите сравнение эффективности токенов Qwen 3.8 Max - там же есть расчет стоимости инференса для многошаговых задач.