Elastic Search, Русская морфология

23 апр. 2018 г.

Это третья статья из цикла про Elastic Search. Речь пойдет о настройке русской морфологии Elastic Search. Настройка морфологии происходит путем добавлления в elastic search специальных анализаторов. Анализаторы учитывают особенности языка и за счет этого улучшаются результаты поиска.

Стандартная морфология

В Elastic Search есть стандартный плагин. Он работает из коробки и обеспечивает базовую морфологию. Возьмем запрос на создания индекса из предыдущей статьи. Здесь созжаются фильтры ru_stop и ru_stemmer, которые затем добавляются в анализатор my_synonyms

curl -X PUT \
  http://localhost:9200/product \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: 9068558d-fd30-4610-0d2d-8ada239fbc1e' \
  -d '{
  "settings": {
    "analysis": {
      "filter": {
        "my_synonym_filter": {
          "type": "synonym",
          "synonyms": [
            "шуруповерт, отвертка"
          ]
        },
        "ru_stop": {
          "type": "stop",
          "stopwords": "_russian_"
        },
        "ru_stemmer": {
          "type": "stemmer",
          "language": "russian"
        }
      },
      "analyzer": {
        "my_synonyms": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "my_synonym_filter",
            "ru_stop",
            "ru_stemmer"
          ]
        }
      }
    }
  }
}'

Проверить работу анализатора русской морфологии можно таким образом:

curl -X POST \
  http://localhost:9200/_analyze \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: a7ba7c82-642b-0b31-d2ef-1c644c6e249e' \
  -d '{
  "analyzer" : "russian",
  "text" : "Отвертка аккумуляторная ИНТЕРСКОЛ ОА-3,6Ф блистер (433.0.2.00) li-ion Номинальное напряжение, В 3,6 Частота вращения, об/мин 210 Макс. Крутящий момент, Нм 5 Число ступеней регулировки крутящего момента 15+1 Масса, кг 0,5 Особенности: Технология Li-ion, Редуктор с металлическими пластинами, компактность, светодиодный фонарь, индикатор заряда, LED-подсветка."
}'

В результате текст бьется на токены, примерно так:

{
    "tokens": [
        {
            "token": "отвертк",
            "start_offset": 0,
            "end_offset": 8,
            "type": "<ALPHANUM>",
            "position": 0
        },
        {
            "token": "аккумуляторн",
            "start_offset": 9,
            "end_offset": 23,
            "type": "<ALPHANUM>",
            "position": 1
        },
        {
            "token": "интерскол",
            "start_offset": 24,
            "end_offset": 33,
            "type": "<ALPHANUM>",
            "position": 2
        },
        {
            "token": "о",
            "start_offset": 34,
            "end_offset": 36,
            "type": "<ALPHANUM>",
            "position": 3
        },
        ...
    ]
}    

Далее в поиске можно попробовать искать по токенам, записи будут возвращаться. По запросу "отвертк" должна будет вернуться запись. По запросу "отвер" вернется пустое множество. Также существует плагин с улучшеной морфологией русского языка. К сожалению, на момент написания статьи, он не портирован на Elastic Search 6.

Далее в статье будет использоваться Elastic Search 5.6.5

Продвинутая морфология. Elastic Search 5.6.5

Запускаем Elastic Search 5.6.5 в docker:

docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:5.6.5

Устанавливаем плагин:

docker exec -it bash elasticsearch
elasticsearch-plugin install http://dl.bintray.com/content/imotov/elasticsearch-plugins/org/elasticsearch/elasticsearch-analysis-morphology/5.6.5/elasticsearch-analysis-morphology-5.6.5.zip

Важный момент. Я делал это в docker и плагин устанавливался автоматически. При этом во время ручной установки возникли проблемы.

docker-compose.yml:

version : '2'
services:
    elasticsearch5:
        build: ./docker/elasticsearch5
        container_name: elasticsearch5
        ports:
          - 9200:9200
          - 9300:9300

docker/elasticsearch5/Dockerfile:

FROM elasticsearch:5.6.5

RUN apt-get update
RUN apt-get install -y mc

RUN elasticsearch-plugin install http://dl.bintray.com/content/imotov/elasticsearch-plugins/org/elasticsearch/elasticsearch-analysis-morphology/5.6.5/elasticsearch-analysis-morphology-5.6.5.zip

Проверяем работу плагина:

curl -X POST \
  http://localhost:9200/_analyze \
  -H 'Content-Type: application/json' \
  -d '{
  "analyzer" : "russian_morphology",
  "text" : "Рукоятка крепится на шарнире. Можно выбрать либо прямое положение, либо пистолетное."
}'

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

{
    "tokens": [
        {
            "token": "рукоятка",
            "start_offset": 0,
            "end_offset": 8,
            "type": "<ALPHANUM>",
            "position": 0
        },
        {
            "token": "крепиться",
            "start_offset": 9,
            "end_offset": 17,
            "type": "<ALPHANUM>",
            "position": 1
        },
        {
            "token": "на",
            "start_offset": 18,
            "end_offset": 20,
            "type": "<ALPHANUM>",
            "position": 2
        },
        {
            "token": "шарнир",
            "start_offset": 21,
            "end_offset": 28,
            "type": "<ALPHANUM>",
            "position": 3
        },
        {
            "token": "можно",
            "start_offset": 30,
            "end_offset": 35,
            "type": "<ALPHANUM>",
            "position": 4
        },
        ...
    ]
}  

Тестируем поиск

Создаем индекс

curl -X PUT \
  http://localhost:9200/rustest \
  -H 'Content-Type: application/json' \
  -d '{
    "settings": {
        "analysis": {
            "analyzer": {
                "my_analyzer": {
                    "type": "custom",
                    "tokenizer": "standard",
                    "filter": ["lowercase", "russian_morphology", "english_morphology", "my_stopwords"]
                }
            },
            "filter": {
                "my_stopwords": {
                    "type": "stop",
                    "stopwords": "а,без,более,бы,был,была,были,было,быть,в,вам,вас,весь,во,вот,все,всего,всех,вы,где,да,даже,для,до,его,ее,если,есть,еще,же,за,здесь,и,из,или,им,их,к,как,ко,когда,кто,ли,либо,мне,может,мы,на,надо,наш,не,него,нее,нет,ни,них,но,ну,о,об,однако,он,она,они,оно,от,очень,по,под,при,с,со,так,также,такой,там,те,тем,то,того,тоже,той,только,том,ты,у,уже,хотя,чего,чей,чем,что,чтобы,чье,чья,эта,эти,это,я,a,an,and,are,as,at,be,but,by,for,if,in,into,is,it,no,not,of,on,or,such,that,the,their,then,there,these,they,this,to,was,will,with"
                }
            }
        }
    }
}'

Создаем маппинг

curl -X PUT \
  http://localhost:9200/rustest/_mapping/doc \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: 412756ab-518e-566c-97eb-401f6f9b8a03' \
  -d '{
   "properties": {
        "body": {
            "type": "text",
            "index": true,
            "search_analyzer": "my_analyzer",
            "analyzer": "my_analyzer",
            "term_vector": "with_positions_offsets_payloads"
        }
   }
}'

Добавляем документ

curl -X PUT \
  http://localhost:9200/rustest/doc/1 \
  -H 'Content-Type: application/json' \
  -d '{"body": "Рукоятка крепится на шарнире. Можно выбрать либо прямое положение, либо пистолетное."}'

Пробуем искать по нему:

curl -X POST \
  'http://localhost:9200/rustest/doc/_search?pretty=true' \
  -H 'Content-Type: application/json' \
  -d '{
  "query": {
    "match": {
      "body": "крепится"
    }
  }
}'

Все запросы из статьи оформлены в виде Postman коллекции. Скачать можно тут: elastic-search-russian-morfology.postman_collection.json

На этом пока все. Спасибо за внимание!