天天看點

[Elasticsearch] 控制相關度 (二) - Lucene中的PSF(Practical Scoring Function)與查詢期間提升

本章翻譯自Elasticsearch官方指南的Controlling Relevance一章。

Lucene中的Practical Scoring Function

對于多詞條查詢(Multiterm Queries),Lucene使用的是布爾模型(Boolean Model),TF/IDF以及向量空間模型(Vector Space Model)來将它們結合在一起,用來收集比對的文檔和對它們進行分值計算。

像下面這樣的多詞條查詢:

GET /my_index/doc/_search
{
  "query": {
    "match": {
      "text": "quick fox"
    }
  }
}      

在内部被重寫成下面這樣:

GET /my_index/doc/_search
{
  "query": {
    "bool": {
      "should": [
        {"term": { "text": "quick" }},
        {"term": { "text": "fox"   }}
      ]
    }
  }
}      

bool查詢實作了布爾模型,在這個例子中,隻有包含了詞條quick,詞條fox或者兩者都包含的文檔才會被傳回。

一旦一份文檔比對了一個查詢,Lucene就會為該查詢計算它的分值,然後将每個比對詞條的分值結合起來。用來計算分值的公式叫做Practical Scoring Function。它看起來有點吓人,但是不要退卻 - 公式中的絕大多數部分你已經知道了。下面我們會介紹它引入的一些新元素。

1   score(q,d)  = 
2            queryNorm(q)  
3          · coord(q,d)    
4          · ∑ (           
5                tf(t in d)   
6              · idf(t)²      
7              · t.getBoost() 
8              · norm(t,d)    
9            ) (t in q) 
           

每行的意義如下:

  1. score(q,d)是文檔d對于查詢q的相關度分值。
  2. queryNorm(q)是查詢歸約因子(Query Normalization Factor),是新添加的部分。
  3. coord(q,d)是Coordination Factor,是新添加的部分。
  4. 文檔d中每個詞條t對于查詢q的權重之和。
  5. tf(t in d)是文檔d中的詞條t的詞條頻度(Term Frequency)。
  6. idf(t)是詞條t的反向索引頻度(Inverse Document Frequency)
  7. t.getBoost()是适用于查詢的提升(Boost),是新添加的部分。
  8. norm(t,d)是字段長度歸約(Field-length Norm),可能結合了索引期間字段提升(Index-time Field-level Boost),是新添加的部分。

你應該知道score,tf以及idf的意思。queryNorm,coord,t.getBoost以及norm是新添加的。

在本章的稍後我們會讨論查詢期間提升(Query-time Boosting),首先對查詢歸約,Coordination以及索引期間字段級别提升進行解釋。

查詢歸約因子(Query Normalization Factor)

查詢歸約因子(queryNorm)會試圖去對一個查詢進行歸約,進而讓多個查詢的結果能夠進行比較。

TIP

雖然查詢歸約的目的是讓不同查詢的結果能夠比較,但是它的效果不怎麼好。相關度_score的唯一目的是将目前查詢的結果以正确的順序被排序。你不應該嘗試去比較不同查詢得到的相關度分值。

該因子會在查詢開始階段就被計算。實際的計算取決于查詢本身,但是一個典型的實作如下所示:

queryNorm = 1 / √sumOfSquaredWeights

sumOfSquaredWeights通過對查詢中每個詞條的IDF進行累加,然後取其平方根得到的。

TIP

相同的查詢歸約因子會被适用在每份文檔上,你也沒有辦法改變它。總而言之,它是可以被忽略的。

Query Coordination

Coordination因子(coord)被用來獎勵那些包含了更多查詢詞條的文檔。文檔中出現了越多的查詢詞條,那麼該文檔就越可能是該查詢的一個高品質比對。

加入我們查詢了quick brown fox,每個詞條的權重都是1.5。沒有Coordination因子時,分值可能會是文檔中每個詞條的權重之和。比如:

  • 含有fox的文檔 -> 分值:1.5
  • 含有quick fox的文檔 -> 分值:3.0
  • 含有quick brown fox的文檔 -> 分值:4.5

而Coordination因子會将分值乘以文檔中比對了的詞條的數量,然後除以查詢中的總詞條數。使用了Coordination因子後,分值是這樣的:

  • 含有fox的文檔 -> 分值:1.5 * 1 / 3 = 0.5
  • 含有quick fox的文檔 -> 分值:3.0 * 2 / 3 = 2.0
  • 含有quick brown fox的文檔 -> 分值:4.5 * 3 / 3 = 4.5

以上的結果中,含有所有三個詞條的文檔的分值會比僅含有兩個詞條的文檔高出許多。

需要記住對于quick brown fox的查詢會被bool查詢重寫如下:

GET /_search
{
  "query": {
    "bool": {
      "should": [
        { "term": { "text": "quick" }},
        { "term": { "text": "brown" }},
        { "term": { "text": "fox"   }}
      ]
    }
  }
}      

bool查詢會對所有should查詢子句預設啟用查詢Coordination,但是你可以禁用它。為什麼你需要禁用它呢?好吧,通常的答案是,并不需要。查詢Coordination通常都起了正面作用。當你使用bool查詢來将多個像match這樣的進階查詢(High-level Query)包裝在一起時,啟用Coordination也是有意義的。比對的查詢子句越多,你的搜尋陳請求和傳回的文檔之間的比對程度就越高。

但是,在某些進階用例中,禁用Coordination也是有其意義的。比如你正在查詢同義詞jump,leap和hop。你不需要在意這些同義詞出現了多少次,因為它們表達了相同的概念。實際上,隻有其中的一個可能會出現。此時,禁用Coordination因子就是一個不錯的選擇:

GET /_search
{
  "query": {
    "bool": {
      "disable_coord": true,
      "should": [
        { "term": { "text": "jump" }},
        { "term": { "text": "hop"  }},
        { "term": { "text": "leap" }}
      ]
    }
  }
}      

當你使用了同義詞(參考同義詞(Synonyms)),這正是在内部發生的:重寫的查詢會為同義詞禁用Coordination。多數禁用Coordination的用例都會被自動地處理;你根本無需擔心它。

索引期間字段級别提升(Index-time Field-level Boosting)

現在來讨論一下字段提升 - 讓該字段比其它字段更重要一些 - 通過在查詢期間使用查詢期間提升(Query-time Boosting)。在索引期間對某個字段進行提升也是可能的。實際上,該提升會适用于字段的每個詞條上,而不是在字段本身。

為了在盡可能少占用空間的前提下,将提升值存儲到索引中,索引期間字段級别提升會和字段長度歸約一起以一個位元組被儲存在索引中。它是之前公式中norm(t,d)傳回的值。

警告

我們強烈建議不要使用字段級别索引期間提升的原因如下:

  • 将此提升和字段長度歸約存儲在一個位元組中意味着字段長度歸約會損失精度。結果是ES不能區分一個含有三個單詞的字段和一個含有五個單詞的字段。
  • 為了修改索引期間提升,你不得不對所有文檔重索引。而查詢期間的提升則可以因查詢而異。
  • 如果一個使用了索引期間提升的字段是多值字段(Multivalue Field),那麼提升值會為每一個值進行乘法操作,導緻該字段的權重飙升。
查詢期間提升(Query-time Boosting)更簡單,簡潔和靈活。

解釋完了查詢歸約,Coordination以及索引期間提升,現在可以開始讨論對影響相關度計算最有用的工具:查詢期間提升。

查詢期間提升(Query-time Boosting)

在調整查詢子句優先級(Prioritizing Clauses)一節中,我們已經介紹過如何在搜尋期間使用boost參數為一個查詢子句增權重重。比如:

GET /_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "title": {
              "query": "quick brown fox",
              "boost": 2 
            }
          }
        },
        {
          "match": { 
            "content": "quick brown fox"
          }
        }
      ]
    }
  }
}      

查詢期間提升是用來調優相關度的主要工具。任何類型的查詢都接受boost參數。将boost設為2并不是簡單地将最終的_score加倍;确切的提升值會經過規範化以及一些内部優化得到。但是,它也意味着一個提升值為2的子句比一個提升值為1的子句要重要兩倍。

實際上,沒有任何公式能夠決定對某個特定的查詢子句,"正确的"提升值應該是多少。它是通過嘗試來得到的。記住boost僅僅是相關度分值中的一個因素;它需要和其它因素競争。比如在上面的例子中,title字段相對于content字段,大概已經有一個"自然的"提升了,該提升來自字段長度歸約(Field-length Norm)(因為标題通常會比相關内容要短一些),是以不要因為你認為某個字段應該被提升而盲目地對它進行提升。适用一個提升值然後檢查得到的結果,再進行修正。

提升索引(Boosting an Index)

當在多個索引中搜尋時,你可以通過indices_boost參數對整個索引進行提升。在下面的例子中,會給予最近索引中的文檔更多的權重:

GET /docs_2014_*/_search 
{
  "indices_boost": { 
    "docs_2014_10": 3,
    "docs_2014_09": 2
  },
  "query": {
    "match": {
      "text": "quick brown fox"
    }
  }
}      

該多索引搜尋(Multi-index Search)會查詢所有以docs_2014_開頭的索引。 索引docs_2014_10中的文檔的提升值為3,索引docs_2014_09中的文檔的提升值為2,其它索引中的文檔的提升值為預設值1。

t.getBoost()

這些提升值在Lucene的Practical Scoring Function中通過t.getBoost()元素表達。提升并不是其在查詢DSL出現的地方被适用的。相反,任何的提升值都會被合并然後傳遞到每個詞條上。t.getBoost()方法傳回的是适用于詞條本身上的提升值,或者是适用于上層查詢的提升值。

TIP

實際上,閱讀解釋API的輸出本身比上述的說明更複雜。你在解釋中根本看不到boost值或者t.getBoost()。提升被融合到了适用于特定詞條上的queryNorm中。盡管我們說過queryNorm對任何詞條都是相同的,但是對于提升過的詞條而言,queryNorm會更高一些。

繼續閱讀