在 Elasticsearch 中应用自定义相似度计算





5.00/5 (3投票s)
自定义相似度脚本示例,以提高搜索结果的相关性
引言
目前,我们一直在讨论改进 Elasticsearch 自动完成功能。在大多数情况下,这已经足够了。但是,在我们的案例中,情况并非如此乐观,我们将探讨原因。
安装
在我们的系统中,我们每个索引使用单个 文档类型。由于我们的系统中有相当多的类型,并且类型及其映射的数量可以由最终用户更改,因此我们有不可预测数量的索引。目前,这个数字是 17 个不同的索引。当然,文档在所有类型中的分布并不相等,这意味着某些索引包含的记录比其他索引多。
你的项目的关键特性是能够跨逻辑索引组执行搜索。这个以及索引之间的数据倾斜会导致一些相关性问题,当我们详细讨论我们的问题时,这些问题将变得显而易见。
另一个值得一提的是,用户搜索查询相当具体。我们项目的目的是剖析大量文档以获得一两个相关的文档。因此,我们不希望出现像 “Ukraine
” 这样可能产生数千个文档的模糊查询。当我们继续讨论我们提出的解决方案时,这一点将很重要。
衡量相似度的标准方法
默认情况下,Elasticsearch 使用 BM25 相似度来对搜索结果进行排名。有三个主要因素决定了文档的分数
- 词频 (TF) — 搜索词在文档中我们正在搜索的字段中出现的次数越多,该文档就越相关。
- 逆文档频率 (IDF) — 在我们正在搜索的字段中包含搜索词的文档越多,该词就越不重要。
- 字段长度 — 如果一个文档在非常短的字段(即,只有几个单词)中包含一个搜索词,那么它比在一个非常长的字段(即,有很多单词)中包含一个搜索词的文档更相关。
让我们更深入地了解 IDF。逆文档频率是考虑全局稀有词的计算函数。单词在整个语料库中越稀有,它就越重要。
让我们看一下 explain 部分的摘录,以便更好地了解它是如何计算的。
{
"value": 0.5389965,
"description": "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:",
"details": [
{
"value": 3,
"description": "n, number of documents containing term",
"details": []
},
{
"value": 5,
"description": "N, total number of documents with field",
"details": []
}
]
},
这意味着 IDF 与索引中的文档数量成反比。在我们的例子中,这会导致危险。回想一下,我们对多个索引执行搜索。想象一下两个不同的索引,一个有 2K 个文档,另一个有 20K 个文档,两者都包含给定查询的完全匹配项。这意味着第一个索引的 IDF 会更高,因此第一个索引的结果会更相关!
然而,对于最终用户来说,索引的数量和其中项目的数量是一个无关紧要的实现细节。用户关心的是输出结果与查询字符串输入匹配的程度。
丢弃 IDF
考虑到用户对跨索引的数据倾斜不感兴趣,并且提出了足够具体的查询,我们已经确定 IDF 在我们的案例中是不相关的。我们如何从搜索计算公式中丢弃它?
答案是 脚本相似度。
让我们重新实现 BM25,但使用恒定的 IDF。
PUT /index
"settings": {
"index": {
"similarity": {
"discarded_idf": {
"type": "scripted",
"script": {
"source": "double tf = Math.sqrt(doc.freq);
double idf = 1.0; double norm = 1 / Math.sqrt(doc.length);
return query.boost * tf * idf * norm;"
}
}
}
}
}
这就是全部:原始的 BM25 公式,但 IDF 对于跨所有索引的每次搜索都是恒定的。完成此操作后,我们可以计算更适合我们用例的相似度。
结论
Elasticsearch 拥有广泛的工具,可让你的搜索查询返回最相关的结果。一旦你用尽了专用的数据类型和丰富的查询 DSL,你也可以通过使用一组预定义的算法或推出自己的算法来调整相似度计算。
历史
- 2022年12月10日 - 初始版本