Недавно я провел небольшое исследование Elasticsearch, чтобы узнать, подходит ли он моим потребностям на работе. Больше всего меня волнует поддержка Python. Если вы занимаетесь наукой о данных, то в Python для Elasticsearch есть три библиотеки, которые вы, возможно, захотите изучить: elasticsearch-py, elasticsearch-dsl-py и eland.

elasticsearch-py предоставляет вам API низкого уровня, с помощью которых вы можете делать все, что вам нужно, с Elasticsearch. elasticsearch-dsl-py - это клиентская библиотека более высокого уровня, которая более питонична и находится поверх elasticsearch-py. Чтобы увидеть разницу, вот код прямо из их github:

elasticsearch-py против elasticsearch-dsl-py

Ниже приведен поисковый запрос с использованием elasticsearch-py:

from elasticsearch import Elasticsearch
client = Elasticsearch()

response = client.search(
    index="my-index",
    body={
      "query": {
        "bool": {
          "must": [{"match": {"title": "python"}}],
          "must_not": [{"match": {"description": "beta"}}],
          "filter": [{"term": {"category": "search"}}]
        }
      },
      "aggs" : {
        "per_tag": {
          "terms": {"field": "tags"},
          "aggs": {
            "max_lines": {"max": {"field": "lines"}}
          }
        }
      }
    }
)

for hit in response['hits']['hits']:
    print(hit['_score'], hit['_source']['title'])

for tag in response['aggregations']['per_tag']['buckets']:
    print(tag['key'], tag['max_lines']['value'])

Для сравнения, вот код, использующий elasticsearch-dsl-py:

from elasticsearch import Elasticsearch
from elasticsearch_dsl import Search

client = Elasticsearch()

s = Search(using=client, index="my-index") \
    .filter("term", category="search") \
    .query("match", title="python")   \
    .exclude("match", description="beta")

s.aggs.bucket('per_tag', 'terms', field='tags') \
    .metric('max_lines', 'max', field='lines')

response = s.execute()

for hit in response:
    print(hit.meta.score, hit.title)

for tag in response.aggregations.per_tag.buckets:
    print(tag.key, tag.max_lines.value)

Думаю, совершенно очевидно, что делать, если вам просто нужно выполнить несколько простых запросов. :)

Машинное обучение с Eland… не так много

eland - это точно не полноценная библиотека машинного обучения. Насколько мне известно, пока он делает следующее:

  1. Позволяет управлять данными в Elasticsearch, как если бы вы работали с фреймом данных pandas, поэтому вы можете выполнять предварительную обработку данных в Elasticsearch вместо того, чтобы делать это в своей оперативной памяти.
  2. Развертывание моделей scikit-learn в Elasticsearch и выполнение там логических выводов.

Вот и все. Если вы пока не слишком разочарованы, читайте дальше. :)

Я использовал примеры игрушек на их веб-сайте, чтобы протестировать некоторые функции самостоятельно с некоторыми изменениями.

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

Создание набора данных игрушек

from datetime import datetime
import eland as ed
from eland.conftest import *
from eland.ml import MLModel
from elasticsearch import Elasticsearch
from elasticsearch_dsl import Search, Q
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.datasets import make_moons
from sklearn.ensemble import IsolationForest
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from tqdm import tqdm
sample_size = 100000
outlier_fraction = 0.01
outlier_set_size = int(sample_size * outlier_fraction)
inlier_set_size = sample_size - outlier_set_size
random_gen = np.random.RandomState(1113)
X_raw, y_raw = make_moons(n_samples=sample_size, shuffle=True, noise=outlier_fraction, random_state=None)
X_normed = 4 * (X_raw - np.array([0.5, 0.25]))X_noise = random_gen.uniform(low=-6, high=6, size=(outlier_set_size, 2))
X = np.concatenate([X_normed, X_noise])
df = pd.DataFrame(X)
df.columns = ['feature_1', 'feature_2']

Давайте визуализируем данные:

plt.figure(figsize=(16, 9))
plt.scatter(X[:, 0], X[:, 1])

Модель обучения

Я выбрал простой Isolation Forest, чтобы посмотреть, что произойдет.

anomaly_algo = IsolationForest(n_estimators=100,
                               contamination=outlier_fraction,
                               random_state=1113)
y_iso = anomaly_algo.fit(X).predict(X)

И вот результат

plt.figure(figsize=(16, 9))
plt.scatter(X[:, 0], X[:, 1], c=y_iso)

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

Подключение и развертывание модели в Elasticsearch

Мне не удалось заставить развертывание работать на моем локальном компьютере. Если вы попытаетесь сделать это, вы получите сообщение об ошибке 403 о том, что ваша лицензия недостаточно эффективна для того, что вам нужно. Я предполагаю, что это либо проблема с настройками, либо я не установил X-Pack. В любом случае я решил, что это слишком сложно, поэтому я пошел на получение 14-дневной пробной учетной записи Elastic Cloud и просто сделал это там.

Вот минимальный код, чтобы заставить его работать (… или не работать).

es = Elasticsearch(cloud_id='<your_cloud_id>', 
                   http_auth=("<your_username>", "<your_password>"))
es_model = MLModel.import_model(es_client=es,
                                model=anomaly_algo,
                                model_id='model',
                                feature_names=df.columns
)

Если вы запустите это, вы получите такую ​​ошибку:

NotImplementedError: Importing ML models of type <class 'sklearn.ensemble._iforest.IsolationForest'>, not currently implemented

Очень плохо. Оказалось, что Elasticsearch поддерживает только пару моделей контролируемого обучения на основе дерева. Я перечислю их здесь, чтобы сэкономить вам время:

  • DecisionTreeClassifier
  • DecisionTreeRegressor
  • RandomForestRegressor
  • RandomForestClassifier
  • XGBClassifier
  • XGBRegressor
  • LGBMRegressor
  • LGBMClassifier

Итак, после некоторого поиска в Google я узнал об этой печальной истине и решил хотя бы опробовать один алгоритм в качестве доказательства концепции.

Контролируемое обучение с помощью Elasticsearch

Поскольку наш набор данных не помечен. Мы можем использовать Лес изоляции, который мы создали для этого чуть выше. Сначала мы назначаем псевдометки каждой строке и меняем значения выбросов на 0 вместо -1.

df['label'] = y_iso
df.loc[df['label'] != 1, 'label'] = 0
df.head()

А затем разделение данных

X_train, X_test, y_train, y_test = train_test_split(df.loc[:, ['feature_1', 'feature_2']], 
                                                    df['label'], 
                                                    test_size=0.33,
                                                    shuffle=True,
                                                    random_state=42)

На этом этапе мы можем построить простую модель дерева решений:

sl_model = DecisionTreeClassifier()
sl_model.fit(X_train, y_train)

Теперь наступает ключевая часть. С помощью всего одной строчки кода мы можем легко развернуть нашу модель в Elasticsearch:

es_model = MLModel.import_model(es_client=es,
                                model=sl_model,
                                model_id='model001',
                                feature_names=list(df.columns)
)

И давайте попробуем делать выводы с помощью Elasticsearch

y_pred = es_model.predict(X_test[:500].values)
plt.figure(figsize=(16, 9))
plt.scatter(x=X_test.iloc[:500, 0], y=X_test.iloc[:500, 1], c=y_pred[:500])

Ключевые выводы

  1. Модели Scikit-learn можно развернуть в Elasticsearch с помощью однострочника.
  2. На данный момент поддерживаются только древовидные модели.
  3. Модели обучения без учителя не поддерживаются.

Чего нельзя делать с Elasticsearch

  1. Фрейм данных pandas можно преобразовать в фрейм данных eland с помощью ed.pandas_to_eland. Но фреймворк eland можно использовать только для обработки данных. Вы не можете обучать свои модели с помощью scikit-learn или каких-либо API-интерфейсов Elasticsearch, поэтому после обработки данных с помощью eland вам нужно будет преобразовать фрейм данных обратно в pandas с помощью to_pandas.
  2. Вы не можете обучать модели на Elasticsearch
  3. Вы не можете делать выводы для данных, хранящихся в Elasticsearch.
  4. Существует ограничение на количество данных, которые вы можете выводить за раз. Все, что слишком велико, приведет к тайм-ауту.