Elasticsearch: неожиданный показатель релевантности для необязательных полей в документах

#elasticsearch #tf-idf

#elasticsearch #tf-idf

Вопрос:

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

Тестовые данные:

 DELETE /my-index

PUT /my-index

POST /my-index/_bulk
{"index":{"_id":"1"}}
{"required_field":"RareWord"}
{"index":{"_id":"2"}}
{"required_field":"RareWord"}
{"index":{"_id":"3"}}
{"required_field":"CommonWord"}
{"index":{"_id":"4"}}
{"required_field":"CommonWord"}
{"index":{"_id":"5"}}
{"required_field":"CommonWord"}
{"index":{"_id":"6"}}
{"required_field":"CommonWord"}
{"index":{"_id":"7"}}
{"required_field":"CommonWord"}
{"index":{"_id":"8"}}
{"required_field":"CommonWord"}
{"index":{"_id":"9"}}
{"required_field":"CommonWord","optional_field":"RareWord AnotherRareWord"}
{"index":{"_id":"10"}}
{"required_field":"CommonWord","optional_field":"RareWord AnotherRareWord"}
 

Поисковый запрос:

Если я выполню поисковый запрос, аналогичный приведенному ниже:

 GET /my-index/_search
{"query":{"multi_match":{"query":"RareWord AnotherRareWord","fields":["required_field","optional_field"]}}}
 

Ожидание

Конечный пользователь ожидает, что документы № 9 и № 10 получат более высокие оценки, чем другие, потому что они содержат точные два слова поискового запроса в их optional_field

Реальность

Документ № 1 набрал бы больше баллов, чем № 10, даже если он содержит только одно из двух слов поискового запроса; что противоположно тому, что, скорее всего, ожидают конечные пользователи.

Более пристальный взгляд на _explain

Вот _explain результаты выполнения того же поискового запроса для документа # 1:

 {
  "_index" : "my-index",
  "_type" : "_doc",
  "_id" : "1",
  "matched" : true,
  "explanation" : {
    "value" : 1.4816045,
    "description" : "max of:",
    "details" : [
      {
        "value" : 1.4816045,
        "description" : "sum of:",
        "details" : [
          {
            "value" : 1.4816045,
            "description" : "weight(required_field:rareword in 0) [PerFieldSimilarity], result of:",
            "details" : [
              {
                "value" : 1.4816045,
                "description" : "score(freq=1.0), computed as boost * idf * tf from:",
                "details" : [
                  {
                    "value" : 2.2,
                    "description" : "boost",
                    "details" : [ ]
                  },
                  {
                    "value" : 1.4816046,
                    "description" : "idf, computed as log(1   (N - n   0.5) / (n   0.5)) from:",
                    "details" : [
                      {
                        "value" : 2,
                        "description" : "n, number of documents containing term",
                        "details" : [ ]
                      },
                      {
                        "value" : 10,
                        "description" : "N, total number of documents with field",
                        "details" : [ ]
                      }
                    ]
                  },
                  {
                    "value" : 0.45454544,
                    "description" : "tf, computed as freq / (freq   k1 * (1 - b   b * dl / avgdl)) from:",
                    "details" : [
                      {
                        "value" : 1.0,
                        "description" : "freq, occurrences of term within document",
                        "details" : [ ]
                      },
                      {
                        "value" : 1.2,
                        "description" : "k1, term saturation parameter",
                        "details" : [ ]
                      },
                      {
                        "value" : 0.75,
                        "description" : "b, length normalization parameter",
                        "details" : [ ]
                      },
                      {
                        "value" : 1.0,
                        "description" : "dl, length of field",
                        "details" : [ ]
                      },
                      {
                        "value" : 1.0,
                        "description" : "avgdl, average length of field",
                        "details" : [ ]
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
}
 

И вот _explain результаты выполнения того же поискового запроса для документа # 10:

 {
  "_index" : "my-index",
  "_type" : "_doc",
  "_id" : "10",
  "matched" : true,
  "explanation" : {
    "value" : 0.36464313,
    "description" : "max of:",
    "details" : [
      {
        "value" : 0.36464313,
        "description" : "sum of:",
        "details" : [
          {
            "value" : 0.18232156,
            "description" : "weight(optional_field:rareword in 9) [PerFieldSimilarity], result of:",
            "details" : [
              {
                "value" : 0.18232156,
                "description" : "score(freq=1.0), computed as boost * idf * tf from:",
                "details" : [
                  {
                    "value" : 2.2,
                    "description" : "boost",
                    "details" : [ ]
                  },
                  {
                    "value" : 0.18232156,
                    "description" : "idf, computed as log(1   (N - n   0.5) / (n   0.5)) from:",
                    "details" : [
                      {
                        "value" : 2,
                        "description" : "n, number of documents containing term",
                        "details" : [ ]
                      },
                      {
                        "value" : 2,
                        "description" : "N, total number of documents with field",
                        "details" : [ ]
                      }
                    ]
                  },
                  {
                    "value" : 0.45454544,
                    "description" : "tf, computed as freq / (freq   k1 * (1 - b   b * dl / avgdl)) from:",
                    "details" : [
                      {
                        "value" : 1.0,
                        "description" : "freq, occurrences of term within document",
                        "details" : [ ]
                      },
                      {
                        "value" : 1.2,
                        "description" : "k1, term saturation parameter",
                        "details" : [ ]
                      },
                      {
                        "value" : 0.75,
                        "description" : "b, length normalization parameter",
                        "details" : [ ]
                      },
                      {
                        "value" : 2.0,
                        "description" : "dl, length of field",
                        "details" : [ ]
                      },
                      {
                        "value" : 2.0,
                        "description" : "avgdl, average length of field",
                        "details" : [ ]
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            "value" : 0.18232156,
            "description" : "weight(optional_field:anotherrareword in 9) [PerFieldSimilarity], result of:",
            "details" : [
              {
                "value" : 0.18232156,
                "description" : "score(freq=1.0), computed as boost * idf * tf from:",
                "details" : [
                  {
                    "value" : 2.2,
                    "description" : "boost",
                    "details" : [ ]
                  },
                  {
                    "value" : 0.18232156,
                    "description" : "idf, computed as log(1   (N - n   0.5) / (n   0.5)) from:",
                    "details" : [
                      {
                        "value" : 2,
                        "description" : "n, number of documents containing term",
                        "details" : [ ]
                      },
                      {
                        "value" : 2,
                        "description" : "N, total number of documents with field",
                        "details" : [ ]
                      }
                    ]
                  },
                  {
                    "value" : 0.45454544,
                    "description" : "tf, computed as freq / (freq   k1 * (1 - b   b * dl / avgdl)) from:",
                    "details" : [
                      {
                        "value" : 1.0,
                        "description" : "freq, occurrences of term within document",
                        "details" : [ ]
                      },
                      {
                        "value" : 1.2,
                        "description" : "k1, term saturation parameter",
                        "details" : [ ]
                      },
                      {
                        "value" : 0.75,
                        "description" : "b, length normalization parameter",
                        "details" : [ ]
                      },
                      {
                        "value" : 2.0,
                        "description" : "dl, length of field",
                        "details" : [ ]
                      },
                      {
                        "value" : 2.0,
                        "description" : "avgdl, average length of field",
                        "details" : [ ]
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
}
 

Как вы можете видеть, документ № 10 оценивается хуже, в основном из-за более низкого значения IDF (0,18232156). Если присмотреться, это потому, что IDF использует N, общее количество документов с полем: 2 вместо того, чтобы просто учитывать общее количество документов в индексе: 10.

Вопрос

Мой вопрос заключается в том, что есть ли какой-либо способ заставить запрос multi_match учитывать все документы (а не только те, которые содержат поле) при вычислении значения IDF для необязательного поля, что приводит к оценке релевантности, которая ближе к ожиданиям конечных пользователей? Или, в качестве альтернативы, есть ли лучший способ написать поисковый запрос, чтобы я получил ожидаемые результаты?

Любая помощь будет с благодарностью принята. Спасибо.

Ответ №1:

Ваша ситуация, похоже, похожа на ситуацию, описанную в типе запроса cross_fields, поэтому вам, вероятно, следует попробовать:

 {
  "multi_match": {
    "query": "RareWord AnotherRareWord",
    "fields": ["required_field","optional_field"], 
    "type": "cross_fields", 
    "operator": "and"
  }
}
 

Комментарии:

1. Спасибо за ваш ответ. cross_fields ведет себя точно так же; причина, по которой документ № 1 не отображается в результатах поиска при выполнении приведенного выше запроса, заключается в операторе «и». Если мы изменим оператор на «или», проблема, с которой я сталкиваюсь, сохраняется. Ваше предложение на самом деле напомнило мне попробовать запрос combined_fields, который не имеет такой же проблемы, но по-прежнему страдает от других ограничений, которые не идеальны в моем случае использования (например, все поля должны использовать один и тот же анализатор, и я не уверен, как использовать его для сопоставления фраз)

2. Да, это имеет смысл. Другим подходом было бы использовать какое-то третье поле в сопоставлении для сбора всех данных во время индексации: PUT /my-index {"mappings": { "properties": { "required_field": {"type": "text", "copy_to": "all_fields"}, "optional_field": {"type": "text", "copy_to": "all_fields"}}}} а затем использовать его для поиска GET /my-index/_search { "query": { "match": { "all_fields": "RareWord AnotherRareWord"}}}

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