elasticsearch评分进阶

2023-11-12

elasticsearch 评分进阶

原文引用自:Advanced Scoring in elasticsearch,作者还有一篇Elasticsearch评分的经验说明,建议爬墙参考slideshare上的资源对照查看。
如有侵权请联系:shinehiy@foxmail.com


之前关于elasticsearch的文章中,提到并解释了Lucene的内建评分算法的机制。也简要的提到了通过给不同的文档的field或query的term来影响最终的评分算法。本文会针对boost进行详细的说明。

为什么要Boost

我开始做评分的时候的有个问题:到底为什么要boost?Lucene的评分算法到底靠不靠谱?我在索引里面放的测试文档比较少的时候效果貌似还不错,但是加了更多的文档,结果就不怎么好了,这个时候我意识到boost的必要性了。Lucene的评分算法在通用的情况下效果较好,但是并不是针对你的特定领域或应用设计的。Boost可以让你对不同的文档类型进行调节,增加领域特定的逻辑,并融入附加的标记。

在给出特定例子之前,先说明下我遇到的搜索应用的情境。
该应用为IGN提供搜索服务,IGN主打:”游戏、娱乐、一切享乐“。我们需要从后端API里面索引下列四种类型的内容:1. 文章;2. 视频; 3. wiki网页;4. ”对象“(游戏、电影、各种秀等等)。默认的,所有类型的结果都以list集合的形式返回。

为不同的文档type进行修正

如果你的文档是同质的话,Lucene评分算法是不错的。但是如果你有不同的文档类型,你需要做一些手工的调整。比如,我们需要索引文档和视频。文档中有很多文字性的内容——文档的body——但是对于视频,只有简短的描述。默认的lucene更偏向于匹配较短的field,所以匹配结果视频的得分往往比得分要高。

因为elasticsearch支持跨多个索引进行搜索,通过为每个type创建分开的索引、并进行跨索引query的查询,对不同的文档type进行调整是可行的。我没有进行测试,但是我觉得对每个query都应该用coordination因素进行标准化,那么一个高分的视频会得到和一个高分文章相同的权重。然而,该方法仍然会独立的考虑每个内容type中的term频率,我并不确定这会如何影响结果。

给文章一个较小的boost值是较简单的方案,特别是我们准备我们因为各种独立的因素会控制不同内容type的重要性。

增加特定领域逻辑

有事,你会有一个特定领域的逻辑Lucene难以识别。比如,我们的评论文章可能是我们网站的内容中最重要的type。因为我们的用户就是来看评论的,我们给评论文章一个小boost值,让他们比其他文章得分高一些。

另外一个例子是树状的wiki页面。视频和对象都有比较短的文本描述。文章通常要长一些,虽然有时候也有一些短文章影响到新闻或者提高一些其他的内容,短文本还好。然而,一个短wiki页面经常是一组树状的标记,所以会比其他结果的得分要低。这与lucene本身的特性相反,Lucene更偏向于匹配短的wiki页面,给出较高的得分。

合并附加标记

大多数情况,我们网站内容的一个关键要素就是文章内容应该随时间而衰减。举个例子,一个刚发布的游戏的评论在本周内非常重要,但是下个月就不那么重要了,一年之后更不用说了。lucene在打分算法中并没有考虑到内容的新颖性/时效性这类要素。但是如果如果时间要素在你的领域中起着关键作用,你就会对其进行boost*(如何进行时间要素的boost在本文后续详解)*。

如果针对一些文章、视频的评论和内容很多,我们就会对游戏、电影和TV show这些对象进行boost。换个更简单的例子就是,销量大的产品应该被boost,或者有更多评论和点击阅读的文章应该被boost。你的特定领域哪个属性更重要,你就应该动手给他进行boost。

Index阶段Boost vs. Query阶段Boost

在对文档进行索引时可以进行boost,也可以在query阶段进行搜索的时候进行boost。如果一篇文档总是比其他的更重要,你应该考虑在进行索引的时候就对这些文档进行boost。预先进行过boost的文档搜索更快,因为在进行搜索时要做的事更少。但是即使你知道一些文档总是比其他的重要,但是你不确定到底有多重要。如果boost太大,如果匹配上了,文档就总在搜索结果的最上面。如果boost因素太小,重要的文档就没法从其他文档中凸显出来。

如果在索引阶段就进行boost,如果你要换boost的值就得重新建一次索引。除非你手工向索引中添加文档,一个一个决定这个文档该给啥boost值,你得用脚本或者程序来建立索引,用一些逻辑或者规则来决定boost。逻辑或规则的改变会影响很多文档,要让逻辑或规则生效,就有得重新进行索引。如果你的索引比较小,那还可以。我们的索引重建一次要好几个小时,所以我们会尽可能不在索引阶段就进行boost。在query阶段使用boost可以随意增加boost,改变boost的标准,随时改变boost的长度。比起运行时的一些额外开销,灵活性是值得的。

即使你在索引阶段和query阶段进行了boost,有一些boost是不得不在query阶段做的,有一些boost是在query阶段才生效,因为在索引阶段无法获得足够的信息。比如,如果你要基于文档的新鲜度(该文档的时间戳时候离当前时间较近),当前时间(索引的当前时刻)就无法从索引阶段获知。这个例子中,如果你经常重建索引,你可以把index时间作为当前时间,这样就可以避免query阶段的boost了。

应用Boost

基本上每一个elsaticsearch的query类型都有boost参数让你可以针对这个query进行调优,但是我们没有使用这个query因为就只有一个主query。主query是query_string的query,对用户的query进行分词,找到匹配项,用Lucene的默认打分算法进行打分。下面我们用一些boost来决定是否符合特定的标准。

在早先的原型中,我通过对主query进行custom_score的包裹完成了boost。顾名思义, custom_scorequery允许你使用定制的逻辑来计算每个文档的得分,可以通过script传入参数。默认的,脚本集成的是MVEL,但是也支持其他语言。你可以通过指定的_score变量来操作得分,所以开始的时候我是这样做的:

{
  "query": {
    "custom_score": {
      "query": { ...the main query... },
      "script": "_score * (doc['class'].value == 'review' ? 1.2 : 1)"
    }
  }
}

确实生效了,但是量大了就不那么好使。随着我增加越来越多的boost,得用好几行才能结束这个表达式。每个文档的field都需要在索引阶段存储,这样脚本才能检索到并操作这个值,这样索引变得很大操作也变慢了。幸运的是,我们可以使用更好的工具完成这个操作,custom_filters_scorequery。文档中提到:

ustom_filters_scorequery允许执行一个query,并且如果命中的结果匹配到了提供的filter(按顺序),就使用boost或是脚本来计算该值。

可以明显简化,并提高基于参数化打分的效率,因为这些filter可以进行缓存提供较高的性能,从而boost/脚本也更简单了。

转成custom_filters_scorequery,上面的例子就变成了这样:

{
  "query": {
    "custom_filters_score": {
      "query": { ...the main query... },
      "filters": [
        {
          "filter": {
            "term": {
              "class": "review"
            }
          },
          "boost": 1.2
        }
      ]
    }
  }
}

如果你想增加boost,就再添一个filter指定其标准,并赋boost。可以使用任何的filter,一个包裹另一个的filter甚至andfilter。如果你有多个filter,那么需要指定这多个匹配filter如何使用score_mode进行融合。默认的,是使用第一个匹配filter的boost,但是如果你有好多个filter都匹配上了你可以设置score_mode,比如设置multiply 应用到所有的boost上。

如下的query boost评论提升了20%,boost文章提升20%(所以评论的文章会boost 44%)(译者注: (1 + 20%)*(1 + 20%)),然后对wiki页面进行惩罚,小于600词长的会降权到80%。

{
  "query": {
    "custom_filters_score": {
      "query": { ...the main query... },
      "filters": [
        {
          "filter": {
            "term": {
              "class": "review"
            }
          },
          "boost": 1.2
        },
        {
          "filter": {
            "term": {
              "type": "article"
            }
          },
          "boost": 1.2
        },
        {
          "filter": {
            "and": [
              {
                "term": {
                  "type": "page"
                }
              },
              {
                "range": {
                  "descriptionLength": {
                    "to": 600
                  }
                }
              }
            ]
          },
          "boost": 0.2
        }
      ],
      "score_mode": "multiply"
    }
  }
}

变量Boost

有时你会想根据一个document里面的field的boost来调节boost。比如,如果你想boost最近的文档,今天发布的文档就会比昨天发布的文档增加boost,而昨天发布的文档比上周发布的文档更大。即使filter会进行缓存,而且跑起来相对比较快。给今天发的文章给个boost,给昨天发布的再给个boost,上周的文章在给个boost一点都不实用。幸运的是,custom_filters_score 的query可以接受一个script ,这些情况下就不用再使用boost了。

Boosting Documents in Solr by Recency, Popularity and Personal Preferences(打不开请挂代理,来自slideshare),Timonthy Potter谈到了solr的recip 函数来计算最近文档的boost值。不幸的是,elasticsearch并没有提供recip函数,但是你可以很简单的就实现整个函数y = a / (m * x + b),并转成script函数用到elasticseach里面来。

在下面的例子中,我用了值m, a 还有b按照slide中第七页的设置:m = 3.16E-11, a = 0.08b = 0.05。因为我们索引中有些未来时间的日期,所以我加了个绝对值函数abs()来计算query时间和文档的时间戳的绝对值。我是对boost值设置为1,即设置一个新鲜度的值,而不是一个衰减值。

{
  "query": {
    "custom_filters_score": {
      "query": { ...the main query... },
      "params": {
        "now": ...current time when query is run, expressed as milliseconds since the epoch...
      },
      "filters": [
        {
          "filter": {
            "exists": {
              "field": "date"
            }
          },
          "script": "(0.08 / ((3.16*pow(10,-11)) * abs(now - doc['date'].date.getMillis()) + 0.05)) + 1.0"
        }
      ]
    }
  }
}

有了这个值,当前的文档会被加权到160%(boost值2.6)。这个值在10天后会掉到100%,一个月后掉到60%,半年后掉到15%,一年后掉到8%,2年后掉到4%。(可见

最后要说明的是,elasticsearch的script进行缓存会执行效率更高,把那些随着query改变的参数通过params值传入,不要把他作为string直接插入到脚本中。这样,脚本就变成静态值了,无法缓存,但是用参数就不会出现这样的情况。


精选答疑:
Nick (June 12, 2013 at 11:34 am)
在跑主query时我遇到了如下问题,我把post_date作为一个field放在了索引里面,存储时设置为了string类型。除了如下错误:

“error” : “ElasticSearchException[Couldn’t parse query from source.]; nested: ElasticSearchParseException[failed to parse date field [link], tried both date format [YYYY-MM-dd HH:mm:ss], and timestamp number]; nested: IllegalArgumentException[Invalid format: \”link\”]; “,

貌似是elasticsearch无法把string转成date。
我从field中移除了post_date,貌似这个script还是不可用,报错如下:

error” : “CompileException[[Error: No field found for [org.elasticsearch.index.fielddata.ScriptDocValues$Longs@33de6c02] in mapping with types [post]]\n[Near : {… *pow(10,-11)) * abs(now – doc[post_date].date.getM ….}]\n ^\n[Line: 1, Column: 35]]; nested: ElasticSearchIllegalArgumentException[No field found for [org.elasticsearch.index.fielddata.ScriptDocValues$Longs@33de6c02] in mapping with types [post]]; “,

我用的curl如下:github链接

作者回复:
第一个报错貌似是elasticsearch在处理date的field时候遇到了链接,你可以检索下索引里面的文档,验证下date field是不是有链接。

第二个不确定,如果文档中没有这个field不应该拿来跑,所以我不太明白这里的”no field found”

提问者Nick:
问题解决了,我在post_date里面用了单引号,所以curl也得用单引号来包裹整个request。我改成双引号就能用了。

其他问答:
1.‘boost’问题,尝试’boost_factor’
2. ES 1.0+不再支持custom_script,可以考虑使用gauss过滤器:

{
    “query”: {
        “function_score”: 
        {
            “query”: { 

            }, // your query
            “functions”: 
            [
                {
                    “gauss”: {
                        “date”: 
                        {
                            “origin”: “now/d”, // using now/d should help with caching if you’re using several days or weeks for scale and often, if you want decay during a single day (ie trending) use now
                            “scale”: “50w”, // how long to get to decay (for trending this should be something like 1d)
                            “offset”: “4w”, // how long doesn’t have any affect (for trending this might be 4h)
                            “decay”: “0.5// after 1 unit of scale the relevance will have decayed by this much (for trending you might want this as 0.3)
                        }
                    }
                }   
            ]
        }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

elasticsearch评分进阶 的相关文章

  • 二叉搜索树-AVL树的实现

    首先 AVL树是一棵加了额外平衡条件的搜索树 这是因为普通的搜索树如果插入的key接近有序的话 二叉树将会退化成一个单链表 导致查找的时间复杂度为O N 而AVL树中用一个平衡因子来制约树的左右子树的高度 保证任何节点的左右子树高度之差最多
  • Android开发人员应该选择哪种语言?

    自 Google 于 2017 年宣布 Kotlin 成为 Google IO 的 Android 开发官方语言以来 想要成为Android开发人员的程序员正陷入两难境地 在讨论这个问题前 我首先要明确一点 不要陷入编程语言战争 不要进行语
  • 【亲测解决】AttributeError: module ‘tensorflow‘ has no attribute ‘__version__‘

    今天在安装使用bert as service时报错 报错信息如下 AttributeError module tensorflow has no attribute version 一看 懵逼了 啥 tensorflow么有 version
  • centos 内核升级

    首先查看centos版本 cat etc centos release 或者 rpm q centos release 查看内核版本 uname sr 查看官方内核 https www kernel org 接下来升级内核 大多数现代发行版
  • 太牛叉了!解决“卡脖子”的国产自主 IDE [狗头.jpg]

    推荐关注 综合整理 程序员的那些事 ID iProgrammer 解决 卡脖子 的自主创新 IDE 最近有一个的国产自主创新的 CEC IDE 震动了程序员圈子 在 CEC IDE 官网简介中的 安全可控 条目自称 国企品牌 自主研发 注意
  • k8s运维 pod、node、namespace、pv处于terminating的原因及处理方法

    1 概述 node pod ns pv由于一些原因在生产中处于terminating的状态 常规方法无法删除 一下总结了一些原因以及删除方法 2 node处于Terminating状态原因及处理方法 node节点不可达的情况下 kubect
  • MATLAB嵌套循环求解1到1000的素数和

    熬夜打卡 代码都跑过一遍的 没有任何问题啦 方法一 matlab的嵌套循环 重在理解 clc clear s 0 for i 2 1000 for j 2 32 if mod i j break end end if j gt i j s
  • 【华为OD机试】阿里巴巴找黄金宝箱(IV)(C++ Python Java)2023 B卷

    时间限制 C C 1秒 其他语言 2秒 空间限制 C C 262144K 其他语言524288K 64bit IO Format lld 题目描述 一贫如洗的樵夫阿里巴巴在去砍柴的路上 无意中发现了强盗集团的藏宝地 藏宝地有编号从0 N的箱
  • 常用的BOM属性 - Kaiqisan

    终于出狱了 今天重新恢复博客的更新 大致谈谈我复习面试上面的查漏补缺的内容 首先讲讲什么是BOM BOM简单来说就是浏览器对象 只有js在浏览器环境运行时才会被赋予的对象 location对象 该对象内所有的属性都与URL有关 常常用于提取
  • 攻防世界————fileclude(内含php伪协议菜鸟讲解)

    先进去发现为一坨php代码 新手勉勉强强看得懂 接下来我们分析代码 WRONG WAY
  • Angular2-使用Augury来调试Angular2程序

    原文链接 http www jianshu com p efecaea287f2 推荐 Augury Angular专用的chrome 调试插件 如题 就在前几天的2016 12 8谷歌开发者大会上 angular2的leader来给我们演
  • idea字体主题集合

    http color themes com view index
  • 意念控制四旋翼 学习笔记

    第一部分 模块原始数据 拿到模块 在网上查了一圈 发现基本没什么有用的资料 很多都是一些相关但是没有实际价值的东西 许多论文都是再谈怎么去做 而没有实实在在的去完成这么一个过程 废话不多说 直接步入正题 昨天在网上才发现这个软件 据评论说是
  • 最近大火的「元宇宙」是什么?

    公众号后台回复 图书 了解更多号主新书内容 作者 腾讯技术工程特约撰稿人 李佳华 本文将介绍元宇宙的由来和底层技术 探讨海内外资本在这条赛道上的布局 元宇宙将会对哪些行业产生变革的影响 这些影响背后凸显了元宇宙的哪些价值 以及元宇宙逐步实现
  • openwrt reboot流程

    openwrt 系统中 当执行了 reboot 命令 系统将会发生什么事情呢 如何进行重启的呢 下面来一起看一下 reboot 应用层操作 首先 reboot 是由busybox 它是一个集成了常用Linux命令和工具的软件 提供的一个Li
  • leetcode算法面试题:串联所有单词的子串问题、单词拆分问题

    串联所有单词的子串问题 给定一个字符串 s 和一些 长度相同 的单词 words 找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置 注意子串要与 words 中的单词完全匹配 中间不能有其他字符 但不需要考虑 word
  • 数据挖掘算法基础-关联规则

    数据挖掘中 被常拿来说的啤酒尿布的例子就是一个很典型的运用关联算法来做购物来分析的例子 常被用于交易数据 关系数据的分析 发现数据集中隐藏的频繁模式 这些频繁模式可以用关联规则的形式表示 有效的关联规则对商家的商品进出货摆放都有很大的指导意
  • 直方图均衡化与直方图规定化

    一 认识图像 当我们面对图像的时候 我们面对的是抽象的矩阵 如下图 下面是0 255的灰度图像的表示 密密麻麻的 那么我们做的直方图 其实就是对这些像素值的统计 如下图所示 其中Bin表示条数 数据和范围是对图的解释 二 为什么要做直方图均
  • qt 嵌入web页面_Qt -在应用程序中嵌入Web内容之环境搭建

    一 Qt应用程序与Web结合的发展 1 从Qt5 5开始 Qt WebKit模块被废弃了 取而代之的是Qt WebEngine模块 当时可以使用该模块将应用程序与Web技术结合 2 Qt WebEngine模块提供了一个Web浏览器引擎 可
  • ChatGPT:概述Vue.js中data函数初始化和created钩子函数调用的顺序和问题解决方法

    ChatGPT 概述Vue js中data函数初始化和created钩子函数调用的顺序和问题解决方法 我将输入一段Vue代码 请你记住 created console log this queryInfo this getClueList

随机推荐

  • Libuv源码分析 —— 6. 事件循环【uv_run】

    通过之前的学习 咱们已经明白了在事件循环中的三个核心内容 分别是 Libuv源码分析 定时器 Libuv源码分析 idle prepare check Libuv源码分析 poll io 现在让咱们从头捋一遍事件循环到底完成了什么功能呢 u
  • scrapy里面的response.xpath(“用xpath插件找打的路径“)返回值为空?

    response xpath 用xpath插件找打的路径 返回值为空 1 可能是因为路径是有问题的 2 可能是start urls的路径是有问题的 可以从network中找找路径 复制一下
  • 使用vant2问题整理

    1 export createVNode imported as createVNode was not found in vue possible exports EffectScope computed customRef defaul
  • C++11移动语义解析

    当给函数传递对象当做函数参数时 可以使用引用类型来减少拷贝对象的代价 尤其是避免容器的拷贝等 但是当把函数内的局部对象当做返回值时 我们无法返回该局部对象的引用 导致每次返回局部对象都会进行拷贝 因为返回局部对象的引用是无意义的 当函数调用
  • 编译原理实验日志

    编译原理 生成四元式 实验原理 构造SLR 1 分析表 调试过程 实验原理 构造SLR 1 分析表 首先求得follow集 follow E follow T follow F 画出DFA状态转换图 调试过程 没有判断 因为字符串中没有表示
  • dubbo优雅停机

    dubbo优雅停机 Dubbo是通过JDK的ShutdownHook来完成优雅停机的 所以如果用户使用 kill 9 PID 等强制关闭指令 是不会执行优雅停机的 只有通过 kill PID 时 才会执行 原理 服务提供方 停止时 先标记为
  • grafana与prometheus实现监控可视化

    1 Grafana基础知识 Grafana是一个开源的指标监测和可视化工具 官方网站为 Grafana The open observability platform Grafana Labs 常用于展示基础设施的时序数据和应用程序运行分析
  • 大话设计模式9—观察者模式(通知者与观察者)

    大话设计模式9 观察者模式 老板回来 我不知道 1 需求 老板回来 我不知道 2 双向耦合的设计 2 1 前台秘书类 2 2 看股票同事类 2 3main函数及输出 3 解耦修改 3 1 抽象观察者类 3 2 前台秘书类 3 3 main函
  • Java编程中出现乱码的原因

    乱码的原因 理解了编码 我们来看乱码 乱码有两种常见原因 一种比较简单 就是简单的解析错误 另外一种比较复杂 在错误解析的基础上进行了编码转换 我们分别介绍 1 解析错误 看个简单的例子 一个法国人采用Windows 1252编码写了个文件
  • Windows/PC(win + R) 电脑常见操作命令50条

    摘要 win R 1 cmd 打开终端 2 gpedit msc 本地组策略编辑器 3 Nslookup IP地址侦测器 4 explorer 文件资源管理器 5 notepad 系统默认记事本 6 cleanmgr 磁盘清理 7 serv
  • 微信公众号运营错误的四个方式

    1 很多广告宣传 很多的微信公众平台注册便是为了更好地宣传策划商品 在开展內容輸出的情况下 沒有立在客户的视角开展內容輸出 消息推送的內容可能是七拼八凑 与微信公众号的精准定位偏移很远 要想取得成功的运营公众号 最先要做的便是深层次发掘总体
  • vue3 + vite 在线预览docx, pdf, pptx(内外网)并实现移动端适配

    一 内网 1 docx 使用docx preview 安装插件 npm i docx preview S 引入依赖 docx import renderAsync from docx preview let docx import meta
  • GO终端读取

    GO终端读取 Go语言获取标准输入 Go语言 fmt 包下有 fmt Scan fmt Scanf fmt Scanln 三个函数 可以在程序运行过程中获取用户输入 func Scan a interface n int err error
  • Spring核心之一:IOC

    IOC Inversion of Control 其实是一种思想 这种思想并不是Spring独有的 而是在软件开发中 大家提出的一种开发原则 类似面向接口编程原则 开闭原则等 网上有很多类似的文章尝试去通俗易懂地解释IOC思想 这里我根据自
  • BugkuCTF-Crypto题小山丘的秘密

    本题考查希尔密码 解题流程 题目信息 1 根据提示知道是希尔 hill 密码 解密网站 www atoolbox net Tool php Id 914 ac csdn flag txt 里给出A 1 一般的希尔密码是A 0 B 1 C 2
  • Vue.js 学习笔记 第5章 内置指令

    本篇目录 5 1 基本指令 5 2 条件渲染指令 5 3 列表渲染指令 v for 5 4 方法与事件 5 5 实战 利用计算属性 指令等知识开发购物车 回顾一下第2 2节 我们己经介绍过指令 Directive 的概念了 Vue js的指
  • JAVA 【基础】 log4j 输出样式

    先了解一下log4j 的打印参数如下 参数 说明 L 输出代码中的行号 l 输出日志事件的发生位置 包括类目名 发生的线程 以及在代码中的行数 如 Testlog main TestLog java 10 m 输出代码中指定的消息 p 输出
  • 使用vs2013编译qt4库

    一 废话 由于项目需要搭建qt msvc2013的环境 而官方提供的qt4库最高只支持和vs2010关联 如果需要配置qt msvc2010只需要安装官方给的qt4库的安装包 然后下载addin就可以了 本人也是第一次搭建该环境 在网上找了
  • GDB 程序调试常用命令

    调试之前 若要在GDB中调试程序在编译时需要加上调试信息 在GCC中添加的方法 GCC g a c o a exe 或下面提供更符合GDB的调试信息 GCC ggdb a c o a exe 运行流程 命令 作用 start 开始执行程序
  • elasticsearch评分进阶

    elasticsearch 评分进阶 原文引用自 Advanced Scoring in elasticsearch 作者还有一篇Elasticsearch评分的经验说明 建议爬墙参考slideshare上的资源对照查看 如有侵权请联系 s