Elasticsearch看似随机的评分和匹配
Elasticsearch seemingly random scoring and matching
我正在使用 bool
搜索来匹配多个字段。这些字段已在索引时使用多个过滤器进行分析,但主要使用 edge_ngram
.
我遇到的问题是评分似乎悬而未决。我希望我对 savvas
的搜索首先匹配 Savvas
的 first_name
字段之一,但它们的得分要晚得多。例如,按得分顺序搜索 savvas
returns:
First name | Last name | Email
___________|_________________|________________________
------ | Sav--- | ---@sa-------------.com
-----s | Sa---- | sa----------s@-----.com
Sa---- | ---- | sa---------@-------.com
Sa---- | -------- | sa-------@---------.com
sa- | ----- | sa----------@------.com
Sa-- | ----s-----s | sa------s-----s@---.com
Sa---- | ----------- | sa-----@-----------.com
Savvas | -------s | ----------@--------.com
Savvas | -------s | --------@----------.com
Sa- | ---s----S------ | sa------s-----@----.com
我已将字段中搜索词的边缘 n-gram 以外的字符替换为 -
,并修改了电子邮件的长度以保护身份。
事实上搜索 ssssssssssssssss
虽然它不存在于我的数据 returns 中最多 s
个字符的项目。这是我不希望发生的事情,因为我没有对我的搜索进行任何手动 ngram。
当我尝试搜索 phone 号码时也会出现此问题,当搜索 782
超过 phone 号码时,我会匹配任何包含字符 78
的电子邮件具有 782
作为精确的 ngram。
似乎 elasticsearch 也在我的搜索查询上执行 ngram,而不仅仅是字段并比较两者,并以某种方式支持较短的匹配。
这是我的查询:
{
'bool': {
'should': [ // Any one of these matches will return a result
{
'match': {
'phone': {
'query': $searchString,
'fuzziness': '0',
'boost': 3 // If phone matches give it precedence
}
}
},
{
'match': {
'email': {
'query': $searchString,
'fuzziness': '0'
}
}
},
{
'multi_match': {
'query': $searchString,
'type': 'cross_fields', // Match if any term is in any of the fields
'fields': ['name.first_name', 'name.last_name'],
'fuzziness': '0'
}
}
],
'minimum_should_match': 1
}
}
以及与之配套的索引设置(抱歉冗长,但我不想排除任何可能重要的内容):
{
"settings":{
"analysis":{
"char_filter":{
"trim":{
"type":"pattern_replace",
"pattern":"^\s*(.*)\s*$",
"replacement":""
},
"tel_strip_chars":{
"type":"pattern_replace",
"pattern":"^(\(\d+\))|^(\+)|\D",
"replacement":""
},
"tel_uk_exit_coded":{
"type":"pattern_replace",
"pattern":"^00(\d+)",
"replacement":"+"
},
"tel_parenthesized_country_code":{
"type":"pattern_replace",
"pattern":"^\((\d+)\)(\d+)",
"replacement":"+"
}
},
"tokenizer":{
"intl_tel_country_code": {
"type":"pattern",
"pattern":"\+(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)(\d{1,14})$",
"group":0
}
},
"filter":{
"autocomplete":{
"type":"edge_ngram",
"min_gram":1,
"max_gram":50
},
"autocomplete_tel":{
"type":"ngram",
"min_gram":3,
"max_gram":20
},
"email":{
"type":"pattern_capture",
"preserve_original":1,
"patterns":[
"([^@]+)",
"(\p{L}+)",
"(\d+)",
"@(.+)",
"([^-@]+)"
]
}
},
"analyzer":{
"name":{
"type":"custom",
"tokenizer":"standard",
"filter":[
"trim",
"lowercase",
"asciifolding",
"autocomplete"
]
},
"email":{
"type":"custom",
"tokenizer":"uax_url_email",
"filter":[
"trim",
"lowercase",
"email",
"unique",
"autocomplete"
]
},
"phone":{
"type":"custom",
"tokenizer":"intl_tel_country_code",
"char_filter":[
"trim",
"tel_strip_chars",
"tel_uk_exit_coded",
"tel_parenthesized_country_code"
],
"filter":[
"autocomplete_tel"
]
}
}
}
},
"mappings":{
"person":{
"properties":{
"address":{
"properties":{
"country":{
"type":"string",
"index_name":"country"
}
}
},
"timezone":{
"type":"string"
},
"name":{
"properties":{
"first_name":{
"type":"string",
"analyzer":"name"
},
"last_name":{
"type":"string",
"analyzer":"name"
}
}
},
"email":{
"type":"string",
"analyzer":"email"
},
"phone":{
"type":"string",
"analyzer":"phone"
},
"id":{
"type":"string"
}
}
}
}
}
我已经使用 Kopf 插件的分析器测试了索引设置,它似乎创建了正确的标记。
理想情况下,我只会与我的索引创建的标记完全匹配,并在我的 bool 应该查询之一中优先匹配更精确的匹配,而不是优先考虑多个 bool 应该匹配。
不过,如果至少它只匹配精确的标记,我会很高兴。我无法使用 term
搜索,因为我的搜索字符串本身需要标记化,只是不对其应用任何 ngram。
总结一下我的要求:
- 在任何单个领域中以最大可能匹配得分第一。
- 然后根据任何单个字段中可能匹配项的最低偏移量进行评分。
- 然后根据匹配的字段数进行评分,优先考虑偏移量较小的匹配项
---更新:---
我使用 dis_max
获得了更好的结果,它似乎成功地匹配了多个 ngram 匹配的更大的 ngram 匹配,但仍然难以查询的 phone
字段除外。这是新查询:
{
'dis_max': {
'tie_breaker': 0.0,
'boost': 1.5,
'queries': [ // Any one of these matches will return a result
[
'match': {
'phone': {
'query': $searchString,
'boost': 1.9
}
}
],
[
'match': {
'email': {
'query': $searchString
}
}
],
[
'multi_match': {
'query': $searchString,
'type': 'cross_fields', // Match if any term is in any of the fields
'fields': ['name.first_name', 'name.last_name'],
'tie_breaker': 0.1,
'boost': 1.5
}
]
}
}
}
可能您不想在搜索字符串上使用自动完成,即名称分析器,仅在索引期间,即映射应该是:
"first_name": {
"type":"string",
"index_analyzer":"name"
}
此外,要在多场比赛中 first_name 得分高于 last_name,您可以提供场级提升如下:
示例:last_name 匹配的相关性是 first_name
的一半
{
'dis_max': {
'tie_breaker': 0.0,
'boost': 1.5,
'queries': [ // Any one of these matches will return a result
[
'match': {
'phone': {
'query': $searchString,
'boost': 1.9
}
}
],
[
'match': {
'email': {
'query': $searchString
}
}
],
[
'multi_match': {
'query': $searchString,
'type': 'cross_fields', // Match if any term is in any of the fields
'fields': ['name.first_name', 'name.last_name^0.5'],
'tie_breaker': 0.1,
'boost': 1.5
}
]
}
}
}
我正在使用 bool
搜索来匹配多个字段。这些字段已在索引时使用多个过滤器进行分析,但主要使用 edge_ngram
.
我遇到的问题是评分似乎悬而未决。我希望我对 savvas
的搜索首先匹配 Savvas
的 first_name
字段之一,但它们的得分要晚得多。例如,按得分顺序搜索 savvas
returns:
First name | Last name | Email
___________|_________________|________________________
------ | Sav--- | ---@sa-------------.com
-----s | Sa---- | sa----------s@-----.com
Sa---- | ---- | sa---------@-------.com
Sa---- | -------- | sa-------@---------.com
sa- | ----- | sa----------@------.com
Sa-- | ----s-----s | sa------s-----s@---.com
Sa---- | ----------- | sa-----@-----------.com
Savvas | -------s | ----------@--------.com
Savvas | -------s | --------@----------.com
Sa- | ---s----S------ | sa------s-----@----.com
我已将字段中搜索词的边缘 n-gram 以外的字符替换为 -
,并修改了电子邮件的长度以保护身份。
事实上搜索 ssssssssssssssss
虽然它不存在于我的数据 returns 中最多 s
个字符的项目。这是我不希望发生的事情,因为我没有对我的搜索进行任何手动 ngram。
当我尝试搜索 phone 号码时也会出现此问题,当搜索 782
超过 phone 号码时,我会匹配任何包含字符 78
的电子邮件具有 782
作为精确的 ngram。
似乎 elasticsearch 也在我的搜索查询上执行 ngram,而不仅仅是字段并比较两者,并以某种方式支持较短的匹配。
这是我的查询:
{
'bool': {
'should': [ // Any one of these matches will return a result
{
'match': {
'phone': {
'query': $searchString,
'fuzziness': '0',
'boost': 3 // If phone matches give it precedence
}
}
},
{
'match': {
'email': {
'query': $searchString,
'fuzziness': '0'
}
}
},
{
'multi_match': {
'query': $searchString,
'type': 'cross_fields', // Match if any term is in any of the fields
'fields': ['name.first_name', 'name.last_name'],
'fuzziness': '0'
}
}
],
'minimum_should_match': 1
}
}
以及与之配套的索引设置(抱歉冗长,但我不想排除任何可能重要的内容):
{
"settings":{
"analysis":{
"char_filter":{
"trim":{
"type":"pattern_replace",
"pattern":"^\s*(.*)\s*$",
"replacement":""
},
"tel_strip_chars":{
"type":"pattern_replace",
"pattern":"^(\(\d+\))|^(\+)|\D",
"replacement":""
},
"tel_uk_exit_coded":{
"type":"pattern_replace",
"pattern":"^00(\d+)",
"replacement":"+"
},
"tel_parenthesized_country_code":{
"type":"pattern_replace",
"pattern":"^\((\d+)\)(\d+)",
"replacement":"+"
}
},
"tokenizer":{
"intl_tel_country_code": {
"type":"pattern",
"pattern":"\+(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)(\d{1,14})$",
"group":0
}
},
"filter":{
"autocomplete":{
"type":"edge_ngram",
"min_gram":1,
"max_gram":50
},
"autocomplete_tel":{
"type":"ngram",
"min_gram":3,
"max_gram":20
},
"email":{
"type":"pattern_capture",
"preserve_original":1,
"patterns":[
"([^@]+)",
"(\p{L}+)",
"(\d+)",
"@(.+)",
"([^-@]+)"
]
}
},
"analyzer":{
"name":{
"type":"custom",
"tokenizer":"standard",
"filter":[
"trim",
"lowercase",
"asciifolding",
"autocomplete"
]
},
"email":{
"type":"custom",
"tokenizer":"uax_url_email",
"filter":[
"trim",
"lowercase",
"email",
"unique",
"autocomplete"
]
},
"phone":{
"type":"custom",
"tokenizer":"intl_tel_country_code",
"char_filter":[
"trim",
"tel_strip_chars",
"tel_uk_exit_coded",
"tel_parenthesized_country_code"
],
"filter":[
"autocomplete_tel"
]
}
}
}
},
"mappings":{
"person":{
"properties":{
"address":{
"properties":{
"country":{
"type":"string",
"index_name":"country"
}
}
},
"timezone":{
"type":"string"
},
"name":{
"properties":{
"first_name":{
"type":"string",
"analyzer":"name"
},
"last_name":{
"type":"string",
"analyzer":"name"
}
}
},
"email":{
"type":"string",
"analyzer":"email"
},
"phone":{
"type":"string",
"analyzer":"phone"
},
"id":{
"type":"string"
}
}
}
}
}
我已经使用 Kopf 插件的分析器测试了索引设置,它似乎创建了正确的标记。
理想情况下,我只会与我的索引创建的标记完全匹配,并在我的 bool 应该查询之一中优先匹配更精确的匹配,而不是优先考虑多个 bool 应该匹配。
不过,如果至少它只匹配精确的标记,我会很高兴。我无法使用 term
搜索,因为我的搜索字符串本身需要标记化,只是不对其应用任何 ngram。
总结一下我的要求:
- 在任何单个领域中以最大可能匹配得分第一。
- 然后根据任何单个字段中可能匹配项的最低偏移量进行评分。
- 然后根据匹配的字段数进行评分,优先考虑偏移量较小的匹配项
---更新:---
我使用 dis_max
获得了更好的结果,它似乎成功地匹配了多个 ngram 匹配的更大的 ngram 匹配,但仍然难以查询的 phone
字段除外。这是新查询:
{
'dis_max': {
'tie_breaker': 0.0,
'boost': 1.5,
'queries': [ // Any one of these matches will return a result
[
'match': {
'phone': {
'query': $searchString,
'boost': 1.9
}
}
],
[
'match': {
'email': {
'query': $searchString
}
}
],
[
'multi_match': {
'query': $searchString,
'type': 'cross_fields', // Match if any term is in any of the fields
'fields': ['name.first_name', 'name.last_name'],
'tie_breaker': 0.1,
'boost': 1.5
}
]
}
}
}
可能您不想在搜索字符串上使用自动完成,即名称分析器,仅在索引期间,即映射应该是:
"first_name": {
"type":"string",
"index_analyzer":"name"
}
此外,要在多场比赛中 first_name 得分高于 last_name,您可以提供场级提升如下:
示例:last_name 匹配的相关性是 first_name
的一半{
'dis_max': {
'tie_breaker': 0.0,
'boost': 1.5,
'queries': [ // Any one of these matches will return a result
[
'match': {
'phone': {
'query': $searchString,
'boost': 1.9
}
}
],
[
'match': {
'email': {
'query': $searchString
}
}
],
[
'multi_match': {
'query': $searchString,
'type': 'cross_fields', // Match if any term is in any of the fields
'fields': ['name.first_name', 'name.last_name^0.5'],
'tie_breaker': 0.1,
'boost': 1.5
}
]
}
}
}