#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 — хорошая идея, и она должна работать в большинстве случаев, когда запросы меняются не очень часто. К сожалению, в моем случае, в зависимости от того, какие поисковые фильтры выбирает пользователь, список полей, которые необходимо включить в поисковый запрос, меняется. Поэтому я не могу скопировать их все в поле, потому что я не знаю заранее, какие поля нужно включить в поиск, если это имеет смысл. Кстати, я очень ценю, что вы отвечаете и предлагаете решения.