Малоиспользуемые возможности ES: векторный поиск

от автора

Привет, меня зовут Евгений Думчев, я разработчик в DD Planet. Сегодня хочу поделиться опытом применения одной из редко используемых, но полезных функций Elasticsearch, которую мы успешно применили в одном из проектов.

Elasticsearch — это высокопроизводительная, масштабируемая, распределенная поисковая и аналитическая система, предназначенная для быстрого поиска, анализа и обработки больших объемов данных.

Основной и наиболее распространенной задачей применения Elasticsearch на проектах является внедрение полнотекстового поиска (поиск по текстовым полям с учетом морфологии, синонимов и релевантности) и точного поиска с применением фильтраций и агрегаций данных. В данном случае разработчик целенаправленно подключает Elasticsearch в приложение для того, чтобы воспользоваться средствами индексации и гибкого поиска данных. Для этого требуется создать индекс с маппингом типов данных и реализовать утилиту-индексатор, которая будет на основе данных из постоянного хранилища данных заполнять индекс документами (индексировать) в соответствии со структурой маппинга. Также в основном API приложении потребуется реализовать запрос к Elasticsearch (существуют библиотеки для большинства языков программирования) для поиска и получения данных из целевого индекса. Архитектурная схема выглядит так:

И еще один, не менее популярный вариант использования Elasticsearch на проектах — в качестве инструмента для централизованного хранения и анализа логов приложений, баз данных и других инфраструктурных элементов. В связке с Logstash (для сбора и обработки логов) и Kibana (для визуализации) он образует стек ELK, который позволяет эффективно искать и анализировать большие объемы данных. Elasticsearch обеспечивает быстрый поиск по логам, агрегацию данных и настройку алертинга для обнаружения аномалий. Это делает его популярным решением для мониторинга инфраструктуры, диагностики проблем и аудита безопасности. Но данное решение не отражает весь масштаб и спектр возможностей, предоставляемых Elasticsearch, поскольку пользователь взаимодействует с поисковым движком только через визуальный интерфейс и функции, доступные в Kibana. Пример интерфейса Kibana:

Более сложные и специализированные возможности Elasticsearch, требующие дополнительной настройки и глубокого понимания системы, применяются реже, но могут оказаться очень полезными. Рассмотрим одну из таких малоиспользуемых возможностей — векторный поиск.

Концепция векторного поиска (vector search) основана на представлении объектов в виде многомерных числовых векторов и определении их близости по метрике расстояния (например, косинусное расстояние, евклидово расстояние или расстояние Джеффри). Близость векторов отражает семантическую или контекстуальную схожесть объектов. Текстовая информация может быть преобразована в вектор с помощью моделей машинного обучения, таких как Word2Vec, BERT или других методов embedding.

Создание и конфигурирование индекса

В Elasticsearch начиная с версии 7.10 появилась поддержка поля dense_vector — специального типа данных, позволяющего хранить и индексировать векторы фиксированной размерности. При создании этого типа данных можно указать следующие параметры:

  • dims – количество измерений (размерность) вектора, обязательный.

  • similarity – алгоритм сравнения векторов. Доступны следующие варианты: l2_norm – евклидово расстояние, dot_product – скалярное произведение, cosine – косинусное расстояние.

  • index – логическое значение (true/false), указывает, нужно ли индексировать вектор для поиска (по умолчанию false).

  • index_options – дополнительные параметры для индексации.

  • type – тип индексации, например, hnsw (Hierarchical Navigable Small World).

  • m – параметр HNSW, определяющий количество связей между узлами (обычно 16-100).

  • ef_construction – параметр HNSW, управляющий точностью построения индекса (обычно 100-200).

Пример создания индекса:

PUT semantic_index {   "settings": {      "number_of_shards": 1,      "number_of_replicas": 0    },   "mappings": {     "properties": {       "vector": {         "type": "dense_vector",         "dims": 512,  // Количество измерений вектора         "similarity": "cosine",  // Тип метрики: косинусное расстояние         "index": true, // Индексировать ли вектор для поиска через script_score           "index_options": { // Параметры для индексации (если "index": true)            "type": "hnsw",            "m": 16, // Количество связей между узлами HNSW (обычно 16-100)           "ef_construction": 100 //Точность построения индекса (обычно 100-200)         }       },       "title": {         "type": "text"       }     }   } }

Автоматически вычислить значения dense_vector встроенными средствами Elasticsearch нельзя, потому что в нем пока нет встроенных механизмов генерации эмбеддингов из текстовых или других данных без предварительно обученной модели машинного обучения. Даже встроенные модели, такие как ELSER, нужно сначала загрузить и активировать. Чтобы получить и проиндексировать семантические вектора, можно воспользоваться следующими способами:

  • получить вектора через внешний сервис и явно передать значения вектора при добавлении документа;

  • подключить предобученную ML-модель для генерации векторов через Inference Pipeline.

Индексация векторов через внешний сервис

Внешний сервис подразумевает решение, которое позволяет предварительно обработать текстовые данные и получить вектора. В качестве такого решения может выступать собственная NLP-модель или сервисы для семантического анализа, например: OpenAI, Hugging Face. 

Как пример, для получения вектора воспользуемся библиотекой transformers от Hugging Face и фреймворком torch. Установим зависимости:

pip install sentence-transformers

И воспользуемся специализированной моделью distiluse-base-multilingual-cased-v2 для поиска смысловой схожести между текстами. Эта многоязычная нейросетевая модель построена на базе DistilBERT и обучена на Natural Language Inference (NLI) и парафразных задачах, то есть способна определять насколько два предложения означают одно и то же. Модель позволяет сформировать 512-мерный вектор.

Скрипт на Python для получения вектора:

from sentence_transformers import SentenceTransformer  # Загружаем модель model = SentenceTransformer('distiluse-base-multilingual-cased-v2')  # Входной текст sentence = "Привет, как настроение?"  # Получаем векторное представление vector = model.encode(sentence)  # Выводим вектор print("Размерность вектора:", vector.shape) print("Векторное представление:", vector)

В результате получаем 512-мерный вектор, значение которого можно проиндексировать в Elasticsearch, указав в поле с типом dense_vector:

PUT semantic_index/_doc/1 {   "title": "Привет, как настроение?",   "vector": [-1.79605780e-03, -4.74534556e-03, ..., -1.98550411e-02]  }  PUT semantic_index/_doc/2 {   "title": "Как дела?",   "vector": [1.28867049e-02, 4.09924565e-03, ..., -2.74000857e-02] }  PUT semantic_index/_doc/3 {   "title": "Который час?",   "vector": [0.01102088, 0.00279999, ..., -0.0050466] }  PUT semantic_index/_doc/4 {   "title": "Здравствуй, как жизнь?",   "vector": [0.00611453, -0.0028536, ..., -0.02347422] }  PUT semantic_index/_doc/5 {   "title": "Сколько времени?",   "vector": [1.17157530e-02, 2.19503101e-02, ..., -7.98434392e-03] }

В результате в индексе сохранено 5 документов и они доступны для поиска.

Индексация векторов с использованием Ingest Pipeline с Inference

Альтернативный вариант получения и индексации векторов — это воспользоваться Ingest Pipeline с Inference. Данный инструмент позволяет интегрировать машинное обучение в обработку данных при индексации. Ограничением является то, что Elasticsearch ML доступен только в платных тарифах. В бесплатной версии не получится использовать Ingest Pipeline Inference или upload моделей.

Для начала нужно установить модель в Elasticsearch. Для этого можно воспользоваться инструментом Eland.

pip install eland  # Установка Eland  eland_import_hub_model \   --url http://localhost:9200 \ # Адрес Elasticsearch   --hub-model-id sentence-transformers/distiluse-base-multilingual-cased-v2 \   --task text_embedding \   --model-id distiluse-base-multilingual-cased-v2 \   --es-username elastic_username \   --es-password elastic_password

И проверить статус установки в Elasticsearch:

GET _ml/trained_models/distiluse-base-multilingual-cased-v2/_stats

А также можно вызвать запрос получения семантического вектора:

POST _ml/trained_models/distiluse-base-multilingual-cased-v2/_infer {   "docs": [     {       "title": "Привет, как настроение?"     }   ] }

Когда модель установлена в Elasticsearch необходимо создать Ingest Pipeline с Inference:

PUT _ingest/pipeline/semantic-pipeline {   "processors": [     {       "inference": {         "model_id": "distiluse-base-multilingual-cased-v2",         "input_output_map": {           "title": "title"         },         "target_field": "vector",         "field_map": {           "title": "title"         }       }     }   ] }

Также можно скорректировать настройки индекса и указать default_pipeline:

PUT semantic_index {   "settings": {      "default_pipeline": "semantic-pipeline"   },   "mappings": {     "properties": {       "vector": {         "type": "dense_vector",         "dims": 512,  // Количество измерений вектора         "similarity": "cosine",  // Тип метрики: косинусное расстояние         "index": true, // Индексировать ли вектор для поиска через script_score           "index_options": { // Параметры для индексации (если "index": true)            "type": "hnsw",            "m": 16, // Количество связей между узлами HNSW (обычно 16-100)           "ef_construction": 100 //Точность построения индекса (обычно 100-200)         }       },       "title": {         "type": "text"       }     }   } }

Теперь при добавлении документа Elasticsearch автоматически создаст dense_vector, используя дефолтный или указанный pipeline с NLP-моделью.

PUT semantic_index/_doc/1 {   "title": "Привет, как настроение?" }  PUT semantic_index/_doc/2?pipeline=semantic-pipeline {   "title": "Как дела?" }  PUT semantic_index/_doc/3 {   "title": "Который час?" }  PUT semantic_index/_doc/4?pipeline=semantic-pipeline {   "title": "Здравствуй, как жизнь?" }  PUT semantic_index/_doc/5 {   "title": "Сколько времени?" }

В результате pipeline автоматически создаст и заполнит поле vector, в индексе будет сохранено 5 документов и они будут доступны для поиска.

Применение запросов векторного поиска

Для векторного поиска семантически схожих объектов используется метод ближайших соседей (kNN). При выполнении knn поиска, указывается вектор запроса (query_vector) и количество ближайших соседей (k), которые нужно найти. Elasticsearch вычисляет расстояние между вектором запроса и векторами документов в индексе, используя метрику расстояния (например, евклидово расстояние или косинусное сходство). Документы ранжируются по их близости к вектору запроса. Чем меньше расстояние, т.е. больше сходство, тем выше документ окажется в результатах поиска. Elasticsearch возвращает k ближайших документов, отсортированных по степени их близости к запросу. 

Сформируем вектора для фразы “Привет, как настроение?” и выполним запрос поиска похожих документов запросом knn:

POST semantic_index/_search {   "_source": {     "exclude": [       "vector"     ]   },   "knn": {     "field": "vector",     "query_vector": [       -0.001796057797037065,       -0.004745345562696457,       -0.029693424701690674,       … ,       -0.019855041056871414     ],     "k": 5,     "num_candidates": 100   } }

В результате получаем:

{   "took": 7,   "timed_out": false,   "_shards": {     "total": 1,     "successful": 1,     "skipped": 0,     "failed": 0   },   "hits": {     "total": {       "value": 5,       "relation": "eq"     },     "max_score": 0.9356358,     "hits": [       {         "_index": "semantic_index",         "_id": "3",         "_score": 0.9356358,         "_source": {           "title": "Здравствуй, как жизнь?"         }       },       {         "_index": "semantic_index",         "_id": "1",         "_score": 0.9054657,         "_source": {           "title": "Как дела?"         }       },       {         "_index": "semantic_index",         "_id": "5",         "_score": 0.7244736,         "_source": {           "title": "Привет, не забудь помыть посуду"         }       },       {         "_index": "semantic_index",         "_id": "2",         "_score": 0.64549077,         "_source": {           "title": "Который час?"         }       },       {         "_index": "semantic_index",         "_id": "4",         "_score": 0.63735604,         "_source": {           "title": "Поставь лайк"         }       }     ]   } }

Из результата выполнения запроса видно, что документы отранжировались по _score с учетом их смысловой близости. Чем значение _score ближе к 1, тем выше семантическая схожесть. В данном случае фраза “Привет, как настроение?” близка с фразами “Здравствуй, как жизнь?” и “Как дела?”, то есть смысловая нагрузка действительно учтена.

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

Потенциал векторного поиска

С появлением поддержки векторов (dense vectors) в Elasticsearch стало возможным выполнять семантический поиск, учитывающий смысловую близость терминов. 

Использование kNN в Elasticsearch позволяет:

  • Искать семантически похожие документы. Представить текст в виде векторов можно за счет моделей BERT, SBERT или OpenAI embeddings.

  • Находить похожие изображения. Векторные представления изображений можно получать с помощью ResNet, ViT или CLIP.

  • Создавать системы рекомендаций. Для этого нужно векторизовать предпочтения пользователя и искать товары, близкие к предпочтениям.

Однако из-за сложности настройки и необходимости интеграции с моделями машинного обучения, эта функция пока не получила широкого распространения.

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


ссылка на оригинал статьи https://habr.com/ru/articles/922544/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *