一、前言

上篇介绍了搜索结果高亮的实现方法,本篇主要介绍搜索结果相关性排序优化。


二、相关概念

2.1 排序

默认情况下,返回结果是按照「相关性」进行排序的——最相关的文档排在最前。

2.1.1 相关性排序(默认)

在 ES 中相关性评分 由一个浮点数表示,并在搜索结果中通过「 _score 」参数返回,默认是按照 _score 降序排列。

2.1.2 按照字段值排序

使用「 sort 」参数实现,可指定一个或多个字段。然而使用 sort 排序过于绝对,它会直接忽略文档本身的相关度,因此仅适合在某些特殊场景使用。

注:如果以字符串字段进行排序,除了索引一份用于全文查询的数据,还需要索引一份原始的未经分析器处理(即 not_analyzed )的数据。这就需要使用「 fields 」参数实现同一个字段多种索引方式,这里的「索引」是动词相当于「存储」的概念。

2.2 相关性算法

ES 5.X 版本将相关性算法由之前的「 TF/IDF 」算法改为了更先进的「 BM25 」算法。

2.2.1 TF/IDF 评分算法

ES版本 < 5 的评分算法,即词频/逆向文档频率。

① 词频( Term frequency )

搜索词在文档中出现的频率,频率越高,相关度越高。计算公式如下:
$$tf(t  in  d) = \sqrt{frequency}$$
搜索词「 t 」在文档「 d 」的词频「 tf 」是该词在文档中出现次数的平方根。

② 逆向文档频率( Inverse document frequency )

搜索词在索引(单个分片)所有文档里出现的频率,频率越高,相关度越低。用人话描述就是「物以稀为贵」,计算公式如下:
$$idf(t) = 1 + log \frac{docCount}{docFreq + 1}$$
搜索词「 t 」的逆向文档频率「 idf 」是索引中的文档总数除以所有包含该词的文档数,然后求其对数。

③ 字段长度归一值( Field length norm )

字段的长度,字段越短,相关度越高。计算公式如下:
$$norm(d) = \frac{1}{\sqrt{numTerms}}$$
字段长度归一值「 norm 」是字段中词数平方根的倒数。

注:前面公式中提到的「文档」实际上是指文档里的某个字段

2.2.2 BM25 评分算法

ES版本 >= 5 的评分算法;BM25 的 BM 是缩写自 Best Match, 25 貌似是经过 25 次迭代调整之后得出的算法。它也是基于 TF / IDF 算法进化来的。

对于给定查询语句「Q」,其中包含关键词「$q_{1}$,...$q_{n}$」,那么文档「D」的 BM25 评分计算公式如下:
$$score(D,Q) = \sum_{i=1}^NIDF(q_{i}) · \frac{f(q_{i},D) · (k_{1}+1)}{f(q_{i},D)+k_{1} · (1-b+b · \frac{|D|}{avgdl})}$$
这个公式看起来很唬人,尤其是那个求和符号,不过分解开来还是比较好理解的。

总体而言,主要还是分三部分,TF - IDF - Document Length

  • IDF 的计算公式调整为如下所示,其中N 为文档总数, $n(q_{i})$ 为包含搜索词 $q_{i}$ 的文档数。
    $$IDF(q_{i}) = 1 + log\frac{N-n(q_{i})+0.5}{n(q_{i})+0.5}$$
  • $f(q_{i},D)$ 为搜索词 $q_{i}$ 在文档 D 中的「 TF 」,| D | 是文档的长度,avgdl 是平均文档长度。
    先不看 IDF 和 Document Length 的部分, 则公式变为 TF * ($k_{1}$ + 1) / (TF + $k_{1}$),
    相比传统的 TF/IDF 而言,BM25 抑制了 TF 对整体评分的影响程度,虽然同样都是增函数,但是 BM25 中,TF 越大,带来的影响无限趋近于 ($k_{1}$ + 1),这里 $k_{1}$ 值通常取 [1.2, 2.0],而传统的 TF/IDF 则会没有临界点的无限增长。
  • 至于文档长度 | D | 的影响,可以看到在命中搜索词的情况下,文档越短,相关性越高,具体影响程度又可以由公式中的 b 来调整,当设值为 0 的时候,就跟将 norms 设置为 false 一样,忽略文档长度的影响。
  • 最后再对所有搜索词的计算结果求和,就是 ES5 中一般查询的得分了。

三、实际案例

3.1 现实需求

要求搜索文章时,搜索词出现在标题时的权重要比出现在内容中高,同时要考虑「引用次数」对最终排序的影响。

3.2 实现方法

3.2.1 调整搜索字段权重

通过调整字段的 boost 参数实现自定义权重,此处将标题的权重调整为内容的两倍。

private SearchQuery getKnowledgeSearchQuery(KnowledgeSearchParam param) {...省略其余部分...BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();boolQuery.must(QueryBuilders.termQuery("isDeleted", IsDeletedEnum.NO.getKey()));boolQuery.should(QueryBuilders.matchQuery(knowledgeTitleFieldName, param.getKeyword()).boost(2.0f));boolQuery.should(QueryBuilders.matchQuery(knowledgeContentFieldName, param.getKeyword()));return new NativeSearchQueryBuilder().withPageable(pageable).withQuery(boolQuery).withHighlightFields(knowledgeTitleField, knowledgeContentField).build();
}
3.2.2 按引用次数提升权重

这里通过 function score 实现重打分操作。根据上面的需求,我们将使用 field value factor 函数指定「 referenceCount 」字段计算分数并与 _score 相加作为最终评分进行排序。

private SearchQuery getKnowledgeSearchQuery(KnowledgeSearchParam param) {...省略其余部分...// 引用次数更多的知识点排在靠前的位置// 对应的公式为:_score = _score + log (1 + 0.1 * referenceCount)ScoreFunctionBuilder scoreFunctionBuilder = ScoreFunctionBuilders.fieldValueFactorFunction("referenceCount").modifier(FieldValueFactorFunction.Modifier.LN1P).factor(0.1f);FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(boolQuery, scoreFunctionBuilder).boostMode(CombineFunction.SUM);return new NativeSearchQueryBuilder().withPageable(pageable).withQuery(functionScoreQuery).withHighlightFields(knowledgeTitleField, knowledgeContentField).build();
}

上述的 function score 是 ES 用于处理文档分值的 DSL(领域专用语言),它预定义了一些计算分值的函数:

① weight

为每个文档应用一个简单的权重提升值:当 weight 为 2 时,最终结果为 2 * _score

② field_value_factor

通过文档中某个字段的值计算出一个分数且使用该值修改 _score,具有以下属性:

属性 描述
field 指定字段名
factor 对字段值进行预处理,乘以指定的数值,默认为 1
modifier 将字段值进行加工,默认为 none
boost_mode 控制函数与 _score 合并的结果,默认为 multiply
③ random_score

为每个用户都使用一个随机评分对结果排序,可以实现对于用户的个性化推荐。

④ 衰减函数

提供一个更复杂的公式,描述了这样一种情况:对于一个字段,它有一个理想值,而字段实际的值越偏离这个理想值就越不符合期望。具有以下属性:

属性 描述
origin(原点) 该字段的理想值,满分 1.0
offset(偏移量) 与原点相差在偏移量之内的值也可以得到满分
scale(衰减规模) 当值超出原点到偏移量这段范围,它所得的分数就开始衰减,衰减规模决定了分数衰减速度的快慢
decay(衰减值) 该字段可以被接受的值,默认为 0.5
⑤ script_score

支持自定义脚本完全控制评分计算

3.2.3 理解评分标准

通过JAVA API 实现相关功能后,输出评分说明可以帮助我们更好的理解评分过程以及后续调整算法参数。

① 首先定义一个打印搜索结果的方法,设置 explain = true 即可输出 explanation 。

public void debugSearchQuery(SearchQuery searchQuery, String indexName) {SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch(indexName).setTypes(indexName);searchRequestBuilder.setSearchType(SearchType.DFS_QUERY_THEN_FETCH);searchRequestBuilder.setFrom(0).setSize(10);searchRequestBuilder.setExplain(true);searchRequestBuilder.setQuery(searchQuery.getQuery());SearchResponse searchResponse;try {searchResponse = searchRequestBuilder.execute().get();long totalCount = searchResponse.getHits().getTotalHits();log.info("总条数 totalCount:" + totalCount);//遍历结果数据SearchHit[] hitList = searchResponse.getHits().getHits();for (SearchHit hit : hitList) {log.info("SearchHit hit explanation:{}\nsource:{}", hit.getExplanation().toString(), hit.getSourceAsString());}} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}
}

② 之后调用接口,其 explanation 结果展示如下:

19.491358 = sum of19.309036 = sum of:19.309036 = sum of:19.309036 = weight(knowledgeTitle.pinyin:test in 181) [PerFieldSimilarity], result of:19.309036 = score(doc=181,freq=1.0 = termFreq=1.0
), product of:2.0 = boost6.2461066 = idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:2.0 = docFreq1289.0 = docCount1.5456858 = tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:1.0 = termFreq=1.01.2 = parameter k10.75 = parameter b29.193172 = avgFieldLength4.0 = fieldLength0.0 = match on required clause, product of:0.0 = # clause1.0 = isDeleted:[0 TO 0], product of:1.0 = boost1.0 = queryNorm0.18232156 = min of:0.18232156 = field value function: ln1p(doc['referenceCount'].value * factor=0.1)3.4028235E38 = maxBoost

其中 idf = 6.2461066,tfNorm = 1.5456858,boost = 2.0,由于此时只有一个搜索字段,因此 score = idf * tfNorm * boost = 19.309036;与此同时 field value function = 0.18232156;最终得分 sum = 19.309036 + 0.18232156 = 19.491358 。


四、结语

至此一个简单需求的相关性排序优化已经实现完毕,由于业务的关系暂时未涉及其他复杂的场景,所以此篇仅仅作为一个入门介绍。


五、参考博文

  • 通过Function Score Query优化Elasticsearch搜索结果(综合排序)
  • Elasticsearch 5.X(Lucene 6) 的 BM25 相关度算法

转载于:https://www.cnblogs.com/orzlin/p/10496869.html

从零搭建 ES 搜索服务(六)相关性排序优化相关推荐

  1. 从零搭建ES搜索服务(一)基本概念及环境搭建

    一.前言 本系列文章最终目标是为了快速搭建一个简易可用的搜索服务.方案并不一定是最优,但实现难度较低. 二.背景 近期公司在重构老系统,需求是要求知识库支持全文检索. 我们知道普通的数据库 like ...

  2. 从零搭建 ES 搜索服务(四)拼音搜索

    一.前言 上篇介绍了 ES 的同义词搜索,使我们的搜索更强大了,然而这还远远不够,在实际使用中还可能希望搜索「fanqie」能将包含「番茄」的结果也罗列出来,这就涉及到拼音搜索了,本篇将介绍如何具体实 ...

  3. No6-6.从零搭建spring-cloud-alibaba微服务框架,添加用户鉴权逻辑,动态数据权限(使用AOP实现)等(六,no6-6)

    代码地址与接口看总目录:[学习笔记]记录冷冷-pig项目的学习过程,大概包括Authorization Server.springcloud.Mybatis Plus~~~_清晨敲代码的博客-CSDN ...

  4. No6-3.从零搭建spring-cloud-alibaba微服务框架,实现资源端用户认证与授权等(三,no6-3)

    代码地址与接口看总目录:[学习笔记]记录冷冷-pig项目的学习过程,大概包括Authorization Server.springcloud.Mybatis Plus~~~_清晨敲代码的博客-CSDN ...

  5. 从零搭建 Spring Cloud 服务(超级详细)

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ ...

  6. java项目: ElasticSearch+Spark构建高相关性搜索服务千人千面推荐系统

    文章目录 1 概述 2 需求分析 3 项目基础搭建[业务系统之基础能力] 4 用户服务.运营后台.商户服务的搭建 用户模型前后端 运营后台 商户入驻: 商户创建.商户查询.商户禁用 5 基础服务: 品 ...

  7. 微服务之如何从零搭建(吹牛逼篇)

    [版权申明] 非商业目的注明出处可自由转载 出自:shusheng007 概述 IT世界唯有变化才是永恒的.这不微服务刚兴起没有几年,现如今已经在全力向云原生时代过度了,有人称其为后微服务时代.云原生 ...

  8. 淘淘商城第43讲——搭建搜索服务工程

    Solr服务配置好之后,接下来我们就要考虑一个问题了,那就是我们要把商品数据导入到索引库里面才行,否则的话,我们是没有办法实现商品搜索这个功能的,可以想见我们势必要搭建一个搜索服务工程了. 我们还是先 ...

  9. 【项目介绍】ElasticSearch7+Spark 构建高相关性搜索服务千人千面推荐系统

    我做的项目是在慕课网买的 项目介绍 项目需求背景:模仿大众点评应用提供用户线下搜索推荐服务门店的需求 技术选型:后端业务:SpringBoot:后端存储:MySQL.mybatis接入:搜索系统:El ...

  10. (转) 淘淘商城系列——搜索服务搭建

    http://blog.csdn.net/yerenyuan_pku/article/details/72886305 Solr服务配置好之后,接下来我们就要考虑一个问题,那就是我们要把商品数据导入到 ...

最新文章

  1. python牛顿法解非线性方程组_matlab实现牛顿迭代法求解非线性方程组.pdf
  2. 大数据导论之为何需要引入大数据
  3. 组策略 从入门到精通 (一) 组策略的还原与备份和汇入
  4. 分布式锁三种实现方式(DB,redis,zookeeper)比较
  5. 怎样查看was的服务器信息,WAS 查看服务状态
  6. R - 一只小蜜蜂...(第二季水)
  7. python数据组织存在维度吗_用Python将统计数据不存在的记录按维度对应指标补齐...
  8. java语言怎样判断文件夹_JAVA语言之如何判断文件,判断文件夹是否存在的代码...
  9. LeetCode 500. Keyboard Row
  10. golang基础之三-字符串,时间,流程控制,函数
  11. html页面打开字都有蓝色背景,为什么有些网页打不开?网页空白页、白底蓝字问题怎么解决?...
  12. List集合排序找出其中的最大和最小值
  13. Vue开发环境搭建 VsCode
  14. t480 拆触摸板_用料不错 ThinkPad翼480笔记本拆机解析
  15. 2019 谷歌dat.GUI组件对中文的支持
  16. 衡量公司盈利能力的重要指标-净资产收益率
  17. xcode 配置wechat_react-native-wechat微信组件的使用
  18. HTML5自动换行的间距设置,div css p段落行高行距怎么设置篇
  19. ibm量子计算机和中国,量子计算机到底哪家强?IBMvs谷歌的世纪之战
  20. Day 16-Vue3 技术_Composition API 的优势

热门文章

  1. 用于 Domino Web Access 的 Notes.ini 变量
  2. 学习《TCP/IP详解 卷一协议》第九章的一点心得
  3. SqlCommandBuilder自动创建dataAdapter数据库操作命令
  4. SOA进入成熟应用阶段仍需时日
  5. Acrobat Pro DC 2021 for Mac(pdf编辑器)中文版
  6. iOS开发之二维码生成(错误问题小记,微信扫描,长按不识别)
  7. mac升级为macOS big sur菜单栏不显示WiFi怎么办?
  8. 解决mac屏幕不能共享问题
  9. Mac用户如何在Deckset中使用Ulysses?
  10. 如何将ImageRanger与外部存储一起使用NAS或USBUSB驱动器?