Как делать контекстуальные эмбеддинги для RAG
Кейс
Делаем RAG для некоторого набора длинных документов. Я недавно допилил продвинутую вариацию семантического чанкинга (нарезки текстов по смыслу), а именно — по темам (топикам). Кайф! Но вот мы нарезали наши тексты по тематическим чанкам, осталось запихать их в векторную базу. Как пихать?
Проблема
Разбили текст на чанки → потеряли глобальный контекст. К примеру, в чанке упоминается «игра», но непонятно — спортивная, компьютерная или настольная? Очевидно и вектор такого чанка закодирует семантику лишь о некоторой «абстрактной игре». Из-за такой неоднозначности релевантность выдачи снижается.
Да, можно вместе с чанком запихать в вектор саммари для всего документа [
1]. Но тогда у всех чанков будет один и тот же текст в составе, что опять же снизит их специфичность. Можем делать «гипотетические» HyDE-эмбеддинги [
2] или индексироваться прямо по саммари [
3]. Все эти подходы слегка нивелируют проблему, но не дают ощутимого прироста.
Вот бы для каждого чанка в контекст летела только та информация, которая снимала бы неоднозначность и «насыщала» его эмбеддинг для ретривала в наших RAG.
Contextual Retrieval от Anthropic 🤖
Решение понятное нам, дуболомам c A100. Давайте скормим LLM весь документ (закешируем промпт, разумеется) и спросим, что стоит утащить в качестве контекста для заданного чанка из текста. Пример полученной контекстуализации:
original_chunk = "The company's revenue grew by 3%"
contextualized_chunk = "This chunk is from an SEC filing on ACME corp's performance in Q2 2023; the previous quarter's revenue was $314 million. The company's revenue grew by 3% over the previous quarter."
Уже поверх таких чанков накидываем эмбеддинги, ну и приправим BM25 до кучи.
Я бы сказал, такое решение просто не может не работать. Мы в команде довольно активно насыщаем свои RAG подобными производными от LLM и это даёт заметные бусты. Очевидны и сложности — всё это может быть долго и дорого в зависимости от LLM, длины её контекста, объёма вашей базы, а также того, как именно вы изначально получили чанки.
Late Chunking от Jina AI ⏳
Как мы обычно векторизуем документ, разбитый на чанки? Да берём каждый чанк и кидаем в модельку независимо друг от друга, отсюда и пропажа контекста. А давайте подавать всё вместе?
То есть засовываем в эмбеддер весь документ, хиддены всех токенов у нас смотрят друг на друга и насыщаются засчёт Attention, а вот уже эти хиддены мы бьём по границам чанков и пулим в вектора. Отсюда и название
Late — мы просто откладываем момент разбиения на чанки до последнего. Один из тех приёмов, о которых кажется можно было додуматься и самому!
В статье Jina говорят, что Late Chunking и LLM Contextual Embeddings перформят аналогично, но первый очевидно дешевле, проще и заметно быстрее. С этим я, конечно, согласен, но и тут
всё не так однозначно™
.
Во-первых — ваш эмбеддер должен иметь такой же большой контекст, что и у LLM, чтобы скормить ему уж если не весь документ, то хотя бы значимую часть. Во-вторых — эмбеддер будет аттендиться между токенами по какой-то сокрытой логике. С помощью промпта подвести модельку к тому, на что стоит обращать внимание, уже не получится. В конце концов, если текст не влазит в контекст, вам снова придётся выбирать, что пришлёпнуть к чанку, чтобы модель смогла добрать нужного контекста. Где гарантия, что контекста, скажем, в 8к токенов вокруг чанка будет достаточно? В защиту могу дополнить, что мы можем пофайнтюнить эмбеддеры целенаправленно под Late Chunking и до какой-то степени невелировать эти недостатки.
Тем не менее, решение настолько простое, что не пробовать его на вкус в своих RAGу я считаю недопустимым для настоящих LLM гурманов!
· Late Chunking:
блог 1,
блог 2,
статья,
код
· Contextual Retrieval:
блог
· Картинка:
блог от Weaviate
Будем пробовать оба подхода, по результатам постараюсь как-то отписаться!
#Review #RAG #Retrieval