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

Комментарии (0)