本文介紹了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語言。
常用情境的排序策略配置
比如想根據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)比如,xxx公司,xxx杭州分公司,那麼“xxx公司”要排在“xxx杭州分公司”的前面。
實現:
#可以在業務排序(精排運算式)是裡配置field_match_ratio函數 field_match_ratio(title)比如搜尋 all:'dim_itm_tb',那麼“dim_itm_tb”想在“dim_itm_tb_dst_itm_relation_dd”前面。
實現:
#可以在業務排序(精排運算式)是裡配置field_match_ratio函數: field_match_ratio(detail)如何?query = item:"iphone 8" OR item: 'iphone 8' 類似這種查詢呢
實現:
#可以在業務排序(精排運算式)是裡配置query_min_slide_window函數: query_min_slide_window(title)我設定了一個精排 text_relevance,搜尋關鍵詞是 "民國",但是不知道為什麼 "民國趣聞-民國", "中國民族史-民國" 等要比 "民國" 排序要靠前。(需要讓“民國”排在最前面)
實現:
#可以在業務排序(精排運算式)是裡配置query_min_slide_window函數: query_min_slide_window(title)搜尋索引鍵在欄位內容重複出現,會導致static_bm25()函數重複算分,如果規避這種情況?
實現:
#在業務排序裡配置query_match_ratio query_match_ratio(title)如何把搜尋存在關鍵詞堆積的文檔給排到後面去?
實現:
#使用query_term_match_count,定義重複多少次為結果堆積。 if(field_term_match_count(title)>3,1,10)如何配置字串不為空白時增加一定的分數?
實現:
可以先在源庫中增加標記欄位(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
建立一個基礎排序和業務排序,名稱分別為test_first_rank_name和test_second_rank_name
並且在test_first_rank_name中配置static_bm25函數,在test_second_rank_name中配置text_relevance函數。
建立一個查詢分析,名稱為test_qp,並且Query改寫原則設定為OR:
把“1”中的測試資料上傳至OpenSearch應用中。
案例分析
案例1
需求:query=title:'黑色幽默周杰倫' ,搜尋出最相關的文檔。
分析:使用者的需求是搜尋出周杰倫的《黑色幽默》,可以看到測試資料中id=4的name內容裡是不相關的,但是term分詞後又都匹配,該文檔會被召回,所以在排序階段需要將此不相關的文檔排到後面去。
操作步驟:完成工作準備中的“2”即可實現。
結果展示:
案例2
需求:query=title:'黑色幽默周杰倫' ,搜尋出最相關的文檔,如果文檔中沒有“周杰倫”相關的率先將“黑色幽默”相關的召回,之後再召回“周杰倫”相關的文檔。
分析:使用者的需求是搜尋出周杰倫的《黑色幽默》,可以看到測試資料中id=4的name內容裡是不相關的,但是term分詞後又都匹配,該文檔會被召回,所以在排序階段需要將此不相關的文檔排到後面去。
操作步驟:完成工作準備中的“2”和“3”即可實現。
結果展示: