elasticSearch(5.3.0)的評分機制的研究


1、  ElasticSearch的評分

在用ElasticSearch作為搜索引擎的時候,如果采用關鍵字進行查詢,ElasticSearch會對每個符合查詢條件的文檔進行評分,在5.3.0的版本中,默認采用的是BM25的評分函數,關於BM25的評分函數,網絡上有較多的講解,這里就不進行詳細說明,貼上幾個連接如下:

http://luokr.com/p/7

https://en.wikipedia.org/wiki/Okapi_BM25

https://www.elastic.co/guide/en/elasticsearch/guide/2.x/pluggable-similarites.html#bm25

在ElasticSearch5.3.0中采用的函數計算如下:

N表示將查詢關鍵字分詞后得到的N個term。

IDF=log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5))

tfNorm=(freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength))

 

docCount:查詢中滿足查詢條件的所有文檔

docFreq:滿足本條term的查詢文檔數目

 

IDF反映的是term的影響因子,如果docCount很大,docFreq很小,標示該term在doc之間具有很好的分辨力,當然IDF值也就越大。

 

freq:查詢term在本doc的field中出現的次數

K1:調優參數默認為1.2

b:調優參數,默認為0.75

fieldLength:是滿足查詢條件的doc的filed的長度

avgFieldLength:是滿足查詢條件的所有doc的filed的長度.

tfNorm反映的該term在所有滿足條件的doc中field中的重要性,一般來說,相同的freq 下,field的長度越短,那么取值就越高。

 

2、  Lucene中BM25的評分研究

在索引中插入3條數據,采用默認的

Analyzer analyzer = new StandardAnalyzer();

數據如下:

"text", "this hour chiness my book"

"text", "this is chiness chiness japan amc set the right context"

"text", "this  book chiness jack1 the right context"

 

在程序中,用"text": "chiness"進行搜索,並且把把日志輸出如下:

 

查找到的文檔總共有:3

---------------

0.16786805 = weight(text:chiness in 1) [BM25Similarity], result of:

  0.16786805 = score(doc=1,freq=2.0 = termFreq=2.0

), product of:

    0.13353139 = idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:

      3.0 = docFreq

      3.0 = docCount

    1.2571429 = tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:

      2.0 = termFreq=2.0

      1.2 = parameter k1

      0.75 = parameter b

      5.3333335 = avgFieldLength

      7.111111 = fieldLength

 

0.16786803

this is chiness chiness japan amc set the right context

---------------

0.14874382 = weight(text:chiness in 0) [BM25Similarity], result of:

  0.14874382 = score(doc=0,freq=1.0 = termFreq=1.0

), product of:

    0.13353139 = idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:

      3.0 = docFreq

      3.0 = docCount

    1.113924 = tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:

      1.0 = termFreq=1.0

      1.2 = parameter k1

      0.75 = parameter b

      5.3333335 = avgFieldLength

      4.0 = fieldLength

 

0.14874382

this hour chiness my book

---------------

0.1346556 = weight(text:chiness in 2) [BM25Similarity], result of:

  0.1346556 = score(doc=2,freq=1.0 = termFreq=1.0

), product of:

    0.13353139 = idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:

      3.0 = docFreq

      3.0 = docCount

    1.008419 = tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:

      1.0 = termFreq=1.0

      1.2 = parameter k1

      0.75 = parameter b

      5.3333335 = avgFieldLength

      5.2244897 = fieldLength

 

0.1346556

this  book chiness jack1 the right context

 

由日志可以知道,評分最高的為文檔1,其次為文檔0,最低的是文檔2,原因是文檔1中chiness出現了兩次,文檔0和2中都只出現了一次,但是由於文檔0的text的fieldLength比文檔2小,所以文檔0的評分比文檔2高。

 

avgFieldLength的長度可以知道是5.3333*3,約等於16,是因為this、is、the是停用詞,去除后的短語是"hour chiness my book","chiness chiness japan amc set  right context"和"book chiness jack1  right context".terms數量一共是16個,除以3那么就是5.3333。

問題來了

這邊有個問題,就是每個文檔的字段長度好像跟我們輸入的不一致,分別是

文檔1,長度是  7.111111

文檔0,長度是  4.0

文檔2,長度是  5.2244897

都不是整數,而且跟我們去除停用詞后的長度不一致,按道理將應該是6、4、5才對。

 

lucene為了降低存儲的空間,在存儲field的長度時,沒有存儲實際長度,而是存儲了一個byte類型的值(0-255),每個值對應了BM25Similarity有NORM_TABLE中的index,

 

在BM25Similarity有NORM_TABLE的float數組,實現了一個區間映射的功能。

 

/** Cache of decoded bytes. */

  private static final float[] NORM_TABLE = new float[256];

 

  static {

    for (int i = 1; i < 256; i++) {

      float f = SmallFloat.byte315ToFloat((byte) i);

      NORM_TABLE[i] = 1.0f / (f * f);

    }

    NORM_TABLE[0] = 1.0f / NORM_TABLE[255]; // otherwise inf

  }

 

輸出內容如下:

0    5.6493154E19

1    2.95147899E18

2    2.04963825E18

3    1.50585663E18

4    1.1529215E18

………………….

112    64.0

113    40.96

114    28.444445

115    20.897959

116    16.0

117    10.24

118    7.111111

119    5.2244897

120    4.0

                   ……………………

253    3.469447E-20

254    2.4093382E-20

255    1.770126E-20

 

 

反向操作吧,代碼如下:

for (int i = 0; i < 100; i++) {

      float x = 1.0f / i;

      float y = (float) Math.sqrt(x);

      System.out.println(i + "    " + SmallFloat.floatToByte315(y));

}

 

0    -1

1    124

2    121

3    120

4    120

5    119

6    118

7    118

8    117

9    117

10    117

11    116

12    116

13    116

14    116

15    116

16    116

17    115

18    115

19    115

如果長度是4,寫入值是120,長度是5,寫入值是119,長度為6和7,那么在存儲的時候寫入值是118,取值的時候,文檔2的取值就是NORM_TABLE[119],文檔1的取值是NORM_TABLE[118],文檔0的取值是NORM_TABLE[120]。

 

 

3、  ElasticSearch的評分注意點

在使用ElasticSearch提供搜索服務的時候,會發現一個很有意思的現象,在ElasticSearch中新建索引並且插入數據,命令如下

curl -XPUT 'http://127.0.0.1:9200/scoretest'

curl -XPUT 'http://127.0.0.1:9200/scoretest/scoretest/_mapping' -d '{"scoretest":{"properties":{"text":{"type":"text"}}}}'

curl -XPUT 'http://127.0.0.1:9200/scoretest/scoretest/1' -d '{"text":"this hour chiness my book"}'

curl -XPUT 'http://127.0.0.1:9200/scoretest/scoretest/2' -d '{"text":"this is chiness chiness japan amc set the right context"}'

curl -XPUT 'http://127.0.0.1:9200/scoretest/scoretest/3' -d '{"text":"this  book chiness jack1 the right context"}'

  執行查詢命令

curl -XGET 'http://127.0.0.1:9200/scoretest/scoretest/_search' -d '{"query":{"match":{"text":"chiness"}}}'

 

結果如下:

問題來了:

chines出現兩次的排名最靠前,chiness出現一次的,長度長的竟然比長度短的排名靠前,這個與我們想象中的不一致,

 

這次增加explain字段查看下分析過程。命令如下:

curl -XGET 'http://127.0.0.1:9200/scoretest/scoretest/_search' -d '{ "explain": true, "query":{"match":{"text":"chiness"}}}'

由於信息較多,就截取下主要的分析過程中的幾個參數:

 

 

docFreq

docCount

avgFieldLength

fieldLength

1

1

1

5

5.2244897

2

1

1

10

10.24

3

1

1

7

7.11111

好像這個四個參數的取值與章節2中完全不一致,

原因是,ElasticSearch在建立index的時候,默認自動回建立5個分片,在插入數據的時候,會根據一致性算法將文檔分配到某一個shard上,在進行搜索的時候,每個shard上獨自進行搜索評分,然后匯總后,根據_score進行排序,然后在返回給前端,我們可以看下上述三個文檔的分布,在我的實驗中分布如下:

1

_shard 3

2

_shard 4

3

_shard 2

 

所以對於index下同一個type下面的數據,最好在插入的時候,數據存放到同一個shard上,這個采用系統默認評分的結果才會保持正確。這里就用到了ES的_routing參數,默認情況下ES是根據doc的_id作為hash的key,其官網描述如下:

         https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-routing-field.html

重新測試下,這回指定_routing:

curl -XPUT 'http://127.0.0.1:9200/scoretestrouting'

curl -XPUT 'http://127.0.0.1:9200/scoretestrouting/scoretestrouting/_mapping' -d '{"scoretestrouting":{"_routing":{"required":true},"properties":{"text":{"type":"text"}}}}'

curl -XPUT 'http://127.0.0.1:9200/scoretestrouting/scoretestrouting/1' -d '{"text":"this hour chiness my book"}'

curl -XPUT 'http://127.0.0.1:9200/scoretestrouting/scoretestrouting/1?routing=wang' -d '{"text":"this hour chiness my book"}'

curl -XPUT 'http://127.0.0.1:9200/scoretestrouting/scoretestrouting/2?routing=wang' -d '{"text":"this is chiness chiness japan amc set the right context"}'

curl -XPUT 'http://127.0.0.1:9200/scoretestrouting/scoretestrouting/3?routing=wang' -d '{"text":"this  book chiness jack1 the right context"}'

搜索命令如下:

curl -XGET 'http://127.0.0.1:9200/scoretestrouting/scoretestrouting/_search?routing=wang' -d '{ "explain": true, "query":{"match":{"text":"chiness"}}}'

查看結果如下:

在添加"explain": true,看下詳細的評分計算過程:

 

docFreq

docCount

avgFieldLength

fieldLength

1

3

3

7.3333335

5.2244897

2

3

3

7.3333335

10.24

3

3

3

7.3333335

7.11111

 

 

在沒有指定_routing參數的情況下,使用_id代替或者父級文檔的_parent字段代替,

         注意點,在使用了指定_routing的情況下,在同一個index的下面,如果使用了不同的_routing,那么有可能存在兩個文檔具有相同的_id,但是存放在兩個不同的shard上。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM