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之 控制相關度原理講解
控制相關度
相關度評分背后的理論
如何計算評分的
Lucene 使用布爾模型(Boolean model) 查找匹配文檔 並主要的借鑒了 詞頻/逆向文檔頻率(term frequency/inverse document frequency) 和 向量空間模型(vector space model),同時加入 協調因子 字段長度歸一化 以及詞或查詢語句權重提升
- 布爾模型
就是在查詢中使用 AND 、 OR 和 NOT (與、或和非) 來匹配文檔 - 詞頻/逆向文檔頻率(TF/IDF)
一個文檔的相關度評分部分取決於每個查詢詞在文檔中的 權重 **
詞的權重**由三個因素決定 - 詞頻
詞在文檔中出現的頻度是多少? 頻度越高,權重 越高
tf(t in d) = √frequency 詞 t 在文檔 d 的詞頻( tf )是該詞在文檔中出現次數的平方根
- 逆向文檔頻率
詞在集合所有文檔里出現的頻率是多少?頻次越高,權重 越低
vidf(t) = 1 + log ( numDocs / (docFreq + 1)) 詞 t 的逆向文檔頻率( idf )是:索引中文檔數量除以所有包含該詞的文檔數,然后求其對數
- 字段長度歸一值
字段的長度是多少? 字段越短,字段的權重 越高 。
**norm(d) = 1 / √numTerms 字段長度歸一值( norm )是字段中詞數平方根的倒數 **
對於 not_analyzed 字符串字段的歸一值默認是禁用的
** 通過實例查詢,來看評分是如何計算的**
GET product/base/_search
{
"size": 1,
"explain": true,
"query": {
"match": {
"name": "上海"
}
}
}
展示出的explanation 內容
{
"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” 的二維查詢向量
假設我們有三個文檔
- I am happy in summer 。
- After Christmas I’m a hippopotamus 。
- 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 ) 可以為那些查詢詞包含度高的文檔提供獎勵,文檔里出現的查詢詞越多,它越有機會成為好的匹配結果。
索引時字段層權重提升
在索引時對這個字段上的數據進行權重的提升
不推薦這樣使用
- 會丟失長度歸一值的精度
- 索引后的文檔不可更改,需要重新創建文檔
- 索引時權重提升的字段有多個值,提升值會按照每個值來自乘,這會導致該字段的權重急劇上升。
查詢時權重提升
查詢腳本
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值的變化趨勢將是這樣的
- new_score = old_score * log(1 + number_of_votes)
帶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
}
}
}
}
}