Привет, меня зовут Евгений Думчев, я разработчик в 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/
Добавить комментарий