當我們能使用match來搜索匹配數據的時候,es會給每一個文檔進行評分(匹配度),並根據評分的大小對結果文檔進行排序。
介紹
es的實時評分機制是基於 Lucene
的基礎上實現的,最常見的是 TF/IDF
和BM25
這兩種評分模型。
TF-IDF屬於向量空間模型,而BM25屬於概率模型,但是他們的公式可能並沒有你想象的那麼大差距。兩種相似度模型都使用idf方法和tf方法的某種乘積來定義單個詞項的權重,然後把和查詢匹配的詞項的權重相加作爲整篇文檔的分數。
對於這兩種算法的詳細介紹以及區別大家可以參考:
在es5.0版本之前使用了TF/IDF
算法實現,而在5.0之後默認使用BM25
方法實現。
作爲開發,我們可以不需要了解非常深入的瞭解公式的由來,但也要做到公式的組成和每個參數的含義。
例子
下面是一個普通的查詢:
PUT /test/test1/1
{"title":"hallo,books"}
PUT /test/test1/2
{"title":"hallo,books boy"}
PUT /test/test1/3
{"title":"hallo hallo,hi hi hi hi"}
PUT /test/test1/4
{"title":"hallo hallo,hi hi hi hi"}
PUT /test/test1/5
{"title":"hi hi hi hi"}
GET /test/test1/_search
{
"query": {
"match": {
"title": "hallo hi"
}
}
}
在搜索的結果中有一個_score字段,代表了es給文檔的評分,默認的排序規則是根據這個字段的大小進行排序,越大則出現在越前面。
當我們搜索一個單詞或者一個詞組乃至一句話的時候,es先會通過Analyzer
分析器拆成多個term(ES學習——分析器和自定義分析器),然後在對每一個term進行BM25
公式評分,最後把每一個term的評分進行加權求和,就是最後的得分。
例如,我們搜索hallo hi
,es通過對應的默認解析器拆分成hallo
和hi
,分別求出分數score(hallo)和score(hi):
總得分Score=score(hallo)+score(hi)。
和sql相似,我們可以通過在搜索條件中添加explain=true,來查看具體的得分過程。
GET /test/test1/_search?explain=true
{
"query": {
"match": {
"title": "hallo hi"
}
}
}
##以下通過篩選,過濾了一些無用的信息
{
"_shard": "[test][0]",【1】
"_score": 1.202173,【2】
"_source": {
"title": "hallo hallo,hi hi hi hi"【3】
},
"_explanation": {
"value": 1.202173,
"description": "sum of:",【4】
"details": [
{
"value": 0.3530123,【5】
"description": "weight(title:hallo in 2) [PerFieldSimilarity], result of:",
"details": [
{
"value": 0.3530123,【7】
"description": "score(doc=2,freq=2.0 = termFreq=2.0\n), product of:",
"details": [
{
"value": 0.2876821,
"description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
"details": [
{
"value": 4,
"description": "docFreq"
},
{
"value": 5,
"description": "docCount"
}
]
},
{
"value": 1.2270917,【8】
"description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
"details": [
{
"value": 2,
"description": "termFreq=2.0"
},
{
"value": 1.2,
"description": "parameter k1"
},
{
"value": 0.75,
"description": "parameter b"
},
{
"value": 4.2,
"description": "avgFieldLength"
},
{
"value": 6,
"description": "fieldLength"
}
]
}
]
}
]
},
{
"value": 0.84916073,【6】
"description": "weight(title:hi in 2) [PerFieldSimilarity], result of:"
##省略和 hallo的計算方式類似
}
]
}
}
從最外層結構往裏分析:
【1】:代表文檔的分片,[test][0]
,在es中每一個分片評分都是單獨計算的
【2】【3】:代表最後的分數和文檔
【4】:總的分數是1.202173,sum of
是默認的加權平均,把hallo
的分數(0.3530123)+hi
的分數(0.84916073)
【5】【6】:分表表示hallo
的評分和hi
的評分。其description
描述的很明白:weight(title:hallo in 2)
【7】【8】:hallo
的評分是由【7】(idf)*【8】(tfNorm)得出
【7】:idf(下面介紹)=log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5))
【8】:tfNorm=(freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength))
公式介紹(BM25)
歸納一下上面的公式:
參考,BM25與TF/IDF的區別
IDF (inverse document frequency):逆文檔頻率,是指一個詞在文檔中出現的次數越多,則他的權值相對越多。例如常用的詞and 或 or 或 a在文檔中出現的次數非常多,相對於用戶而言重要性很小,但是elasticSearch等不常見的詞進行搜索的時候相對於用戶來說,權重應該更大。
IDF
在_explanation中也已經寫明瞭IDF的計算公式:
其中docCount代表該分片
總的文檔數量數量,docFreq代表有這個分詞的文檔數量。
在例子中,總共的文檔數爲5,包含hallo的文檔數爲4,所以docFreq=4,docCount=5。IDF最終算出來爲0.2876821,且在同一分片中所有的文檔的IDF且相同。
tfNorm
tfNorm代表此term在此文檔中的重要程度,在此doc中出現次數越多,越重要;並且文檔的長度越短,也見解說明改term在此doc中越重要。
具體公式:
其中:
freq:在文檔中出現的次數
k1:爲調優參數,固定值,默認1.2,合理的值需要依賴文檔的數據。
b:爲調優參數,固定值,默認爲0.75,合理的值需要依賴文檔的數據。
fieldLength:是滿足查詢條件的doc的filed的長度
avgFieldLength:是滿足查詢條件的所有doc的filed的長度.
注意點
評分都是以分片爲單位的,若兩個文檔分佈在不同的分片,會出現即使內容完全相同但是分數有差異的情況(IDF的docCount和docFreq有區別)或者出現更加符合我們心裏預期的文檔分數反而較低的情況。
方案:
- 把結構相似的文檔通過route到一個分片上。個人認爲若文檔種類差異較大可以使用這種方法,但是會出現分片數據不均勻的問題。
- 自定義評分處理。有公式看出IDF只與出現次數和文檔總數有關,在一個分片中,次數和文檔總數是固定的,在全局中也是一樣。 設置忽略IDF。 但是這種方法,在多個term查詢的時候,相當於忽略了IDF對分數的影響。
- 業務上可以容忍,不做修改。當文檔的基數很大,且文檔分配很均勻的時候,兩個相同的文檔分數的差異會相對減少。