用户对于搜索引擎最关注的两方面一是召回,即满足条件的doc全部可以被召回;二是排序,即在满足条件的文档中将相关度最高的文档优先召回。其中,二往往是需要根据用户的实际业务需求进行调整,因此就需要用户对OpenSearch-行业算法版在排序方面提供的能力有一定的了解,本文将详细介绍OpenSearch-行业算法版在排序方面的能力,并且列举一些常见场景如何通过OpenSearch的排序能力实现。
sort子句与排序策略的关系
简单来说sort子句在OpenSearch-行业算法版中代表全局排序,而排序策略可以理解为sort子句中的一个层级的排序,排序策略是通过系统内置的函数结合表达式形成一种复杂的文档算分逻辑来实现用户复杂的业务场景,但最终参与排序的还是排序策略中表达式算出的最终得分。举个例子,某个业务希望通过文档的新旧程度进行排序,同时新旧程度相同的文档,可以根据文档的相似程度进行二级排序,此时实现用户上述需求,就需要通过sort子句同时结合排序策略,假设用户业务表中有一个字段为create_time,同时检索的字段为name,则可以在sort子句中加入:
sort=-create_time;-RANK
同时在基础排序设置static_bm25() 函数,在业务排序中设置text_relevance(name)函数即可。(排序策略的配置步骤可排序策略配置进行参考)。
这里的RANK就表示获取排序策略的得分,而“-” 表示倒序,“+”表示正序。
系统默认在不配置sort子句的情况下用-RANK作为排序条件,而如果配置了sort子句,在需要排序策略分排序的时候需要显示的写入-RANK,否则系统将不会自动引入排序策略分作为排序条件。
为了更形象的表达sort子句和排序策略的关系,这里以一个案例的方式说明:
假设OpenSearch有一个应用,应用结构为:
字段 | 类型 | 索引 |
id | int | 关键字 |
name | text | 中文通用 |
age | int | 关键字 |
为name设置了一个基础排序,表达式内容如下:
为name设置了一个业务排序,表达式内容如下:
同时在检索时,配置sort子句为:
sort=age;-RANK
打开排序明细查看算分详情:
首先可以看到排序分为13,10000.2259030193,因为sort子句设置的是age;-RANK,因此13表示文档字段age的值,而10000.2259030193表示排序策略的最终得分,OpenSearch会先根据age的值进行正序排序,age值相同的文档再根据排序策略的最终得分倒序排序。
再看排序公式:
FirstRank:
expression[static_bm25()], result[0.496452].
SecondRank:
expression[text_relevance(name)], result[0.225903].
FirstRank表示基础排序得分,SecondRank表示业务排序得分,最终的排序分为10000.2259030193,为什么最终排序策略得分为10000.2259030193而不是[0.496452+0.225903] 在下一节会详细说明。
由上述现象,可以得出结论:在OpenSearch中sort子句类似于SQL中order by的功能,可以直接通过文档中的属性字段进行排序,也支持复杂的排序策略进行算分,而排序策略又有内置的函数支持和算分规则,最终根据sort子句的“+”,“-”,以及排序字段控制文档排序方式以及文档得分。
排序策略说明
排序策略打分原理
对于排序策略的算分分为两个阶段:基础排序和业务排序,通过query召回并通过filter过滤后的文档,首先进入基础排序,根据基础排序表达式海选出文档得分较高的文档,然后取出TOP N个结果再按照业务排序表达式进行精细算分,最终返回排序策略的最终得分。算分规则如下:
若只配置了基础排序,则文档得分为(10000+基础排序表达式计算的结果),总分最大为20000,超过20000结果仍为20000。
若只配置了业务排序,则文档得分为(10000+业务排序表达式计算的结果),总分无上限。
若同时配置了基础排序和业务排序,那么进入业务排序的文档最终得分为(10000+业务排序表达式计算的结果),其余文档最终得分为(10000+基础排序表达式计算的结果,总分最大为20000,超过20000结果仍为20000)。
再结合上一节排序算分为:
FirstRank:
expression[static_bm25()], result[0.496452].
SecondRank:
expression[text_relevance(name)], result[0.225903].
最终得分为10000.2259030193。
通过上述原理,可理解为该命中的doc,在基础排序阶段在召回100W的doc中,参与了基础排序,通过static_bm25函数算分为0.496452,同时该文档的基础排序分正好使该文档排在了所有命中文档的前200内,参与了业务排序,在文档从基础排序进入业务排序时文档分会默认加10000分,同时舍弃基础排序的得分,以业务排序得分+10000分为最终的排序策略得分,由于该文档在业务排序中通过text_relevance函数算分为0.225903,所以该文档的最终排序策略得分为10000.2259030193。
业务排序函数用法
注意:以下内置函数中引用到的应用结构中的字段均需要设置为属性字段,否则会报错Invalid formula。
函数 | 描述 | 案例 |
i in (value1, value2, …, valuen) | 如果i的值在集合[value1, value2, …,valuen]中出现,则该表达式值为1,否则为0。 | 字段age=5 age in (1,2,3,4,5) # 结果为1 age in (6,7,8,9) # 结果为0 |
if(cond, then_value, else_value) | 如果cond的值非0,则该if表达式的实际值为then_value,否则为else_value。 如if( 2,3, 5)的值为3,if( 0,3,5)的值为5。 | 字段a=1 if(a==1,5,10) #结果为5 if(1,5,10) #结果为5 if(a==2,5,10) #结果为10 if(0,5,10) #结果为10 |
random() | 返回[0,1]间的一个随机值。 | - |
now() | 返回当前时间,自Epoch ( 00:00:00 UTC,January 1,1970)开始计算,单位是秒。 | - |
若排序策略的表达式仍无法满足复杂场景的算分逻辑,可以通过cava插件,编写脚本进行复杂场景的算分,关于cava插件的使用以及原理此处不再赘述,有兴趣的用户可以参考排序插件开发-Cava语言
常用场景的排序策略配置
1. 比如想根据age>10 的 +10分,age>40的+20分,根据weight>60的+30,最后根据最后得分排序。
实现1:
#业务排序表达式设置为:默认是匹配到只加1分
(age>10)*10+(age>40)*20+(weight>60)*30
实现2:
#精排表达式设置为:
if(age>10,10,0) + if(age>40,20,0) +if(weight>60,30,0)
2. 比如,xxx公司,xxx杭州分公司,那么“xxx公司”要排在“xxx杭州分公司”的前面。
实现:
#可以在业务排序(精排表达式)是里配置field_match_ratioc函数
field_match_ratio(title)
3. 比如搜索 all:'dim_itm_tb',那么“dim_itm_tb”想在“dim_itm_tb_dst_itm_relation_dd”前面。
实现:
#可以在业务排序(精排表达式)是里配置field_match_ratioc函数:
field_match_ratio(detail)
4. 如何实现query = item:"iphone 8" OR item: 'iphone 8' 类似这种查询呢
实现:
#可以在业务排序(精排表达式)是里配置query_min_slide_window函数:
query_min_slide_window(title)
5. 我设置了一个精排 text_relevance,搜索关键词是 "民国",但是不知道为什么 "民国趣闻-民国", "中国民族史-民国" 等要比 "民国" 排序要考前。(需要让“民国”排在最前面)
实现:
#可以在业务排序(精排表达式)是里配置query_min_slide_window函数:
query_min_slide_window(title)
6. 搜索关键字在字段内容重复出现,会导致static_bm25()函数重复算分,如果规避这种情况?
实现:
#在业务排序里配置query_match_ratio
query_match_ratio(title)
7. 如何把搜索存在关键词堆积的文档给排到后面去?
实现:
#使用query_term_match_count,定义重复多少次为结果堆积。
if(field_term_match_count(title)>3,1,10)
8.如何配置字符串不为空时增加一定的分数?
实现:
可以先在源库中增加标记字段(mark),如过被判断字段为空就标记成0,不为空就标记成1。然后在精排表达式中使用if函数进行判断。
精排表达式设置为:当mark=1时排序分加500
if(mark==1,500,0)
案例分享
函数介绍
在排序阶段,我们可以通过在基础排序阶段(粗排)设置static_bm25函数和在业务排序阶段(精排)设置text_relevance函数来获取文本的相关性得分。相关介绍:
static_bm25:静态文本相关性,用户衡量query与文本的匹配度,值域为[0,1];
text_relevance:关键词在字段上的文本匹配度,值域为[0,1]
准备工作
为了方便展示文本相关性得分对排序的影响,这里准备以下几条数据,id表示主键,name表示文本内容:
id name
1 黑色幽默,又称为“黑色喜剧”,是产生于1960年代美国的一个现代主义文学流派
2 《黑色幽默》是周杰伦演唱的一首歌曲
3 周杰伦,台湾华语流行歌曲男歌手、音乐家、编曲家、唱片制片人、魔术师。
4 黑夜将至,周围一切都是黑色的,为了缓解压抑的气氛,周杰伦很幽默的说了一个笑话
5 黑色幽默女版(原唱:周杰伦Jay Chou)
6 Jay Chou 周杰伦【黑色幽默 Black Humor】-Official Music Video
2. 创建一个基础排序和业务排序,名称分别为test_first_rank_name和test_second_rank_name
并且在test_first_rank_name中配置static_bm25函数,在test_second_rank_name中配置text_relevance函数。
3. 创建一个查询分析,名称为test_qp,并且Query改写策略设置为OR:
4. 把“1”中的测试数据上传至opensearch应用中。
案例分析
案例1
需求:query=title:'黑色幽默周杰伦' ,搜索出最相关的文档。
分析:用户的需求是搜索出周杰伦的《黑色幽默》,可以看到测试数据中id=4的name内容里是不相关的,但是term分词后又都匹配,该文档会被召回,所以在排序阶段需要将此不相关的文档排到后面去。
操作步骤:完成工作准备中的“2”即可实现。
结果展示:
案例2
需求:query=title:'黑色幽默周杰伦' ,搜索出最相关的文档,如果文档中没有“周杰伦”相关的率先将“黑色幽默”相关的召回,之后在召回“周杰伦”相关的文档。
分析:用户的需求是搜索出周杰伦的《黑色幽默》,可以看到测试数据中id=4的name内容里是不相关的,但是term分词后又都匹配,该文档会被召回,所以在排序阶段需要将此不相关的文档排到后面去。
操作步骤:完成工作准备中的“2”和“3”即可实现。
结果展示: