В Elasticsearch значения текстовых полей анализируются при добавлении или обновлении документов. Что означает, что текст анализируется? При индексировании документа его полнотекстовые поля проходят через процесс анализа. Под полнотекстовыми полями я имею в виду поля типа текст, а не поля ключевых слов, которые не анализируются. Я вернусь к деталям этого процесса через некоторое время, но в основном он включает в себя разделение текста на термины, понижение символов и т. д. В более общем смысле, процесс анализа включает в себя токенизацию и нормализацию блока текста.
Это делается для того, чтобы облегчить поиск по тексту. Вы имеете полный контроль над процессом анализа, потому что можно управлять тем, какой анализатор используется. В большинстве случаев достаточно стандартного анализатора, но я еще вернусь к тому, как изменить процесс анализа, если вам это понадобится.
Результаты анализа - это то, что хранится в индексе, в который добавляется документ. Точнее, проанализированные термины хранятся в так называемом инвертированном индексе, к которому я скоро вернусь. Это означает, что при выполнении поисковых запросов мы ищем результаты анализа, а не документы в том виде, в котором они были добавлены в индекс.
Итак, напомним... Когда мы индексируем документ, Elasticsearch берет полные текстовые поля документа и пропускает их через процесс анализа. Текстовые поля токенизируются в термины, а термины преобразуются в строчные буквы. По крайней мере, это поведение по умолчанию. Результаты этого процесса анализа добавляются в так называемый инвертированный индекс, по которому мы выполняем поисковые запросы. Подробнее об этом чуть позже, потому что сначала пришло время рассмотреть процесс анализа и его работу более подробно.
Более подробный взгляд на анализаторы
Итак, я только что познакомил вас с тем, как Elasticsearch анализирует документы при их индексировании. Теперь я хочу более подробно рассмотреть, как это работает. Работа в рамках процесса анализа, о котором я говорил минуту назад, выполняется так называемым анализатором. Анализатор состоит из трех частей: фильтров символов, фильтров лексем и токенизатора. Анализатор - это, по сути, пакет этих строительных блоков, каждый из которых изменяет входной поток. Так, при индексировании документа он проходит через следующий поток.
Во-первых, можно добавить ноль или более символьных фильтров. Символьный фильтр получает исходный текст текстового поля, а затем может преобразовать значение, добавляя, удаляя или изменяя символы. Примером этого может быть удаление любой HTML-разметки.
После этого токенизатор разбивает текст на отдельные лексемы, которые обычно представляют собой слова. Поэтому если у нас есть предложение из десяти слов, мы получим массив из десяти лексем. Анализатор может иметь только один токенизатор. По умолчанию используется токенизатор с именем standard, который использует алгоритм Unicode Text Segmentation. Не вдаваясь в подробности, можно сказать, что он в основном разделяет текст по пробелам, а также удаляет большинство символов, таких как запятые, точки, запятые с запятой и т.д. Это связано с тем, что большинство символов не являются полезными при поиске, поскольку они предназначены для чтения человеком. Можно изменить токенизатор, возможно, на токенизатор пробельных символов, который сохраняет эти символы, но это редко бывает хорошей идеей, так как стандартный токенизатор лучше в большинстве случаев.
Помимо разбиения текста на лексемы, токенизаторы также отвечают за запись положения лексем, включая смещения начальных и конечных символов для слов, которые представляют лексемы. Это позволяет сопоставить лексемы с исходными словами, что используется для выделения совпадающих слов. Расположение лексем используется при выполнении нечеткого поиска по фразе и поиска по близости.
После разделения текста на лексемы он проходит через ноль или более фильтров лексем. Фильтр лексем может добавлять, удалять или изменять лексемы. Это похоже на фильтр символов, но фильтры токенов работают с потоком токенов, а не с потоком символов.
Существует несколько различных фильтров маркеров, самый простой из которых - фильтр маркеров нижнего регистра, который просто преобразует все символы в нижний регистр. Еще один фильтр маркеров, который вы можете использовать, называется stop. Он удаляет обычные слова, которые называются стоп-словами. Это такие слова, как "the", "a", "and", "at" и т. д. Это слова, которые на самом деле не представляют никакой ценности для поля с точки зрения возможности поиска, потому что каждое слово придает документу очень малое значение с точки зрения релевантности. Еще один фильтр маркеров, о котором я хочу упомянуть, - это фильтр синонимов, который полезен для придания похожим словам одинакового значения. Например, слова "приятный" и "хороший" имеют одинаковую семантику, хотя это разные слова, и то же самое можно сказать о словах "ужасный" и "ужасный". Поэтому, используя фильтр синонимов, вы можете найти документы, содержащие слово "приятный", даже если вы ищете слово "хороший", потому что значение у них одинаковое, и поэтому документ с высокой вероятностью будет таким же релевантным, как если бы в запросе использовалось другое слово.
Итак, теперь, когда мы рассмотрели различные части анализаторов, давайте вкратце рассмотрим пример. Когда Elasticsearch обнаруживает строковое поле в документе, он конфигурирует его как полнотекстовое поле и применяет стандартный анализатор. Это происходит автоматически, если вы не проинструктируете Elasticsearch об обратном. Следующий пример - это поведение по умолчанию со стандартным анализатором.
В стандартном анализаторе отсутствуют символьные фильтры, поэтому вводимый текст сразу попадает в токенизатор. Стандартный анализатор использует токенизатор с именем standard, который делает то, о чем я говорил ранее: отфильтровывает различные символы и разделяет текст по пробелам.
Таким образом, предложение "Я в настроении пить полусухое красное вино!" разбивается на лексемы с удалением некоторых символов. В частности, дефис и восклицательный знак удаляются, но обратите внимание, что апостроф не удаляется. Затем этот массив лексем отправляется в цепочку фильтров лексем. Первый из них - фильтр лексем с именем standard. Этот фильтр токенов фактически ничего не делает. Его единственная цель - служить в качестве заполнителя на случай, если в будущем потребуется добавить какую-либо фильтрацию по умолчанию. Фильтр стоп-лексики для стоп-слов добавлен по умолчанию, но он отключен. Это означает, что единственным активным фильтром лексем, который используется в стандартном анализаторе, является фильтр строчных букв, который преобразует все буквы в строчные. В данном примере это влияет только на первый токен. И это все! Это процесс, через который по умолчанию проходят все полнотекстовые поля.
Существует API Analyze, который можно использовать для проверки результатов применения к некоторому тексту символьных фильтров, токенизаторов, токен-фильтров и анализаторов в целом. Это очень полезно для понимания того, как работают различные части анализаторов, поэтому давайте рассмотрим это вкратце.
Использование API Analyze
Мы можем использовать API Analyze, чтобы проверить, как определенные символьные фильтры, токенизаторы, фильтры токенов или анализаторы обрабатывают вводимый текст. Я хочу использовать пример предложения из предыдущего примера и пройти через каждый шаг, который выполняет стандартный анализатор. Конечный результат будет таким же, как вы видели раньше, поэтому я хочу показать вам, как использовать API Analyze и как с его помощью можно отладить и лучше понять процесс анализа данного фрагмента текста.
Поскольку стандартный анализатор не использует фильтр символов, первым шагом будет токенизатор, а именно стандартный токенизатор.
1 2 3 4 5 | POST _analyze { "tokenizer": "standard", "text": "I'm in the mood for drinking semi-dry red wine!" } |
Результатом этого запроса будет то, что вы видели раньше, но кроме лексем, вы также получите некоторую дополнительную информацию из API Analyze, например, смещение символов.
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | { "tokens": [ { "token": "I'm", "start_offset": 0, "end_offset": 3, "type": "", "position": 0 }, { "token": "in", "start_offset": 4, "end_offset": 6, "type": "", "position": 1 }, { "token": "the", "start_offset": 7, "end_offset": 10, "type": "", "position": 2 }, { "token": "mood", "start_offset": 11, "end_offset": 15, "type": "", "position": 3 }, { "token": "for", "start_offset": 16, "end_offset": 19, "type": "", "position": 4 }, { "token": "drinking", "start_offset": 20, "end_offset": 28, "type": "", "position": 5 }, { "token": "semi", "start_offset": 29, "end_offset": 33, "type": "", "position": 6 }, { "token": "dry", "start_offset": 34, "end_offset": 37, "type": "", "position": 7 }, { "token": "red", "start_offset": 38, "end_offset": 41, "type": "", "position": 8 }, { "token": "wine", "start_offset": 42, "end_offset": 46, "type": "", "position": 9 } ] } |
Далее идут фильтры токенов. Стандартными фильтрами токенов являются стандартный и строчный, причем первый на сегодняшний день ничего не делает. Поэтому мы можем просто использовать фильтр строчных лексем, чтобы увидеть тот же эффект.
1 2 3 4 5 | POST _analyze { "filter": [ "lowercase" ], "text": "I'm in the mood for drinking semi-dry red wine!" } |
1 2 3 4 5 6 7 8 9 10 11 | { "tokens": [ { "token": "i'm in the mood for drinking semi-dry red wine!", "start_offset": 0, "end_offset": 47, "type": "word", "position": 0 } ] } |
Как вы можете видеть, первое слово написано строчными буквами, и это единственное слово, которое было написано заглавными буквами. Если бы мы хотели добавить фильтр символов, мы бы добавили ключ с именем char_filter.
Это были шаги, которые выполняет стандартный анализатор, и результаты соответствуют тому, что я показал вам ранее в этом посте. Если я выполню запрос, который просто определит анализатор как стандартный, вы увидите те же результаты, потому что он выполняет те же шаги, которые вы только что видели.
1 2 3 4 5 | POST _analyze { "analyzer": "standard", "text": "I'm in the mood for drinking semi-dry red wine!" } |
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | { "tokens": [ { "token": "i'm", "start_offset": 0, "end_offset": 3, "type": "", "position": 0 }, { "token": "in", "start_offset": 4, "end_offset": 6, "type": "", "position": 1 }, { "token": "the", "start_offset": 7, "end_offset": 10, "type": "", "position": 2 }, { "token": "mood", "start_offset": 11, "end_offset": 15, "type": "", "position": 3 }, { "token": "for", "start_offset": 16, "end_offset": 19, "type": "", "position": 4 }, { "token": "drinking", "start_offset": 20, "end_offset": 28, "type": "", "position": 5 }, { "token": "semi", "start_offset": 29, "end_offset": 33, "type": "", "position": 6 }, { "token": "dry", "start_offset": 34, "end_offset": 37, "type": "", "position": 7 }, { "token": "red", "start_offset": 38, "end_offset": 41, "type": "", "position": 8 }, { "token": "wine", "start_offset": 42, "end_offset": 46, "type": "", "position": 9 } ] } |
Именно так анализируются текстовые поля при индексировании документов. Но что происходит с результатами анализа? Они хранятся в так называемом инвертированном индексе.