一.数据导入
- 本人测试数据集下载https://download.csdn.net/download/qq_30285985/12645770
- 更多数据集下载https://www.cluebenchmarks.com/dataSet_search.html
二.创建索引
#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可查看分数是如何计算的。
![](/qrcode.jpg)
#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分。
四.打分控制
1.字段权重修改
在实际的搜索过程中title字段和content字段的权重一定是不一样的,就比如一篇作文,标题的权重一定比内容高。
可在^2,将权重放大2倍。
{
"query":{
"multi_match":{
"query": "奖品",
"fields": [ "content", "title^2" ]
}
}
}
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
}
}
}
}
3.点赞数更加平滑
按照点赞来评分排序的话,看似是一个很好的选择,但是0个赞和1个赞对比是有很大区别的,但是100个赞和101个赞对比呢?显得就没有那么大的区别了。
所以可以通过modifier
以平滑的方式来处理votes的值。换句话说,我们希望最开始的一些赞更重要,但是其重要性会随着数字的增加而降低。
0 个赞与 1 个赞的区别应该比 10 个赞与 11 个赞的区别大很多。
点赞数打分算法:
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
。想要了解更多信息请参照:feild_value_factor
4.点赞平滑程度调节
前面说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
}
}
}
}
5.越近越好
点赞评分先告一段落,在大部分场景位置评分的权重相对会高一点,比如搜索酒店,正常来说会搜索附近的小于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左右的,都可以进行比较友好的评分。
参考文档:官方文档