總是搜不到想要的內容?Elasticsearch搜索排名優化了解一下

導語 | Elasticsearch(下文簡稱ES) 是當前熱門的開源全文搜索引擎,利用它我們可以方便快捷搭建出搜索平臺,但通用的配置還需要根據平臺內容的具體情況做進一步優化,才能產生令用戶滿意的搜索結果。下文將介紹對 ES 搜索排名的優化實踐,希望與大家一同交流。文章作者:曹毅,騰訊應用開發工程師。

一、引言

雖然使用 ES 可以非常方便快速地搭建出搜索平臺,但搜出來的結果往往不符合預期。因爲 ES 是一個通用的全文搜索引擎,它無法理解被搜索的內容,通用的配置也無法適合所有內容的搜索。所以 ES 在搜索中的應用需要針對具體的平臺做很多的優化纔可以達到良好的效果。

ES 搜索結果排序是通過 query 關鍵字與文檔內容計算相關性評分來實現的。想掌握相關性評分並不容易。首先 ES 對中文並不是很友好,需要安裝插件與做一些預處理,其次影響相關性評分的因素比較多,這些因素可控性高,靈活性高。

下文將爲大家介紹 ES 搜索排名優化上的實踐經驗,本篇文章示例索引數據來自一份報告文檔,如下圖所示:

 

 

 

 

 

 

 

二、優化 ES Query DSL

構建完搜索平臺後,我們首先要進行 ES Query DSL 的優化。針對 ES 的優化,關鍵點就在於優化 Query DSL,只要 DSL 使用恰當,搜索的最終效果就會很好。

1. 最初使用的 multi_match

當我們構建好索引同步好數據以後,想比較快實現全文索引的方式就是使用 multi_match 來查詢,例如:

這樣使用非常方便和快速,並且實現了全文搜索的需求。但是搜索的結果可能不太符合預期。但是不用擔心,我們可以繼續往下優化。 

2. 使用 bool 查詢的 filter 增加篩選

在應用中,我們應該避免直接讓用戶針對所有內容進行查詢,這樣會返回大量的命中結果,如果結果的排序稍微有一點出入,用戶將無法獲取到更精準的內容。

針對這種情況,我們可以給內容增加一些標籤、分類等篩選項提供給用戶做選擇,以達到更好的結果排名。這樣搜索時被 ES 引擎評分的目標結果將會變少,評分的抖動影響會更小。

實現這個功能就使用到 bool 查詢的過濾器。bool 查詢中提供了4個語句,must / filter / should / must_not,其中 filter / must_not 屬於過濾器,must / should 屬於查詢器。關於過濾器,你需要知道以下兩點:

  • 過濾器並不計算相關性評分,因爲被過濾掉的內容不會影響返回內容的排序;

  • 過濾器可以使用 ES 內部的緩存,所以過濾器可以提高查詢速度。

這裏需要注意:雖然 must 查詢像是一種正向過濾器,但是它所查詢的結果將會返回並會和其他的查詢一起計算相關性評分,因此無法使用緩存,與過濾器並不一樣。

一般一個文檔擁有多個可以被篩選的屬性,例如 id、時間、標籤、分類等。爲了搜索的質量我們應該認真地對文檔進行打標籤和分類處理,因爲一旦選擇了過濾,即使用戶的搜索關鍵詞再匹配文檔也不會被返回了。示例 Query DSL 如下:

上面的示例中,存在一個小技巧,即使用標籤的 id 來進行篩選。因爲 tags 字段是text 類型的,term 查詢是精確匹配,不要將其應用到 text 類型的字段上,如果text字段要被過濾器使用,在 mappings 中應該要使用 string 類型(它將字段映射到兩個類型上,text 和 keyword )或者 keyword 類型。

3. 使用 match_phrase 提高搜索短語的權重

在這個階段,搜索的時候經常會出現搜索結果和搜索關鍵詞不是連續匹配的情況。例如搜索關鍵詞爲:“2020年微信用戶研究報告”,而返回的結果大多數是匹配“微信”、“用戶”、“研究”、“報告”這些零散的關鍵詞,而用戶想要匹配整個短語的結果卻在後面。

這並不是 ES 的 bug,在瞭解這種行爲之前,我們需要先弄清楚 ES 是如何處理match 的?

首先 multi_match 會把多個字段的匹配轉換成多個 match 查詢組合,挨個對字段進行 match 查詢。match 執行查詢時,先把查詢關鍵詞經過 search_analyzer 設置的分析器分析,再把分析器得到的結果挨個放進 bool 查詢中的 should 語句,這些 should 沒有權重與順序的差別,並且只要命中一個should 語句的文檔都會被返回。轉換語句如下圖所示,前面是原語句,後面是轉換後的語句:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

這樣就導致了有的文檔只擁有查詢短語中若干個詞,但評分卻比可以匹配整個短語的文檔高的情況。那我們如何考慮詞的順序呢?先別急,我們再來看看 ES 中的倒排索引。

我們都知道倒排索引中記錄了一個詞到包含詞文檔的 ID,但倒排索引當然不會這麼簡單。倒排列表中記錄了單詞對應的文檔集合,由倒排索引項組成。倒排索引項中主要包含如下信息:

  • 文檔ID:用於獲取文檔;

  • 單詞詞頻(TF):用於相關性計算(TF-IDF,BM25);

  • 位置:記錄單詞在文檔中的分詞位置,會有多個,用於短語查詢;

  • 偏移:記錄在文檔中的開始位置與結束位置,用於高亮。

這下我們就很清楚了,ES 專門記錄了詞語的位置信息用於查詢,在DSL中是使用 match_phrase 查詢。match_phrase 要求必須命中所有分詞,並且返回的文檔命中的詞也要按照查詢短語的順序,詞的間距可以使用 slop 設置。

match_phrase 雖然幫我們解決了順序的問題,但是它要求比較苛刻,需要命中所有分詞。如果單獨使用它來進行搜索,會發現搜索出來的結果相比 match 會大大減少,這是因爲匹配若干個詞的文檔和匹配順序不對的文檔都沒被返回。

這時候可以採用 bool 查詢的 should 語句,同時使用 match 與 match_phrase 查詢語句,這樣相當於 match_pharse 提高了搜索短語順序的權重,使得能夠順序匹配到的文檔相關性評分更高。如下是示例DSL:

這裏有一點需要注意,在倒排索引項中 text  類型的數組裏,每個元素記錄的位置是連續的。例如某文檔數據中的 tags:["騰訊CDC","京東研究院”],“CDC” 與“京東”的位置是連續的,如果搜索 “CDC京東”,那此文檔的評分將會比較高。

這種情況是不應該發生的,我們應該在設置索引mappings時,給 tags 字段設置上 position_increment_gap ,來增加數組元素之間的位置,此位置要超過查詢所使用的 slop,例如:

4. 使用 boost 調整查詢語句的權重

前文提到的搜索實現,有一個顯而易見的問題:所有字段都無權重之分。根據常識我們知道,title 的權重應該高於其他字段,顯然不能和其他字段是一樣的得分。

查詢時可以用 boost 配置來增加權重,不過這裏設置的對象並不是某個字段,而是查詢語句。設置後,查詢語句的得分等於默認得分乘以 boost。

設置 boost 有幾個需要注意的地方:

  • 數據質量高的字段可以相應提高權重;

  • match_phrase 語句的權重應該高於相應字段 match 查詢的權重,因爲文檔中按順序匹配的短語可能數量不會太多,但是查詢關鍵詞被分詞後的詞語將會很多,match的得分將會比較高,則 match 的得分將會沖淡 match_phrase 的影響;

  • 在 mappings 設置中,可以針對字段設置權重,查詢時不用再針對字段使用 boost 設置。

具體示例 DSL 如下:

5. 使用 function_score 增加更多的評分因素

影響文檔評分的還有一些因素,例如我可能會經常考慮以下問題:

  • 時間越近的文檔信息比較及時,對用戶更有用,應該排在前面;

  • 平臺中熱門的文檔,可能用戶比較喜歡,應該比其他文檔好;

  • 文檔質量比較高的,更希望讓用戶看到,那些缺失標籤與摘要的文檔並不希望用戶總是看到;

  • 運營人員有時候想讓用戶搜到正在推廣的文檔;

  • ……

我們可以通過增加更多的影響報告評分的因素來實現以上場景,這些因素包括:時間、熱度、質量評分、運營權重等。

這些因素有一個特點,就是在數據搭建階段我們就能確定其權重,並且和查詢關鍵詞沒有什麼關係。這些文檔本身就具有的權重屬性我們可以認爲是靜態評分,需要和查詢關鍵詞來計算出的相關性評分稱爲動態評分,所以一個文檔的最終評分應該是動態評分與靜態評分的結合。

靜態評分相關的屬性不應該隨便設置。爲了給用戶一個更好的體驗,靜態評分的影響應該具有:

  • 穩定性:不要經常有大幅度的變動,如果大幅度變化會導致用戶搜索相同的關鍵詞過段時間出來的結果會不同;

  • 連續性:方便我們其他的優化也能影響總評分,例如對於熱度 0.1 與 1000 的文檔,即使用戶搜索 0.1 熱度文檔的匹配度爲 1000 熱度文檔的 100 倍,但結果排名依然比不過 1000 熱度的文檔;

  • 區分度:在連續穩定的情況下,應該有一定的區分度,也即分值的間隔應該合理。如果有 1000 份文檔,在 1.0 分到 1.001 分之間,這其實是沒有實際意義的,因爲對文檔排名的影響太少了。

新增加的這些因素並沒有太通用的查詢語句,不過 ES 提供了 function_score 來讓我們自定義評分計算公式,也提供了多種類型方便我們快速應用。function_score 提供了五種類型

  • script_score,這是最靈活的方式,可以自定義算法;

  • weight,乘以一個權重數值;

  • random_score,隨機分數;

  • field_value_factor,使用某個字段來影響總分數;

  • decay fucntion,包括gauss、exp、linear三種衰減函數。

因爲類型比較多,下面只介紹使用較多的 filed_value_factor 與 decay function 的實際案例。

(1)filed_value_factor

熱度、推薦權重等對評分的影響可以按權重相乘,剛好適合 filed_value_factor 這種類型的函數,實現如下:

上面這段 DSL 的含義是:sqrt (1.2 * doc['likes'].value),如果缺失了此字段則爲1。missing 這裏的設置有個小技巧,比如假定缺失摘要的文檔爲質量低的文檔,可以適當降低權重,那我們可以把 missing 設置爲小於1的數值,factor 填 1 即可。

(2)decay function

衰減函數是一個非常有用的函數,它可以幫我們實現平滑過渡,使距離某個點越近的文檔分數越高,越遠的分數越低。使用衰減函數很容易實現時間越近的文檔得分就越高的場景。

ES提供了三個衰減函數,我們先來看一下這三種衰減函數的差別,截取官方文檔展示圖如下:

  • linear,是兩條線性函數,從直線和橫軸相交處以外,評分都爲0;

  • exp,是指數函數,先劇烈的衰減,然後緩慢衰減;

  • guass,高斯衰減是最常用的,先緩慢再劇烈再緩慢,scale相交的點附近衰減比較劇烈。

當我們想選取一定範圍內的結果,或者一定範圍內的結果比較重要時,例如某個時間、地域(圓形)、價格範圍內,都可以使用高斯衰減函數。高斯衰減函數有4個參數可以設置

  • origin:中心點,或字段可能的最佳值,落在原點 origin 上的文檔評分 _score 爲滿分 1.0 ;

  • scale:衰減率,即一個文檔從原點 origin 下落時,評分 _score 改變的速度;

  • decay:從原點 origin 衰減到 scale 所得的評分 _score ,默認值爲 0.5 ;

  • offset:以原點 origin 爲中心點,爲其設置一個非零的偏移量 offset 覆蓋一個範圍,而不只是單個原點。在範圍 -offset <= origin <= +offset 內的所有評分 _score 都是 1.0 。

假定搜索引擎中三年內的文檔會比較重要,三年之前的信息價值降低,就可以選擇 origin 爲今天,scale 爲三年,decay 爲 0.5,offset 爲三個月,DSL 如下:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

6. 最終結果

到這裏我們的 Query DSL 已經優化的差不多了,現在的搜索結果終於可以讓人滿意了。我們來看一下最終的DSL語句示例:(示例並非實際運行的代碼)

 

三、優化相關性算法

 

上文我們討論了相關性評分應該由動態評分和靜態評分結合得出,靜態評分我們已經有了優化的方法,下文我們再來討論一下動態評分的計算。

所謂動態評分,就是用戶每次查詢都要計算用戶查詢關鍵詞與文檔的相關性,更細一點來說,就是實時計算全文搜索字段的相關性。

ES 提供了一種非常好的方式,實現了可插拔式的配置,允許我們控制每個字段的相關性算法。在 Mappings 設置階段,我們可以調整 similarity 的參數並給不同的字段設置不同的 similarity 來達到調整相關性算法的目的。ES 提供了幾種可用的 similarity,我們接下來主要討論 BM 25。

ES 默認的相關性算法就是 BM25,它是一個基於概率模型的詞與文檔的相關性算法,可以看做是基於向量空間模型的 TF-IDF 算法的升級。ES 在7.0.0版本已經廢棄了 TF-IDF 算法,完全使用 BM25 替換,BM25 與 TF-IDF 算法的對比和細節本文不描述。

先來看看 wikipedia 上 BM25 的公式:

一眼看上去有這麼多變量,是不是覺得難以理解?不過不用擔心,我們需要調整的參數其實只有兩個。這些變量裏除了 k1 與 b ,其餘都是可以直接從文檔中算出的,所以在 ES 中 BM25 公式其實就是靠調整這兩個參數來影響評分。

k1 這個參數控制着詞頻結果在詞頻飽和度中的上升速度。默認值爲 1.2 。值越小飽和度變化越快,值越大飽和度變化越慢。詞頻飽和度可以參看下面官方文檔的截圖,圖中反應了詞頻對應的得分曲線,k1 控制 tf of BM25 這條曲線。

b 這個參數控制着字段長歸一值所起的作用, 0.0 會禁用歸一化, 1.0 會啓用完全歸一化。默認值爲 0.75 。

在優化 BM25 的 k1 和 b 時,我們要根據搜索內容的特點入手,仔細分析檢索的需求。

例如在示例的索引數據中 content 字段的質量參差不齊,甚至有些文檔可能會缺失此字段,但此文檔對應的真實數據(可能是某文件、某視頻等)質量很高,因此放入 ES 中 content 字段的長度並不能反映文檔真實的情況,更不希望 content 短的文檔被突出,所以我們要弱化文檔長度對評分的影響。

根據 k1 和 b 的描述,我們將 BM25 模型中的 b 值從默認的 0.75 降低,具體降低到多少才合適,還需要進一步的嘗試。這裏我以調整到 0.2 爲例,寫出對應的 settings 和 mappings :

k1 和 b 的默認值適用於絕大多數文檔集合,但最優值還是會因爲文檔集不同而有所區別,爲了找到文檔集合的最優值,就必須對參數進行反覆修改驗證。

 

四、優化的建議

 

對 ES 搜索的優化應該把大部分精力花在文檔數據質量提升和查詢 DSL 組合調優上,需要反覆嘗試各種查詢的組合和調整權重,在 DSL 的優化已經達到較好程度之前,儘量不要調整 similarity。

更不要在初期就引入太多的插件,例如近義詞,拼音等,這樣會影響你的優化,它們只是提高搜索召回率的工具,並不一定會提高準確率。更專業的平臺應該做好更專業的搜索引導與建議,而不是讓用戶盲目的去嘗試搜索。

搜索的調優也不能一直關注技術方面,還要關注用戶。搜索質量的好壞是一個比較主觀的評價,想要了解用戶是否滿意搜索結果,只能通過監測搜索結果和用戶的行爲,例如用戶重複搜索的頻率,翻頁的頻次等。

如果搜索能返回相關性較高的文檔,用戶應該會在第一次搜索便得到想要的內容,如果返回相關性不太好的結果,用戶可能會來回點擊並嘗試新的搜索條件。

 

五、使用 _explain 做 bad case 分析

 

到這裏,我們的搜索排名優化就告一段落,但可能時不時還會有一些用戶反饋搜索的結果不準確。雖然有可能是用戶自身不會使用搜索引擎(這裏應該從產品上引導用戶寫出更好的查詢關鍵詞),但更多時候應該還是排名優化沒有做好。

所以如果發現了 bad case,我們應該記錄這些 bad case ,從這些問題入手仔細分析問題出在哪裏,然後慢慢優化。

分析 bad case 可以使用 ES 提供的 _explain 查詢 api,它會返回我們使用某 DSL 查到某文檔的得分細節,通過這些細節,我們就能知道產生 bad case 的原因,然後有針對性的下手優化了。

 

六、結語

 

ES 是一個通用型的全文檢索引擎,如果想用 ES 搭建一個專業的搜索平臺,必須要經過搜索調優纔可以達到可用的狀態。本文總結了基於 ES 的搜索優化,主要是優化 DSL 與相關性計算,希望讀者可以從中學習到有用的知識。

看騰訊技術,學雲計算知識,來雲+社區: https://cloud.tencent.com/developer

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章