ElasticSearch之 控制相關度原理講解

ElasticSearch 系列文章

1 ES 入門之一 安裝ElasticSearcha

2 ES 記錄之如何建立一個索引映射

3 ElasticSearch 學習記錄之Text keyword 兩種基本類型區別

4 ES 入門記錄之 match和term查詢的區別

5 ElasticSearch 學習記錄之ES幾種常見的聚合操做

6 ElasticSearch 學習記錄之父子結構的查詢

7 ElasticSearch 學習記錄之ES查詢添加排序字段和使用missing或existing字段查詢

8 ElasticSearch 學習記錄之ES高亮搜索

9 ElasticSearch 學習記錄之ES短語匹配基本用法

10 ElasticSearch 學習記錄之 分佈式文檔存儲往ES中存數據和取數據的原理

11 ElasticSearch 學習記錄之集羣分片內部原理

12 ElasticSearch 學習記錄之ES如何操做Lucene段

13 ElasticSearch 學習記錄之如任何設計可擴容的索引結構

14 ElasticSearch之 控制相關度原理講解







控制相關度

相關度評分背後的理論

如何計算評分的html

Lucene 使用布爾模型(Boolean model) 查找匹配文檔 並主要的借鑑了 詞頻/逆向文檔頻率(term frequency/inverse document frequency) 和 向量空間模型(vector space model),同時加入 協調因子 字段長度歸一化 以及詞或查詢語句權重提高node

  • 布爾模型
    就是在查詢中使用 AND 、 OR 和 NOT (與、或和非) 來匹配文檔
  • 詞頻/逆向文檔頻率(TF/IDF)
    一個文檔的相關度評分部分取決於每一個查詢詞在文檔中的 權重
    詞的權重由三個因素決定
  • 詞頻web

    詞在文檔中出現的頻度是多少? 頻度越高,權重 越高
    tf(t in d) = √frequency 詞 t 在文檔 d 的詞頻( tf )是該詞在文檔中出現次數的平方根算法

  • 逆向文檔頻率spring

    詞在集合全部文檔裏出現的頻率是多少?頻次越高,權重 越低
    vidf(t) = 1 + log ( numDocs / (docFreq + 1)) 詞 t 的逆向文檔頻率( idf )是:索引中文檔數量除以全部包含該詞的文檔數,而後求其對數api

  • 字段長度歸一值數組

    字段的長度是多少? 字段越短,字段的權重 越高 。
    norm(d) = 1 / √numTerms 字段長度歸一值( norm )是字段中詞數平方根的倒數 session

對於 not_analyzed 字符串字段的歸一值默認是禁用的
** 經過實例查詢,來看評分是如何計算的**app

GET product/base/_search
{
  "size": 1, 
  "explain": true,
  "query": {
    "match": {
      "name": "上海"
    }
  }
}

展現出的explanation 內容dom

{
  "took": 17,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 19,
    "max_score": 4.266216,
    "hits": [
      {
        "_shard": "[product_v3][3]",
        "_node": "N_gFl4xjTNmIxr9SY9FAQw",
        "_index": "product_v3",
        "_type": "base",
        "_id": "1-134473",
        "_score": 4.266216,
        "_source": {
          "productSource": 1,
          "departureCitys": [
            "上海"
          ],
          "keywords": [
            "上海",
            "哈哈"
          ],
          "pattern": "101",
          "buyQuantity": 34,
          "managerRecommend": "111。",
          "averageScore": 0,
          "themes": [
            {
              "category": "遊玩景點",
              "items": [
                {
                  "code": 4724,
                  "name": "太原+五臺山+大同+平遙"
                }
              ]
            }
          ],
          "installmentFlag": 0,
          "madeType": 0,
          "bussinessProductId": "1-134473",
          "attribute": 91,
          "supplierName": "春秋旅遊",
          "passbyCities": [
            "上海"
          ],
          "pictureLabels": [],
          "productId": 134473,
          "weight": 22,
          "picture": "http://webresourcetest.springtour.com/Images/gallery/201702/43362c75-91ae-43af-8cfd-85b43ec9199e_201702211501_500_350.jpg",
          "productThemes": [
            "太原+五臺山+大同+平遙"
          ],
          "brandId": 2,
          "name": "上海3日2晚上海名牌",
          "dayNum": 3,
          "online": 1
        },
        "_explanation": {
          "value": 4.266216,
          "description": "sum of:",
          "details": [
            {
              "value": 4.266216,
              "description": "weight(name:上海 in 54) [PerFieldSimilarity], result of:",
              "details": [
                {
                  "value": 4.266216,
                  "description": "score(doc=54,freq=2.0 = termFreq=2.0\n), product of:",
                  "details": [
                    {
                      "value": 2.730029,
                      "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                      "details": [
                        {
                          "value": 4,
                          "description": "docFreq",
                          "details": []
                        },
                        {
                          "value": 68,
                          "description": "docCount",
                          "details": []
                        }
                      ]
                    },
                    {
                      "value": 1.5626998,
                      "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
                      "details": [
                        {
                          "value": 2,
                          "description": "termFreq=2.0",
                          "details": []
                        },
                        {
                          "value": 1.2,
                          "description": "parameter k1",
                          "details": []
                        },
                        {
                          "value": 0.75,
                          "description": "parameter b",
                          "details": []
                        },
                        {
                          "value": 12.411765,
                          "description": "avgFieldLength",
                          "details": []
                        },
                        {
                          "value": 7.111111,
                          "description": "fieldLength",
                          "details": []
                        }
                      ]
                    }
                  ]
                }
              ]
            },
            {
              "value": 0,
              "description": "match on required clause, product of:",
              "details": [
                {
                  "value": 0,
                  "description": "# clause",
                  "details": []
                },
                {
                  "value": 1,
                  "description": "_type:base, product of:",
                  "details": [
                    {
                      "value": 1,
                      "description": "boost",
                      "details": []
                    },
                    {
                      "value": 1,
                      "description": "queryNorm",
                      "details": []
                    }
                  ]
                }
              ]
            }
          ]
        }
      }
    ]
  }
}

因爲展現的內容過多,咱們將其簡化 就會簡化到下面這樣的主幹

weight(text:上海 in 0) [PerFieldSimilarity]:  0.15342641 //   詞 fox 在文檔的內部 Lucene doc ID 爲 0 ,字段是 text 裏的最終評分
result of:
    fieldWeight in 0                         0.15342641
    product of:
        tf(freq=1.0), with freq of 1:        1.0 //詞 上海 在該文檔 text 字段中只出現了一次。
        idf(docFreq=1, maxDocs=1):           0.30685282 //上海 在全部文檔 text 字段索引的逆向文檔頻率。
        fieldNorm(doc=0):                    0.5 // 該字段的字段長度歸一值。
  • 可是問題來了,咱們一般來講都不是一個字段查詢,而是多個字段。這樣咱們就有一個合併多詞權重----- 向量空間模型(vector space model )
    向量空間模型是將文檔和查詢都以向量的形式表示
    向量實際上就是包含多個數的一維數組
    [1,2,5,22,3,8]
    在向量空間模型裏, 向量空間模型裏的每一個數字都表明一個詞的 權重 。
    若是咱們查詢 happy hippopotamus 由於happy 比較常見,因此這個詞的權重較低假設爲2。另一個詞hippopotamus 不常常見,因此權重交大假設爲5 。因此咱們建立一個二維向量[2,5] ——在座標系下做條直線,線的起點是 (0,0) 終點是 (2,5)
    happy hippopotamus」 的二維查詢向量

假設咱們有三個文檔

  1. I am happy in summer 。
  2. After Christmas I’m a hippopotamus 。
  3. The happy hippopotamus helped Harry 。

咱們爲每一個文檔建立包含每一個查詢此 的權重向量

  • 文檔 1: (happy,____________) —— [2,0]
  • 文檔 2: ( ___ ,hippopotamus) —— [0,5]
  • 文檔 3: (happy,hippopotamus) —— [2,5]

happy hippopotamus」 查詢及文檔向量

向量之間是能夠比較的,只要測量查詢向量和文檔向量之間的角度就能夠獲得每一個文檔的相關度

咱們只是利用二維的演示這個原理,其實在現實中 確定有多於兩個的。這樣的咱們可使用線性代數 ——做爲數學中處理向量的一個分支——爲咱們提供了計算兩個多維向量間角度工具
爲何向量之間的角度能夠表示他們的相關度 使用了 餘弦近似度(cosine similarity)。

Lucene 的實用評分函數

在一個多詞查詢中,ES是如何對這些多詞進行處理的呢?

Lucene 使用 布爾模型(Boolean model) 、 TF/IDF 以及 向量空間模型(vector space model) ,而後將它們組合到單個高效的包裏以收集匹配文檔並進行評分計算

咱們使用例子來看多詞查詢 是怎麼轉化爲基本查詢的

GET /my_index/doc/_search
{
  "query": {
    "match": {
      "text": "quick fox"
    }
  }
}
會在內部被重寫爲
GET /my_index/doc/_search
{
  "query": {
    "bool": {
      "should": [
        {"term": { "text": "quick" }},
        {"term": { "text": "fox"   }}
      ]
    }
  }
}

bool 查詢實現了布爾模型

  • 要是匹配到數據 咱們使用實用評分函數 進行計算評分

實用評分函數 計算公式

score(q,d)  =  //文檔 d 與查詢 q 的相關度評分。
            queryNorm(q)  //查詢歸一化 因子 
          · coord(q,d)    //協調 因子 
          · ∑ ( //查詢 q 中每一個詞 t 對於文檔 d 的權重和       
                tf(t in d)  //詞 t 在文檔 d 中的 詞頻 。 
              · idf(t)²   //詞 t 的 逆向文檔頻率    
              · t.getBoost() //查詢中使用的 boost(
                  · norm(t,d)    //是 字段長度歸一值 ,與 索引時字段層 boost (若是存在)的和
            ) (t in q)

查詢歸一因子 queryNorm
查詢歸一因子 ( queryNorm )試圖將查詢 歸一化 , 這樣就能將兩個不一樣的查詢結果相比較。

查詢協調
協調因子 ( coord ) 能夠爲那些查詢詞包含度高的文檔提供獎勵,文檔裏出現的查詢詞越多,它越有機會成爲好的匹配結果。

索引時字段層權重提高
在索引時對這個字段上的數據進行權重的提高
不推薦這樣使用

  1. 會丟失長度歸一值的精度
  2. 索引後的文檔不可更改,須要從新建立文檔
  3. 索引時權重提高的字段有多個值,提高值會按照每一個值來自乘,這會致使該字段的權重急劇上升。

查詢時權重提高

查詢腳本

GET /_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "title": {
              "query": "quick brown fox",
              "boost": 2 
            }
          }
        },
        {
          "match": { 
            "content": "quick brown fox"
          }
        }
      ]
    }
  }
}

** 將 boost 設置爲 2 ,並不表明最終的評分 _score 是原值的兩倍;實際的權重值會通過歸一化和一些其餘內部優化過程**

  • 提高索引權重
    當在多個索引中搜索時, 可使用參數 indices_boost 來提高整個索引的權重

    GET /docs_2014_*/_search
    {
    "indices_boost": {
    "docs_2014_10": 3,
    "docs_2014_09": 2
    },
    "query": {
    "match": {
    "text": "quick brown fox"
    }
    }
    }

  • t.getBoost()
    權重提高不會被應用於它在查詢表達式中出現的層,而是會被合併下轉至每一個詞中。 t.getBoost() 始終返回當前詞的權重或當前分析鏈上查詢的權重

使用查詢結構修改相關度

假如一個業務場景,咱們想搜索Apple,可是咱們可能會返回水果 食譜和公司,咱們可使用must_not語句來排除咱們不想要的,而將結果範圍縮小在Apple公司的相關的結果

GET /_search
{
  "query": {
    "bool": {
      "must": {
        "match": {
          "text": "apple"
        }
      },
      "must_not": {
        "match": {
          "text": "pie tart fruit crumble tree"
        }
      }
    }
  }
}

可是咱們會不會發現,使用這種方法是否是過於嚴格了?會不會使咱們遺漏一些咱們所須要的數據呢?

咱們樂意使用 權重提高查詢 方法

GET /_search
{
  "query": {
    "boosting": {
      "positive": {
        "match": {
          "text": "apple"
        }
      },
      "negative": {
        "match": {
          "text": "pie tart fruit crumble tree"
        }
      },
      "negative_boost": 0.5
    }
  }
}

操做的過程就是那些匹配 positive 查詢的文檔羅列出來,對於那些同時還匹配 negative 查詢的文檔將經過文檔的原始 _score 與 negative_boost 相乘的方式降級

function_score 查詢 的做用

  • 容許爲每一個與主查詢匹配的文檔應用一個函數, 以達到改變甚至徹底替換原始查詢評分 _score
  • 也可以使用過濾器對結果的子集應用不一樣的函數
    • 預約義的函數有下面這些
      • weight 權重,提高值
      • field_value_factor 使用這個值來修改_score
      • random_score ,每一個用戶產生隨機排序,對具體用戶,看到的順序始終是一致的
  • 衰減函數 -- lineear exp gauss
  • script_score 也能夠本身使用自定義腳本徹底控制平均分計算、

根據產品的屬性來提高權重

下面咱們使用下面的這個查詢來演示咱們提高權重的過程
使用function_score

GET  product/_search
{
  "query": {
    "function_score": {//function_score 查詢將主查詢和函數包括在內
      "query": {//先查詢主查詢
        "multi_match": {
          "query": "上海",
          "fields": ["name", "keywords"]
        }
      },
       "field_value_factor": {field_value_factor 函數會被應用到每一個與主 query 匹配的文檔
         "field": "averageScore"//使用這個字段對score值進行計算
       }
    }
  }
}
//每一個文檔的最終評分score作了下面的修改
new_score = old_score * number_of_votes
  • 這樣的話,咱們的score值會根據成這樣的比例增加

  • 咱們能夠根據更改modifyer 屬性來更改變換score函數的 類型
  • 假如咱們使用log1p 參數值,那麼咱們的公式會以下的
    • new_score = old_score * log(1 + number_of_votes)
      咱們的score值的變化趨勢將是這樣的

帶modifyer 參數的請求

GET /blogposts/post/_search
{
  "query": {
    "function_score": {
      "query": {
        "multi_match": {
          "query":    "popularity",
          "fields": [ "title", "content" ]
        }
      },
      "field_value_factor": {
        "field":    "votes",
        "modifier": "log1p" 
      }
    }
  }
}

修飾詞 modifyer 值能夠爲多種

  • none 默認
  • log 取對數值
  • log1p 加1 取對數值
  • log2p 加2 取對數值
  • ln 去天然對數
  • ln1p 加一 取天然對數
  • ln2p 加2 取天然對數
  • square 取平方
  • sqrt 開根號
  • reciprocal 倒數

factor

  • 使用指定字段與factor 的積來調節score的值
    咱們經過在factor屬性添加值來改變score

    GET product/_search
    {
    "query": {
    "function_score": {
    "query": {
    "multi_match": {
    "query": "上海",
    "fields": ["name","keywords" ]
    }
    },
    "field_value_factor": {
    "field": "buyQuantity",
    "modifier": "ln2p",
    "factor": 2
    }
    }
    }
    }

    使用過factor 的計算是這樣的
    new_score = old_score * log(2 + factor * buyQuantity)
  • 在函數圖表上顯示的話,就是這樣的

boost_mode

  • 此屬性控制 來控制函數和查詢評分score合併後的結果
    • multiply 與 評分score函數值的積(默認)
    • sum 和
    • min 較小的值
    • max 較大的值
    • replace 代替score
      請求參數
    GET product/_search
    {
    "query": {
    "function_score": {
    "query": {
    "multi_match": {
    "query": "上海",
    "fields": ["name","keywords" ]
    }
    },
    "field_value_factor": {
    "field": "buyQuantity",
    "modifier": "ln2p",
    "factor": 2
    },
    "boost_mode": "sum" //修改此參數
    }
    }
    }
    //如今咱們的計算得分的函數是這樣的
    new_score = old_score + log(2 + 2 * buyQuantity)

max_boost

  • 可使用 max_boost 參數限制一個函數的最大效果
過濾集提高權重

經過整合多個函數,來提高權重信息
整合多個函數的查詢

GET /_search
{
  "query": {
    "function_score": {
      "filter": { //function_score 查詢有個 filter 過濾器而不是 query 查詢
        "term": { "name": "上海" }
      },
      "functions": [ //functions 關鍵字存儲着一個將被應用的函數列表
        {
          "filter": { "term": { "name": "杭州" }}, 
          "weight": 1
        },
        {
          "filter": { "term": { "name": "北京" }}, 
          "weight": 1
        },
        {
          "filter": { "term": { "name": "三亞" }}, 
          "weight": 2  //三亞 比其餘特性更重要,因此它有更高 weight
        }
      ],
      "score_mode": "sum" score_mode 指定各個函數的值進行組合運算的方式。
    }
  }
}
  • 過濾和查詢
    • function_score 查詢接受 query 或 filter ,若是沒有特別指定,則默認使用 match_all 查詢
  • 函數functions
    • functions 關鍵字保持着一個將要被使用的函數列表
    • 函數只會被應用到那些與過濾器匹配的文檔
  • score_mode 評分模式
    • multiply 函數結果 積
    • sum 函數結果 和
    • avg 函數結果 平均值
    • max 函數結果 最大值
    • min 函數結果 最小
    • first 使用的首個函數的結構做爲結果
隨機評分

random_score 函數的做用
andom_score 函數會輸出一個 0 到 1 之間的數, 當種子 seed 值相同時,生成的隨機結果是一致的

GET /_search
{
  "query": {
    "function_score": {
      "filter": {
        "term": { "city": "Barcelona" }
      },
      "functions": [
        {
          "filter": { "term": { "features": "wifi" }},
          "weight": 1
        },
        {
          "filter": { "term": { "features": "garden" }},
          "weight": 1
        },
        {
          "filter": { "term": { "features": "pool" }},
          "weight": 2
        },
        {
          "random_score": { 
            "seed":  "the users session id" 
          }
        }
      ],
      "score_mode": "sum"
    }
  }
}
//  1 **random_score** 語句沒有任何過濾器 filter ,因此會被應用到全部文檔        
// 2  將用戶的會話 ID 做爲種子 seed ,讓該用戶的隨機始終保持一致,相同的種子 seed 會產生相同的隨機結果。
越近越好

function_score 查詢會提供一組 衰減函數(decay functions) , 讓咱們有能力在兩個滑動標準,如地點和價格,之間權衡
三種衰減函數

  • linear 線性
  • exp 指數
  • gauss 高斯函數
    三個函數接受下面的參數,做爲函數的變化曲線值
    origin
  • 中心點 或字段可能的最佳值,落在原點 origin 上的文檔評分 _score 爲滿分 1.0
    scale
  • 衰減率,即一個文檔從原點 origin 下落時,評分 _score 改變的速度
    decay
  • 從原點 origin 衰減到 scale 所得的評分 _score ,默認值爲 0.5
    offset
  • 以原點 origin 爲中心點,爲其設置一個非零的偏移量 offset 覆蓋一個範圍,而不僅是單個原點。在範圍 -offset <= origin <= +offset 內的全部評分 _score 都是 1.0
    下面的是衰減函數曲線

  • linear 線性函數
  • exp 指數函數 先快後慢
  • gauss 高斯函數 先慢後快最後慢

腳本實例

GET product/base/_search
{
  "query": {
    "function_score": {
      "query": {},
      "functions": [
        {
          "gauss": {
            "averageScore": {
              "origin": "100",
              "scale": "20",
              "offset": "50"
            }
          }
        },
        {
          "exp": {
            "productId": {
              "origin": "1500",
              "scale": "100",
              "offset": "50"
            }
          },
          "weight": 2
        }
      ]
    }
  }
}
腳本評分
可插拔的類似算法
  • ES默認使用 Lucene 的實用評分函數
  • 也支持其餘類型類似算法
    • BM25
    • Classic similarity
    • DFR similarity
    • DFI similarity
    • IB similarity
    • LM similarity
  • Okapi BM25
    • 此類似度算法是比較好的算法
    • BM25 源自 機率相關模型(probabilistic relevance model) ,而不是向量空間模型
  • 詞頻飽和度
    • 某個詞出現的次數對得分計算的做用
      TF/IDF 與 BM25 的詞頻飽和度 的
  • 字段長度歸一化
    • Lucene 會認爲較短字段比較長字段更重要
    • BM25 固然也認爲較短字段應該有更多的權重,可是它會分別考慮每一個字段內容的平均長度
  • BM 25 調優
    • BM 25 能夠提供參數進行調優
    • k1
      • 這個參數控制着詞頻結果在詞頻飽和度中的上升速度。默認值爲 1.2 。值越小飽和度變化越快,值越大飽和度變化越慢
    • b
      • 這個參數控制着字段長歸一值所起的做用, 0.0 會禁用歸一化, 1.0 會啓用徹底歸一化。默認值爲 0.75
更改類似度

在作字段映射的時候進行更改類似度

PUT /my_index
{
  "mappings": {
    "doc": {
      "properties": {
        "title": {
          "type":       "string",
          "similarity": "BM25" // title 字段使用 BM25 類似度算法
        },
        "body": {
          "type":       "string",
          "similarity": "default" 
          //    body 字段用默認類似度算法
        }
      }
  }
}

Elasticsearch 不支持更改已有字段的類似度算法 similarity 映射

配置BM25類似度

PUT /my_index
{
  "settings": {
    "similarity": {
      "my_bm25": { //建立一個基於內置 BM25 ,名爲 my_bm25 的自定義類似度算法
        "type": "BM25",
        "b":    0 
        //禁用字段長度規範化(field-length normalization)
      }
    }
  },
  "mappings": {
    "doc": {
      "properties": {
        "title": {
          "type":       "string",
          "similarity": "my_bm25"
          //title 字段使用自定義類似度算法 my_bm25 
        },
        "body": {
          "type":       "string",
          "similarity": "BM25" 
          //字段 body 使用內置類似度算法 BM25 
        }
      }
    }
  }
}
相關文章
相關標籤/搜索