# Языковые анализаторы, синонимы, nGram

**URL:** https://discuss.elastic.co/t/ngram/116096
**Category:** Вопросы на русском языке
**Created:** [January 18, 2018, 3:46pm UTC](https://discuss.elastic.co/t/ngram/116096 "2018-01-18T15:46:53Z")
**Posts on this page:** 14
**Page:** 1

<div class="post-metadata">

### Author: ![v.ishchenko](https://avatars.discourse-cdn.com/v4/letter/v/8797f3/32.png) [@v.ishchenko](https://discuss.elastic.co/u/v.ishchenko)
#### Post date: [January 18, 2018, 3:46pm UTC](https://discuss.elastic.co/t/ngram/116096/1 "2018-01-18T15:46:53Z")

</div>

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

Стоит ли выделить анализатор для языков/синонимов/nGram отдельно (и применять их на этапе индексации к мультифилду) или  
использовать их комбинацию в филтрах в одном/нескольких анализаторах?

Может подскажите ресурсы с бест практикс, спасибо

---

<div class="post-metadata">

### Author: ![Igor\_Motov](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/igor_motov/32/45193_2.png) [@Igor\_Motov](https://discuss.elastic.co/u/Igor_Motov)
#### Post date: [January 22, 2018, 4:09pm UTC](https://discuss.elastic.co/t/ngram/116096/2 "2018-01-22T16:09:19Z")

</div>

Я бы начал с изучения [Definitive Guide](https://www.elastic.co/guide/en/elasticsearch/guide/current/languages.html), после этого поискал бы по этому форуму. Этот вопрос тут уже [несколько раз обсуждался](https://discuss.elastic.co/t/topic/104899). Если будут какие-нибудь конкретные вопросы - то спрашивайте.

> использовать их комбинацию в филтрах в одном/нескольких анализаторах?

nGram и стеммниг в одном анализаторе не уживаются, но синонимы можно использовать и в том и в другом анализаторе. Только при nGram синонимы будут хорошо работать только при индексировании.

---

<div class="post-metadata">

### Author: ![v.ishchenko](https://avatars.discourse-cdn.com/v4/letter/v/8797f3/32.png) [@v.ishchenko](https://discuss.elastic.co/u/v.ishchenko)
#### Post date: [January 23, 2018, 4:52pm UTC](https://discuss.elastic.co/t/ngram/116096/3 "2018-01-23T16:52:15Z")

</div>

У меня есть мапинг (без плагина для русс/анг.)

```auto
{
  "settings": {
    "analysis": {
      "filter": {
        "russian_stop": {
          "type": "stop",
          "stopwords": "_russian_"
        },
        "russian_stemmer": {
          "type": "stemmer",
          "language": "russian"
        },
        "russian_english_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"
        },
        "english_stop": {
          "type": "stop",
          "stopwords": "_english_"
        },
        "english_stemmer": {
          "type": "stemmer",
          "language": "english"
        },
        "english_possessive_stemmer": {
          "type": "stemmer",
          "language": "possessive_english"
        },
        "synonym": {
          "type": "synonym",
          "synonyms": [
            "бенеттон => united colors of benetton",
            "нью беланс => new balance",
            "зара => zara",
            "женская, женские, женский, девушкам => женщинам",
            "мужчина, мужские, мужской, мужская, парням, парню => мужчинам"
          ],
          "ignore_case": true,
          "expand": false
        },
        "edgengram": {
          "type": "edge_ngram",
          "min_gram": 3,
          "max_gram": 50
        }
      },
      "analyzer": {
        "index_edgengram": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "edgengram"
          ]
        },
        "russian_english": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "english_possessive_stemmer",
            "lowercase",
            "synonym",
            "russian_stop",
            "english_stop",
            "russian_stemmer",
            "english_stemmer",
            "russian_english_stopwords"
          ]
        }
      }
    }
  },
  "mappings": {
    "page": {
      "properties": {
        "title": {
          "type": "keyword",
          "fields": {
            "org": {
              "type": "text"
            },
            "raw": {
              "type": "text",
              "analyzer": "russian_english",
              "search_analyzer": "russian_english"
            },
            "ng": {
              "type": "text",
              "analyzer": "index_edgengram",
              "search_analyzer": "russian_english"
            }
          },
          "copy_to": [
            "search_data"
          ],
          "ignore_above": 256
        },
        "body": {
          "type": "keyword",
          "fields": {
            "org": {
              "type": "text"
            },
            "raw": {
              "type": "text",
              "analyzer": "russian_english",
              "search_analyzer": "russian_english"
            },
            "ng": {
              "type": "text",
              "analyzer": "index_edgengram",
              "search_analyzer": "russian_english"
            }
          },
          "copy_to": [
            "search_data"
          ],
          "ignore_above": 256
        },        
        "search_data": {
          "type": "text"
        }
      }
    }
  }
}
'

```

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

```auto
curl -XPOST 'http://localhost:9200/test/page' -d'
{"title": "United Colors Of Benetton", "body": "Серый"}
'

curl -XPOST 'http://localhost:9200/test/page' -d'
{"title": "United Colors Of Benetton", "body": "Белый"}
'

```

Теперь поиск: multi\_match + cross\_fields

```auto
curl 'http://localhost:9200/test/page/_search?pretty' -d'
{"query" :
  {"multi_match": 
    {"query": "Бенеттон Серый",
    "type": "cross_fields",
    "fields": ["*.org^10", "*.raw^5", "*.ng"],
    "operator": "and",
    "tie_breaker": 1,
    "minimum_should_match": "80%"}}}
}'

```

Получаем два документа, а должны один.  
Вот так все нормально:

```auto
curl 'http://localhost:9200/test/page/_search?pretty' -d'
{"query" :
  {"multi_match": 
    {"query": "United Colors Of Benetton Серый",
    "type": "cross_fields",
    "fields": ["*.org^10", "*.raw^5", "*.ng"],
    "operator": "and",
    "tie_breaker": 1,
    "minimum_should_match": "80%"}}}
}'

```

спасибо

---

<div class="post-metadata">

### Author: ![Igor\_Motov](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/igor_motov/32/45193_2.png) [@Igor\_Motov](https://discuss.elastic.co/u/Igor_Motov)
#### Post date: [January 23, 2018, 7:52pm UTC](https://discuss.elastic.co/t/ngram/116096/4 "2018-01-23T19:52:09Z")

</div>

Режим cross\_field не рассчитан на работу с полями, использующими разные анализаторы. Посмотрите [вот это разъяснение](https://www.elastic.co/guide/en/elasticsearch/reference/6.1/query-dsl-multi-match-query.html#_literal_cross_field_literal_and_analysis) в докумнтации. Там же есть пример, как с этим бороться.

---

<div class="post-metadata">

### Author: ![v.ishchenko](https://avatars.discourse-cdn.com/v4/letter/v/8797f3/32.png) [@v.ishchenko](https://discuss.elastic.co/u/v.ishchenko)
#### Post date: [January 24, 2018, 9:47am UTC](https://discuss.elastic.co/t/ngram/116096/5 "2018-01-24T09:47:59Z")

</div>

Указав явно analyzer и одно поле, всеравно получаем два документа:

```auto
curl 'http://localhost:9200/test/page/_search?pretty' -d'
{"query" :
  {"multi_match": 
     {"query": "Бенеттон Серый",
     "type": "cross_fields",
     "fields": ["*.raw"],
    "operator": "and",
     "analyzer": "russian_english",
     "minimum_should_match": "80%"}}}
 }'
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.79400253,
    "hits" : [
      {
        "_index" : "test",
        "_type" : "page",
        "_id" : "AWEj5tVmvfv5mY1-erl9",
        "_score" : 0.79400253,
        "_source" : {
          "title" : "United Colors Of Benetton",
          "body" : "Серый"
        }
      },
      {
        "_index" : "test",
        "_type" : "page",
        "_id" : "AWEj5vIBvfv5mY1-erl-",
        "_score" : 0.7594807,
        "_source" : {
          "title" : "United Colors Of Benetton",
          "body" : "Белый"
        }
      }
    ]
  }
}

```

Аналогично с этим запросом

```auto
curl 'http://localhost:9200/test/page/_search?pretty' -d'
{
  "query": {
    "bool": {
      "should": [
        {
          "multi_match": {
            "query": "Бенеттон Серый",
            "type": "cross_fields",
            "fields": [
              "*.raw^5"
            ],
            "operator": "and",
            "minimum_should_match": "80%"
          }
        },
        {
          "multi_match": {
            "query": "Бенеттон Серый",
            "type": "cross_fields",
             "operator": "and",
            "fields": [
              "*.ng"
            ]
          }
        }
      ]
    }
  }
}
'

```

Вот \_analyze по этому индексу

```auto
curl -XGET 'http://localhost:9200/test/_analyze?pretty' -d'
{
   "analyzer": "russian_english",
   "text" : "Бенеттон Серый"
 }
 '
{
  "tokens" : [
    {
      "token" : "unit",
      "start_offset" : 0,
      "end_offset" : 8,
      "type" : "SYNONYM",
      "position" : 0
    },
    {
      "token" : "сер",
      "start_offset" : 9,
      "end_offset" : 14,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "color",
      "start_offset" : 9,
      "end_offset" : 14,
      "type" : "SYNONYM",
      "position" : 1
    },
    {
      "token" : "benetton",
      "start_offset" : 9,
      "end_offset" : 14,
      "type" : "SYNONYM",
      "position" : 3
    }
  ]
}

```

---

<div class="post-metadata">

### Author: ![Igor\_Motov](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/igor_motov/32/45193_2.png) [@Igor\_Motov](https://discuss.elastic.co/u/Igor_Motov)
#### Post date: [January 24, 2018, 2:57pm UTC](https://discuss.elastic.co/t/ngram/116096/6 "2018-01-24T14:57:45Z")

</div>

Да это известная проблема с многословными синонима. Проблема тут:

> [@v.ishchenko](#):
>
> ```auto
> {
> "token" : "сер",
> "start_offset" : 9,
> "end_offset" : 14,
> "type" : "&lt;ALPHANUM&gt;",
> "position" : 1 <--------- 
> },
> {
> "token" : "color",
> "start_offset" : 9,
> "end_offset" : 14,
> "type" : "SYNONYM",
> "position" : 1 <--------- 
> }
> 
> ```

Когда запрос обрабатывается, elasticsearch думает, что `серый` и `color` - это одно и тоже слово потому, что у них одна и та же позиция. В результате, он генерирует вот такой запрос:

```auto
GET /test/_validate/query?pretty&explain&rewrite
{
  "query": {
    "multi_match": {
      "query": "Бенеттон Серый",
      "type": "cross_fields",
      "fields": [
        "*.raw"
      ],
      "operator": "and",
      "analyzer": "russian_english",
      "minimum_should_match": "80%"
    }
  }
}

```

```auto
{
  "valid": true,
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "explanations": [
    {
      "index": "test",
      "valid": true,
      "explanation": "+(title.raw:unit | body.raw:unit) +(title.raw:сер | title.raw:color | body.raw:сер | body.raw:color) +(title.raw:benetton | body.raw:benetton)"
    }
  ]
}

```

Если мы это запрос перепишем с AND и OR, то получим

```auto
(title.raw:unit OR body.raw:unit) AND
(title.raw:сер OR title.raw:color OR body.raw:сер OR body.raw:color) AND
(title.raw:benetton OR body.raw:benetton)

```

Эту проблему можно решить заменив многословные синонимы на однословные как-то так:

```auto
"united colors of benetton => united_colors_of_benetton, benetton",
"benetton => united_colors_of_benetton",
"бенеттон => united_colors_of_benetton",

```

Но лучшее решение будет переключиться на `synonym_graph` фильтр при поиске:

```auto
PUT test
{
  "settings": {
    "analysis": {
      "filter": {
        "russian_stop": {
          "type": "stop",
          "stopwords": "_russian_"
        },
        "russian_stemmer": {
          "type": "stemmer",
          "language": "russian"
        },
        "russian_english_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"
        },
        "english_stop": {
          "type": "stop",
          "stopwords": "_english_"
        },
        "english_stemmer": {
          "type": "stemmer",
          "language": "english"
        },
        "english_possessive_stemmer": {
          "type": "stemmer",
          "language": "possessive_english"
        },
        "search_synonyms" : {
          "type": "synonym_graph",
          "synonyms": [
            "бенеттон => united colors of benetton",
            "нью беланс => new balance",
            "зара => zara",
            "женская, женские, женский, девушкам => женщинам",
            "мужчина, мужские, мужской, мужская, парням, парню => мужчинам"
          ]
        },
        "synonym": {
          "type": "synonym",
          "synonyms": [
            "бенеттон => united colors of benetton",
            "нью беланс => new balance",
            "зара => zara",
            "женская, женские, женский, девушкам => женщинам",
            "мужчина, мужские, мужской, мужская, парням, парню => мужчинам"
          ],
          "ignore_case": true,
          "expand": false
        },
        "edgengram": {
          "type": "edge_ngram",
          "min_gram": 3,
          "max_gram": 50
        }
      },
      "analyzer": {
        "index_edgengram": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "edgengram"
          ]
        },
        "russian_english": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "english_possessive_stemmer",
            "lowercase",
            "synonym",
            "russian_stop",
            "english_stop",
            "russian_stemmer",
            "english_stemmer",
            "russian_english_stopwords"
          ]
        },
        "search_russian_english": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "english_possessive_stemmer",
            "lowercase",
            "search_synonyms",
            "russian_stop",
            "english_stop",
            "russian_stemmer",
            "english_stemmer",
            "russian_english_stopwords"
          ]
        }
      }
    }
  },
  "mappings": {
    "page": {
      "properties": {
        "title": {
          "type": "keyword",
          "fields": {
            "org": {
              "type": "text"
            },
            "raw": {
              "type": "text",
              "analyzer": "russian_english",
              "search_analyzer": "russian_english"
            },
            "ng": {
              "type": "text",
              "analyzer": "index_edgengram",
              "search_analyzer": "russian_english"
            }
          },
          "copy_to": [
            "search_data"
          ],
          "ignore_above": 256
        },
        "body": {
          "type": "keyword",
          "fields": {
            "org": {
              "type": "text"
            },
            "raw": {
              "type": "text",
              "analyzer": "russian_english",
              "search_analyzer": "russian_english"
            },
            "ng": {
              "type": "text",
              "analyzer": "index_edgengram",
              "search_analyzer": "russian_english"
            }
          },
          "copy_to": [
            "search_data"
          ],
          "ignore_above": 256
        },        
        "search_data": {
          "type": "text"
        }
      }
    }
  }
}

```

---

<div class="post-metadata">

### Author: ![v.ishchenko](https://avatars.discourse-cdn.com/v4/letter/v/8797f3/32.png) [@v.ishchenko](https://discuss.elastic.co/u/v.ishchenko)
#### Post date: [January 24, 2018, 3:25pm UTC](https://discuss.elastic.co/t/ngram/116096/7 "2018-01-24T15:25:15Z")

</div>

Да, круто!  
Я подозревал что то не то с синонимами, пробовал и так и сяк, но только вы указали на одинаковый position.  
Я смотрел в сторону synonym\_graph но варнинги в доке "This functionality is experimental .." испугали меня.  
Спасибо большое.

---

<div class="post-metadata">

### Author: ![v.ishchenko](https://avatars.discourse-cdn.com/v4/letter/v/8797f3/32.png) [@v.ishchenko](https://discuss.elastic.co/u/v.ishchenko)
#### Post date: [January 31, 2018, 5:42pm UTC](https://discuss.elastic.co/t/ngram/116096/8 "2018-01-31T17:42:25Z")

</div>

Подскажите еще, можно ли что то сделть в таком случае.  
В текущую схему что выше добавим еще один документ:

```auto
curl -XPOST 'http://localhost:9200/test/page' -d'
{"title": "джин", "body": "крепкий"}

```

Простой поиск с index\_edgengram

```auto
curl 'http://localhost:9200/test/page/_search?pretty' -d'
{
  "query": {
    "bool": {
      "should": [
        {
          "multi_match": {
            "query": "джинсы",
            "type": "cross_fields",
            "fields": [
              "*.ng"
            ],
            "operator": "and",
            "analyzer": "index_edgengram"
          }
        }
      ]
    }
  }
}
'

```

и мы получаем "джин" хотя просили "джинсы"

```auto
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.46029136,
    "hits" : [
      {
        "_index" : "test",
        "_type" : "page",
        "_id" : "AWFNQQrjvfv5mY1-erm4",
        "_score" : 0.46029136,
        "_source" : {
          "title" : "джин",
          "body" : "крепкий"
        }
      }
    ]
  }
}

_validate/query?explain:
{"valid":true,"_shards":{"total":1,"successful":1,"failed":0},"explanations":[{"index":"test","valid":true,"explanation":"+(blended(terms:[body.ng:джи, body.ng:джин, body.ng:джинс, body.ng:джинсы, title.ng:джи, title.ng:джин, title.ng:джинс, title.ng:джинсы])) #_type:page"}]

```

---

<div class="post-metadata">

### Author: ![Igor\_Motov](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/igor_motov/32/45193_2.png) [@Igor\_Motov](https://discuss.elastic.co/u/Igor_Motov)
#### Post date: [February 1, 2018, 2:09pm UTC](https://discuss.elastic.co/t/ngram/116096/9 "2018-02-01T14:09:28Z")

</div>

edgeNGram надо применять при индексации, а не при поиске.

---

<div class="post-metadata">

### Author: ![v.ishchenko](https://avatars.discourse-cdn.com/v4/letter/v/8797f3/32.png) [@v.ishchenko](https://discuss.elastic.co/u/v.ishchenko)
#### Post date: [February 1, 2018, 3:19pm UTC](https://discuss.elastic.co/t/ngram/116096/10 "2018-02-01T15:19:43Z")

</div>

> [@Igor\_Motov](#):
>
> и индексации, а не пр

поправочка) я имел в виду в вот таком запросе

```auto
curl 'http://localhost:9200/test/page/_search?pretty' -d'
{
  "query": {
    "bool": {
      "should": [
        {
          "multi_match": {
            "query": "джинсы",
            "type": "cross_fields",
            "fields": [
              "*.raw^5"
            ],
            "operator": "and",
            "analyzer": "search_russian_english",
            "minimum_should_match": "80%"
          }
        },
        {
          "multi_match": {
            "query": "джинсы",
            "type": "cross_fields",
            "fields": [
              "*.ng"
            ],
            "operator": "and",
            "analyzer": "index_edgengram"
          }
        }
      ]
    }
  }
}
'

```

---

<div class="post-metadata">

### Author: ![Igor\_Motov](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/igor_motov/32/45193_2.png) [@Igor\_Motov](https://discuss.elastic.co/u/Igor_Motov)
#### Post date: [February 1, 2018, 3:32pm UTC](https://discuss.elastic.co/t/ngram/116096/11 "2018-02-01T15:32:19Z")

</div>

Надо менять меппинг на edgeNGram, а не запрос.

---

<div class="post-metadata">

### Author: ![v.ishchenko](https://avatars.discourse-cdn.com/v4/letter/v/8797f3/32.png) [@v.ishchenko](https://discuss.elastic.co/u/v.ishchenko)
#### Post date: [February 1, 2018, 3:38pm UTC](https://discuss.elastic.co/t/ngram/116096/12 "2018-02-01T15:38:43Z")

</div>

> [@Igor\_Motov](#):
>
> edgeNGram

не пойму, у нас есть body.ng и title.ng которые мы вовремя индексации разложили

```auto
 "ng": {
              "type": "text",
              "analyzer": "index_edgengram",
              "search_analyzer": "russian_english"
            }

```

понял, просто в самом запросе не нужно указывать анализаторы мы их и так в мапинге раставили.

---

<div class="post-metadata">

### Author: ![Igor\_Motov](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/igor_motov/32/45193_2.png) [@Igor\_Motov](https://discuss.elastic.co/u/Igor_Motov)
#### Post date: [February 1, 2018, 11:27pm UTC](https://discuss.elastic.co/t/ngram/116096/13 "2018-02-01T23:27:15Z")

</div>

> [@v.ishchenko](#):
>
> понял, просто в самом запросе не нужно указывать анализаторы мы их и так в мапинге раставили.

У вас с `search_analyzer` стеммер используется. Это может привести к странным результатам. По-хорошему, там должен быть такой-же анализатор, что и при индексировании, но без edgeNGram фильтра. То есть, если вы индексировали с

```auto
        "index_edgengram": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "edgengram"
          ]
        },

```

то вам надо искать с

```auto
        "search_edgengram": {
          "tokenizer": "standard",
          "filter": [
            "lowercase"
          ]
        },

```

---

<div class="post-metadata">

### Author: ![system](https://us1.discourse-cdn.com/elastic/original/3X/1/a/1ac57faf039f6b580b3f104ef42a2a89e41014de.png) [@system](https://discuss.elastic.co/u/system)
#### Post date: [March 1, 2018, 11:27pm UTC](https://discuss.elastic.co/t/ngram/116096/14 "2018-03-01T23:27:23Z")

</div>

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.
