前言
一個搜索引擎使用的時候必定需要排序這個模塊蹭秋,如果在不選擇按照某一字段排序的情況下扰付,都是按照打分的高低進行一個默認(rèn)排序的,所以如果正式使用的話仁讨,必須對默認(rèn)排序的打分策略有一個詳細(xì)的了解才可以羽莺,否則被問起來為什么這個在前面,那個在后面洞豁?
評分模型
將查詢作為輸入盐固,將每一個因素最后通過公式綜合起來,返回該文檔的最終得分丈挟。這個綜合考量的過程刁卜,就是將相關(guān)的文檔被優(yōu)先返回的考量過程。
Elasticsearch是基于Lucene的曙咽,所以它的評分機制也是基于Lucene的蛔趴。在Lucene中把這種相關(guān)性稱為得分(score),確定文檔和查詢有多大相關(guān)性的過程被稱為打分(scoring)例朱。
ES最常用的評分模型是 TF/IDF和BM25夺脾,TF-IDF屬于向量空間模型之拨,而BM25屬于概率模型茉继,但是他們的評分公式差別并不大咧叭,都使用IDF方法和TF方法的某種乘積來定義單個詞項的權(quán)重,然后把和查詢匹配的詞項的權(quán)重相加作為整篇文檔的分?jǐn)?shù)烁竭。
在ES 5.0版本之前使用了TF/IDF算法實現(xiàn)菲茬,而在5.0之后默認(rèn)使用BM25方法實現(xiàn)。
相關(guān)性算分
relevance score相關(guān)性算分:簡單來說派撕,就是計算出婉弹,一個索引中的文本,與搜索文本终吼,他們之間的關(guān)聯(lián)匹配程度镀赌。
通過倒排索引可以獲取與查詢語句相匹配的文檔列表,那么如何將最符合用戶查詢需求的文檔放到前列呢际跪?
本質(zhì)是一個排序問題商佛,排序的依據(jù)是相關(guān)性算分。
Elasticsearch使用的是 term frequency/inverse document frequency算法姆打,簡稱為TF/IDF算法良姆。TF詞頻(Term Frequency),IDF逆向文件頻率(Inverse Document Frequency)
相關(guān)性算分的幾個重要概念如下:
-
Term Frequency(TF)詞頻:即單詞在該文檔中出現(xiàn)額次數(shù)幔戏,詞頻越高玛追,相關(guān)度越高。
-
Inverse Document Frequency(IDF)逆向文檔頻率:與文檔頻率相反闲延,簡單理解為1/DF痊剖。即單詞出現(xiàn)的文檔數(shù)越少,相關(guān)度越高垒玲。
Document Frequency(DF)文檔頻率:即單詞出現(xiàn)的文檔數(shù)陆馁。
Field-length Norm:文檔越短,相關(guān)性越高侍匙,field長度氮惯,field越長,相關(guān)度越弱
ES目前主要有兩個相關(guān)性算分模型想暗,如下:
- TF/IDF 模型
- BM25 模型妇汗,5.x之后的默認(rèn)模型
相關(guān)性算分-TF/IDF 模型
-
TF/IDF模型是Lucene的經(jīng)典模型,其計算公式如下:
-
可以通過explain參數(shù)來查看具體的計算方式说莫,但要注意:
- es的算分是按照shard進行的杨箭,即shard的分?jǐn)?shù)計算是相互獨立的,所以在使用explain的時候要注意分片數(shù)储狭。
-
可以設(shè)置索引的分片數(shù)為1來避免這個問題互婿。
相關(guān)性算分-BM25 模型
-
BM25 模型中BM指的Best Match捣郊,25指的是在BM25中的計算公式是第25次迭代優(yōu)化,是針對TF/IDF的一個優(yōu)化慈参,其計算公式如下:
對IDF的改良
BM25中的IDF公式為:
原版BM25的log中是沒有加1的呛牲,Lucene為了防止產(chǎn)生負(fù)值,做了一點小優(yōu)化驮配。雖然對公式進行了更改娘扩,但其實和原來的公式?jīng)]有實質(zhì)性的差異,下面是新舊函數(shù)曲線對比:
對TF的改良
BM25中TF的公式為:
其中tf是傳統(tǒng)的詞頻值壮锻。先來看下改良前后的函數(shù)曲線對比(下圖中k=1.2):
可以看到琐旁,傳統(tǒng)的tf計算公式中,詞頻越高猜绣,tf值就越大灰殴,沒有上限。但BM中的tf掰邢,隨著詞頻的增長牺陶,tf值會無限逼近(k+1),相當(dāng)于是有上限的尸变。這就是二者的區(qū)別义图。一般 k取 1.2,Lucene中也使用1.2作為 k 的默認(rèn)值召烂。
在傳統(tǒng)的計算公式中碱工,還有一個norm。BM25將這個因素加到了TF的計算公式中奏夫,結(jié)合了norm因素的BM25中的TF計算公式為:
和之前相比怕篷,就是給分母上面的 k 加了一個乘數(shù) (1.0?b+b?L)(1.0?b+b?L)。 其中的 L 的計算公式為:
其中酗昼,|d|是當(dāng)前文檔的長度廊谓,avgDl 是語料庫中所有文檔的平均長度。
b 是一個常數(shù)麻削,用來控制 L 對最總評分影響的大小蒸痹,一般取0~1之間的數(shù)(取0則代表完全忽略 L )。Lucene中 b 的默認(rèn)值為 0.75呛哟。
通過這些細(xì)節(jié)上的改良叠荠,BM25在很多實際場景中的表現(xiàn)都優(yōu)于傳統(tǒng)的TF-IDF,所以從Lucene 6.0.0版本開始扫责,上位成為默認(rèn)的相似度評分算法榛鼎。
配置
{
"settings":{
"index":{
"analysis":{
"analyzer":"ik_smart"
}
},
"similarity":{
"my_custom_similarity":{
"type":"BM25",
"k1":1.2,
"b":0.75,
"discount_overlaps":false
}
}
},
"mappings":{
"doc":{
"properties":{
"title":{
"type":"text",
"similarity":"my_custom_similarity"
}
}
}
}
}
上例是通過similarity屬性來指定打分模型,用到了以下三個參數(shù):
- k1:控制對于得分而言詞頻(TF)的重要性,默認(rèn)為1.2者娱。
- b:是介于0 ~ 1之間的數(shù)值抡笼,控制文檔篇幅對于得分的影響程度,默認(rèn)為0.75黄鳍。
- discount_overlaps:在某個字段中推姻,多少個分詞出現(xiàn)在同一位置,是否應(yīng)該影響長度的標(biāo)準(zhǔn)化际起,默認(rèn)值是true拾碌。
如果我們要使用某種特定的打分模型,并且希望應(yīng)用到全局街望,那么就在elasticsearch.yml配置文件中加入:
index.similarity.default.type: BM25
評分中的boosting
通過boosting可以人為控制某個字段的在評分過程中的比重,有兩種類型:
- 索引期間的boosting
- 查詢期間的boosting
通過在mapping中設(shè)置boost參數(shù)弟跑,可以在索引期間改變字段的評分權(quán)重:
{
"mappings":{
"doc":{
"properties":{
"name":{
"boost":2.0,
"type":"text"
},
"age":{
"type":"long"
}
}
}
}
}
需要注意的是:在索引期間修改的文檔boosting是存儲在索引中的灾前,要想修改boosting必須重新索引該篇文檔。
一旦映射建立完成孟辑,那么所有name字段都會自動擁有一個boost值哎甲,并且是以降低精度的數(shù)值存儲在Lucene內(nèi)部的索引結(jié)構(gòu)中。只有一個字節(jié)用于存儲浮點型數(shù)值(存不下就損失精度了)饲嗽,計算文檔的最終得分時可能會損失精度炭玫。
另外,boost是應(yīng)用與詞條的貌虾。因此吞加,再被boost的字段中如果匹配上了多個詞條,就意味著計算多次的boost尽狠,這將會進一步增加字段的權(quán)重衔憨,可能會影響最終的文檔得分。
查詢期間的boosting可以避免上述問題袄膏。
幾乎所有的查詢類型都支持boost践图,例如:
GET /book/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"name":{
"query": "java",
"boost": 2.5
}
}
},
{
"match": {
"description": "java 程序員"
}
}
]
}
}
}
就對于最終得分而言,加了boost的name查詢更有影響力沉馆。也只有在bool查詢中码党,boost更有意義。
boost也可以用于multi_match查詢斥黑。
GET /book/_search
{
"query":{
"multi_match":{
"query":"java 程序員",
"fields":[
"name",
"description"
],
"boost":2.5
}
}
}
除此之外揖盘,我們還可以使用特殊的語法,只為特定的字段指定一個boost心赶。通過在字段名稱后添加一個^符號和boost的值扣讼。告訴ES只需對那個字段進行boost:
GET /book/_search
{
"query":{
"multi_match":{
"query":"java 程序員",
"fields":[
"name^3",
"description"
]
}
}
}
上例中,title字段被boost了3倍缨叫。
需要注意的是:在使用boost的時候椭符,無論是字段或者詞條荔燎,都是按照相對值來boost的,而不是乘以乘數(shù)销钝。如果對于所有的待搜索詞條boost了同樣的值有咨,那么就好像沒有boost一樣。因為Lucene會標(biāo)準(zhǔn)化boost的值蒸健。如果boost一個字段4倍座享,不是意味著該字段的得分就是乘以4的結(jié)果。
explain評分細(xì)節(jié)
ES背后的評分過程比我們想象的要復(fù)雜似忧,有時候某個查詢結(jié)果可能跟我們的預(yù)期不太一樣渣叛,這時候可以通過explain讓ES解釋一下評分細(xì)節(jié)。
GET /book/_search
{
"query": {
"match": {
"name": "spring"
}
},
"explain": true,
"_source": "name",
"size": 1
}
由于結(jié)果太長盯捌,我們這里對結(jié)果進行了過濾("size": 1返回一篇文檔)淳衙,只查看指定的字段("_source": "name"只返回name字段)。
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.9331132,
"hits" : [
{
"_shard" : "[book][0]",
"_node" : "jSOjG5zoTwuvHsd5KJTUZw",
"_index" : "book",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.9331132,
"_source" : {
"name" : "spring開發(fā)基礎(chǔ)"
},
"_explanation" : {
"value" : 0.9331132,
"description" : "weight(name:spring in 2) [PerFieldSimilarity], result of:",
"details" : [
{
"value" : 0.9331132,
"description" : "score(freq=1.0), product of:",
"details" : [
{
"value" : 2.2,
"description" : "boost",
"details" : [ ]
},
{
"value" : 0.98082924,
"description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:",
"details" : [
{
"value" : 1,
"description" : "n, number of documents containing term",
"details" : [ ]
},
{
"value" : 3,
"description" : "N, total number of documents with field",
"details" : [ ]
}
]
},
{
"value" : 0.43243244,
"description" : "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:",
"details" : [
{
"value" : 1.0,
"description" : "freq, occurrences of term within document",
"details" : [ ]
},
{
"value" : 1.2,
"description" : "k1, term saturation parameter",
"details" : [ ]
},
{
"value" : 0.75,
"description" : "b, length normalization parameter",
"details" : [ ]
},
{
"value" : 3.0,
"description" : "dl, length of field",
"details" : [ ]
},
{
"value" : 2.6666667,
"description" : "avgdl, average length of field",
"details" : [ ]
}
]
}
]
}
]
}
}
]
}
}
在新增的_explanation字段中饺著,可以看到value值是0.9331132箫攀,那么是怎么算出來的呢?
分詞spring在描述字段(name)出現(xiàn)了1次幼衰,所以TF的綜合得分經(jīng)過"description" : "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:"計算靴跛,得分是0.43243244。
那么逆文檔詞頻呢渡嚣?根據(jù)"description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:"計算得分是0.98082924梢睛。
需要注意的是,explain的特性會給ES帶來額外的性能開銷严拒,一般只在調(diào)試時使用扬绪。
分析一個document是如何被匹配上的
GET /book/_explain/3
{
"query": {
"match": {
"description": "java程序員"
}
}
}
Doc value
搜索的時候,要依靠倒排索引裤唠;排序的時候挤牛,需要依靠正排索引,看到每個document的每個field种蘸,然后進行排序墓赴,所謂的正排索引,其實就是doc values航瞭。
在建立索引的時候诫硕,一方面會建立倒排索引,以供搜索用刊侯;一方面會建立正排索引章办,也就是doc values,以供排序,聚合藕届,過濾等操作使用挪蹭。
doc values是被保存在磁盤上的,此時如果內(nèi)存足夠休偶,os會自動將其緩存在內(nèi)存中梁厉,性能還是會很高;如果內(nèi)存不足夠踏兜,os會將其寫入磁盤上词顾。
DocValues默認(rèn)是啟用的,可以在創(chuàng)建索引的時候關(guān)閉碱妆,如果后面要開啟DocValues肉盹,需要做reindex操作。
參考:
https://www.elastic.co/guide/cn/elasticsearch/guide/current/scoring-theory.html
https://blog.csdn.net/qq_29860591/article/details/109574595
http://www.reibang.com/p/2624f61f1d02
http://www.dtmao.cc/news_show_378736.shtml
https://blog.csdn.net/molong1208/article/details/50623948
https://www.cnblogs.com/Neeo/articles/10721071.html