Наверное, каждый знает, насколько порой бывает сложно справиться с написанием и редактированием текста: будь то банальная прокрастинация, «проблема чистого листа» или поиск ошибок и опечаток по всем правилам русского языка. А порой нам просто нужно сделать текст чуть попроще, чтобы случайно не перегрузить его сложными оборотами, или покороче, чтобы он вместился в маленький пост в соцсетях.
В начале года Браузер обновился и обзавёлся новыми нейросетевыми функциями. Сегодня мне бы хотелось остановиться на нейроредакторе, который облегчает монотонную и трудоёмкую работу с текстом. Под катом — история о том, как мы улучшали предыдущее решение и в итоге пришли к идее отдельного инструмента. Ещё расскажу, как мы обрабатываем кастомный промт и почему переписывание и генерация — это разные задачи.
Итак, в феврале 2024 года Браузер получил новую функцию — нейроредактирование. Тогда оно было доступно только в полях ввода на сайтах. И до сегодняшнего дня пользователь мог выбирать из трёх вариантов обработки текста: исправить ошибки, улучшить и сократить.
На тот момент модели, которые выполняли эти задачи, представляли собой следующее:
За каждое действие отвечала отдельная модель. В режиме исправления от модели требовалось исправить грамматические ошибки в тексте, в режимах сокращения и улучшения — переформулировать его.
Для каждой модели мы собрали свой датасет для обучения, состоящий из 5000 публичных текстов в интернете. Сами тексты при этом были максимально «живыми»: где‑то был сленг, мат, специальные символы.
Все модели были дообучением YandexGPT Lite с использованием LoRa‑адаптера. LoRa мы выбрали из‑за ограниченных размеров датасета и лучшего качества, которого получилось добиться.
После релиза мы поняли, что нет предела совершенству. Мы не могли обойти стороной поддержку английского языка для всех трёх фич для текстов. Для этого мы скооперировались с командой Переводчика: они помогли с реализацией англоязычной модели исправления и поделились датасетами для остальных моделей. Дистилляцию делали мы сами. И всё это — за пару‑тройку недель после релиза.
После этого началось затишье. Мы стали анализировать поведение пользователей и качать базовое качество.
А вот на базовом качестве остановимся подробнее. Для функции исправления ошибок в целом есть занятная история. Все же любят истории про техдолг?
Так вот, во время первого релиза у нас не было офлайн‑метрики для валидации экспериментов, и мы пользовались помощью редакторов для оценки качества моделей исправления ошибок. При этом казалось, будто эту историю легко автоматизировать и сэкономить время для валидации экспериментов.
В качестве стартовой точки у нас была диффалка (инструмент, который показывает разницу версий), написанная на Go. Диффалка работает на основе алгоритма поиска LCS‑индексов (Longest Common Subsequence): то есть мы ищем наидлиннейшие общие подпоследовательности. Обнаруженную разницу между текстами считаем изменением, которое внесла модель, и подсвечиваем красным, что было исправлено, и зелёным‑ на что исправили.
Теперь к идее: имея такой дифф, можно посчитать количество ошибок, которые модель не исправляет, и на основе этого валидировать гипотезы о качестве. Если мы умеем считать дифф, то нам ничего мешает посчитать его между двумя текстами, где первый текст — результат исправления от модели, а второй корректный вариант — от редактора. Далее ошибки можно специализировать на пунктуацию и орфографию. Благо тестовый датасет с парами «текст с ошибками — текст без ошибок» у нас уже был.
В итоге мы начали экономить время, но от редакторов не отказались: они помогают нам валидировать модели перед выкаткой в прод. Так мы можем быть уверены, что все работает, как нужно, и мы не упустили ничего критичного.
А теперь уже можно чуть больше поэксперементировать с разными вариантами моделей и способами обучений — чем мы и занялись. На текущий момент у нас есть три успешных кейса:
Переход с архитектуры Decoder на Encoder‑Decoder. Если кратко, то раньше модель состояла из одного основного элемента — декодера. Он пытался сделать два дела одновременно: понять суть текста с ошибками и исправить их.
При смене архитектуры появилось два элемента: кодировщик и декодер. У каждого своя цель: кодировщик пытается понять исходный текст и переводит его в векторное представление, в то же время декодер на основе информации из кодировщика и оригинального текста генерирует корректный вариант. В этом случае ответственность за результат разделяется на уровне архитектуры, что помогает модели либо стать умнее при том же количестве параметров, либо поумерить свои аппетиты и стать быстрее при схожем качестве.
Для нас такой переход оказался полезным: за счёт него мы сократили время генерации в два раза и не просели в качестве.
Curriculum learning. Это подход, похожий на то, как учатся люди, — постепенно, начиная с простых заданий и переходя к более сложным. Мы сделали так же: ушли от простого shuffle и стали сортировать обучающие примеры.
При этом для задачи исправления ошибок есть достаточно лёгкий, но не идеально точный способ понять сложность — расстояние Левенштейна. Чем оно больше, тем более сложный пример модель видит перед собой. Такой способ сортировки не учитывает саму сложность ошибок, но позволяет научить модель сначала просто копировать текст, а затем шаг за шагом исправлять всё больше ошибок.
Фаза претрейна. Причём это немного другая фаза, которая уже привычна тем, кто знаком с LLM. В нашем случае мы разделили процесс SFT на две части:
претрейн на слегка «грязном», но большом датасете;
дообучение на датасете поменьше, но очень хорошем.
Датасет для претрейна тоже собрали в две фазы:
Прогнали очень много текстов через уже готовую модель исправления ошибок и получили эталонные тексты.
Добавили искусственных ошибок: ничего хитрого, просто рандом и набор правил для случайной порчи текстов — опечатки, дублирование букв, лишние запятые.
За два этих действия мы получили пары текстов с искусственными ошибками и эталонные — те, в которых модель исправила большинство ошибок. Из‑за того, что модель неидеальна, у датасета есть некоторая доля неисправленных ошибок, но это компенсируется второй фазой дообучения на идеальных, но малочисленных данных.
Далее мы обучили новую модель, где сначала претрейнили её, а потом дообучили на идеальных данных. Так у нас получилось стабилизировать поведение на длинных текстах.
В итоге: благодаря всем этим экспериментам нам удалось сделать так, что модель исправления получила ускорение ×2, а на опенсорсных датасетах мы добились роста качества в среднем +10% в зависимости от домена.
Итак, у нас есть пул моделей для узкого спектра задач, есть поддержка различных сторонних редакторов и есть пользователи, чей фидбэк можно узнать. И мы поняли, что нам нужен…
Мы засучили рукава и пошли в народ провели кастдев. Мы выяснили, что нейроредактирование пользователям нравится, однако есть один неудобный момент: необходимо искать точку входа, то есть поле для ввода текста, на что тратится время.
Так мы поняли, что отдельный редактор — это логическое продолжение развития всей фичи нейроредактирования.
Почему? Во‑первых, мы даём постоянную точку входа, а во‑вторых, — возможность редактировать полученный от одной модели текст другими моделями. Счастье, пони и кисельные берега.
Интересный факт: обычно сервисы сначала позиционируются как простые текстовые редакторы или инструменты для перевода текста, а уже потом добавляют ИИ‑фишки для упрощения работы. Браузер сделал ровно наоборот: сначала ИИ‑фишки, а потом уже редактор 🙃
При этом пользователя уже сложно чем‑то удивить. Большинство фич современного редактора — это уже просто правила хорошего тона, например поддержка Markdown или облачные сохранения. Поэтому мы ориентируемся на упрощение и удобство использования, причиняя юзеру пользу с помощью LLM.
Вопрос, как приносить пользу таким способом, в целом сложен. Во‑первых, непонятно, какие функции нужны, а какие не будут пользоваться спросом. Во‑вторых, сделать редактор удобным с точки зрения UI тоже непросто: нужно быть ненавязчивым и при этом предлагать несложные, но частотные однокнопочные действия.
С самого начала мы понимали, что хотим позиционировать себя как инструмент. Находится ли под капотом LLM или что‑то другое не так уж и важно, если оно выполняет свою задачу. Из‑за таких вводных мы пришли к выводу, что первостепенный элемент редактора — это поле для ввода текста, а всё остальное — это дополнительное удобство.
Теперь к более интересным вещам. Мы расширили количество кнопочных действий — они все спрятались на кнопочке «Переписать».
Суммарно получается девять действий:
исправление ошибок;
перевод текста;
улучшение (произошёл ребрендинг, и теперь это называется «Красиво»);
другими словами;
кратко;
проще;
сложнее;
более формально;
в разговорном стиле.
Все эти действия мы выбирали по кастдевам, количественным экспериментам, анализу конкурентов и просто ранжируя внутренней чуйкой. При этом мы понимаем, что ими невозможно покрыть всё многообразие вариаций работы с текстом. Поэтому мы добавили возможность решить любую задачу, которую можно выразить в виде указаний по преобразованию текста.
Чтобы получить указания и выполнить, их нужно куда‑то сформулировать. Для этого появилось поле ввода «Помочь с текстом» или, как мы называем эту фичу между собой, — «кастомный промт».
Главная функция — приблизить наш редактор к возможностям диалоговых систем, позволяя ему выходить за границы «готовых» кнопок, но не лишая пользователя состояния потока при работе с текстом.
Редактор — более нестандартный продукт, чем привычные диалоговые системы. При работе с ними важную роль играет контекст, на котором и строится помощь пользователю. Как правило, системе нужно хранить в памяти 5–10 сообщений, чтобы правильно выполнять все запросы. В редакторе мы решили оставить контекст в виде одного.
Мы дообучили модель редактора на задачи, которые отличались от базовых. Это позволило заинферить модель на основе YandexGPT 3 Light, но при этом не просесть в качестве относительно результатов работы модели версии YandexGPT 3 Pro.
Но чтобы дообучить модель, нужно сперва понять, какие именно текстовые задачи пользователи чаще всего решают с помощью диалоговых систем. Что мы сделали:
Получили обезличенный диалоговый датасет с GPT‑моделями и принялись за анализ.
Отсеяли обычные разговоры от задач, которые выполняются генеративными моделями (то есть непосредственно от текстовых задач).
Сделали классификатор, который умеет понимать, какая из задач с текстом происходит: переписывание, дописывание, генерация или фактчек.
Отфильтровали классификатором только задачи на переписывание и дописывание.
Поделили с помощью YandexGPT 3 Pro сообщения в диалогах на две части: обрабатываемый текст и промт.
Интересный момент: на четвёртом этапе мы поняли, что задачи переписывания и генерации находятся в соотношении 1: 1, и тогда мы начали смотреть, как именно просят переписать текст.
Чтобы понимать картину в целом, нам нужно было проанализировать именно поток промтов. Однако в логах это не всегда связка «промт + текст», чаще всего это что‑то раздельное: текст, промт, уточнение. Часто эти задачи могут чередоваться только с помощью контекста в процессе диалога. Поэтому нам и понадобился пятый этап и помощь YandexGPT 3 Pro.
Кстати, именно так мы выяснили, что самым частотным запросом оказался рефраз. Пользователи просили просто переписать текст без какой‑то конкретики. Так и появилось действие «Другими словами».
Потом мы начали работу над кастомным промтом. Изучая диалоговый датасет, мы заметили, что в нём есть много запросов улучшить текст и исправить ошибки. А это значит, что диалог с моделью стал более привычной формой общения с нейросетями. А ещё после бета‑тестирования мы пришли к выводу, что пользователь ожидает одинаковый результат и после нажатия кнопки «Исправить», и после запроса «Исправь текст» в строке для промтирования.
Возможно, многим действительно проще взаимодействовать через текстовые команды, чем через кнопочные решения. Поэтому мы сделали так, что независимо от способа запроса (через кнопку или промт) классификатор перенаправит пользователя в ту модель, которая заточена под эту задачу.
В итоге сформировалась архитектура, в которой есть запрос, классификатор и модели, принимающие задачи. Мы разделили оставшиеся кастомные задачи на две категории: переписывание и генерация. Переписывание включает изменение структуры текста без изменения его смысла, в то время как генерация добавляет и придумывает новый контент.
Также мы выбрали задачи, в которых точно уверены, что универсальная модель не сможет приблизиться в качестве к специализированным: исправление ошибок, улучшение и рефраз. Дообучили классификатор на каждую из них и начали перенаправлять запросы.
На схеме видно, что поток распределяется на пять частей, каждый — отдельная модель. Их распределяет классификатор, который умеет определять семь категорий. Три из них — исправление, улучшение и рефраз — это те же самые модели, которые доступны по нажатию кнопки в верхней части редактора.
Оставшиеся задачи — переписывание, дописывание, генерация и фактчек — это категории, на которые мы разбиваем задачи с текстом. И если их отношение друг к другу попытаться визуализировать, то выглядит это примерно так:
Переписывание — это задача, в которой вы явно просите обработать каким‑то образом текст, не предполагая, что смысл текста значительно изменится, но ожидая, что изменится структура, стиль или действующее лицо. Например, под это подходит промт «Сократи текст в один абзац».
Дописывание — задача, которая предполагает, что вы хотите дописать новую информацию в конкретное место уже существующего текста. Например, подойдёт промт «Допиши заключение».
Генерация похожа на дописывание, но главное её отличие в том, что здесь результат в основном содержит новую информацию. То есть здесь мы ориентируемся в первую очередь на создание текста с нуля.
Фактчек — это подвид генерации. Он отличается тем, что запрос ориентирован не на креативный контент, а на получение энциклопедической информации. Запросы в этой категории выглядят примерно так: «Когда был совершён первый полет в космос?»
Эти четыре категории мы классифицируем как разные, но в продовой схеме переписывание и дописывание, а также генерация и фактчек объединены — по разной модели на каждую специализацию.
Такой подход со специализированными моделями становится для нас выгодным. Во многом за счёт облегчения нагрузки: специализированные модели проще дообучать, они точнее, компактнее и быстрее, но менее функциональны, чем одна большая универсальная модель. При этом для нас не проблема при необходимости разделить поток и генерации, и фактчека: отдельно генерация с большим креативом и отдельно фактчек с более точной информацией.
Возможно у вас возник закономерный вопрос: «Зачем разделять достаточно схожие задачи, которые обычно решаются одной моделью?»
Мы исходили из идеи, что в отличие от задачи генерации, где первостепенна именно широта знаний и минимальный набор навыков, переписывание обычно требует бо́льшего разнообразия навыков и небольшой широты знаний о мире. Именно из‑за этого (а ещё из‑за желания иметь возможность точечно донастраивать модели под наш домен) было принято решение разделить эти задачи.
В случае переписывания у нас был понятный набор критериев, которые мы хотели доработать: точечно «дотюнить» навыки, сделать модель менее разговорчивой и расширить поддержку Маркдауна.
Под капотом наш редактор отправляет в модели текст, размеченный Маркдауном. Новые модели делались с учётом этого условия. Но старые модели нейроредактирования делались без этого требования.
Если вы давно пользуетесь нейроредактированием, то могли столкнуться с тем, что модели достаточно свободно обращались с Маркдауном. Они запросто могли убрать часть спецсимволов или, наоборот, добавить: поведение было слегка непредсказуемым. Об этом мы узнали от редакторов, которые тестировали процесс автокорректировки опечаток и ошибок: они как раз использовали тексты с разметкой.
Само собой, такое поведение нужно было исправить. Мы пошли путём восстановления разметки. Он состоит из трёх этапов:
Прогон маркдаун‑текста через модель.
Воскрешение пропавшей разметки руками редакторов.
Переобучение модели.
Это позволило добиться сохранения разметки 1: 1 в модели исправления, в случае улучшения и сокращения числа получились скромнее из‑за более агрессивного обращения моделей со структурой текста.
Нейроредактор получился одним из самых масштабных нейропроектов Браузера. Здесь объединились знания и усилия множества команд. Он вызвал огромное количество баталий и споров. За кулисами — титаническая работа большой команды фронтендеров, бэкендеров и менеджеров. А ещё это позволило расширить количество активных специально обученных для браузера LLM‑моделей семейства YandexGPT — теперь их более 15.
Для нас этот проект стал настоящим испытанием. Надеемся, что это обновление сделает вашу браузерную жизнь комфортнее и проще.
Пишите и переписывайте!