控制相关度
ES使用布尔模型(Boolean model)查找匹配文档,并用一个实用评分函数(practical scoring function)的公式来计算相关度。这个公式借鉴了词频/逆向文档频率(term frequency/inverse document frequency)和向量空间模型(vector space model),同时加入了一些现代的新特性,如协调因子(coordination factor),字段长度归一化(field length normalization),以及词或查询语句权重提升。
1、布尔模型
只是在查询中使用AND/OR/NOT这样的条件来插好匹配的文档
full AND text AND search AND (elasticsearch OR lucene)
会将所有包括词 full 、 text 和 search ,以及 elasticsearch 或 lucene 的文档作为结果集。
2、词频/逆向文档频率(TF/IDF)
词的权重由三个因素决定
--词频,词在文档中出现的“频度”越高,权重越高
如果不在意刺在某个字段中出现的频度,只在意是否出现过,可以禁用词频统计。(index_options设置为docs,not_analyzed的默认值就是这个)
--逆向文档频率,词在集合所用文档里出现的“频率”是多少,频次越高,权重越低。
--字段长度归一值,字段的长度是多少?字段越短,字段的权重越高。
("norms": { "enabled": false }禁用归一值,not_analyzed的默认值就是这个)
以上三个因素,是在索引时计算并存储的
3、向量空间模型
查询通常不止一个词,所以需要一种合并多词权重的方式,也就是向量空间模型
向量空间模型提供一种比较多词查询的方式,单个评分代表文档与查询的匹配程度,为了做到这点,这个模型将文档和查询都以向量的形式表示:
向量实际上就是包含多个数的一堆数组(每个数都代表一个词的权重),例如:
[1,2,5,22,3,8]
4、查询归一化
试图将查询“归一化”,这样就能将两个不同的查询结果相比较
5、查询协调
协调因子(coord),可以为那些查询词包含度高的文档提供奖励,文档里出现的查询词越多,越有机会成为好的匹配结果。
例如查询 quick brown fox
文档里有 fox → 评分: 1.5
文档里有 quick fox → 评分: 3.0
文档里有 quick brown fox → 评分: 4.5
协调因子将评分与文档里匹配词的数量相乘,然后除以查询所有词的数量。
文档里有 fox → 评分: 1.5 * 1 / 3 = 0.5
文档里有 quick fox → 评分: 3.0 * 2 / 3 = 2.0
文档里有 quick brown fox → 评分: 4.5 * 3 / 3 = 4.5
6、索引时字段层权重提升
……
查询时权重提升
boost参数,用来影响权重
当在多个索引中搜索时,可以使用indices_boost来提升整个索引的权重
使用查询结构修改相关度
越深的查询层级,对相关度的影响就越小
Not Quite Not
假如我们检索Apple,是想搜索苹果公司,就需要把一些例如水果的东西排查掉(must_not),但是排除掉这些词之后,可能丢失了与苹果公司相关的文档。
权重提升查询能解决这个问题(包含结果,但是降低排名)
GET /_search
{
"query": {
"boosting": {
"positive": {
"match": {
"text": "apple"
}
},
"negative": {
"match": {
"text": "pie tart fruit crumble tree"
}
},
"negative_boost": 0.5
}
}
}
boosting查询接受positive和negative查询。只有匹配positive查询的文档罗列出来,还匹配negative查询的文档通过文档的原始_score月negative_boost相乘的方式降级
忽略TF/IDF
有时候我们不关心TF/IDF,只想知道一个词是否在某个字段出现过。
constant_score查询可以包含查询或过滤,为任意一个匹配的文档指定评分1,也可以使用boost提升权重,但是协调因子和查询归一化因子仍然会被考虑在内。
--
默认情况下not_analyzed字段会禁用字段长度归一值,并将index_options设为docs,禁用词频。但是每个词的倒排文档频率仍然会被考虑。同样可以使用constant_score来解决这个问题
function_score查询
允许为每个与主查询匹配的文档应用一个函数,以达到改变甚至完全替换原始查询评分_score的目的。
也能用过滤器对结果的子集应用不同的函数,这样既能高效评分,又能利用过滤器缓存
ES预定义函数:
weight:当weight为2时,最终结果为2*_score;
field_value_factor:使用这个值来修改_score
random_score:为每个用户都使用一个不同的随机评分对结果排序,但对某一具体用户来说,看到的顺序始终是一致的。
衰减函数:linear、exp、gauss
将浮动值结合到_score中,例如结合publist_date
如果需求超出以上范围时,用自定义脚本可以完全控制评分计算。(script_score)
按受欢迎度提升权重
function_score可以与field_value_factor结合使用,例如:
GET /blogposts/post/_search
{
"query": {
"function_score": { (1)
"query": { (2)
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": { (3)
"field": "votes" (4)
}
}
}
}
1、function_score查询将主查询和函数包括在内。
2、主查询优先执行
3、field_value_factor函数会被应用到每个与主查询匹配的文档
4、每个文档的votes字段都必须有值供function_score计算。如果没有值就必须指定missing
新的评分:new_score = _score * votes
然而_score通常处于0到10之间,有votes为10的会掩盖掉全文评分,为0的评分会被置0
一种更好的方式是使用modifier,例如:
GET /blogposts/post/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p" (1)
}
}
}
}
此时新的评分:new_score = old_score * log(1 + number_of_votes)
modifier可以为:none (默认状态)、 log 、 log1p 、 log2p 、 ln 、 ln1p 、 ln2p 、 square 、 sqrt 以及 reciprocal
还可以使用factor参数
new_score = old_score * log(1 + factor * number_of_votes)
boost_mode可以控制函数与查询评分_score合并后的结果
接受的参数值:
multiply、sum、min、max、replace
参数max_boost可以限制一个函数的最大效果
GET /blogposts/post/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p",
"factor": 0.1
},
"boost_mode": "sum",
"max_boost": 1.5 (1)
}
}
}
无论field_value_factor 函数的结果如何,最大就是1.5
过滤集提升权重
过滤器可以将结果划分为多个子集(每个特性一个过滤器),可以为每个子集使用不同的函数。
GET /_search
{
"query": {
"function_score": {
"filter": { (1)
"term": { "city": "Barcelona" }
},
"functions": [ (2)
{
"filter": { "term": { "features": "wifi" }}, (3)
"weight": 1
},
{
"filter": { "term": { "features": "garden" }}, (3)
"weight": 1
},
{
"filter": { "term": { "features": "pool" }}, (3)
"weight": 2 (4)
}
],
"score_mode": "sum", (5)
}
}
}
1、function_score查询有个filter过滤器而不是query查询
2、functions关键字存储着一个将被应用的函数列表
3、函数会被应用于和filter过滤器匹配的文档
4、pool比其他特性更重要,所以它有更高weight
5、score_mode指定各个函数的值进行组合运算的方式。
随机评分
相同_score的文档每次都会以相同次序出现,此时需要引入一些随机性(只针对_score为整数这样的情况)。
random_score函数会输出一个0到1之间的数,当种子seed值相同时,生成的随机结果是一致的。
GET /_search
{
"query": {
"function_score": {
"filter": {
"term": { "city": "Barcelona" }
},
"functions": [
{
"filter": { "term": { "features": "wifi" }},
"weight": 1
},
{
"filter": { "term": { "features": "garden" }},
"weight": 1
},
{
"filter": { "term": { "features": "pool" }},
"weight": 2
},
{
"random_score": { (1)
"seed": "the users session id" (2)
}
}
],
"score_mode": "sum"
}
}
}
1、random_score没用任何过滤器filter,所以会被应用到所有文档
2、将用户的ID作为种子seed,会让该用户的随机始终保持一致。
越近越好
function_score衰减函数linear、exp、gauss(线性、指数和高斯函数),他们可以操作数值、时间以及经纬度地理坐标点这样的字段,能接受以下参数:
origin:中心点或字段可能的最佳值,与origin相同的文档_score为满分1.0
scale:衰减率,即一个文档从origin下落时,_score改变的速度
decay:从origin衰减到scale所得的评分_score,默认0.5
offset:以origin为中心点,为其设置一个非0的偏移量offset覆盖一个范围,而不只是单个原点。在范围内origin±offset内的所有_score都是1.0
样例:户希望租一个离伦敦市中心近( { "lat": 51.50, "lon": 0.12} )且每晚不超过 £100 英镑的度假屋
GET /_search
{
"query": {
"function_score": {
"functions": [
{
"gauss": {
"location": { (1)
"origin": { "lat": 51.5, "lon": 0.12 },
"offset": "2km",
"scale": "3km"
}
}
},
{
"gauss": {
"price": { (2)
"origin": "50", (3)
"offset": "50",
"scale": "20"
}
},
"weight": 2 (4)
}
]
}
}
}
脚本评分
function_score内置函数无法满足应用场景,可以使用script_score函数自行实现逻辑
ES5之后推荐脚本语言:painless
可插拔的相似度算法
较为高深……无法直视
更改相似度
……
调试相关度是最后10%要做的事情
……