lucene 的評分機制


lucene 的評分機制

elasticsearch是基於lucene的,所以他的評分機制也是基於lucene的。評分就是我們搜索的短語和索引中每篇文檔的相關度打分。
如果沒有干預評分算法的時候,每次查詢,lucene會基於一個評分算法來計算所有文檔和搜索語句的相關評分。
使用lucene的評分機制基本能夠把最符合用戶需要的搜索放在最前面。
當然有的時候,我們可能想要自定義評分算法,這個就和lucene的評分算法沒有什么關系了。當然,我們大多數應該還是會根據自己的需求,來調整lucene本身的算法。

lucene的評分公式

lucene的評分是叫做TF/IDF算法,基本意思就是詞頻算法。
根據分詞詞庫,所有的文檔在建立索引的時候進行分詞划分。進行搜索的時候,也對搜索的短語進行分詞划分。
TF代表分詞項在文檔中出現的次數(term frequency),IDF代表分詞項在多少個文檔中出現(inverse document frequency)。

lucene的算法簡單來說就是將搜索的短語進行分詞得出分詞項,每個分詞項和每個索引中的文檔根據TF/IDF進行詞頻出現的評分計算。
然后每個分詞項的得分相加,就是這個搜索對應的文檔得分。

評分公式

這個評分公式有6個部分組成

  • coord(q,d) 評分因子,基於文檔中出現查詢項的個數。越多的查詢項在一個文檔中,說明文檔的匹配程度越高。
  • queryNorm(q)查詢的標准查詢
  • tf(t in d) 指項t在文檔d中出現的次數frequency。具體值為次數的開根號。
  • idf(t) 反轉文檔頻率, 出現項t的文檔數docFreq
  • t.getBoost 查詢時候查詢項加權
  • norm(t,d) 長度相關的加權因子

coord(q, d)

這個評分因子的計算公式是:

public float coord(int overlap, int maxOverlap) {
    return overlap / (float)maxOverlap;
}
  • overlap: 文檔中命中檢索的個數
  • maxOverlap: 檢索條件的個數

比如檢索"english book", 現在有一個文檔是"this is an chinese book"。
那么,這個搜索對應這個文檔的overlap為1(因為匹配了book),而maxOverlap為2(因為檢索條件有兩個book和english)。
最后得到的這個搜索對應這個文檔的coord值為0.5。

queryNorm(q)

這個因素對所有文檔都是一樣的值,所以它不影響排序結果。比如如果我們希望所有文檔的評分大一點,那么我們就需要設置這個值。

public float queryNorm(float sumOfSquaredWeights) {
    return (float)(1.0 / Math.sqrt(sumOfSquaredWeights));
}

tf(t in d)

項t在文檔d中出現的次數

public float tf(float freq) {
    return (float)Math.sqrt(freq);
}

比如有個文檔叫做"this is book about chinese book", 我的搜索項為"book",那么這個搜索項對應文檔的freq就為2,那么tf值就為根號2,即1.4142135

idf

public float idf(long docFreq, long numDocs) {
    return (float)(Math.log(numDocs/(double)(docFreq+1)) + 1.0);
}

這里的兩個值解釋下

  • docFreq 指的是項出現的文檔數,就是有多少個文檔符合這個搜索
  • numDocs 指的是索引中有多少個文檔。

我在用es實際看這里的時候遇到一個問題,numDocs數和實際的文檔數不一致,最后弄明白了,這里的numDocs指的是分片的文檔數據,而不是所有分片的文檔數。
所以使用es分析這個公式的時候,最好將分片數設置為1。

比如我現在有三個文檔,分別為:

  • this book is about english
  • this book is about chinese
  • this book is about japan

我要搜索的詞語是"chinese",那么對第二篇文檔來說,docFreq值就是1,因為只有一個文檔符合這個搜索,而numDocs就是3。最后算出idf的值是:

(float)(Math.log(numDocs/(double)(docFreq+1)) + 1.0) = ln(3/(1+1)) + 1 = ln(1.5) + 1 = 0.40546510810816 + 1 = 1.40546510810816

t.getBoost

查詢時期項t的加權,這個就是一個影響值,比如我希望匹配chinese的權重更高,就可以把它的boost設置為2

norm(t,d)

這個項是長度的加權因子,目的是為了將同樣匹配的文檔,比較短的放比較前面。
比如兩個文檔:

  • chinese
  • chinese book

我搜索chinese的時候,第一個文檔會放比較前面。因為它更符合"完全匹配"。

norm(t,d) = doc.getBoost()· lengthNorm· ∏ f.getBoost()


public float lengthNorm(FieldInvertState state) {
    final int numTerms;
    if (discountOverlaps)
        numTerms = state.getLength() - state.getNumOverlap();
    else
        numTerms = state.getLength();
    return state.getBoost() * ((float) (1.0 / Math.sqrt(numTerms)));
}

這里的doc.getBoost表示文檔的權重,f.getBoost表示字段的權重,如果這兩個都設置為1,那么nor(t,d)就和lengthNorm一樣的值。

比如我現在有一個文檔:

  • chinese book

搜索的詞語為chinese, 那么numTerms為2,lengthNorm的值為 1/sqrt(2) = 0.71428571428571。

但是非常遺憾,如果你使用explain去查看es的時候,發現lengthNorm顯示的只有0.625。
這個官方給出的原因是精度問題,norm在存儲的時候會進行壓縮,查詢的時候進行解壓,而這個解壓是不可逆的,即decode(encode(0.714)) = 0.625。

示例

es中可以使用_explain接口進行評分解釋查看。

比如現在我的文檔為:

  • chinese book

搜索詞為:

{
  "query": {
    "match": {
      "content": "chinese"
    }
  }
}

explain得到的結果為:

{
    "_index": "scoretest",
    "_type": "test",
    "_id": "2",
    "matched": true,
    "explanation": {
        "value": 0.8784157,
        "description": "weight(content:chinese in 1) [PerFieldSimilarity], result of:",
        "details": [
            {
                "value": 0.8784157,
                "description": "fieldWeight in 1, product of:",
                "details": [
                    {
                        "value": 1,
                        "description": "tf(freq=1.0), with freq of:",
                        "details": [
                            {
                                "value": 1,
                                "description": "termFreq=1.0"
                            }
                        ]
                    },
                    {
                        "value": 1.4054651,
                        "description": "idf(docFreq=1, maxDocs=3)"
                    },
                    {
                        "value": 0.625,
                        "description": "fieldNorm(doc=1)"
                    }
                ]
            }
        ]
    }
}

看到這篇文檔的總得分為 0.8784157

  • tf(t in d): 1
  • idf: ln(3/(1+1)) + 1 = 1.4054651
  • norm(t,d): decode(encode(1/sqrt(2))) = 0.625
  • 總分: 1.4054651 * 0.625 = 0.8784157


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM