1. 程式人生 > >ElasticStack系列之十二 & 搜索結果研究

ElasticStack系列之十二 & 搜索結果研究

想要 查看 關系 獲得 pos 過程 利用 根據 sea

問題

  使用 ElasticSearch 做搜索 時,比如用戶輸入 --> 檸檬,搜出來的結果 --> 檸檬汽水,檸檬味牙膏等在前面,真正想要的水果那個 檸檬 在後面。已經在中文分詞中加了 檸檬,還是不管用,正常來說 tf、idf 都一樣,影響排序的只有 field norms。按道理 “檸檬” 的 field length 最短,那麽得分應該最高才對,為什麽它沒有排在第一位呢?

  我這裏補充一下:ElasticSearch5.x 以後使用的相關度算法為 BM25,但他仍然是一種相關性算法,只是對 TF/IDF 的改進。 用 BM25 還是 TF/IDF 和本問題的根源沒有關系。

驗證

  為了驗證這個結果,我實際測試了一下,過程如下:

  創建一個空索引,使用 ik_max_word 分詞器並寫入 3 條數據

PUT testindex/
{
  "mappings": {
    "logs": {
      "properties": {
        "product": {
          "type": "text",
          "analyzer": "ik_max_word"
        }
      }
    }
  }
}

PUT testindex/logs/1
{"product":"檸檬"}

PUT testindex/logs/2
{"product":"檸檬汽水"} PUT testindex/logs/3 {"product":"檸檬味牙膏"}

  查詢關鍵詞"檸檬"

POST testindex/_search
{
  "query": {
    "match": {
      "product": "檸檬"
    }
  }
}

  查詢結果:

"hits": {
    "total": 3,
    "max_score": 0.85747814,
    "hits": [
      {
        "_index": "testindex",
        "_type": "logs"
, "_id": "3", "_score": 0.85747814, "_source": { "product": "檸檬味牙膏" } }, { "_index": "testindex", "_type": "logs", "_id": "2", "_score": 0.80226827, "_source": { "product": "檸檬汽水" } }, { "_index": "testindex", "_type": "logs", "_id": "1", "_score": 0.7594807, "_source": { "product": "檸檬" } } ] }

  "檸檬" 居然真分數最低,非常出乎我的意料。於是,我在查詢裏打開 "explain":true 選項,查看分數是怎麽計算的,發現 doc frequency, avgfieldlength 看著都不對。

問題解釋

  簡而言之,ElasticSearch 的相關性打分計算是每個 shard 獨立做的。一個索引默認 5 個shard,如果像示例裏那樣,寫入的文檔比較少,可能這些文檔分布在不同的 shard,造成各個 shard 分別計算各自的得分的時候,並沒有將這幾條文檔放在一起產生統計數據。 各自的打分不具有可比性。

  所以,後面我又做了一個測試,刪掉這個索引,重新創建一個,將 shard 設置為 1,重新寫入同樣 3 條文檔後再搜索,"檸檬” 是排第一位返回的。

  那麽怎麽看待這個問題?

  因為 ElasticSearch 是分布式搜索系統,各個shard獨立搜索,獨立計算該shard上的文檔打分,當數據量比較大的情況下,上面說的差異統計上看基本被抹平了,通常沒什麽問題。但如果索引的文檔比較少,不同 shard 之間對同一個搜索關鍵詞的統計數據差異可能就比較大,這種情況下只能使用一個 shard 來解決了。

  故這裏就可以解釋我最初問題中所說的,通過 explain api 去查看打分的過程時,doc frequency 和 avgfieldlength 這類參與打分的統計值看起來“不正確”。之所為認為它“不正確”,是因為一開始沒意識到,ES默認的打分是每個shard單獨進行的,並非參考的全局統計數據。

建議

  測試說 “檸檬” 就是水果,是因為頭腦裏認為“檸檬” 二字植入了領域類別信息。 但是計算機只能處理文本,原始文本信息裏 “水果” 這個領域信息是缺失的。 因此需要根據具體的應用場景,對信息做補充,才可能提高精準度。

  故在做實際搜索應用的時候,則需要根據應用所針對的領域範圍,補充打分需要的其他參考信息來提高搜索準確率。 比如收集用戶搜索的常用熱詞,為熱詞增加權重,或者為信息添加類別信息,為某些類別提高權重等等。

  另外:

    可以在搜索的時候增加參數:?search_type=dfs_query_then_fetch ,即先分別獲得每個分片本地的 IDF ,然後根據結果再計算整個索引的全局 IDF,也是可以實現。不過這種方式代價比較高,適合測試的時候使用,生成環境下還是不建議使用的。

  推薦可以看一篇 github 上的 issue

總結

  ElasticSearch 這類搜索引擎主要提供 “搜的到” 的功能,而能否 “搜的準”,需要理解 ElasticSearch 能提供的打分機制,並結合領域信息,對信息做提煉或者補充,然後利用這些打分機制去調配,讓搜索結果符合應用的需求。但搜的“準不準”,一千個人有一千個理解,做好不是一件容易的事。

  BM25 和 基於tf-idf的向量空間模型,我覺得有本質的區別,前者是概率模型,後者是代數模型。

ElasticStack系列之十二 & 搜索結果研究