Elasticsearch 是基于 Lucene 的世界范围内最流行的全文检索框架,其文档相似度算法包含 TF/IDF 和 BM25,从 ES 5.0开始 BM25 算法已经成为 ES 默认的相似度评分模块。
TF-IDF 和 BM25 都是计算文本相似性的常用算法。TF-IDF 的计算方法简单,计算复杂度低,但对高频词不敏感,参数难以调节。BM25 是在 TF-IDF 的基础上进行改进的,它考虑了文档的长度和查询词在文档中出现的次数,在大多数情况下都能够产生比 TF-IDF 更准确的相关性评分
TF-IDF 和 BM25 的主要区别在于计算方法的不同。TF-IDF 的计算方法为:
TF-IDF(t,d) = TF(t,d) * IDF(t)
其中:
BM25 的计算方法为:
BM25(t,d) = (k1 + 1) * TF(t,d) / (k1 * (1 - b + b * df / docLength) + TF(t,d))
其中:
TF-IDF 和 BM25 的区别主要体现在以下几个方面:
在实际业务中,有关查询场景的评分可以分为如下四类:
这类场景下,纯粹把 ES 当作检索库使用,不关注相似度评分,那么可以使用 constant query 或者使用 bool query 中的 filter 来进行过滤即可,这样可以提高检索性能
默认评分,也就是框架默认评分。这类场景下,仅使用最简单的查询方式,比如 查询 name:"tom",并没有人为额外干预评分的机制,仅靠默认的评分算法的得到 rank 列表 ,做为检索结果
此种场景下比较常见,比如查询 name:"tom"^10 name:"cat"^5, 或者更加复杂的结合通过 Function Score Query 来完成更加复杂的业务
这种场景下,一般在推荐业务中比较常见,其完全忽略框架的评分策略,而采纳业务方或者产品方定义的评分规则,实现起来一般比较复杂,看一个例子:
- GET /pi_ent_work/_search
- {
- "query": {
- "function_score": {
- "query": {
- "bool": {
- "must": [
- {
- "query_string": {
- "boost": 0, # 注意此处禁用框架评分
- "query": "prov:(33 OR 36)"
- }
- }
- ],
- "must_not": [
- {
- "terms": {
- "id": [
- "123"
- ]
- }
- }
- ],
- "filter": [
- {
- "term": {
- "count": {
- "value": "1"
- }
- }
- }
- ]
- }
- },
- "score_mode": "sum",
- "boost_mode": "replace",
- "functions": [
- {
- "script_score": {
- "script": {
- "lang": "expression",
- # 完全采用自定义评分并与数据中的某个字段关联
- "source": " _score*0.8 + doc['custom_score'].value*0.4"
- }
- }
- },
- {
- "weight": 6,
- "filter": {
- "query_string": {
- "query": "prov:(33 OR 36)"
- }
- }
- },
- {
- "weight": 4,
- "filter": {
- "query_string": {
- "query": " product_id:112900 "
- }
- }
- },
- {
- "weight": 2,
- "filter": {
- "query_string": {
- "query": "price:[* TO 3]"
- }
- }
- }
- ]
- }
- },
- "size": 100,
- "_source": {
- "includes": [
- "id",
- "_score",
- "prov",
- "product_id",
- "custom_score",
- "count"
- ]
- }
- }
-
上面的例子完全忽略了框架评分,而全部采用自己指定的规则评分,在 ES 中可以结合 Function Score Query来实现
在实际工作中,搜索和推荐业务会比较依赖全文检索框架,很多情况下框架的默认的评分机制并不能很好的满足我们的需求,所以需要结合一些自定义评分策略来完善我们的 rank 效果