- bm25模型
- 查询时权重提升
- 修改查询结构
- 修改评分
- constant_score查询
- 函数评分 function_score
- 脚本评分 script_score
- 更改相似度模型
- 多字段排序
前面介绍了es的分词原理,分词决定了如何找到匹配的文档,而在全文搜索引擎里面,结果的展示相当重要,不仅要找到结果,还要对结果进行适当的排序.
es通过相似度对文档进行打分,默认情况下按照评分高低排序,不过如果使用term精确查询的话就不会计算相似度.
BM25算法
es在5.0之后默认采用的相似度算法是BM25,而需要注意的是官方中文文档是基于2.x版本写的,当时使用的是tf/idf算法.
bm25公式如下
公式的详细介绍可以参考lucene官网
https://lucene.apache.org/core/8_0_0/core/org/apache/lucene/search/similarities/BM25Similarity.html
不过这里并不打算深入分析这条公式,我们可以通过几个简单的概念学会如何利用它
f(qi,D)表示的是一个词对一个文档的相关性的权重,es使用的公式是idf.
在idf中有三个概念
- 词频: tf(term frequency),该词在文档中出现的次数越多该值越高
- 逆向文档频率: idf(inverse document frequency),该词在索引中所有文档出现的次数越多该值越低,可表示关键词的特异程度
- 字段长度归一化(field-length norm): 字段长度越短,字段权重越高,表示关键词在字段中的占比
也就是tf越高,idf和字段长度归一化越低,文档的评分越高
在tf/idf中并没有对词频上限作出限制,因此当关键词在某个字段大量出现时文档的得分就会非常高,但是这些得分几乎都来自这个字段,相当于这个字段的权重被提高了,其他字段的影响被削弱了.
所以bm25引入了k1和b两个可调节参数来惩罚上述的情况. k1控制着词频结果在词频饱和度中的上升速度.默认值为1.2,值越小饱和度变化越快.
b控制着字段长度归一化的作用.0表示禁用归一化,1表示完全归一化,默认值是0.75
下面可以看到在词频上升过程中两种模型的曲线变化
调整排序
下面介绍一些调整文档最终排序的方法
查询时权重提升
当进行多字段查询时,可以用boost独立控制每个字段的权重.要注意的是权重的比例不直接等于最终评分的比例.
GET /_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": {
"query": "quick brown fox",
"boost": 2
}
}
},
{
"match": {
"content": "quick brown fox"
}
}
]
}
}
}
修改查询结构
es的几种类型的查询是可以自由组合的
GET /_search
{
"query": {
"bool": {
"should": [
{ "term": { "text": "quick" }},
{ "term": { "text": "brown" }},
{ "term": { "text": "red" }},
{ "term": { "text": "fox" }}
]
}
}
}
GET /_search
{
"query": {
"bool": {
"should": [
{ "term": { "text": "quick" }},
{ "term": { "text": "fox" }},
{
"bool": {
"should": [
{ "term": { "text": "brown" }},
{ "term": { "text": "red" }}
]
}
}
]
}
}
}
constant_score查询
有时候我们想使用分词,但又不希望进行复杂的评分,就可以通过constant_score来禁用bm25,这样每个命中字段的得分都是1
GET /_search
{
"query": {
"bool": {
"should": [
{ "constant_score": {
"query": { "match": { "description": "wifi" }}
}},
{ "constant_score": {
"query": { "match": { "description": "garden" }}
}},
{ "constant_score": {
"query": { "match": { "description": "pool" }}
}}
]
}
}
}
上面是官网中文文档的例子,不过在6.x版本之后,constant_score里面不能使用query查询,只能使用filter,但由于filter本身恰好是不打分的,所以constant_score现在很少会使用了.
GET /_search
{
"query": {
"constant_score": {
"filter": {
"term": { "user.id": "kimchy" }
},
"boost": 1.2
}
}
}
function_score查询
如果想使用自定义的公式替代bm25,则可以使用function_score.
function_score允许为每个主查询的评分额外应用一个函数,可以修改或者完全替换原来的公式.
内置的函数包括
- weight : 为每个文档应用一个简单而不被规范化的权重提升值:当
weight
为2
时,最终结果为2 * _score
。 - field_value_factor :使用这个值来修改
_score
,如将popularity
或votes
(受欢迎或赞)作为考虑因素。最终结果为 field_value_factor * _score - random_score :为每个用户都使用一个不同的随机评分对结果排序,但对某一具体用户来说,看到的顺序始终是一致的。
- script_score : 自定义脚本,最终武器,脚本里可以使用tf,idf等变量
GET /blogposts/post/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes"
}
}
}
}
上面的语句表示查询在title和content里搜索和’popularity’相关的内容,并且votes越高评分越高,最终评分为_socore * votes.
上面的查询还可以改进下,如果点赞数太高,可能相关度低的文章也会排名靠前,可以对点赞数取log,此外votes有可能为空值,得加下默认值
GET /_search
{
"query": {
"function_score": {
"field_value_factor": {
"field": [ "title", "content" ],
"factor": 1.2,
"modifier": "log1p",
"missing": 1
}
}
}
}
function_score默认是与原始评分乘算,可以用boost_mode来改写成加算
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"
}
}
}
script_score查询
GET /_search
{
"query": {
"function_score": {
"query": {
"match": { "message": "elasticsearch" }
},
"script_score": {
"script": {
"source": "Math.log(2 + doc['my-int'].value)"
}
}
}
}
}
source里面是自定义脚本,可以用java或者groovy语句
一个简单的利用tf和idf的脚本如下
{
"script": {
"source": "double tf = Math.sqrt(doc.freq); double idf = 1.0; double norm = 1/Math.sqrt(doc.length); return query.boost * tf * idf * norm;"
}
}
脚本里可以利用的上下文变量可参考官网https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-similarity-context.html
更改相似度模型
可以在索引设置里自定义相似度算法,内置的算法有BM25,DFR,DFI,IB等
PUT /my_index
{
"settings": {
"similarity": {
"my_bm25": {
"type": "BM25",
"b": 0
}
}
},
"mappings": {
"doc": {
"properties": {
"title": {
"type": "string",
"similarity": "my_bm25"
},
"body": {
"type": "string",
"similarity": "BM25"
}
}
}
}
}
上面自定义了一个bm25算法,b=0表示禁用了归一化
多字段排序
es默认使用_socre排序,有时候搜索结果可能有很多文档得分相同,需要加些规则让它们在这种情况下也能排序固定
{
"sort": [
{
"_score": {
"order": "desc"
}
},
{
"time": {
"order": "desc"
}
},
{
"_id": {
"order": "desc"
}
}
]
}