【Elasticsearch】使用 Java API Client 實作 function score


https://unsplash.com/photos/_DyUcJalGAc

上一篇文章中,我們用 ElasticSearch 的 Java API Client 撰寫查詢條件。而在搜尋之餘,本文將示範如何透過這款 library 使用 function score,替 document 進行評分。

一、程式專案介紹

本文的範例專案將放置於 GitHub 上,repo 連結在此

(一)資料類別介紹

本文所示範的是將學生資料存放到 ES,因此筆者設計了一個學生類別(Student),其內部還包含課程(Course)與職務(Job)的資料。

(二)範例資料

由於會在 ES 上新增 document,因此準備了以下範例資料。

(三)Repository 層簡介

專案中實作了一個 Repository 層,名為 StudentEsRepository。其中包含了存取 ES 的邏輯,如下:

其中 find 方法是用來進行搜尋,且接收了一個 SearchInfo 物件。SearchInfo 是筆者自訂的類別,它包裝了搜尋時會用到的參數,其定義如下:

我們會將用來計算 document 分數的函數,存放在 functionScores 變數中。

(四)準備測試類別

在本文,筆者將透過撰寫測試的方式,來使用 Repository 元件發出搜尋請求。於是準備了以下測試類別:

由於 ES 預設是按照 document 分數來排序,因此筆者撰寫了名為 assertDocumentIds 的方法。它接受兩個參數,比對搜尋到的資料與順序是否如預期。

二、Function Score API 的使用方式

讓我們回顧一下在搜尋時使用 function score 的 DSL 寫法,以下是一個範例的搜尋請求。

{
    "query": {
        "function_score": {
            "query": {
                "match_all": {}
            },
            "functions": [],            
            "score_mode": "sum",
            "boost_mode": "replace",
            "max_boost": 30.0
        }
    }
}

關於各種 function score 以及相關參數的定義,筆者在此不贅述,讀者可參考「【ElasticSearch 8】使用 function score 在查詢時評分」文章。

我們知道在程式中發出搜尋請求時,會建立一個 SearchRequest 物件。若想使用 function score,就會在 SearchRequest 所接收的 Query 物件中添加相關的參數。

SearchInfo 中有代表查詢條件的 boolQuery 變數,以及代表函數的 functionScores 變數。現在筆者改寫其中的 toQuery 方法,調整產生 Query 物件的方式。

在搜尋未使用 function score 時,採用原本的 boolQuery,再轉為 Query 物件即可。

若使用了 function score,則會建立出 FunctionScoreQuery 物件,再轉為 Query 物件。過程中除了傳入原本的查詢條件,也使用 functions 方法傳入函數。

至於其餘的 score_mode、boost_mode 與 max_boost 參數,則可很直覺地透過對應的方法來提供。

接下來的小節,筆者將在 SearchUtils 類別示範如何建立出函數物件。要注意的是,library 使用「FunctionScore」這個類別當作函數的傳遞介面,因此實作出的函數都會轉換為 FunctionScore,才能實際使用。


三、Field Value Factor

需建立 FieldValueFactorScoreFunction 物件。相關的參數都可透過它的 builder 所提供的方法添加上去。

以下範例的計分方式,是先將年級乘 0.5,再做平方。若年級欄位沒有值,則預設為 0。寫成測試如下:

四、Weight 與 Filter

我們可直接建立 FunctionScore 物件,並在過程中透過 builder 提供的 filter 與 weight 方法,給予對應的參數。

其中 filter 方法接受一個 Query 物件,代表 document 需符合該條件,才能得到 weight 的分數。

若想將上一節介紹的 FieldValueFactor 的函數計算結果,也給予一個權重,則可將其傳入到 FunctionScore 中。

以下範例的計分方式,是就讀財務金融系者得 3 分,修習程式設計課程者得 1.5 分,每升一個年級得 0.5 分。寫成測試如下:

對於科系與課程,準備了代表條件的 TermQuery。而年級則採用 FieldValueFactor 計算,但實作方式稍微不同。此處是從 FunctionScore 中取出 FieldValueFactorScoreFunction 物件,用來建立出另一個函數。


五、衰減函數(Decay Function)

(一)用於數值欄位

需建立出 DecayPlacement 物件,用以包裝衰減函數相關的參數,包含 origin、offset、scale 與 decay。

我們知道衰減函數分為 linear、exp 與 gauss 三種。此處筆者再撰寫一方法,用 DecayPlacement 建立出 gauss 函數。

在此建立出 DecayFunction 物件,過程中添加了相關參數與 document 的欄位名稱。

以下範例是將 gauss 衰減函數用於操行分數的欄位。在設計上,我們假設 85 到 100 分為最優。而 75 分者,document 分數為 0.5。更低分者,分數變化率開始緩和。寫成測試如下:

(二)用於日期欄位

對於日期欄位,衰減函數的參數可用時間的毫秒數(以數值表示)或時間單位(以字串表示)。若選擇時間單位,前提是在 mapping 中有將該欄位的 type 設為「date」才行。

筆者撰寫另一個建立 DecayPlacement 的方法,它接受字串參數,藉此用「表示式」(expression)的方式傳入時間參數。

以下範例是將 gauss 衰減函數用於英文檢定通過日的欄位。在設計上,我們假設 90 天內通過者為最優。而 360 天前通過者,document 分數為 0.5。更早通過者,分數變化率開始緩和。寫成測試如下:

上一篇:【ElasticSearch 8】使用 Java API Client 實作查詢條件與搜尋

留言