elasticsearch——影響文檔評分

一般情況下,類似關系型數據庫中影響返回結果的方式,在elasticsearch中也存在,包括sort、size等。但是這都是簡單粗暴的對返回結果進行直接處理,很大的可能會影響返回文檔的相關度。比如,通過sort操作對搜索結果按時間排序,這時排在前面的文檔很可能相關度非常小,而相關度大的文檔則因為時間排序被放在了下面。這顯然不是我們想要的結果。elasticsearch提供了方法,允許我們用除了搜索之外的其他因素影響返回文檔的順序,同時兼顧文檔的相關度。

簡單粗暴地評分

首先先說一下elasticsearch的搜索評分邏輯。
查詢的權重基于三個因素:詞頻、逆向文檔頻率和字段長度歸一值。

  • 詞頻:查詢詞在該文檔中出現的頻率。頻率越高,權重越高。
  • 逆向文檔頻率:查詢詞在所有文檔中出現的頻率。頻率越高,權重越低。可以降低日常使用的高頻率詞的權重。
  • 字段長度歸一值:查詢字段的長度。字段長度越長,查詢詞權重越高,反之越低。

不同詞對搜索結果的影響基本取決于以上三個因素,這里不列出詳細的計算公式。

如何影響文檔評分

首先,影響文檔評分的操作推薦是查詢的時候進行,這樣靈活性更好。這里不介紹簡單的評分提升或者降低,直接介紹elasticsearch中控制文檔評分的終極武器:function_scor。

function_score

function_score是query結構的一個子集,它對每一個符合查詢的文檔應用一個或一組函數,達到影響甚至替換原始查詢評分的目的。這個操作可以很方便的實現復雜的查詢邏輯。
Elasticsearch 預定義了一些函數:

  • weight:為每個文檔應用一個簡單而不被規范化的權重提升值:當 weight 為 2 時,最終結果為 2 * _score 。
  • field_value_factor:使用這個值來修改 _score ,如將 popularity 或 votes (受歡迎或贊)作為考慮因素。
  • random_score:為每個用戶都使用一個不同的隨機評分對結果排序,但對某一具體用戶來說,看到的順序始終是一致的。
  • 衰減函數 —— linear 、 exp 、 gauss:將浮動值結合到評分 _score 中,例如結合 publish_date 獲得最近發布的文檔,結合 geo_location 獲得更接近某個具體經緯度(lat/lon)地點的文檔,結合 price 獲得更接近某個特定價格的文檔。
  • script_score:如果需求超出以上范圍時,用自定義腳本可以完全控制評分計算,實現所需邏輯。

以上函數開箱即用,一般情況下,elasticsearch提供的函數就可以滿足需求,如果有特殊要求,也可以使用最后一個script_score自己寫控制評分腳本。但是script_score對性能有較大影響,能不用就不用。
下面簡單說明幾個需求,來看看elasticsearch是如何通過function_score實現的。

點擊數影響評分

如果想要在原來搜索的基礎上,加入點擊數的影響,即將點擊數高的文檔放到搜索結果靠上的位置,但是搜索的評分仍然是主要的依據。

PUT /blogposts/post/1
{
  "title":   "About popularity",
  "content": "In this post we will talk about...",
  "votes":   6
}

這是一篇文章的文檔,其中保存了點擊數。這里可以通過field_value_factor實現相關需求。

GET /blogposts/post/_search
{
  "query": {
    "function_score": { 
      "query": {
        "multi_match": {
          "query":    "popularity",
          "fields": [ "title", "content" ]
        }
      },
      "field_value_factor": { 
        "field": "votes" 
      }
    }
  }
}

function_score嵌入了一個query查詢中,然后內部又設定了一個query,內部的query查詢是主查詢。在function_score內部還有一個field_value_factor函數,這個函數會對符合每一個主查詢的文檔使用。每個文檔的最終評分都會進行如下的計算:new_score = old_score * number_of_votes
默認的field_value_factor計算是線性的,votes的原始值直接用來計算,這通常不會產生好的結果。一般情況下,通過對數計算取代現行計算,可以取得更平滑的結果。

GET /blogposts/post/_search
{
  "query": {
    "function_score": {
      "query": {
        "multi_match": {
          "query":    "popularity",
          "fields": [ "title", "content" ]
        }
      },
      "field_value_factor": {
        "field":    "votes",
        "modifier": "log1p"
      }
    }
  }
}

將field_value_factor設為對數計算,計算公式:new_score = old_score * log(1 + number_of_votes)
field_value_factor提供了眾多參數,設置相關計算的各種參數,這里不再一一列舉。如果想要細致的調整搜索結果的話,參考官方文檔進行。

對查詢結果進行隨機評分

作為網站的所有者,總會希望讓廣告有更高的展現率。在當前查詢下,有相同評分 _score 的文檔會每次都以相同次序出現,為了提高展現率,在此引入一些隨機性可能會是個好主意,這能保證有相同評分的文檔都能有均等相似的展現機率。
我們想讓每個用戶看到不同的隨機次序,但也同時希望如果是同一用戶翻頁瀏覽時,結果的相對次序能始終保持一致。這種行為被稱為一致隨機。
random_score 函數會輸出一個 0 到 1 之間的數,當種子 seed 值相同時,生成的隨機結果是一致的。例如:

GET /blogposts/post/_search
{
  "query": {
    "function_score": {
      "query": {
        "multi_match": {
          "query":    "popularity",
          "fields": [ "title", "content" ]
        }
      },
      "random_score": {
        "seed":  "userID"
      }
    }
  }
}

使用每個用戶的id作為seed傳入,可以使每個用戶的隨機保持一致,同時在不同用戶之間保持不同的隨機性。

時間、空間上的“越近越好”

一般來說,搜索要求具有時間相關性。也就是說,用戶只想看到時間較近的文檔,而時間較遠的文檔,就算相關度較高,用戶也不想看到。空間上也有相關的性質,比如以某個點為中心,周圍一定距離以內的文檔排在返回結果的前面,而超過一定距離的文檔就算相關度較高也排在后面。elasticsearch提供了衰減函數,可以對文檔的相關性按某個維度進行衰減。

這里不能直接使用sort。因為如果直接使用sort,會讓相關度較低的文檔排在前面,維度近了但是相關度很差,達不到相關度較高、同時某個維度較 近 的需求。

elasticsearch提供了三個衰減函數,分別是linear、exp和gauss(線性、指數和高斯函數),它們可以操作數值、時間以及經緯度地理坐標點這樣的字段(一般衰減也沒有用字符串做衰減的)。所有三個函數都能接受以下參數:

  • origin:中心點 或字段可能的最佳值,落在原點 origin 上的文檔評分 _score 為滿分 1.0 。
  • scale:衰減率,即一個文檔從原點 origin 下落時,評分 _score 改變的速度。(例如,每 £10 歐元或每 100 米)。
  • decay:從原點 origin 衰減到 scale 所得的評分 _score ,默認值為 0.5 。
  • offset:以原點 origin 為中心點,為其設置一個非零的偏移量 offset 覆蓋一個范圍,而不只是單個原點。在范圍 -offset <= origin <= +offset 內的所有評分 _score 都是 1.0 。
    衰減曲線

比如,想要搜索某條博客,同時發表時間近的排在前面,引入基于時間的衰減函數。

GET /blogposts/post/_search
{
  "query": {
    "function_score": {
      "query": {
        "multi_match": {
          "query":    "popularity",
          "fields": [ "title", "content" ]
        }
      },
      "gauss" :{
          "timestamp": {
            "origin": "now timestamp",
            "offset": "5d",
            "scale": "10d"
          }
       }
    }
  }
}

按時間和地理空間做衰減,offset 和 scale 必須加上單位。
這里我們確定以當前的時間為衰減函數的中心, 5 天以內的文檔,相關度不做處理;5 天到 15 天,衰減系數逐漸降低到0.5;15 天之外,系數繼降低。

比如,想將地理空間和價格的影響引入搜索,可以這樣實現:

GET /_search
{
  "query": {
    "function_score": {
      "functions": [
        {
          "gauss": {
            "location": {
              "origin": { "lat": 51.5, "lon": 0.12 },
              "offset": "2km",
              "scale":  "3km"
            }
          }
        },
        {
          "gauss": {
            "price": { 
              "origin": "50", 
              "offset": "50",
              "scale":  "20"
            }
          },
          "weight": 2
        }
      ]
    }
  }
}

這里地理空間類型,在衰減函數的參數里要加上單位,而普通的數值類型不用加單位。

通過衰減函數,可以在相關度為主的前提下,引入時間、空間、數值等其他因素,從而影響搜索的返回結果。比起簡單粗暴的sort,衰減函數可以保證相關度高的依然排在靠前的位置。

終極武器script_score

elasticsearch支持用戶自己寫groovy腳本,來自定義復雜的評分影響邏輯。因為日常中不太實用,同時腳本對性能影響較大,所以這里不做介紹。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。