elasticsearch评分进阶
来源:互联网 发布:表格删除重复数据 编辑:程序博客网 时间:2024/05/19 07:10
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_score
query允许你使用定制的逻辑来计算每个文档的得分,可以通过script
传入参数。默认的,脚本集成的是MVEL,但是也支持其他语言。你可以通过指定的_score
变量来操作得分,所以开始的时候我是这样做的:
{ "query": { "custom_score": { "query": { ...the main query... }, "script": "_score * (doc['class'].value == 'review' ? 1.2 : 1)" } }}
确实生效了,但是量大了就不那么好使。随着我增加越来越多的boost,得用好几行才能结束这个表达式。每个文档的field都需要在索引阶段存储,这样脚本才能检索到并操作这个值,这样索引变得很大操作也变慢了。幸运的是,我们可以使用更好的工具完成这个操作,custom_filters_score
query。文档中提到:
ustom_filters_score
query允许执行一个query,并且如果命中的结果匹配到了提供的filter(按顺序),就使用boost或是脚本来计算该值。
可以明显简化,并提高基于参数化打分的效率,因为这些filter可以进行缓存提供较高的性能,从而boost/脚本也更简单了。
转成custom_filters_score
query,上面的例子就变成了这样:
{ "query": { "custom_filters_score": { "query": { ...the main query... }, "filters": [ { "filter": { "term": { "class": "review" } }, "boost": 1.2 } ] } }}
如果你想增加boost,就再添一个filter指定其标准,并赋boost。可以使用任何的filter,一个包裹另一个的filter甚至and
filter。如果你有多个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.08
,b = 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) } } } ] } }}
- elasticsearch评分进阶
- elasticsearch sort评分
- ElasticSearch评分机制
- Elasticsearch如何评分?-Apache Lucene默认评分公式解释
- 关于elasticsearch的评分_score,测试结果
- ReactNative进阶之评分控件的封装
- 26.Elasticsearch API初步进阶
- ElasticSearch 命令-(进阶篇)
- ElasticSearch源码解析(五):排序(评分公式)
- Elasticsearch 5.x (四) lucene 的评分机制
- Elasticsearch 评分score计算中的Boost 和 queryNorm
- 评分
- 弹性搜索(elasticsearch)进阶--服务维护
- 【Linux进阶】Ubuntu 16.04安装ElasticSearch
- Elasticsearch顶尖高手系列-高手进阶篇
- ElasticSearch之分词器进阶-修复ansj分词器bug
- Elasticsearch之高亮进阶-高性能高亮器, 让Elasticsearch飞一会儿
- 进阶篇之纯css+字体实现五角星(半颗星)评分
- Java Swing JTable 表格【15:组合列表框作为表格元素】
- 关于SYP'BLOG,关于Problem&Solutions
- 第二天,tranform ,图片浏览器,帧动画代码(我很勤劳的0.0)
- JS之浏览器对象
- Java中的BIO、NIO、AIO(NIO2)
- elasticsearch评分进阶
- 【codeforces 111C】 Petya and Spiders
- 【bzoj1560】【jsoi2009】【火星藏宝图】【dp】
- android中的回调
- lightoj 1374 Confusion in the Problemset
- leetcode笔记:Find Median from Data Stream
- Leetcode236: Interleaving String
- iOS程序员从小白到大神必读资料汇总(一)
- 详细整理:UITableView优化技巧