本文共 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/