Многоязычный поиск в Elasticsearch: от Hello до Donaudampfschifffahrtsgesellschaftskapitän

ee985cbeb9a19f25a7a04b7aabc6a9c7.jpg

Привет, Хабр!

Когда ваш поиск начинает обслуживать пользователей, говорящих на разных языках, кажется, что начинается некий лингвистический хаос. Однако с Elasticsearch это не так уж сложно — если знаешь, как правильно настроить токенизацию, стемминг и аннотирование документов под каждый язык. Сегодня мы рассмотрим тему многоязычного поиска: разберём, как Elasticsearch понимает текст на разных языках, как обрабатывает сложные случаи вроде китайских иероглифов или немецких сложных слов, и как сделать так, чтобы результаты поиска были релевантны для каждого пользователя, независимо от его языка.

Токенизация текста

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

Проблемы токенизации в разных языках

  • Английский: Кажется, всё просто, но тут всплывают вопросы: что делать с пунктуацией? Как обработать сокращения? И что делать со словами вроде «isn’t»?

  • Китайский: Здесь токенизация становится игрой в угадайку, потому что иероглифы идут подряд без пробелов. Как разделить слова, если пробелов нет?

  • Немецкий: Встречал слова вроде Donaudampfschifffahrtsgesellschaftskapitän? Это слово из 40+ букв — и вот его надо как-то правильно разрезать на части.

  • Русский: Проблемы падежей, склонений и приставок. Слово «машина» может быть «машин», «машинами», «машине» — и каждая форма должна попадать в результат поиска.

Теперь разберёмся, как это решить с помощью анализаторов и фильтров в Elasticsearch.

Elasticsearch использует анализаторы для токенизации и обработки текста. Эти анализаторы — не волшебные существа, которые знают все языки мира, а набор фильтров, которые применяются по цепочке к тексту. Каждый язык имеет свои нюансы, и правильный выбор анализатора — ключ к успеху.

Токенизация для английского текста

Начнём с простого. Для английского языка можно использовать базовый Standard Analyzer, который разделяет текст на слова, игнорируя пунктуацию и приводя всё к нижнему регистру. Но иногда этого недостаточно.

Пример настройки анализатора:

{
  "settings": {
    "analysis": {
      "analyzer": {
        "english_custom": {
          "type": "standard",
          "stopwords": "_english_",
          "filter": ["lowercase", "porter_stem"]
        }
      }
    }
  }
}

Здесь мы добавили porter_stem фильтр, который сокращает слова до их основы (например,»running» станет »run»).

Токенизация для китайского текста

В китайском нет пробелов, а каждый иероглиф может быть самостоятельным словом или частью большого составного слова. Для китайского текста Elasticsearch предлагает использовать Smart Chinese Analyzer.

Пример:

{
  "settings": {
    "analysis": {
      "analyzer": {
        "chinese": {
          "type": "smartcn"
        }
      }
    }
  }
}

Этот анализатор использует встроенные словари для токенизации текста. Smart Chinese Analyzer автоматически разделит иероглифы на слова.

Токенизация для немецкого текста

Теперь немецкий. Токенизация в немецком может стать настоящей проблемой из-за составных слов. Классический пример: Donaudampfschifffahrtsgesellschaftskapitän. Для работы с такими словами можно использовать German Analyzer, который включает фильтры для стемминга и обработки сложных слов.

Пример настройки:

{
  "settings": {
    "analysis": {
      "analyzer": {
        "german_custom": {
          "type": "standard",
          "stopwords": "_german_",
          "filter": ["lowercase", "german_normalization", "german_stem"]
        }
      }
    }
  }
}

Тут у нас german_normalization, который обрабатывает такие особенности языка, как умлауты (ä, ö, ü), и german_stem, который сокращает слова до их корня, сохраняя смысл.

Токенизация для русского текста

Для русского языка проблемы, как я уже упомянул, возникают с падежами и склонениями. Здесь нам на помощь приходит Russian Analyzer, который умеет обрабатывать морфологию русского языка.

Пример:

{
  "settings": {
    "analysis": {
      "analyzer": {
        "russian_custom": {
          "type": "standard",
          "stopwords": "_russian_",
          "filter": ["lowercase", "russian_morphology", "russian_stop"]
        }
      }
    }
  }
}

Здесь фильтр russian_morphology работает с падежами и словоформами, а russian_stop фильтрует ненужные слова вроде «и», «в», «на».

Как не облажаться с токенизацией

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

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

Не забывайте про языковые особенности. Например, в немецком умлауты могут быть заменены на гласные без точек, а в русском необходимо учитывать, что буква «ё» и «е» могут быть взаимозаменяемы в поиске.

Stemming и Lemmatization

А сейчас поговорим о том, как не потерять смысл в многоязычном поиске, когда есть такие слова, как »идти»,»идёт»,»шел», и все они должны быть поняты Elasticsearch как одно и то же понятие. Именно здесь помогают stemming и lemmatization — два мощных инструмента, которые помогают поисковому движку делать текстовое сравнение умнее.

Разберемся в терминах:

  • Stemming — это процесс, который отрезает окончания слов, оставляя их основу (stem). Например,»playing»,»played» и »plays» будут сведены к »play». Метод грубый, но быстрый.

  • Lemmatization — более интеллектуальный процесс. Лемматизация приводит слово к его базовой форме (lemma) с учётом грамматического контекста. Например, слова »better» и »good» сведутся к одной лемме — »good». Более сложная задача, но результат точнее.

В разных языках лемматизация и стемминг работают по-разному. Что эффективно для английского языка, не всегда сработает для русского или немецкого. Основная проблема — грамматические различия между языками. Например:

  • В русском языке слово »идти» может изменяться по времени, числу и падежам.

  • В немецком сложные слова объединяются в одну строку.

  • В китайском нет морфологии, но важны контекстные лексические значения.

Stemming и lemmatization в Elasticsearch

Elasticsearch имеет встроенные анализаторы и фильтры для стемминга и лемматизации на разных языках. =

Stemming для английского языка

Для английского стемминг можно настроить с использованием Porter Stemming Algorithm — он преобразует слова к их основным формам.

Пример настройки:

{
  "settings": {
    "analysis": {
      "filter": {
        "english_stemmer": {
          "type": "stemmer",
          "language": "english"
        }
      },
      "analyzer": {
        "english_analyzer": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "english_stemmer"
          ]
        }
      }
    }
  }
}

Этот пример настроит фильтр стемминга для английского языка. Когда ты отправляешь текст, вроде »running»,»ran», или »runs», анализатор приводит все эти слова к »run».

Stemming для немецкого языка

Для немецкого в есть German Normalization Filter и German Light Stemmer.

Пример настройки:

{
  "settings": {
    "analysis": {
      "filter": {
        "german_normalization": {
          "type": "german_normalization"
        },
        "german_stemmer": {
          "type": "stemmer",
          "language": "light_german"
        }
      },
      "analyzer": {
        "german_analyzer": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "german_normalization",
            "german_stemmer"
          ]
        }
      }
    }
  }
}

Фильтр german_normalization обрабатывает буквы с умлаутами и заменяет ß на »ss», что улучшает корректность токенизации.

Лемматизация для русского языка

С русским языком сложнее: из-за развитой морфологии стемминг не всегда даёт точные результаты. Поэтому для русского языка часто лучше использовать лемматизацию. В Elasticsearch для русского языка можно воспользоваться специальным фильтром морфологии.

Пример настройки:

{
  "settings": {
    "analysis": {
      "filter": {
        "russian_morphology": {
          "type": "russian_morphology"
        }
      },
      "analyzer": {
        "russian_analyzer": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "russian_morphology"
          ]
        }
      }
    }
  }
}

Здесь мы используем russian_morphology — этот фильтр обрабатывает различные формы слов, приводя их к одной основе. Например,»идти»,»идёт» и »шел» будут рассматриваться как одно и то же слово.

Лемматизация для испанского языка

Испанский язык имеет свои особенности. Для него также применим лемматизатор, который приводит к нормальной форме глаголы и существительные.

Пример настройки:

{
  "settings": {
    "analysis": {
      "filter": {
        "russian_stop": {
          "type": "stop",
          "stopwords": "_russian_"
        }
      },
      "analyzer": {
        "russian_analyzer_with_stop": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "russian_morphology",
            "russian_stop"
          ]
        }
      }
    }
  }
}

Здесь мы используем лёгкий стеммер для испанского, который обрабатывает флективные формы, такие как »hablando» (говорящий) и »hablar» (говорить).

Настройка стоп-слов для многоязычного поиска

Стоп-слова — это слова, которые не несут смысловой нагрузки для поиска (например, предлоги и союзы). В разных языках свои стоп-слова. Elasticsearch имеет встроенные списки стоп-слов для популярных языков, которые можно настроить по своему усмотрению.

Пример настройки анализатора со стоп-словами:

{
  "settings": {
    "analysis": {
      "filter": {
        "russian_stop": {
          "type": "stop",
          "stopwords": "_russian_"
        }
      },
      "analyzer": {
        "russian_analyzer_with_stop": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "russian_morphology",
            "russian_stop"
          ]
        }
      }
    }
  }
}

Здесь фильтр russian_stop автоматически удаляет из текста русские предлоги и другие часто встречающиеся слова, которые не нужны для индексации.

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

Аннотирование и метаданные

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

Если твои пользователи из разных регионов и говорят на разных языках, ты обязан учитывать это в индексации и настройке поиска. Допустим, твой поиск должен обрабатывать документы на русском, английском и китайском. Для каждого языка у тебя должны быть свои настройки токенизации, фильтров, и самое главное — идентификация языка. Без грамотной работы с метаданными ты либо потеряешь релевантность, либо будешь перегружать поиск ненужной информацией.

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

Поле _lang: как определить язык документа

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

Пример структуры данных с полем _lang:

{
  "title": "Добро пожаловать",
  "body": "Это пример русского текста.",
  "_lang": "ru"
}

Здесь явно указываем, что язык документа — русский. Это важно, потому что для каждого языка Elasticsearch использует свои токенизаторы, стеммеры и фильтры.

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

Пример конфигурации индекса:

{
  "settings": {
    "analysis": {
      "analyzer": {
        "default": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase"]
        },
        "english_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "english_stemmer"]
        },
        "russian_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "russian_morphology"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "fields": {
          "english": {
            "type": "text",
            "analyzer": "english_analyzer"
          },
          "russian": {
            "type": "text",
            "analyzer": "russian_analyzer"
          }
        }
      },
      "body": {
        "type": "text",
        "fields": {
          "english": {
            "type": "text",
            "analyzer": "english_analyzer"
          },
          "russian": {
            "type": "text",
            "analyzer": "russian_analyzer"
          }
        }
      },
      "_lang": {
        "type": "keyword"
      }
    }
  }
}

Здесь задаём разные анализаторы для английского и русского языков. Поле _lang используется как ключ для определения языка документа, что помогает Elasticsearch автоматически применять нужный анализатор.

Как работать с метаданными

Само поле _lang — это лишь один из примеров. Можно расширять аннотирование, добавляя метаданные о типе контента, версии документа и времени создания. Это позволяет строить более сложные запросы и фильтровать данные на основе этих параметров.

Вот несколько метаданных, которые помогут в многоязычном поиске:

  1. _lang (язык) — как мы уже говорили, это обязательное поле для многоязычных проектов.

  2. version (версия) — полезно для поиска по версиям документа. Например, если ты хранишь черновики и финальные версии.

  3. timestamp (время создания) — важно для фильтрации документов по дате, особенно когда нужно искать самые свежие материалы на заданном языке.

Пример структуры с дополнительными метаданными:

{
  "title": "Welcome",
  "body": "This is an example of English text.",
  "_lang": "en",
  "version": "1.0",
  "timestamp": "2024-09-20T12:00:00"
}

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

Пример настройки индекса:

{
  "settings": {
    "index": {
      "number_of_shards": 3,
      "number_of_replicas": 1
    },
    "analysis": {
      "analyzer": {
        "default": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text"
      },
      "body": {
        "type": "text"
      },
      "_lang": {
        "type": "keyword"
      },
      "version": {
        "type": "keyword"
      },
      "timestamp": {
        "type": "date",
        "format": "yyyy-MM-dd'T'HH:mm:ss"
      }
    }
  }
}

Здесь указываем, что поле _lang должно быть проиндексировано как keyword (точное значение).

Теперь, когда документы содержат метаданные, можно использовать их для фильтрации и сортировки. Например, хочется получить все русскоязычные документы, отсортированные по дате создания:

Пример запроса:

{
  "query": {
    "bool": {
      "must": [
        { "match": { "_lang": "ru" } }
      ]
    }
  },
  "sort": [
    { "timestamp": { "order": "desc" } }
  ]
}

Этот запрос вернёт все документы на русском языке, отсортированные по времени создания (от более новых к старым).

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

Заключение

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

Надеюсь, данное руководство поможет вам сделать этот процесс более удобным.

Больше про инфраструктуру приложений коллеги из OTUS рассказывают в рамках практических онлайн-курсов. С полным списком курсов можете ознакомиться в каталоге.

Каталог курсов

© Habrahabr.ru