本文共 9802 字,大约阅读时间需要 32 分钟。
#GET http://192.168.16.128:9200/es_news{ "settings": { "number_of_shards" : 1, "number_of_replicas" : 0 }, "mappings":{ "properties":{ "id":{ "type":"long"}, "title":{ "type":"text","analyzer":"ik_max_word", "search_analyzer": "ik_smart" }, "content":{ "type":"text","analyzer":"ik_smart"}, "shortname":{ "type":"text","analyzer":"ik_max_word"}, "location":{ "type":"geo_point"} } } }
数据导入->
自定义添加数据。
POST http://192.168.16.128:9200/es_news/_doc{ "title": "我中了一个奖品", "content": "奖品内容是苹果电脑", "location": "88.884874,29.263792", "shortname": "日喀则"}
每个文档都有相关性评分,用一个正浮点数字段 _score
来表示 。 _score
的评分越高,相关性越高。换句话说,就是_score
越高,就离我们想要搜索到的结果越相近。
Elasticsearch 的相似度算法被定义为检索词频率/反向文档频率, TF/IDF ,包括以下内容:
检索词频率
检索词在该字段出现的频率?出现频率越高,相关性也越高。 字段中出现过 5 次要比只出现过 1 次的相关性高。如:检索词 honeymoon 在这个文档的 tweet 字段中的出现次数。
反向文档频率
每个检索词在索引中出现的频率?频率越高,相关性越低。检索词出现在多数文档中会比出现在少数文档中的权重更低。如:检索词 honeymoon 在索引上所有文档的 tweet 字段中出现的次数。
字段长度准则
字段的长度是多少?长度越长,相关性越低。 检索词出现在一个短的 title 要比同样的词出现在一个长的 content 字段权重更大。如:在这个文档中, tweet 字段内容的长度 -- 内容越长,值越小。
通过explain为true可查看分数是如何计算的。
#GET http://192.168.16.128:9200/es_news/_search{ "explain":"true", "query":{ "match":{ "content":"奖品" } }}
查询结果如下:
{ "took":5, "timed_out":false, "_shards":{ "total":1, "successful":1, "skipped":0, "failed":0 }, "hits":{ "total":{ "value":3, "relation":"eq" }, "max_score":9.761058, "hits":[ { "_shard":"[es_news][0]", "_node":"VkN6WwZZQISzsHaUZA5EBw", "_index":"es_news", "_type":"_doc", "_id":"BFY7b3MB_Kk3jjQO7RPr", "_score":9.761058, "_source":{ "title":"我中了一个奖品", "content":"奖品内容是苹果电脑", "location":"88.884874,29.263792", "shortname":"日喀则" }, "_explanation":{ "value":9.761058, "description":"weight(content:奖品 in 1400) [PerFieldSimilarity], result of:", "details":[ { "value":9.761058, "description":"score(freq=1.0), computed as boost * idf * tf from:", "details":[ { "value":2.2, "description":"boost", "details":[ ] }, { "value":5.9928923, "description":"idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:", "details":[ { "value":3, "description":"n, number of documents containing term", "details":[ ] }, { "value":1401, "description":"N, total number of documents with field", "details":[ ] } ] }, { "value":0.7403511, "description":"tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:", "details":[ { "value":1, "description":"freq, occurrences of term within document", "details":[ ] }, { "value":1.2, "description":"k1, term saturation parameter", "details":[ ] }, { "value":0.75, "description":"b, length normalization parameter", "details":[ ] }, { "value":3, "description":"dl, length of field", "details":[ ] }, { "value":53.243397, "description":"avgdl, average length of field", "details":[ ] } ] } ] } ] } } ] }}
在_explanation.details
字段中说明了分数是如何计算的。score(freq=1.0), computed as boost * idf * tf from:
说明分数是由 boost,idf,tf三个字段相乘得到的。boost为2.2分,idf为5.9928923分,tf为0.7403511分,最后相乘为9.761058分。
在实际的搜索过程中title字段和content字段的权重一定是不一样的,就比如一篇作文,标题的权重一定比内容高。
可在^2,将权重放大2倍。{ "query":{ "multi_match":{ "query": "奖品", "fields": [ "content", "title^2" ] } }}
遇到复杂的情况时,简单粗暴的权重翻倍就无法满足我们的需求了。
可以通过function-score
来进行更详细的评分设置。 比如只按照点赞数进行打分呢? ##PUT /blogposts/post/1{ "title": "About popularity", "content": "In this post we will talk about...", "votes": 6}
##GET /blogposts/post/_search{ "query": { "function_score": { "query": { "multi_match": { "query": "popularity", "fields": [ "title", "content" ] } }, "field_value_factor": { "field": "votes" "missing":"1" ## 表示没有votes字段的时候默认为1 } } }}
按照点赞来评分排序的话,看似是一个很好的选择,但是0个赞和1个赞对比是有很大区别的,但是100个赞和101个赞对比呢?显得就没有那么大的区别了。
所以可以通过modifier
以平滑的方式来处理votes的值。换句话说,我们希望最开始的一些赞更重要,但是其重要性会随着数字的增加而降低。
点赞数打分算法:
new_score = old_score * number_of_votes
平滑点赞算法
new_score = old_score * log(1 + number_of_votes)
请求如下:
##GET /blogposts/post/_search{ "query": { "function_score": { "query": { "multi_match": { "query": "popularity", "fields": [ "title", "content" ] } }, "field_value_factor": { "field": "votes", "modifier": "log1p" } } }}
修饰语 modifier
的值可以为: none
(默认状态)、 log
、 log1p
、 log2p
、 ln
、 ln1p
、 ln2p
、 square
、 sqrt
以及 reciprocal
。想要了解更多信息请参照:
前面说modifier
可以使得点赞的分数变得更加平滑,但是这个平滑的程度如何控制呢?看图!
默认情况下分数计算方式:
new_score = old_score * log(1 + number_of_votes)
通过factor来调节幅度:
new_score = old_score * log(1 + factor * number_of_votes)
请求方式
##GET /blogposts/post/_search{ "query": { "function_score": { "query": { "multi_match": { "query": "popularity", "fields": [ "title", "content" ] } }, "field_value_factor": { "field": "votes", "modifier": "log1p", "factor": 2 } } }}
点赞评分先告一段落,在大部分场景位置评分的权重相对会高一点,比如搜索酒店,正常来说会搜索附近的小于100块钱一晚酒店。
function_score
提供了一组衰减函数。让我们有能力在两个滑动标准,如地点和价格,之间权衡。
有三种衰减函数—— 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
。
原点 origin
(即中心点)的值都是 40
, offset
是 5
,也就是在范围 40 - 5 <= value <= 40 + 5
内的所有值都会被当作原点 origin
处理——所有这些点的评分都是满分 1.0
。
在此范围之外,评分开始衰减,衰减率由 scale
值(此例中的值为 5
)和 衰减值 decay
(此例中为默认值 0.5
)共同决定。结果是所有三个曲线在 origin +/- (offset + scale)
处的评分都是 0.5
,即点 30
和 50
处。
linear
、 exp
和 gauss
(线性、指数和高斯)函数三者之间的区别在于范围( origin +/- (offset + scale)
)之外的曲线形状:
linear
线性函数是条直线,一旦直线与横轴 0 相交,所有其他值的评分都是 0.0
。exp
指数函数是先剧烈衰减然后变缓。gauss
高斯函数是钟形的——它的衰减速率是先缓慢,然后变快,最后又放缓。选择曲线的依据完全由期望评分 _score
的衰减速率来决定,即距原点 origin
的值。
如此的话搜索酒店大概可这样请求:
#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 } ] } }}
但是明明想是钱是小于100块钱酒店?为什么price的origin要50呢?在这里暂且认为用户喜欢的是越低越好。如果是设置成100的话,和100偏差较大的酒店评分就会变得很低。所以设置成50,这样50左右的,都可以进行比较友好的评分。
参考文档:
转载地址:http://zygdz.baihongyu.com/