概要
上一篇中多次提到了按相關性評分,本篇我們就來簡單瞭解一下相關性評分的算法,以及正排索引排序的優勢。
評分算法
Elasticsearch進行全文搜索時,Boolean Model是匹配的基礎,先用boolean model將匹配的文檔挑選出來,然後再運用評分函數計算相關度,參與的函數如我們提到的TF/IDF、Length Norm等,再加上一些控制權重的參數設置,得到最後的評分。
Boolean Model
作爲全文搜索的基礎,Boolean Model的結果決定文檔是否要進行下一步的評分操作,使用AND、OR、NOT這種邏輯操作符來判斷查找的文檔,整個過程不評分,非常快速地將匹配的文檔篩選出來。
由於Elastisearch各個版本相關度評分算法有異同,我們以6.3.1版本的BM25算法爲準。
TFNORM/IDF
由Boolean Model之後得到的文檔,我們開始計算文檔的評分,每個文檔的評分取決於每個關鍵詞在文檔中的權重,權重我們會從以下幾個方面考慮:
TFNORM
即詞頻長度(Term Frequency Norm),單個term在文檔中出現的頻率,並結合字段長度,出現次數越高,字段長度越低,分越高,計算公式:
tfNorm(t in d) = (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength))
詞t在文檔d的詞頻(tf):freq表示出現頻率,k1、b爲ES參數,fieldLength爲該字段長度,avgFieldLength爲平均字段長度,公式瞭解一下即可。
IDF
即逆向文檔頻率(inversed document frequency),單個term在所有文檔裏出現的頻率是多少?出現次數越高,分越低,計算公式:
idf(t) = log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5))
詞 t 的逆向文檔頻率(idf):索引中文檔數量與該詞的文檔數比率,然後求其對數
例如:"and"、"the","的"、"了"、"呢"這種詞在文檔裏到處都是,出現的頻率特別高,反倒是不常出現的詞"Elastic","成都"可以幫助我們快速縮小範圍找到感興趣的文檔。
結果合併
一個term經過上面兩個算法計算後,會得到兩個不同的值,這兩個得分相乘得到一個綜合性的分數,這個分數就是最終的_score
我們用explain來看一下這個綜合分數的詳細信息:
搜索請求:
GET /music/children/_search
{
"explain": true,
"query": {
"term": {
"name": "teeth"
}
}
}
響應結果:
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.73617005,
"hits": [
{
"_explanation": {
"value": 0.7361701,
"description": "weight(name:teeth in 1) [PerFieldSimilarity], result of:",
"details": [
{
"value": 0.7361701,
"description": "score(doc=1,freq=1.0 = termFreq=1.0\n), product of:",
"details": [
{
"value": 0.6931472,
"description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
"details": [
{
"value": 1,
"description": "docFreq",
"details": []
},
{
"value": 2,
"description": "docCount",
"details": []
}
]
},
{
"value": 1.0620689,
"description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
"details": [
{
"value": 1,
"description": "termFreq=1.0",
"details": []
},
{
"value": 1.2,
"description": "parameter k1",
"details": []
},
{
"value": 0.75,
"description": "parameter b",
"details": []
},
{
"value": 3.5,
"description": "avgFieldLength",
"details": []
},
{
"value": 3,
"description": "fieldLength",
"details": []
}
]
}
]
}
]
}
}
]
}
}
_explanation節點,裏面包含了description、value、details字段,從這裏我們可以知道計算的類型,計算結果和任何我們需求的計算細節。從示例中我們可以看到IDF和TF的計算公式和計算結果。
我們可以看到最終得分是0.73617005,其中tfNorm得分1.0620689,idf得分0.6931472,經過相乘1.0620689 * 0.6931472 = 0.73617005
多個term查詢
上面的案例是隻有一個term查詢的,如果有多個term查詢,如:
GET /music/children/_search
{
"explain": true,
"query": {
"match": {
"name": "your teeth"
}
}
}
我們可以看到,總的_score就是將每個term查詢的_score求和。
Lucene公式
這裏我們先看query score的計算公式:
我們從左往右看,公式依次的含義如下:
- score(D,Q):這個公式最終的結果,Q表示query,D表示doc,表示一個query對一個doc的最終的總得分。
- IDF(qi):idf算法對一個term的值。
- f(xxx)/xxx:這一大串公式,即tf norm的計算公式。
- ∑ 求和符號:idf和tf norm結果相乘,最後求和。
這個求相關性分數的公式瞭解一下即可,可以結合上面的案例去理解這個公式。
這個公式可以參見wiki: [Okapi BM25]https://en.wikipedia.org/wiki/Okapi_BM25
文檔是如何被匹配到的
如果對搜索的結果有異議,我們可以指定文檔ID進行查看,該文檔爲什麼能被匹配上,也是使用explain參數,示例如下:
GET /music/children/4/_explain
{
"query": {
"match": {
"content": "wake up morning"
}
}
}
4爲文檔ID,此請求的含義表示針對如下的搜索條件,文檔ID爲4的記錄是爲何能匹配上的,響應的結果也是非常長,節選一部分:
{
"_index": "music",
"_type": "children",
"_id": "4",
"matched": true,
"explanation": {
"value": 0.9549814,
"description": "sum of:",
"details": [
{
"value": 0.62577873,
"description": "weight(content:wake in 0) [PerFieldSimilarity], result of:",
"details": [
{
"value": 0.62577873,
"description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
"details": [
{
"value": 0.6931472,
"description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
"details": [
{
"value": 1,
"description": "docFreq",
"details": []
},
{
"value": 2,
"description": "docCount",
"details": []
}
]
},
注意關鍵屬性matched,如果是true,則explanation會有非常詳細的信息,每個分詞後的關鍵詞,相關性得分是多少,都會詳細列舉出來,很多匹配過程的細節,都能在上面找到證據。如果是false,響應報文則是這樣:
{
"_index": "music",
"_type": "children",
"_id": "1",
"matched": false,
"explanation": {
"value": 0,
"description": "No matching clauses",
"details": []
}
}
這是一個非常實用的工具,研發過程中出現讓人困惑的搜索結果,都可以用它進行分析。
正排索引
ES在索引文檔時,會建立倒排索引,倒排索引的檢索性能非常高,但對排序來說,卻不是理想的結構。
因此ES索引文檔時,還會建立正排索引,即Doc Values,這是一種列式存儲結構,默認情況下每個字段都會存儲到Doc Values裏。
所以整個搜索排序過程中,正排搜索和倒排搜索是這樣配合的:
- 倒排索引負責關鍵詞的檢索,快速得到匹配的結果集。
- 正排索引完成排序、過濾、聚合的功能,得到期望的文檔順序。
應用場景
Elasticsearch中的Doc Values常被應用到以下場景:
- 對某個字段排序
- 對某個字段聚合
- 對某個字段過濾
- 某些與字段相關的計算、腳本執行等
性能
Doc Values是被保存到磁盤上的,如果os cache內存足夠,整個working set將自動緩存到內存中,性能非常高,如果內存不充裕,Doc Values會將其寫入到磁盤上。
整體來說,性能還是可以的,合理的os cache設置,可以極大地提高查詢的性能。
小結
本篇主要介紹了相關性評分算法的基礎知識,能夠使用工具查看評分的詳細過程,可以輔助解釋一些困惑的現象,最後簡單介紹了一下正排索引的應用場景。