Elasticsearch——評分機制詳解

前言

一個搜索引擎使用的時候必定需要排序這個模塊蹭秋,如果在不選擇按照某一字段排序的情況下扰付,都是按照打分的高低進行一個默認(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

https://www.cnblogs.com/jpfss/p/10775376.html

https://zhuanlan.zhihu.com/p/27951938

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末山橄,一起剝皮案震驚了整個濱河市垮媒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌航棱,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萌衬,死亡現(xiàn)場離奇詭異饮醇,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宗挥,“玉大人赡矢,你說我怎么就攤上這事∧焉螅” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長毁嗦。 經(jīng)常有香客問我,道長回铛,這世上最難降的妖魔是什么狗准? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮茵肃,結(jié)果婚禮上腔长,老公的妹妹穿的比我還像新娘。我一直安慰自己验残,他們只是感情好捞附,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般鸟召。 火紅的嫁衣襯著肌膚如雪胆绊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天药版,我揣著相機與錄音辑舷,去河邊找鬼。 笑死槽片,一個胖子當(dāng)著我的面吹牛何缓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播还栓,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼碌廓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了剩盒?” 一聲冷哼從身側(cè)響起谷婆,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辽聊,沒想到半個月后纪挎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡跟匆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年异袄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玛臂。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡烤蜕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出迹冤,到底是詐尸還是另有隱情讽营,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布泡徙,位于F島的核電站橱鹏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏锋勺。R本人自食惡果不足惜蚀瘸,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望庶橱。 院中可真熱鬧贮勃,春花似錦、人聲如沸苏章。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至泉孩,卻和暖如春硼端,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背寓搬。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工珍昨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人句喷。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓镣典,卻偏偏與公主長得像,于是被迫代替她去往敵國和親唾琼。 傳聞我的和親對象是個殘疾皇子兄春,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容