Калина Алексей блог программиста

Венигрет или Винегрет? Опечаточники от Elasticsearch

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

Elasticsearch, Kibana и весь этот стек

Elasticsearch - это NoSQL база данных, которая гордо именуется поисковым движком, так как имеет богатство возможностей в полнотекстовом поиске. Он написан на Java и базируется на Lucene (библиотека от Apache). У Elasticsearch есть большое число плюсов и фич, с которыми можно ознакомиться на официальном сайте или же подождать других постов на эту тему. Этот движок достаточно популярен. К примеру, на нем базируется полнотекстовый поиск на github.com.

Также в стек ELK входят Kibana и Logstash. С последним мы не будем иметь дело, он нужен для анализа логов. В то время как с Kibana мы познакомимся и посмотрим на некоторые возможности этой платформы для визуалиазации и анализа запросов к Elasticsearch.

Глоссарий

Чтобы говорить на одном языке об Elasticsearch, необходимо рассказать о некоторых его специфичных терминах.

Индекс - аналог таблицы в реляционной базе данных. Соответственно, когда речь идет об индексировании данных, имеется ввиду загрузка их в Elasticsearch.

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

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

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

Фильтр символов обеспечивает преобразование конкретных символов в строке. Из коробки Elasticsearch предлагает три фильтра: замена и удаление html тегов, кастомный маппинг символов и преобразование регулярными выражениями.

Токенизатор разбивает поток символов на токены. В системе есть разные варианты токенизаторов, например, разбиение по пробелам или по символам, не являющимся буквой.

Фильтр токенов - фича, применяемая анализатором на последнем этапе. Она позволяет приводить все токены к нижнему регистру, удалять стоп-слова, добавлять синонимы и многое другое.

Задача

Теперь мы готовы перейти непосредственно к изучению suggest’ов от Elasticsearch. Для того, чтобы рассматривать нашу задачу на реальном примере, я воспользуюсь датасетом с информацией о вопросах с популярной американской теле-игры Jeopardy. Ее можно назвать аналогом нашей викторины Своя игра. В этом датасете содержится информация о категории вопроса, его текст, ответ, дата игры и другая мета-информация.

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

Этап индексирования я пропущу. У нас есть данные в Elasticsearch, и мы хотим получить простейший suggest. Для этого обратимся к Kibana. Эта система содержит в себе инструментарий для общения с Elasticsearch с помощью запросов. Он называется Console. Отправим запрос на suggest по тексту presidenti (среди категорий есть та, в которой спрашиваются места рождения президентов: presidential states of birth). В блоке справа мы видим пять возможных вариантов, отсортированных по score (для всех ответов высчитываются очки для сравнения их релевантности).

kibana

Term Suggester

Термы - это токены, которые были получены в результате действий анализатора. Term suggester предоставляет термы, которые отличаются от переданного в запросе на некоторое расстояние редактирования (edit distance). Под этим термином понимается минимальное число вставок, удалений и замен символов, для того чтобы две строки стали эквивалентны.

Рассмотрим различные параметры, благодаря которым можно управлять качеством suggester’а и подстраивать его под свои нужды. Они должны размещаться в теле запроса внутри слова term, как видно в скрине из Kibana.

Есть несколько параметров общих для всех видов suggester’ов:

Рассмотрим применение suggester’a с дефолтными параметрами и size, равным 3.

term-suggest

Как видите, suggester предлагает нам слова, которые могут отличаться от введенного на два символа. Можно изменить это поведение, установив в параметре max_edits значение 1 (других вариантов нет). Также есть возможность изменить минимальную длину совпадения префиксов строк (prefix_length) и минимальную длину предлагаемого слова (min_word_length). Установим эти значения в 3 (по умолчанию 1) и 5 (по умолчанию 4), соответственно.

term-suggest

Теперь suggester проводит более жесткий отбор кандидатов. Кроме того, возможно еще более тонкая настройка. Необязательно использовать расстояние редактирования для сравнения двух строк. Существует множество других метрик, и Elasticsearch поддерживает некоторые из них, например, Levenstein или Jaro-Winkler. Также можно установить пороговые значения для частоты появление терма в документах (min_doc_freq и max_term_freq)

Однако, никакими параметрами не удастся заставить этот suggester решить нашу задачу. Его возможности ограничиваются работой с отдельными термами, и когда мы вводим более одного слова, он предоставляет подсказки для каждого по отдельности, что для в нашем случае неприемлимо. Поэтому перейдем к следующему suggester’у.

Phrase Suggester

В отличие от предыдущего, phrase suggester основывается на анализе n-грамм и может предсказывать целые предложения. Он учитывает частоту встречающихся токенов и совпадение подсказок с реальными фразами из документов.

Однако для того, чтобы phrase suggester заработал, нам потребуется обновить маппинг (а как следствие и переиндексировать данные). Сначала мы добавим в настройки индекса новый анализатор. Он использует стандартный токенизатор, который разбивает строку на токены, исключая знаки препинания, и основывается на стандартном алгоритме сегментации текста Unicode. Также мы будем использовать в анализаторе кастомный фильтр токенов: shingle, который разбивает строку на токены, состоящие из нескольких слов. В нашем случае все токены будут состоять из двух или трех элементов.

{
  "analysis": {
    "analyzer": {
      "phrase": {
        "type": "custom",
        "tokenizer": "standard",
        "filter": ["standard", "lowercase", "shingle"]
      }
    },
    "filter": {
      "shingle": {
        "type": "shingle",
        "min_shingle_size": 2,
        "max_shingle_size": 3
      }
    }
  }
}

После обновления настроек нам также нужно пролить новый маппинг. В нем мы указываем, что при индексировании для поля category нужно использовать наш phrase анализатор.

{
  "properties": {
    "category": {
      "type": "text",
      "analyzer": "phrase"
    }
  }
}

Теперь можно проверить suggester в действии.

phrase-suggest

Phrase suggester также поддерживает достаточно тонкую настройку. Например, вы можете установить свои разделители слов или размер шинглов. Одной из моих любимых фич является подсветка (highlight). Suggester может выделять слова, в которых есть ошибка, так как вы пожелаете. К примеру, можно выделять их тегами, которые определят их внешний вид на web-странице. Пример с выделением угловыми скобками:

phrase-suggest

Тем не менее, по-прежнему не все так радужно. Несмотря на то, что Jeopardy американская игра и от них можно ожидать даже птичьи штаты, в этом датасете не было категорий с названием presidential states of birds. Однако, suggester подсказывает нам любые слова что он знает, включая эти. Поэтому нам нужно добавить еще несколько параметров для более точной работы Elasticsearch.

Phrase suggester поддерживает возможность отсекать подсказки, которые нас не удовлетворяют путем дополнительного поискового запроса. Для этого добавим параметр collate с проверкой на то, что полученный suggest действительно содержится в поле category хотя бы в одном документе нашего индекса.

{
  "collate": {
    "query": {
      "inline": {
        "match": {
          "category": {
            "query": "{{ suggestion }}",
            "operator": "and"
          }
        }
      }
    }
  }
}

Также добавим параметр max_errors и увеличим количество возможных ошибок в запросе до трех (по умолчанию 1). Теперь можно проверить работу окончательного варианта suggester’а.

phrase-suggest

Phase suggester предлагает еще массу других возможностей, которые мы сегодня не рассмотрели. Среди них: использование статистических выкладок во время принятия решения по конкретному ответу (real_word_error_likelihood и confidence) и даже предварительный term suggest к словам, содержащимся в токене, и составление suggest’a уже на их основе (direct generators).

Заключение

Elasticsearch обладает очень интересными и полезными возможностями. К слову, в нем есть третий вид suggester’ов - Completion. Он предназначен для составления автодополнения (autocomplete) к тексту, вводимому пользователем. Однако, на сегодня все, надеюсь Elasticsearch вас заинтересовал и вы будете использовать его в своих проектах.

Датасет Jeopardy