Перейти к содержанию

2.5 Семантический поиск. Продвинутые стратегии

Точная выдача релевантной информации из больших корпусов — ключ к умным системам, таким как чат-боты и системы вопросов и ответов (QA). Базовый семантический поиск является хорошим началом, но встречаются «краевые случаи», когда его качества и разнообразия результатов недостаточно. В этой главе мы рассмотрим продвинутые техники извлечения (retrieval) для повышения точности и разнообразия поиска.

Поиск, опирающийся только на семантическую близость, не всегда даёт наиболее информативный и разнообразный набор результатов. Расширенные методы добавляют механизмы контроля разнообразия и релевантности, что особенно важно для сложных запросов, требующих учёта нюансов.

Переходим к Maximum Marginal Relevance (MMR). MMR (Maximum Marginal Relevance) балансирует релевантность и разнообразие: он выбирает документы, которые одновременно близки к запросу и при этом непохожи друг на друга. Это снижает избыточность и помогает охватить разные аспекты ответа.

Процедура выглядит следующим образом: сначала отбираются «кандидаты» по семантической близости; затем из них выбирается итоговый набор, одновременно учитывающий релевантность запросу и непохожесть на уже выбранные документы. Так формируется более «широкий» и полезный набор результатов.

Далее — Self-Query Retrieval. Метод Self-Query Retrieval подходит для запросов, которые содержат как семантическую информацию, так и метаданные (например: «фильмы про пришельцев, снятые в 1980 году»). Он делит запрос на смысловую часть (для поиска по эмбеддингам) и фильтр по метаданным (например, «год выпуска = 1980»).

Наконец, Contextual Compression (контекстуальное сжатие) — это извлечение из найденных документов только самых релевантных фрагментов. Этот подход полезен, когда нет необходимости использовать весь документ целиком. Он требует дополнительной обработки (поиска релевантных частей), но значительно повышает точность и конкретику ответа.

Переходя к практической части продвинутых техник ретривала для усиления семантического поиска, отметим, что извлечение релевантных документов — критически важный этап в системах RAG (Retrieval-Augmented Generation), таких как чат-боты и системы вопросов и ответов (QA). Здесь рассматриваются техники, которые помогают обрабатывать «краевые случаи» базового поиска и повышают разнообразие и специфичность выдачи.

Извлечение релевантных документов — критически важный этап в системах RAG (Retrieval-Augmented Generation), таких как чат-боты и системы вопросов и ответов (QA). Здесь рассматриваются техники, которые помогают обрабатывать «краевые случаи» базового поиска и повышают разнообразие и специфичность выдачи.

Перед началом работы подключите необходимые библиотеки и настройте доступ к внешним сервисам (например, OpenAI для получения эмбеддингов).

# Импортируем необходимые библиотеки
import os
from openai import OpenAI
import sys

# Добавляем корневую директорию в sys.path для корректной работы относительных импортов
sys.path.append('../..')

# Загружаем переменные окружения из файла .env для безопасного управления ключами API
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())

# Устанавливаем ключ OpenAI API из переменных окружения
client = OpenAI()

# Убедитесь, что у вас установлены необходимые пакеты, включая `lark` для парсинга (если требуется)
# !pip install lark

Далее настроим векторное хранилище, которое будет эффективно выполнять поиск по смысловому значению (с использованием эмбеддингов, преобразованных в векторы высокой размерности).

# Импортируем векторное хранилище Chroma и эмбеддинги OpenAI из библиотеки langchain
from langchain.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings

# Указываем директорию, где векторная база данных будет сохранять свои данные
persist_directory = 'vector_db/chroma/'

# Инициализируем функцию эмбеддинга, используя модель OpenAI
embedding_function = OpenAIEmbeddings()

# Создаём экземпляр векторной базы данных Chroma с указанной директорией сохранения и функцией эмбеддинга
vector_database = Chroma(
    persist_directory=persist_directory,
    embedding_function=embedding_function
)

# Выводим текущее количество записей в векторной базе данных для проверки готовности к использованию
print(vector_database._collection.count())

Добавим небольшой набор примеров, чтобы продемонстрировать поиск по схожести и MMR.

# Определяем список текстов для заполнения базы данных
texts = [
    "The Death Cap mushroom has a notable large fruiting body, often found above ground.",
    "Among mushrooms, the Death Cap stands out for its large fruiting body, sometimes appearing in all-white.",
    "The Death Cap, known for its toxicity, is one of the most dangerous mushrooms.",
]

# Создаём меньшую векторную базу данных из заданных текстов для демонстрационных целей
demo_vector_database = Chroma.from_texts(texts, embedding_function=embedding_function)

# Определяем запрос для поиска в векторной базе данных
query_text = "Discuss mushrooms characterized by their significant white fruiting bodies"

# Выполняем поиск по сходству для запроса, получая топ-2 наиболее релевантных записи
similar_texts = demo_vector_database.similarity_search(query_text, k=2)
print("Результаты поиска по сходству:", similar_texts)

# Выполняем поиск с максимальной маргинальной релевантностью для поиска разнообразных, но релевантных ответов, получая дополнительные кандидаты для сравнения
diverse_texts = demo_vector_database.max_marginal_relevance_search(query_text, k=2, fetch_k=3)
print("Результаты разнообразного поиска:", diverse_texts)

Частая проблема в поиске — получение однотипных результатов. MMR балансирует релевантность и разнообразие, уменьшая повторы и раскрывая тему более широко. Практическая реализация MMR:

# Определяем запрос, который ищет информацию
query_for_information = "what insights are available on data analysis tools?"

# Выполняем стандартный поиск по сходству для нахождения топ-3 релевантных документов
top_similar_documents = vector_database.similarity_search(query_for_information, k=3)

# Отображаем начало содержимого из первых двух документов для сравнения
print(top_similar_documents[0].page_content[:100])
print(top_similar_documents[1].page_content[:100])

# Отмечаем потенциальное перекрытие в информации. Для введения разнообразия применяем MMR.
diverse_documents = vector_database.max_marginal_relevance_search(query_for_information, k=3)

# Отображаем начало содержимого из первых двух разнообразных документов для наблюдения разницы
print(diverse_documents[0].page_content[:100])
print(diverse_documents[1].page_content[:100])

Этот пример показывает разницу между обычным поиском по схожести (similarity-поиск) и выдачей с использованием MMR: второй подход даёт релевантные, но менее повторяющиеся результаты.

Повышение точности с помощью метаданных

Метаданные помогают уточнять запросы и фильтровать результаты по различным атрибутам (источник, дата и т. п.).

Поиск с фильтрами по метаданным

# Определяем запрос с учётом конкретного контекста
specific_query = "what discussions were there about regression analysis in the third lecture?"

# Выполняем поиск по сходству с фильтром метаданных для нацеливания на документы из конкретной лекции
targeted_documents = vector_database.similarity_search(
    specific_query,
    k=3,
    filter={"source": "documents/cs229_lectures/MachineLearning-Lecture03.pdf"}
)

# Перебираем результаты для отображения их метаданных, подчёркивая специфичность поиска
for document in targeted_documents:
    print(document.metadata)

Связка метаданных и Self-Query Retrievers

Self-Query Retriever извлекает из одной пользовательской фразы как смысловой запрос, так и фильтры по метаданным — без необходимости ручного задания.

Инициализация и описание метаданных

Перед тем как выполнить поиск с учётом метаданных, необходимо настроить окружение и определить атрибуты метаданных, которые мы намерены использовать:

# Импортируем необходимые модули из библиотеки langchain
from langchain_openai import OpenAI
from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain.chains.query_constructor.base import AttributeInfo

# Определяем атрибуты метаданных с подробными описаниями
metadata_attributes = [
    AttributeInfo(
        name="source",
        description="Указывает документ лекции, ограниченный конкретными файлами в директории `docs/cs229_lectures`.",
        type="string",
    ),
    AttributeInfo(
        name="page",
        description="Номер страницы в документе лекции.",
        type="integer",
    ),
]

# Примечание: Переход к использованию модели OpenAI gpt-3.5-turbo-instruct из-за устаревания предыдущей модели по умолчанию.
document_content_description = "Подробные заметки лекции"
language_model = OpenAI(model='gpt-4o-mini', temperature=0)

Настройка Self-Query Retriever

Следующий шаг включает настройку self-query retriever с нашей языковой моделью, векторной базой данных и определёнными атрибутами метаданных:

# Инициализируем self-query retriever с языковой моделью, векторной базой данных и атрибутами метаданных
self_query_retriever = SelfQueryRetriever.from_llm(
    language_model,
    vector_database,
    document_content_description,
    metadata_attributes,
    verbose=True
)

Выполнение запроса с автоматическим выводом метаданных

Теперь выполним поиск, который автоматически выводит релевантные метаданные из самого запроса:

# Определяем запрос, который указывает контекст в вопросе
specific_query = "what insights are provided on regression analysis in the third lecture?"

# Примечание: Первое выполнение может вызвать предупреждение об устаревании для `predict_and_parse`, которое можно игнорировать.
# Получаем документы, релевантные конкретному запросу, используя выведенные метаданные для точности
relevant_documents = self_query_retriever.get_relevant_documents(specific_query)

# Отображаем метаданные полученных документов для демонстрации специфичности поиска
for document in relevant_documents:
    print(document.metadata)

Implementing Contextual Compression

Contextual compression works by extracting segments of a document that are most relevant to a given query. This method not only reduces the computational load on LLMs but also enhances the quality of the responses by focusing on the most pertinent information.

Setting Up the Environment

Перед тем как углубиться в специфику контекстуального сжатия, убедитесь, что ваше окружение правильно настроено с необходимыми библиотеками:

# Импортируем необходимые классы для контекстуального сжатия и извлечения документов
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain_openai import OpenAI

Initializing the Compression Tools

Следующий шаг включает инициализацию механизма сжатия с предобученной языковой моделью, которая будет использоваться для идентификации и извлечения релевантных частей документов:

# Инициализируем языковую модель с конкретной конфигурацией для обеспечения детерминированного поведения
language_model = OpenAI(temperature=0, model="gpt-4o-mini")

# Создаем компрессор, используя языковую модель для извлечения релевантных текстовых сегментов
document_compressor = LLMChainExtractor.from_llm(language_model)

Creating the Contextual Compression Retriever

С готовым компрессором мы теперь можем настроить retriever, который интегрирует контекстуальное сжатие в процесс извлечения:

# Объединяем компрессор документов с существующим retriever векторной базы данных
compression_retriever = ContextualCompressionRetriever(
    base_compressor=document_compressor,
    base_retriever=vector_database.as_retriever()
)

Выполним запрос и посмотрим, как retriever с контекстуальным сжатием возвращает более сфокусированный набор документов:

# Определяем запрос, для которого мы ищем релевантные сегменты документов
query_text = "what insights are offered on data analysis tools?"

# Получаем документы, релевантные запросу, автоматически сжатые для релевантности
compressed_documents = compression_retriever.get_relevant_documents(query_text)

# Функция для красивого форматирования и вывода содержимого сжатых документов
def pretty_print_documents(documents):
    print(f"\n{'-' * 100}\n".join([f"Document {index + 1}:\n\n" + doc.page_content for index, doc in enumerate(documents)]))

# Отображаем сжатые документы
pretty_print_documents(compressed_documents)

Контекстуальное сжатие направлено на извлечение сути документов путем фокусировки на сегментах, наиболее релевантных запросу. В сочетании со стратегией MMR оно балансирует релевантность с разнообразием в полученных документах, обеспечивая более широкую перспективу по запрашиваемой теме. Настройка Retriever с сжатием и MMR:

# Инициализируем retriever с контекстуальным сжатием и MMR для разнообразного и релевантного извлечения документов
compression_based_retriever = ContextualCompressionRetriever(
    base_compressor=document_compressor,
    base_retriever=vector_database.as_retriever(search_type="mmr")
)

# Определяем запрос для тестирования комбинированного подхода
query_for_insights = "what insights are available on statistical analysis methods?"

# Получаем сжатые документы, используя retriever с контекстуальным сжатием
compressed_documents = compression_based_retriever.get_relevant_documents(query_for_insights)

# Используем вспомогательную функцию для вывода содержимого полученных, сжатых документов
pretty_print_documents(compressed_documents)

Этот подход оптимизирует извлечение документов, обеспечивая, что результаты не только релевантны, но и разнообразны, предотвращая избыточность и улучшая понимание пользователем предметной области.

Помимо семантического поиска, существуют и другие методы извлечения информации. TF-IDF (Term Frequency-Inverse Document Frequency) — статистическая мера важности слова для документа в коллекции: учитывается частота слова в документе и редкость во всей коллекции; высокие значения указывают на хорошие дескрипторы и подходят для поиска по точным совпадениям. SVM (Support Vector Machine) можно применять для классификации документов и косвенного улучшения извлечения — фильтруя или ранжируя документы по предопределённым категориям.

Полезные ссылки

Теоретические вопросы

  1. В чём заключаются основные преимущества использования Maximum Marginal Relevance (MMR) в сравнении со стандартным поиском по схожести для извлечения документов?
  2. Как метаданные способствуют повышению точности и релевантности при семантическом поиске?
  3. Опишите принцип работы Self-Query Retriever и его ключевое преимущество.
  4. Когда целесообразно использовать TF-IDF и SVM в контексте извлечения информации, и в чём их отличия от методов, основанных на эмбеддингах?

Практические задания

  1. Реализация MMR с другими параметрами: Модифицируйте предоставленный код для MMR, экспериментируя с различными значениями k и fetch_k. Проанализируйте, как изменения этих параметров влияют на разнообразие и релевантность извлекаемых документов.
  2. Расширение метаданных: Добавьте новые типы метаданных (например, автор, дата публикации, ключевые слова) к вашим документам. Затем используйте эти метаданные для выполнения более детализированных запросов с помощью фильтров.
  3. Интеграция с Self-Query Retriever: Расширьте описание атрибутов метаданных для Self-Query Retriever, включив в него новые поля метаданных из предыдущего задания. Проверьте его способность автоматически формировать сложные запросы с учётом добавленных фильтров.
  4. Сравнение методов: Реализуйте простой поиск на основе TF-IDF или SVM для вашей коллекции документов. Сравните результаты с результатами семантического поиска, отмечая сильные и слабые стороны каждого подхода в различных сценариях.

Лучшие практики

Помимо эффективного использования различных техник ретривала, крайне важно придерживаться лучших практик, чтобы гарантировать максимальную производительность и надёжность систем извлечения.

Выбор подходящей стратегии

Выбор между MMR, Self-Query Retriever или традиционным поиском по схожести зависит от конкретных требований к приложению. Для задач, где требуется разнообразие результатов, MMR будет оптимальным. Если запросы пользователей содержат явные метаданные, то Self-Query Retriever упростит процесс. Стандартный поиск по схожести подходит для простых запросов.

Оптимизация производительности

Производительность векторной базы данных, особенно при больших объёмах данных, имеет решающее значение. Регулярная индексация, кэширование популярных запросов и оптимизация аппаратного обеспечения могут значительно ускорить процесс извлечения. Использование распределённых векторных баз данных также может помочь в масштабировании.

Управление метаданными

Хорошо структурированные и точные метаданные значительно улучшают качество поиска. Важно иметь продуманную схему метаданных и обеспечить её единообразное применение во всей коллекции документов. Автоматическая генерация метаданных с помощью LLM также может быть полезной, но требует тщательной валидации.

Мониторинг и итерации

Системы извлечения требуют постоянного мониторинга производительности и качества результатов. Сбор обратной связи от пользователей, анализ метрик релевантности и A/B-тестирование различных стратегий извлечения помогут в итеративном улучшении системы.

Заключение

Эта глава исследовала различные продвинутые техники извлечения, разработанные для улучшения производительности систем семантического поиска. Решая ограничения, связанные с разнообразием, специфичностью и релевантностью информации, эти методы предлагают путь к более интеллектуальным и эффективным системам извлечения. Через практическое применение MMR, self-query retrieval, контекстуального сжатия и альтернативных методов извлечения документов, разработчики могут создавать системы, которые не только понимают семантическое содержимое запросов, но и предоставляют богатые, разнообразные и целенаправленные ответы.

Соблюдение лучших практик в реализации этих техник обеспечивает, что системы извлечения являются как эффективными, так и действенными. Поскольку область NLP продолжает развиваться, информированность о последних достижениях в технологиях извлечения будет ключом к поддержанию преимущества в возможностях семантического поиска.

В итоге, интеграция продвинутых техник извлечения в системы семантического поиска представляет значительный шаг вперед в разработке интеллектуальных систем извлечения информации. Тщательно выбирая и оптимизируя эти методы, разработчики могут создавать решения, которые значительно улучшают пользовательский опыт, предоставляя точную, разнообразную и контекстуально релевантную информацию в ответ на сложные запросы.

Вопросы по теории:

  1. Опишите принцип максимальной маргинальной релевантности (MMR) и его роль в повышении качества извлечения информации.
  2. Как метод self-query retrieval помогает обрабатывать запросы, сочетающие семантическую часть и метаданные?
  3. Объясните концепцию контекстуального сжатия при извлечении документов и её значение.
  4. Перечислите шаги по настройке окружения для продвинутых техник извлечения с использованием API OpenAI и библиотеки LangChain.
  5. Как инициализация векторной базы данных способствует эффективному поиску семантического сходства?
  6. Опишите процесс наполнения и использования векторной базы данных для задач поиска по сходству и диверсифицированного поиска.
  7. В контексте продвинутого извлечения документов какие преимущества даёт использование MMR для обеспечения разнообразия результатов?
  8. Как можно использовать метаданные для повышения специфичности результатов в системах извлечения документов?
  9. Обсудите преимущества и сложности реализации ретриверов self-query в семантическом поиске.
  10. Объясните роль контекстуального сжатия в снижении вычислительной нагрузки и повышении качества ответов в системах извлечения.
  11. Каковы ключевые лучшие практики внедрения продвинутых техник извлечения в системах семантического поиска?
  12. Сравните эффективность векторных методов извлечения с альтернативными стратегиями, такими как TF‑IDF и SVM, в задачах извлечения документов.
  13. Как интеграция продвинутых техник извлечения улучшает производительность и пользовательский опыт систем семантического поиска?
  14. Обсудите потенциальное влияние развития технологий НЛП на будущее продвинутых техник извлечения для семантического поиска.

Практические задания:

  1. Реализуйте класс Python VectorDatabase со следующими методами:
  2. __init__(self, persist_directory: str): конструктор, инициализирующий векторную базу данных и каталог для персистентного хранения.
  3. add_text(self, text: str): встраивает заданный текст в вектор высокой размерности с помощью эмбеддингов OpenAI и сохраняет его в базе. Предположите наличие функции openai_embedding(text: str) -> List[float], возвращающей вектор эмбеддинга.
  4. similarity_search(self, query: str, k: int) -> List[str]: выполняет поиск по сходству для запроса, возвращая топ‑k наиболее похожих текстов из базы. Для реализации используйте упрощённую (заглушечную) функцию сходства.

  5. Создайте функцию compress_document, которая принимает список строк (документ) и строку запроса и возвращает список строк, где каждая строка — это сжатый фрагмент документа, релевантный запросу. Предположите наличие внешней утилиты compress_segment(segment: str, query: str) -> str, сжимающей отдельный фрагмент документа под запрос.

  6. Разработайте функцию max_marginal_relevance, которая принимает список идентификаторов документов, запрос и два параметра lambda и k, а затем возвращает список из k идентификаторов, отобранных по критерию Maximum Marginal Relevance (MMR). Предположите наличие функции сходства similarity(doc_id: str, query: str) -> float, измеряющей сходство документа с запросом, и функции диверсификации diversity(doc_id1: str, doc_id2: str) -> float, измеряющей различие между двумя документами.

  7. Напишите функцию initialize_vector_db, демонстрирующую, как наполнить векторную базу данных списком заранее заданных текстов, а затем выполнить поиск по сходству и диверсифицированный поиск. Функция должна напечатать результаты обоих поисков. Используйте класс VectorDatabase, реализованный в задании 2, в качестве векторной базы данных.