【问题标题】:Elasticsearch seemingly random scoring and matchingElasticsearch 看似随机的评分和匹配
【发布时间】:2015-10-14 21:31:37
【问题描述】:

我正在使用bool 搜索来匹配多个字段。这些字段已在索引时使用多个过滤器进行分析,但主要使用edge_ngram

我遇到的问题是得分似乎悬而未决。我希望我对savvas 的搜索首先匹配我的Savvasfirst_name 字段之一,但它们的得分要晚得多。例如,搜索savvas 按得分顺序返回:

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 虽然它在我的数据中不存在,但会返回其中包含最多 s 字符的项目。由于我没有对搜索进行任何手动 ngram,因此我不希望发生这种情况。

当我尝试搜索电话号码时也会出现此问题,在搜索 782 时,我会匹配包含字符 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":"$1"
                },
                "tel_strip_chars":{
                    "type":"pattern_replace",
                    "pattern":"^(\\(\\d+\\))|^(\\+)|\\D",
                    "replacement":"$1$2"
                },
                "tel_uk_exit_coded":{
                    "type":"pattern_replace",
                    "pattern":"^00(\\d+)",
                    "replacement":"+$1"
                },
                "tel_parenthesized_country_code":{
                    "type":"pattern_replace",
                    "pattern":"^\\((\\d+)\\)(\\d+)",
                    "replacement":"+$1$2"
                }
            },
            "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 的 should 查询中优先考虑更精确的匹配,而不是优先考虑多个 bool should 匹配。

但是,如果至少它只匹配确切的标记,我会很高兴。我不能使用 term 搜索,因为我的搜索字符串本身需要被标记化,而无需对其应用任何 ngram。

总结一下我的要求:

  • 在任何单个字段中最大可能匹配的得分第一。
  • 然后根据任何单个字段中可能匹配的最低偏移量进行评分。
  • 然后根据匹配的字段数评分,优先考虑较低的偏移匹配

--- 更新:---

我使用dis_max 获得了更好的结果,除了仍然难以查询的phone 字段之外,它似乎成功地匹配了多个 ngram 匹配的更大 ngram 匹配。这是新的查询:

{
    '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
                }
            ]
        }
    }
}

【问题讨论】:

  • 可能你不想在搜索字符串上使用 autocomplete 即名称分析器,只有在索引期间,即映射应该是 "first_name":{ "type ":"string", "index_analyzer":"name" },
  • @keety 啊,所以 'analyzer' 会影响两者,但 'index_analyzer' 只会影响一个......这似乎是我想要的。
  • @keety 试过了,谢谢它删除了所有不正确的匹配项,现在匹配精确的 ngram。现在我只需要弄清楚如何将位置因素考虑在内,并在我的cross_fields 查询中得分name.first_name 略高于name.last_name。如果您想将其放入答案中,我会给您应得的支持。
  • 一种方法是在多场比赛中提供场提升我已经更新了答案

标签: elasticsearch scoring booleanquery


【解决方案1】:

您可能不想在搜索字符串上使用自动完成功能,即名称分析器,仅在索引期间,即映射应该是:

"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
                }
            ]
        }
    }
}

【讨论】:

  • 非常感谢!我编辑了您的问题,以包括您从 cmets 获得的其他答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-01
  • 2016-04-19
  • 2012-04-05
  • 2010-11-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多