1. 程式人生 > 其它 >DAY 125 ES--打分機制

DAY 125 ES--打分機制

一、打分機制

.................

身為吃瓜群眾,要從專業的角度來分析,就事論事哈: 就搜尋結果本身而言,xx返回了正確的結果(是的,人家已經調整了,現在搜沒問題!)。因為返回的結果中,都包含了搜尋的關鍵字。而我們從邏輯上來看,這他孃的一堆廣告算是咋回事!這個吐槽是從使用者的角度出發的。很顯然,返回的結果中,尤其是前幾條,有時甚至是前幾頁,都跟我們想要的結果相差深遠! 進一步說,僅僅以二元的方式來考慮文件和查詢的匹配可能是有意義的,也就是百度搜索引擎返回了二元的匹配結果:是的,找到了,不,老孃沒找到!雖然返回了結果,其中也包含了我們想要的結果,即便你要在大堆的廣告中找正確的結果實屬不易,但就像大家都習慣了廣告中插播電視劇一樣,習慣就好嘛!xx從x的角度出發,為廣告的詞條增加權重,至於那個真正的結果,我擦,你也沒給我錢........ 而需要xx才能訪問的xx瀏覽器,在正確的給使用者返回二元結果之前,更多的考慮文件的相關性(relevancy),因為就某個結果而言,如果A文件要比B文件更和結果相關,那麼A文件在結果中就要比B文件靠前,再加上以其他的優化,最終將所有結果返回,而使用者最期待的那條結果很可能排在最高位,這豈不美哉? 確定文件和查詢有多麼相關的過程被稱為打分(scoring)

二 文件打分的運作機制:TF-IDF

Lucenees的打分機制是一個公式。將查詢作為輸入,使用不同的手段來確定每一篇文件的得分,將每一個因素最後通過公式綜合起來,返回該文件的最終得分。這個綜合考量的過程,就是我們希望相關的文件被優先返回的考量過程。在Lucenees中這種相關性稱為得分。 在開始計算得分之前,es使用了被搜尋詞條的頻率和它有多常見來影響得分,從兩個方面理解:

  • 一個詞條在某篇文件中出現的次數越多,該文件就越相關。

  • 一個詞條如果在不同的文件中出現的次數越多,它就越不相關!

我們稱之為TF-IDFTF是詞頻(term frequency),而IDF是逆文件頻率(inverse document frequency)。

2.1 詞頻:TF

考慮一篇文件得分的首要方式,是檢視一個詞條在文件中出現的次數,比如某篇文章圍繞es的打分展開的,那麼文章中肯定會多次出現相關字眼,當查詢時,我們認為該篇文件更符合,所以,這篇文件的得分會更高。 閒的蛋疼的可以Ctrl + f搜一下相關的關鍵詞(es,得分、打分)之類的試試。

2.2 逆文件頻率:IDF

相對於詞頻,逆文件頻率稍顯複雜,如果一個詞條在索引中的不同文件中出現的次數越多,那麼它就越不重要。 來個例子,示例地址

The rules-which require employees to work from 9 am to 9 pm
In the weeks that followed the creation of 996.ICU in March
The 996.ICU page was soon blocked on multiple platforms including the messaging tool WeChat and the UC Browser.

假如es索引中,有上述3篇文件:

  • 詞條ICU的文件頻率是2,因為它出現在2篇文件中,文件的逆源自得分乘以1/DFDF是該詞條的文件頻率,這就意味著,由於ICU詞條擁有更高的文件頻率,所以,它的權重會降低。

  • 詞條the的文件頻率是3,它在3篇文件中都出現了,注意:儘管the在後兩篇文件出都出現兩次,但是它的詞頻是還是3,因為,逆文件詞頻只檢查詞條是否出現在某篇文件中,而不檢查它在這篇文件中出現了多少次,那是詞頻該乾的事兒

逆文件詞頻是一個重要的因素,用來平衡詞條的詞頻。比如我們搜尋the 996.ICU。單詞the幾乎出現在所有的文件中(中文中比如),如果這個鬼東西要不被均衡一下,那麼the的頻率將完全淹沒996.ICU。所以,逆文件詞頻就有效的均衡了the這個常見詞的相關性影響。以達到實際的相關性得分將會對查詢的詞條有一個更準確地描述。 當詞頻和逆文件詞頻計算完成。就可以使用TF-IDF公式來計算文件的得分了。

三 Lucene評分公式

之前的討論Lucene預設評分公式被稱為TF-IDF,一個基於詞頻和逆文件詞頻的公式。Lucene實用評分公式如下:

你以為我會著重介紹這個該死的公式?! 我只能說,詞條的詞頻越高,得分越高;相似地,索引中詞條越罕見,逆文件頻率越高,其中再加商調和因子和查詢標準化,調和因子考慮了搜尋過多少文件以及發現了多少詞條;查詢標準化,是試圖讓不同的查詢結果具有可比性,這顯然.....很困難。 我們稱這種預設的打分方法是TF-IDF和向量空間模型(vector space model)的結合。

四 其他的打分方法

除了TF-IDF結合向量空間模型的實用評分模式,是esLucene最為主流的評分機制,但這並不是唯一的,除了TF-IDF這種實用模型之外,其他的模型包括:

  • Okapi BM25。

  • 隨機性分歧(Divergence from randomness),即DFR相似度。

  • LM Dirichlet相似度。

  • LM Jelinek Mercer相似度。

這裡簡要的介紹BM25幾種主要設定,即k1bdiscount_overlaps

  • k1和b是數值的設定,用於調整得分是如何計算的。

  • k1控制對於得分而言詞頻(TF)的重要性。

  • b是介於0 ~ 1之間的數值,它控制了文件篇幅對於得分的影響程度。

  • 預設情況下,k1設定為1.2,而b則被設定為0.75

  • discount_overlaps的設定用於告訴es,在某個欄位中,多少個分詞出現在同一位置,是否應該影響長度的標準化,預設值是true

五 配置打分模型

5.1 簡要配置BM25打分模型

BM25(是不是跟pm2.5好像!!!)是一種基於概率的打分框架。我們來簡要的配置一下:

PUT w2
{
"mappings": {
"doc": {
"properties": {
"title": {
"type": "text",
"similarity": "BM25"
}
}
}
}
}

PUT w2/doc/1
{
"title":"The rules-which require employees to work from 9 am to 9 pm"
}

PUT w2/doc/2
{
"title":"In the weeks that followed the creation of 996.ICU in March"
}

PUT w2/doc/3
{
"title":"The 996.ICU page was soon blocked on multiple platforms including the messaging tool WeChat and the UC Browser."
}

GET w2/doc/_search
{
"query": {
"match": {
"title": "the 996"
}
}
}

上例是通過similarity引數來指定打分模型。至於查詢,還是當資料量比較大的時候,多試幾次,比較容易發現不同之處。

5.2 為BM25配置高階的settings

PUT w3
{
"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"
}
}
}
}
}

PUT w3/doc/1
{
"title":"The rules-which require employees to work from 9 am to 9 pm"
}

PUT w3/doc/2
{
"title":"In the weeks that followed the creation of 996.ICU in March"
}

PUT w3/doc/3
{
"title":"The 996.ICU page was soon blocked on multiple platforms including the messaging tool WeChat and the UC Browser."
}

GET w3/doc/_search
{
"query": {
"match": {
"title": "the 996"
}
}
}

5.3 配置全域性打分模型

如果我們要使用某種特定的打分模型,並且希望應用到全域性,那麼就在elasticsearch.yml配置檔案中加入:

index.similarity.default.type: BM25

六 boosting

boosting是一個用來修改文件相關性的程式。boosting有兩種型別:

  • 索引的時候,比如我們在定義mappings的時候。

  • 查詢一篇文件的時候。

以上兩種方式都可以提升一個篇文件的得分。需要注意的是:在索引期間修改的文件boosting是儲存在索引中的,要想修改boosting必須重新索引該篇文件

6.1 索引期間的boosting

啥也不說了,都在酒裡!上程式碼:

PUT w4
{
"mappings": {
"doc": {
"properties": {
"name": {
"boost": 2.0,
"type": "text"
},
"age": {
"type": "long"
}
}
}
}
}

一勞永逸是沒錯,但一般不推薦這麼玩。

原因之一是因為一旦對映建立完成,那麼所有name欄位都會自動擁有一個boost值。要想修改這個值,那就必須重新索引文件。 另一個原因是,boost值是以降低精度的數值儲存在Lucene內部的索引結構中。只有一個位元組用於儲存浮點型數值(存不下就損失精度了),所以,計算文件的最終得分時可能會損失精度。 最後,boost是應用與詞條的。因此,再被boost的欄位中如果匹配上了多個詞條,就意味著計算多次的boost,這將會進一步增加欄位的權重,可能會影響最終的文件得分。 現在我們再來介紹另一種方式。

6.2 查詢期間的boosting

es中,幾乎所有的查詢型別都支援boost,正如你想象的那些match、multi_match等等。 來個示例,在查詢期間,使用match查詢進行boosting

PUT w5
{
"mappings":{
"doc":{
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"content": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
}

PUT w5/doc/1
{
"title":"Lucene is cool",
"content": "Lucene is cool"
}

PUT w5/doc/2
{
"title":"Elasticsearch builds on top of lucene",
"content":"Elasticsearch builds on top of lucene"
}

PUT w5/doc/3
{
"title":"Elasticsearch rocks",
"content":"Elasticsearch rocks"
}

來查詢:

GET w5/doc/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title":{
"query": "elasticserach rocks",
"boost": 2.5
}
}
},
{
"match": {
"content": "elasticserach rocks"
}
}
]
}
}
}

就對於最終得分而言,content欄位,加了boosttitle查詢更有影響力。也只有在bool查詢中,boost更有意義。

6.3 跨越多個欄位的查詢

boost也可以用於multi_match查詢。

GET w5/doc/_search
{
"query": {
"multi_match": {
"query": "elasticserach rocks",
"fields": ["title", "content"],
"boost": 2.5
}
}
}

除此之外,我們還可以使用特殊的語法,只為特定的欄位指定一個boost。通過在欄位名稱後新增一個^符號和boost的值。告訴es只需對那個欄位進行boost

GET w5/doc/_search
{
"query": {
"multi_match": {
"query": "elasticserach rocks",
"fields": ["title^3", "content"]
}
}
}

上例中,title欄位被boost了3倍。 需要注意的是:在使用boost的時候,無論是欄位或者詞條,都是按照相對值來boost的,而不是乘以乘數。如果對於所有的待搜尋詞條boost了同樣的值,那麼就好像沒有boost一樣(廢話,就像大家都同時長高一米似的)!因為Lucene會標準化boost的值。如果boost一個欄位4倍,不是意味著該欄位的得分就是乘以4的結果。所以,如果你的得分不是按照嚴格的乘法結果,也不要擔心。

七 使用“解釋”來理解文件是如何評分的

一切都不是你想的那樣!是的,在es中,一個文件要比另一個文件更符合某個查詢很可能跟我們想象的不太一樣! 這一小節,我們來研究下esLucene內部使用了怎樣的公式來計算得分。 我們通過explain=true來告訴es,你要給灑家解釋一下為什麼這個得分是這樣的?!背後到底以有什麼py交易! 比如我們來查詢:

GET py1/doc/_search
{
"query": {
"match": {
"title": "北京"
}
},
"explain": true,
"_source": "title",
"size": 1
}

由於結果太長,我們這裡對結果進行了過濾("size": 1返回一篇文件),只檢視指定的欄位("_source": "title"只返回title欄位)。 看結果:

{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 24,
"max_score" : 4.9223156,
"hits" : [
{
"_shard" : "[py1][1]",
"_node" : "NRwiP9PLRFCTJA7w3H9eqA",
"_index" : "py1",
"_type" : "doc",
"_id" : "NIjS1mkBuoj17MYtV-dX",
"_score" : 4.9223156,
"_source" : {
"title" : "大寫的尷尬 插混為啥在北京不受待見?"
},
"_explanation" : {
"value" : 4.9223156,
"description" : "weight(title:北京 in 36) [PerFieldSimilarity], result of:",
"details" : [
{
"value" : 4.9223156,
"description" : "score(doc=36,freq=1.0 = termFreq=1.0\n), product of:",
"details" : [
{
"value" : 4.562031,
"description" : "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
"details" : [
{
"value" : 4.0,
"description" : "docFreq",
"details" : [ ]
},
{
"value" : 430.0,
"description" : "docCount",
"details" : [ ]
}
]
},
{
"value" : 1.0789746,
"description" : "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
"details" : [
{
"value" : 1.0,
"description" : "termFreq=1.0",
"details" : [ ]
},
{
"value" : 1.2,
"description" : "parameter k1",
"details" : [ ]
},
{
"value" : 0.75,
"description" : "parameter b",
"details" : [ ]
},
{
"value" : 12.1790695,
"description" : "avgFieldLength",
"details" : [ ]
},
{
"value" : 10.0,
"description" : "fieldLength",
"details" : [ ]
}
]
}
]
}
]
}
}
]
}
}

在新增的_explanation欄位中,可以看到value值是4.9223156,那麼是怎麼算出來的呢? 來分析,分詞北京在描述欄位(title)出現了1次,所以TF的綜合得分經過"description" : "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:"計算,得分是1.0789746 那麼逆文件詞頻呢?根據"description" : "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:"計算得分是4.562031 所以最終得分是:

1.0789746 * 4.562031 = 4.9223155734126

結果在四捨五入後就是4.9223156 需要注意的是,explain的特性會給es帶來額外的效能開銷。所以,除了在除錯時可以使用,生產環境下,應避免使用explain