Создание поисковой системы, позволяющей использовать синонимы, - один из самых эффективных способов повысить релевантность результатов поиска для ваших конечных пользователей. Вы можете обнаружить, что пользователи жалуются на то, что не получают результатов, которые они себе представляют, потому что вместо того, чтобы ввести точное ключевое слово в результат, они использовали синоним слова и не получили желаемых результатов.
Например, представим, что вы создали поисковую систему для мебельного магазина. Вы импортировали эти 3 документа:
1 2 3 4 5 6 7 8 9 10 11 12 | { "id":"1" "product":"red leather sofa" } { "id":"2" "product":"1950 FABRIC couch " } { "id":"3" "product":"corner sectional, leather, extra large" } |
Когда пользователь ищет слово "couch", он получит только второй результат и не увидит два других релевантных продукта. В этом и заключается важность включения синонимов в Elasticsearch; мы хотим, чтобы пользователь, набравший "couch", "sofa" или "sectional", увидел все три результата без необходимости самостоятельно придумывать все возможные синонимы. Наше решение: фильтр токенов синонимов.
Обзор анализа текста
Прежде чем мы перейдем к рассмотрению особенностей работы фильтра синонимов, давайте пройдемся по некоторым исходным данным, чтобы лучше понять, что мы делаем.
Elasticsearch предлагает широкий набор функций для индексирования данных. Помимо того, что Elasticsearch является отличным местом для хранения данных, он выполняет некоторые базовые анализаторы данных за кулисами, чтобы сделать их пригодными для поиска. Анализаторы - это инструкции, которые даются Elasticsearch по поводу того, как должны быть проиндексированы ваши текстовые поля.
По умолчанию каждый индекс имеет анализатор по умолчанию, который используется, если не указан ни один, называемый стандартным анализатором, который подходит для многих случаев. Прелесть Elasticsearch в том, что вы можете создать и указать собственный анализатор, который будет соответствовать вашим потребностям.
Анализатор состоит из двух основных компонентов: токенизатора и нуля или более фильтров токенов. Есть еще один компонент под названием символьные фильтры, который позволяет заменять определенные символы или их шаблоны, но в этой статье мы не будем их рассматривать.
Токенизатор
Токенизатор определяет, как Elasticsearch возьмет набор слов и разделит его на отдельные термины, называемые "токенами". Наиболее распространенным токенизатором является токенизатор пробелов, который разбивает набор слов на пробелы. Например, такое поле, как "red leather sofa", будет проиндексировано в elasticsearch как 3 лексемы: "red", "leather" и "sofa". Или "1950 FABRIC couch" будет проиндексирован как "1950", "FABRIC" и "couch". Если пользователь введет любой из этих терминов, будут возвращены документы с любым из этих терминов.
Без токенизаторов все многословные поля сохранялись бы в виде полного предложения, для возврата результата которого требовалось бы точное совпадение, например, при поиске именно термина "red leather sofa". Не волнуйтесь, этот процесс токенизации происходит за кулисами, и вы по-прежнему будете видеть свой документ именно в том виде, в котором вы его импортировали, а не в виде кусков текста. Elasticsearch предлагает множество различных типов токенизаторов: токены, которые создаются при изменении регистра (нижний на верхний), при переходе от одного класса символов к другому (буквы к цифрам) и т. д..
Фильтр токенов
После создания токена он проходит через фильтры анализатора. Фильтр токенов - это операция, выполняемая над токенами, которая изменяет их тем или иным способом. Например, вы можете преобразовать все лексемы в строчные ("FABRIC" в "fabric"), удалить пробелы из каждой лексемы (изменив лексему "red leather sofa" на "redleathersofa") или взять каждую лексему и очистить ее до стеблей.
Что такое стебли? В английском языке вы не хотите, чтобы слова "walk", "walks", "walking" и "walked" индексировались как разные слова, поэтому фильтр лексем со стеблями возьмет все четыре этих слова и превратит их в "walk". Опять же, это не изменяет и не редактирует импортированный документ, вы по-прежнему будете видеть его в том виде, в котором импортировали, но при поиске все версии этого слова будут автоматически индексироваться как один и тот же стемпинг. Вы можете наложить столько фильтров маркеров, сколько захотите, чтобы они соответствовали вашему сценарию использования, единственное, на что следует обратить внимание, - это то, что фильтры маркеров соединяются и применяются в том же порядке, в каком они были указаны.
Токен-фильтры - это именно то место, где вы можете добавить поддержку синонимов в свои документы с помощью фильтра синонимов.
Как внедрить синонимы в Elasticsearch с помощью фильтра токенов синонимов
Фильтр синонимов возьмет каждый токен, созданный в процессе токенизации, о котором говорилось выше, и проверит, соответствует ли он какому-либо термину, который вы определили в списке синонимов.
- Если да, то будет произведен маппинг синонимов на эти токены.
- Если вы импортируете документ с "red leather sofa" с помощью токенизатора пробелов, вы получите токены "red", "leather" и "sofa".
- Если добавить фильтр токенов синонимов для маппинга слова "sofa" на "couch" и "sectional", то когда Elasticsearch создаст токен "sofa", он также создаст токены для синонимов, так что при вводе слова "sofa" будет возвращен документ, сохраненный с "red leather sofa".
Не вдаваясь в подробности, чтобы не потеряться в деталях, вы можете решить, будут ли эти маркеры создаваться в момент индексации документов или на лету в момент поиска. Другими словами, для анализаторов времени индексации вы можете сохранить слово "sofa" как "couch" и "sectional" в качестве дополнительного маркера в индексе (занимая немного больше места для хранения), или, наоборот, для анализаторов времени поиска вы можете сохранить слово "sofa" только как "sofa" в индексе, но когда пользователь ищет слово "couch", оно расширяется до слов "couch" и "sectional" во время поиска.
Одно из главных преимуществ использования анализаторов времени поиска по сравнению с анализаторами времени индекса заключается в том, что вы можете редактировать и добавлять синонимы на лету, не переиндексируя все документы. Переиндексация может стать головной болью, если вам придется повторять ее каждый раз, когда вы захотите внести изменения в список синонимов. Как правило, лучше всего использовать анализаторы синонимов во время поиска, а не во время индексации. Подробнее об использовании анализаторов времени поиска и времени индекса читайте в этой статье.
В маппинге для конкретного поля вы можете определить, как вы хотите сохранить лексемы в индексе (анализаторы времени индекса) и как вы хотите, чтобы термины анализировались при поиске пользователем (анализаторы времени поиска). По умолчанию они одинаковы.
Пример
Давайте рассмотрим пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | PUT /furniture-index { "settings": { "index": { "analysis": { "analyzer": { "my_synonym_analyzer": { "tokenizer": "whitespace", "filter": [ "lowercase", "my_synonym_token_filter" ] } }, "filter": { "my_synonym_token_filter": { "type": "synonym", "synonyms": [ "couch, sectional, sofa", "big, large => lrg", "small, tiny => sm, smll" ] } } } } }, "mappings": { "properties": { "product": { "type": "text", "analyzer": "standard", "search_analyzer": "my_synonym_analyzer" } } } } |
Сверху вниз мы создаем "my_synonym_analyzer", который будет выполнять токенизацию на белом пространстве, и фильтр токенов, который сначала переводит поиск в нижний регистр, а затем смотрит на `my_synonym_token_filter`, чтобы перебрать потенциальные синонимы.
Как видите, в "my_synonym_token_filter" синонимы определены тремя способами: первый способ делает три термина "couch", "sectional" и "sofa" эквивалентными, так что если пользователь ищет любое из этих трех слов, все остальные появятся.
Второй способ - однонаправленный: если пользователь набирает "big" или "large", будут возвращены результаты с "lrg", но если набрать "lrg", "big" или "large" не будет возвращено. При вводе "small" или "tiny" будут возвращены любые результаты с "sm" или "smll". В конце команды мы задаем маппинг для поля product, чтобы в качестве анализатора времени индекса использовался предопределенный анализатор `standard`, а в качестве анализатора времени поиска - пользовательский анализатор `my_synonym_analyzer`.
Давайте добавим несколько примеров документов:
1 2 3 4 5 6 7 | POST furniture-index/_bulk {"index":{"_id":"1"}} {"product":"smll red leather sofa"} {"index":{"_id":"2"}} {"product":"sm 1950 FABRIC couch"} {"index":{"_id":"3"}} {"product":"lrg corner sectional, leather"} |
Давайте запустим пример поиска:
1 2 3 4 5 6 7 8 | GET furniture-index/_search { "query": { "match": { "product": "sofa" } } } |
Все 3 продукта вернулись!
Пока что мы управляем списком синонимов непосредственно в настройках индекса. В зависимости от того, каким количеством синонимов вам нужно управлять, это может быть не очень хорошей идеей, поскольку хранение синонимов внутри настроек индекса приведет к увеличению состояния кластера и может вызвать проблемы с производительностью.
Существует два других способа более эффективного и оптимального управления синонимами:
- Через файл синонимов
- Через новый API Synonyms, выпущенный в версии 8.10.
Далее мы расскажем о каждом из вариантов.
Если вы хотите выбрать первый вариант, то сначала вам нужно создать текстовый файл под названием "my_list_synonyms.txt" в директории `config` ВСЕХ ваших экземпляров Elasticsearch. Если вы работаете на Elastic Cloud, у вас не будет доступа к файловой системе ваших узлов, но вы сможете работать с файлами синонимов через пользовательские связки. Допустимыми форматами этого файла являются Solr и WordNet:
1 2 3 | couch, sectional, sofa big, large => lrg small, tiny => sm, smll |
Нам также нужно изменить настройки индекса для этого в `my_synonym_token_filter`, добавив путь к текстовому файлу синонимов, который мы только что создали:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | PUT /furniture-index { "settings": { "index": { "analysis": { "analyzer": { "my_synonym_analyzer": { "tokenizer": "whitespace", "filter": [ "lowercase" , "my_synonym_token_filter" ] } }, "filter": { "my_synonym_token_filter": { "type": "synonym", "synonyms_path": "my_list_synonyms.txt", "updateable": true } } } } }, "mappings": { "properties": { "product": { "type": "text", "analyzer": "standard", "search_analyzer": "my_synonym_analyzer" } } } } |
Теперь, когда мы захотим обновить синонимы, поскольку мы установили фильтр на `"updateable": true`, мы просто отредактируем файл и отправим следующие две команды (на версии 7.3 и выше):
1 2 | POST /furniture-index/_reload_search_analyzers POST /furniture-index/_cache/clear?request=true |
Это перезагрузит все изменения, внесенные в файл синонимов, и очистит кэш запросов, чтобы убедиться, что в нем нет ответов, основанных на предыдущей версии вашего файла синонимов!
Для тех, кто работает с версией старше 7.3, вам нужно временно закрыть, а затем снова открыть индекс, в который вы хотите перезагрузить файл синонимов, а не отправлять две вышеуказанные команды. При повторном открытии индекс перезагрузит новый файл синонимов.
Если в вашем кластере несколько узлов, обязательно обновите файл синонимов на каждом узле, чтобы обеспечить согласованный результат.
Второй вариант - использовать новый API Synonyms, который был выпущен в бета-версии в версии 8.10. Этот вариант является наиболее гибким, поскольку он не влияет на состояние кластера, как мы видели ранее, и не требует загрузки файла синонимов на все узлы и очистки всех кэшей.
Этот новый API просто хранит синонимы в системном индексе, как и любые другие документы. Он позволяет создавать наборы синонимов и связанные с ними правила. Наборы синонимов - это просто способ последовательного хранения связанных синонимов, который можно динамически обновлять во время поиска. Количество наборов не ограничено. Каждый набор синонимов содержит правила синонимов. Давайте посмотрим, как определить такой набор синонимов и применить его к нашему `my_synonym_analyzer`.
Для начала нам нужно создать наш первый набор синонимов с помощью следующей команды (обратите внимание, что используемый формат должен соответствовать формату Solr):
1 2 3 4 5 6 7 8 9 10 11 12 13 | PUT _synonyms/furniture-synonyms { "synonyms_set": [ { "id": "couch-synonyms", "synonyms": "couch, sectional, sofa" }, { "id": "large-synonyms", "synonyms": "big, large => lrg" } ] } |
Итак, мы создали набор синонимов под названием `furniture-synonyms`, но он содержит только первые два правила синонимов, которые мы использовали ранее, для "couch" и "large". Мы забыли добавить правило для "маленького", но нам не нужно обновлять весь набор синонимов, мы можем просто добавить одно правило в существующий набор синонимов, как показано ниже:
1 2 3 4 | PUT _synonyms/furniture-synonyms/small-synonyms { "synonyms": "small, tiny => sm, smll" } |
Теперь к ранее созданному набору синонимов добавлено новое правило с идентификатором `small-synonyms`. И не нужно ничего перезагружать, все наборы синонимов и правила перезагружаются автоматически, и поисковые анализаторы готовы их подхватить. Видите, как все просто?
Теперь, когда наши синонимы сохранены, мы можем определить наш поисковый анализатор `my_synonym_token_filter` с помощью следующей команды (обратите внимание, что мы показываем только ту часть, которая изменилась):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | PUT /furniture-index { "settings": { "index": { "analysis": { … "filter": { "my_synonym_token_filter": { "type": "synonym", "synonyms_set": "furniture-synonyms", "updateable": true } } } } }, … } |
Как видите, нам нужно указать только `synonyms_set вместо `synonyms_file, все остальное осталось прежним.
В зависимости от ваших потребностей, ограничений среды и используемой версии у вас теперь есть различные варианты управления синонимами.
Заключение
В этой статье мы рассказали о важности анализаторов текста, а также о том, почему анализаторы, работающие в режиме поиска, более гибкие, чем анализаторы, работающие в режиме индекса. Затем мы рассмотрели преимущества фильтра маркеров синонимов в процессе анализа.
В завершение мы показали три различных способа управления синонимами: встроенный в настройки индекса, через текстовый файл, который необходимо загрузить на все узлы (начиная с версии 7.3), и через новый API Synonyms (начиная с версии 8.10), который обеспечивает наибольшую гибкость.
Чтобы обеспечить пользователям релевантный поиск, требуется множество тонких настроек, и синонимы - отличный способ предоставить пользователям тот опыт, на который вы рассчитываете. Как и в случае с любой другой системой, важно постоянно тестировать и вносить изменения в систему, чтобы она оставалась актуальной и работала так, как вы задумали.