Вот так когда-то отвечала языковая модель, когда её просили привести пример стихотворения Бальмонта. Стихотворение с таким названием действительно есть, но начинается оно совсем не так:
Есть в русской природе усталая нежность,
Безмолвная боль затаенной печали,
Безвыходность горя, безгласность, безбрежность,
Холодная высь, уходящие дали.
К сожалению, генеративные модели могут галлюцинировать и выдумывать ответ. С таким мы боремся с помощью внешней информации.
Мы, Александр Кайгородов и Светлана Маргасова, обучаем генеративные модели в Яндексе. В этой статье мы расскажем, как заставить генеративные модели перестать придумывать несуществующие факты и как научиться находить эти ошибки, если они всё же случаются. Вы узнаете о том, как использовать внешнюю информацию, опираясь на которую мы можем выполнять как обусловленную генерацию (Retrieval Augmented Generation), так и фактологическую оценку имеющихся генераций (Fact-Check).
Зачем нам внешняя информация для генерации
Первая и самая главная причина — для борьбы с галлюцинированием. Но есть ещё несколько проблем, которые мы попутно решаем с помощью внешней информации (этот подход называется RAG — Retrieval Augmented Generation):
Расширяем ограниченное представление о мире, которое модель получила в ходе претрейна. Например, знакомим модель с последними новостями и событиями, которые на момент её обучения ещё просто не произошли.
Узнаём, какой источник данных модель использовала для ответа.
С внешней информацией мы работаем на двух стадиях: на первой ищем релевантный запросу документ, а на второй генерируем ответ на основании запроса пользователя и найденного документа.
Под каждую задачу мы собираем базу знаний из материалов, которые потенциально полезны для генерации. Наполнение базы зависит от форматов, с которыми умеет работать генеративная модель (тексты, таблицы, картинки), и от задачи. Например, для задач общей тематики мы можем положить туда тексты из интернета или снапшоты из «Википедии». А если хотим встраивать модель в поддержку какого-нибудь сервиса — положим тексты документации.
Для обучения нужны размеченные запросы и ответы — классические данные для SFT (supervised fine-tuning). При этом релевантных документов для конкретного запроса у нас нет. Чтобы найти их в базе знаний, мы используем модель Retriever. Она строит эмбеддинг запроса и всех документов в базе и находит ближайшие по определённой метрике близости, например по косинусному расстоянию. Для построения эмбеддингов можно использовать модели на основе BERT или GPT, предобученные на парах близких текстов.
Мы используем одну модель с двумя разными линейными слоями на выходе, но можно строить и две полностью независимые — под запросы и под документы. Обычно модель Retriever небольшая в сравнении с генеративной.
Модель Generator — это классическая GPT-подобная языковая модель. Но если обычно на вход поступает только запрос, то здесь мы подаём ещё и документ — как правило, просто конкатенируем его с запросом.
Модели мы обучаем вместе. Во время обучения мы извлекаем k релевантных документов из базы.
Лосс модели Generator — это обычная кросс-энтропия на каждой тройке «запрос — ответ — документ».
А чтобы обучать Retriever, нам нужно выдавать тексты, которые будут полезнее для генерации. Для этого мы можем сближать распределение релевантности документов относительно запроса, которое выдаёт Retriever, с распределением правдоподобия ответа при условии каждого из документов, которое выдаёт Generator.
Для сближения мы минимизируем KL-дивергенцию.
Казалось бы, всё понятно и просто, но в процессе реализации этой схемы мы столкнулись с рядом проблем.
С какими проблемами мы столкнулись и как их решали
Проблема 1. Эмбеддинги документов устаревают. Если база знаний большая, то перестраивать эмбеддинги при каждой итерации сложно. Вместо этого мы их замораживаем. Ещё можно обновлять базу раз в несколько итераций (а не каждую итерацию) или использовать модель-переаранжировщик, которая обучает эмбеддинги документов и выдаёт из большой выдачи нужные k документов.
Проблема 2. Когда база документов объёмная, локально искать близкие документы долго и тяжело. Для этого пришлось бы хранить локально все эмбеддинги, чтобы искать по ним ближайшие, и все тексты, чтобы подавать в генерацию. Мы сделали специальный сервис, который позволяет ходить по API в аппроксимированный KNN, отправлять эмбеддинг запроса и получать эмбеддинги k релевантных документов.
Проблема 3. Во время обучения Generator может научиться просто игнорировать внешнюю информацию. Чтобы с этим бороться, мы пробовали разные подходы и их комбинации.
На обучении можно подсматривать в ответ и доставать релевантные документы на основании не только запроса, но и ответа. А во время инференса дистиллировать такую модель Embedder в классический Embedder, который видит только запрос.
Также помогает подливка supervised-данных, когда есть разметка на запросы и ответы и релевантные для ответа документы.
Можно смотреть на retrieval utility — профит от каждого документа, который находится как разница лосса Generator без документа и с документом. То есть из лосса Generator, при котором он вообще не видит никакой документ, мы вычитаем лосс, когда он видит документ. А если документы не нужны и Generator и так выдаёт хороший ответ, то мы штрафуем такие примеры и взвешиваем лосс пропорционально профиту.
Проблема 4. Embedder-коллапс — это ситуация, когда модель начинает выдавать одни и те же документы на любые запросы. С этим тоже помогает retrieval utility. Мы не обучаем Embedder на примерах, где нет пользы ни от одного документа, потому что там не нужно сближать релевантности.
Ещё при Embedder-коллапсе помогают штрафы за выдачу для повторяющихся документов.
Какие результаты мы получили
Чтобы проверить, насколько наши решения повышают точность, у нас есть разные срезы в инструкциях. Один из них — срез Open QA. Это особый тип запросов к модели, которые требуют от неё демонстрации знаний о внешнем мире и глубокого понимания связей между этими знаниями. Например, когда и где родился архитектор Константин Мельников. Baseline-модель (то есть модель, работающая без предоставления внешней информации в контексте генерации) может придумать город и год, но RAG-модель посмотрит сниппет из «Википедии» и ответит правильно.
Асессорская разметка показывает, что в таких случаях RAG выигрывает по SbS 60% на 40% против baseline. А вот с общими вопросами всё сложнее, потому что разница не настолько большая: 55% побед у RAG против 45% у baseline (это значит, что только в 10% случаев ответы одной модели лучше другой). В целом у нас сокращается количество логических и фактических ошибок, к тому же бонусом у нас уменьшается сложность текста. Но, к сожалению, некоторые пункты у нас растут: сомнительная информация, неполный ответ и вредность. С этим всем мы боремся.
Если резюмировать, то при применении RAG уменьшается количество фактических и логических ошибок, а также повышается понятность генерации. Но при этом инференс становится дольше (нужно потратить время на retrieval, и к тому же контекст увеличивается на длину документа) и повышаются затраты на хранение базы данных.
Важно учесть, что в предыдущих замерах фактические логические ошибки мы оценивали асессорами. Это долго, и нам бы хотелось автоматизировать этот процесс. Об этом — в следующем разделе.
Фактчек: как проверять ошибки моделью, а не людьми
Раньше фактические и логические ошибки выявляли асессоры в процессе разметки. Сейчас мы используем автоматизированный пайплайн фактологической проверки частей генерации с использованием внешней информации. Фактчек позволяет оценивать правдивость генерации с помощью поиска в ней истинных и ложных утверждений модели.
На первом этапе мы выделяем факты — логические сегменты, которые будем проверять. Давайте попробуем это сделать на примере праздников в Италии. В данном случае разбили генерацию ответа по пунктуации.
Далее переходим к работе с внешней информацией: проверим, действительно ли Рождество в Италии празднуют в эти даты.
Для проверки сгенерированных фактов необходимы документы. Для поиска документов необходимо сформировать запрос в Яндекс Поиск. На данный момент мы используем модель YandexGPT в режиме Zero-Shot , но ищем способы повышения качества генерации вопросов через Few-Shot или SFT. Для генерации вопроса мы подавали изначальный запрос, выделенный факт и генерацию-ответ. То есть мы подали полный контекст того, на что обращает внимание человек, когда пытается проверить факт.
Далее — поиск. Мы можем задавать несколько вопросов для проверки одного факта. По каждому из них мы получаем группу из топ-k (в наших экспериментах чаще всего k = 9) релевантных им документов. Так мы получаем опорные документы, которые подтверждают или опровергают факт генерации. В данном случае опорный документ позволяет ответить на вопрос, реальный это факт или ложный.
Следующий шаг — мультикласс-классификация фактов. Каждый класс представляет из себя оценку фактологичности факта.
Такой классификатор на входе использует запрос, факт и документ, при помощи которого проводится оценка истинности факта (при этом таких документов может быть несколько). Классификация выполняется поточечно, относительно каждого конкретного факта в генерации по релевантному для факта документу.
В данном случае мы нашли группу документов, каждый из которых опровергает факт из генерации, то есть разночтений нет. Но бывает, что документы дают противоречивую информацию: какие-то подтверждают факт, какие-то опровергают.
То же самое — с агрегацией фактчека в отношении каждого факта по всей генерации. В своих задачах мы стараемся принимать наиболее консервативные решения. В случае если хотя бы один из документов опровергает истинность факта или ни один документ не может достаточно уверенно подтвердить его истинность, то мы принимает решение о ложности факта. Аналогично мы принимаем решение о ложности всей генерации, даже если есть хотя бы один ложный факт.
Разметка и оценка качества
Чтобы обучить модель скоринга и оценить качество всего пайплайна, нам требуются размеченные данные. Как было сказано выше, для разбивки генерации на факты используется подход с RegEx. Данная разбивка попадает в руки асессорам, которые с помощью поиска или предварительно подобранных документов проводят фактологическую разметку сегментов.
В разметке для обучения модели мультимногоклассовой классификации мы разделяем на семь классов:
Правда.
Полуправда.
Мнения разделяются.
Факт не требует подтверждения.
Факт не подтверждается внешними источниками.
Ложь.
Абсурд.
Теперь мы можем тренировать модели пайплайна и проводить его итоговую оценку, используя полученную разметку фактов генерации. Оценку можно проводить как на уровне всей генерации, так и на уровне фактов.
В качестве baseline-решения мы попросили модель самостоятельно проводить фактчек внутри её же генерации, но без использования документа. ROC-AUC-метрика показывает, что, используя внешние источники информации для осуществления проверки фактов генерации, мы повышаем качество классификации на 20%.
Вместо выводов
Итак, мы можем использовать внешнюю информацию для двух задач: генерации и фактчека. Причём при фактчеке мы, по сути, делаем то же самое, но в обратном порядке.
Представленные подходы с использованием внешней информации уже сейчас показывают хорошие результаты повышения фактологичности генераций и позволяют автоматизировать процедуру фактчека генераций моделей. Ну а мы будем продолжать улучшать подходы, чтобы наши нейросети никого не вводили в заблуждение, цитируя Бальмонта.