elasticsearch——影响文档评分

一般情况下,类似关系型数据库中影响返回结果的方式,在elasticsearch中也存在,包括sort、size等。但是这都是简单粗暴的对返回结果进行直接处理,很大的可能会影响返回文档的相关度。比如,通过sort操作对搜索结果按时间排序,这时排在前面的文档很可能相关度非常小,而相关度大的文档则因为时间排序被放在了下面。这显然不是我们想要的结果。elasticsearch提供了方法,允许我们用除了搜索之外的其他因素影响返回文档的顺序,同时兼顾文档的相关度。

简单粗暴地评分

首先先说一下elasticsearch的搜索评分逻辑。
查询的权重基于三个因素:词频、逆向文档频率和字段长度归一值。

  • 词频:查询词在该文档中出现的频率。频率越高,权重越高。
  • 逆向文档频率:查询词在所有文档中出现的频率。频率越高,权重越低。可以降低日常使用的高频率词的权重。
  • 字段长度归一值:查询字段的长度。字段长度越长,查询词权重越高,反之越低。

不同词对搜索结果的影响基本取决于以上三个因素,这里不列出详细的计算公式。

如何影响文档评分

首先,影响文档评分的操作推荐是查询的时候进行,这样灵活性更好。这里不介绍简单的评分提升或者降低,直接介绍elasticsearch中控制文档评分的终极武器:function_scor。

function_score

function_score是query结构的一个子集,它对每一个符合查询的文档应用一个或一组函数,达到影响甚至替换原始查询评分的目的。这个操作可以很方便的实现复杂的查询逻辑。
Elasticsearch 预定义了一些函数:

  • weight:为每个文档应用一个简单而不被规范化的权重提升值:当 weight 为 2 时,最终结果为 2 * _score 。
  • field_value_factor:使用这个值来修改 _score ,如将 popularity 或 votes (受欢迎或赞)作为考虑因素。
  • random_score:为每个用户都使用一个不同的随机评分对结果排序,但对某一具体用户来说,看到的顺序始终是一致的。
  • 衰减函数 —— linear 、 exp 、 gauss:将浮动值结合到评分 _score 中,例如结合 publish_date 获得最近发布的文档,结合 geo_location 获得更接近某个具体经纬度(lat/lon)地点的文档,结合 price 获得更接近某个特定价格的文档。
  • script_score:如果需求超出以上范围时,用自定义脚本可以完全控制评分计算,实现所需逻辑。

以上函数开箱即用,一般情况下,elasticsearch提供的函数就可以满足需求,如果有特殊要求,也可以使用最后一个script_score自己写控制评分脚本。但是script_score对性能有较大影响,能不用就不用。
下面简单说明几个需求,来看看elasticsearch是如何通过function_score实现的。

点击数影响评分

如果想要在原来搜索的基础上,加入点击数的影响,即将点击数高的文档放到搜索结果靠上的位置,但是搜索的评分仍然是主要的依据。

PUT /blogposts/post/1
{
  "title":   "About popularity",
  "content": "In this post we will talk about...",
  "votes":   6
}

这是一篇文章的文档,其中保存了点击数。这里可以通过field_value_factor实现相关需求。

GET /blogposts/post/_search
{
  "query": {
    "function_score": { 
      "query": {
        "multi_match": {
          "query":    "popularity",
          "fields": [ "title", "content" ]
        }
      },
      "field_value_factor": { 
        "field": "votes" 
      }
    }
  }
}

function_score嵌入了一个query查询中,然后内部又设定了一个query,内部的query查询是主查询。在function_score内部还有一个field_value_factor函数,这个函数会对符合每一个主查询的文档使用。每个文档的最终评分都会进行如下的计算:new_score = old_score * number_of_votes
默认的field_value_factor计算是线性的,votes的原始值直接用来计算,这通常不会产生好的结果。一般情况下,通过对数计算取代现行计算,可以取得更平滑的结果。

GET /blogposts/post/_search
{
  "query": {
    "function_score": {
      "query": {
        "multi_match": {
          "query":    "popularity",
          "fields": [ "title", "content" ]
        }
      },
      "field_value_factor": {
        "field":    "votes",
        "modifier": "log1p"
      }
    }
  }
}

将field_value_factor设为对数计算,计算公式:new_score = old_score * log(1 + number_of_votes)
field_value_factor提供了众多参数,设置相关计算的各种参数,这里不再一一列举。如果想要细致的调整搜索结果的话,参考官方文档进行。

对查询结果进行随机评分

作为网站的所有者,总会希望让广告有更高的展现率。在当前查询下,有相同评分 _score 的文档会每次都以相同次序出现,为了提高展现率,在此引入一些随机性可能会是个好主意,这能保证有相同评分的文档都能有均等相似的展现机率。
我们想让每个用户看到不同的随机次序,但也同时希望如果是同一用户翻页浏览时,结果的相对次序能始终保持一致。这种行为被称为一致随机。
random_score 函数会输出一个 0 到 1 之间的数,当种子 seed 值相同时,生成的随机结果是一致的。例如:

GET /blogposts/post/_search
{
  "query": {
    "function_score": {
      "query": {
        "multi_match": {
          "query":    "popularity",
          "fields": [ "title", "content" ]
        }
      },
      "random_score": {
        "seed":  "userID"
      }
    }
  }
}

使用每个用户的id作为seed传入,可以使每个用户的随机保持一致,同时在不同用户之间保持不同的随机性。

时间、空间上的“越近越好”

一般来说,搜索要求具有时间相关性。也就是说,用户只想看到时间较近的文档,而时间较远的文档,就算相关度较高,用户也不想看到。空间上也有相关的性质,比如以某个点为中心,周围一定距离以内的文档排在返回结果的前面,而超过一定距离的文档就算相关度较高也排在后面。elasticsearch提供了衰减函数,可以对文档的相关性按某个维度进行衰减。

这里不能直接使用sort。因为如果直接使用sort,会让相关度较低的文档排在前面,维度近了但是相关度很差,达不到相关度较高、同时某个维度较 近 的需求。

elasticsearch提供了三个衰减函数,分别是linear、exp和gauss(线性、指数和高斯函数),它们可以操作数值、时间以及经纬度地理坐标点这样的字段(一般衰减也没有用字符串做衰减的)。所有三个函数都能接受以下参数:

  • origin:中心点 或字段可能的最佳值,落在原点 origin 上的文档评分 _score 为满分 1.0 。
  • scale:衰减率,即一个文档从原点 origin 下落时,评分 _score 改变的速度。(例如,每 £10 欧元或每 100 米)。
  • decay:从原点 origin 衰减到 scale 所得的评分 _score ,默认值为 0.5 。
  • offset:以原点 origin 为中心点,为其设置一个非零的偏移量 offset 覆盖一个范围,而不只是单个原点。在范围 -offset <= origin <= +offset 内的所有评分 _score 都是 1.0 。
    衰减曲线

比如,想要搜索某条博客,同时发表时间近的排在前面,引入基于时间的衰减函数。

GET /blogposts/post/_search
{
  "query": {
    "function_score": {
      "query": {
        "multi_match": {
          "query":    "popularity",
          "fields": [ "title", "content" ]
        }
      },
      "gauss" :{
          "timestamp": {
            "origin": "now timestamp",
            "offset": "5d",
            "scale": "10d"
          }
       }
    }
  }
}

按时间和地理空间做衰减,offset 和 scale 必须加上单位。
这里我们确定以当前的时间为衰减函数的中心, 5 天以内的文档,相关度不做处理;5 天到 15 天,衰减系数逐渐降低到0.5;15 天之外,系数继降低。

比如,想将地理空间和价格的影响引入搜索,可以这样实现:

GET /_search
{
  "query": {
    "function_score": {
      "functions": [
        {
          "gauss": {
            "location": {
              "origin": { "lat": 51.5, "lon": 0.12 },
              "offset": "2km",
              "scale":  "3km"
            }
          }
        },
        {
          "gauss": {
            "price": { 
              "origin": "50", 
              "offset": "50",
              "scale":  "20"
            }
          },
          "weight": 2
        }
      ]
    }
  }
}

这里地理空间类型,在衰减函数的参数里要加上单位,而普通的数值类型不用加单位。

通过衰减函数,可以在相关度为主的前提下,引入时间、空间、数值等其他因素,从而影响搜索的返回结果。比起简单粗暴的sort,衰减函数可以保证相关度高的依然排在靠前的位置。

终极武器script_score

elasticsearch支持用户自己写groovy脚本,来自定义复杂的评分影响逻辑。因为日常中不太实用,同时脚本对性能影响较大,所以这里不做介绍。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270