一、function_score 簡介
function_score內容較多,此篇主要是對function_score內容做系統性的講解,之后會出幾篇實際應用的方法,參考以下鏈接
ElasticSearch - function_score (field_value_factor具體實例)(https://blog.csdn.net/weixin_40341116/article/details/80913067)
ElasticSearch - function_score (weight具體實例)(https://blog.csdn.net/weixin_40341116/article/details/80931573)
ElasticSearch - function_score (衰減函數 linear、exp、gauss 具體實例)(https://blog.csdn.net/weixin_40341116/article/details/81003513)
1. 在使用ES進行全文搜索時,搜索結果默認會以文檔的相關度進行排序,而這個 "文檔的相關度",是可以透過 function_score 自己定義的,也就是說我們可以透過使用function_score,來控制 "怎麽樣的文檔相關度更高" 這件事
- function_score是專門用於處理文檔_score的DSL,它允許爲每個主查詢query匹配的文檔應用加強函數, 以達到改變原始查詢評分 score的目的
- function_score會在主查詢query結束后對每一個匹配的文檔進行一系列的重打分操作,能夠對多個字段一起進行綜合評估,且能夠使用 filter 將結果划分爲多個子集 (每個特性一個filter),並爲每個子集使用不同的加強函數
2. function_score 提供了幾種加強_score計算的函數
weight : 設置一個簡單而不被規范化的權重提升值
- weight加強函數和 boost參數類似,可以用於任何查詢,不過有一點差別是weight不會被Lucene nomalize成難以理解的浮點數,而是直接被應用 (boost會被nomalize)
- 例如當 weight 爲 2 時,最終結果爲new_score = old_score * 2
field_value_factor : 將某個字段的值乘上old_score
- 像是將 字段shareCount 或是 字段likiCount 作爲考慮因素,new_score = old_score * 那個文檔的likeCount的值
random_score : 爲每個用戶都使用一個不同的隨機評分對結果排序,但對某一具體用戶來說,看到的順序始終是一致的
衰減函數 (linear、exp、guass) : 以某個字段的值為基准,距離某個值越近得分越高
script_score : 當需求超出以上范圍時,可以用自定義腳本完全控制評分計算,不過因為還要額外維護腳本不好維護,因此盡量使用ES提供的評分函數,需求真的無法滿足再使用script_score
3. function_scroe其他輔助的參數
- boost_mode : 決定 old_score 和 加強score 如何合併
- multiply(默認) : new_score = old_score * 加強score
- sum : new_score = old_score + 加強score
- min : old_score 和 加強score 取較小值,new_score = min(old_score, 加強score)
- max : old_score 和 加強score 取較大值,new_score = max(old_score, 加強score)
- replace : 加強score直接替換掉old_score,new_score = 加強score
- score_mode : 決定functions裡面的加強score們怎麽合併,會先合併加強score們成一個總加強score,再使用總加強score去和old_score做合併,換言之就是會先執行score_mode,再執行boost_mode
- multiply (默認)
- sum
- avg
- first : 使用首個函數(可以有filter,也可以沒有)的結果作為最終結果
- max
- min
- max_boost : 限制加強函數的最大效果,就是限制加強score最大能多少,但要注意不會限制old_score
- 如果加強score超過了max_boost限制的值,會把加強score的值設成max_boost的值
- 假設加強score是5,而max_boost是2,因為加強score超出了max_boost的限制,所以max_boost就會把加強score改為2
- 簡單的說,就是加強score = min(加強score, max_boost)
4. function_score查詢模板
如果要使用function_score改變分數,要使用function_score查詢
簡單的說,就是在一個function_score內部的query的全文搜索得到的_score基礎上,給他加上其他字段的評分標准,就能夠得到把 "全文搜索 + 其他字段" 綜合起來評分的效果
單個加強函數的查詢模板
GET 127.0.0.1/mytest/doc/_search { "query": { "function_score": { "query": {.....}, //主查詢,查詢完后這裡自己會有一個評分,就是old_score "field_value_factor": {...}, //在old_score的基礎上,給他加強其他字段的評分,這裡會產生一個加強score ,如果只有一個加強function時,直接將加強函數名寫在query下面就可以了 "boost_mode": "multiply", //指定用哪種方式結合old_score和加強score成為new_score "max_boost": 1.5 //限制加強score的最高分,但是不會限制old_score } } }
多個加強函數的查詢模板
如果有多個加強函數,那就要使用functions來包含這些加強函數們,functions是一個數組,裡面放著的是將要被使用的加強函數列表
可以為functions裡的加強函數指定一個filter,這樣做的話,只有在文檔滿足此filter的要求,此filter的加強函數才會應用到文擋上,也可以不指定filter,這樣的話此加強函數就會應用到全部的文擋上
一個文檔可以一次滿足多條加強函數和多個filter,如果一次滿足多個,那麽就會產生多個加強score,因此ES會使用score_mode定義的方式來合併這些加強score們,得到一個總加強score,得到總加強score之后,才會再使用boost_mode定義的方式去和old_score做合併
像是下面的例子,field_value_factor和gauss這兩個加強函數會應用到所有文檔上,而weight只會應用到滿足filter的文檔上,假設有個文檔滿足了filter的條件,那他就會得到3個加強score,這3個加強score會使用sum的方式合併成一個總加強score,然后才和old_score使用multiply的方式合併
GET 127.0.0.1/mytest/doc/_search { "query": { "function_score": { "query": {.....}, "functions": [ //可以有多個加強函數(或是filter+加強函數),每一個加強函數會產生一個加強score,因 此functions會有多個加強score { "field_value_factor": ... }, { "gauss": ... }, { "filter": {...}, "weight": ... } ], "score_mode": "sum", //決定加強score們怎麽合併, "boost_mode": "multiply" //決定總加強score怎麼和old_score合併 } } }
5. 不要執著在調整function_score上
-
文檔相關度的調整非常玄,"最相關的文檔" 是一個難以觸及的模糊概念,每個人對文檔排序有著不同的想法,這很容易使人陷入持續反覆調整,但是確沒有明顯的進展
-
為了避免跳入這種死循環,在調整function_score時,一定要搭配監控用戶操作,才有意義
- 像是如果返回的文檔是用戶想要的高相關的文檔,那麽用戶就會選擇前10個中的一個文檔,得到想要的結果,反之,用戶可能會來回點擊,或是嘗試新的搜索條件
- 一旦有了這些監控手段,想要調適完美的function_score就不是問題
-
因此調整function_score的重點在於,要透過監控用戶、和用戶互動,慢慢去調整我們的搜索條件,而不要妄想一步登天,第一次就把文檔的相關度調整到最好,這幾乎是不可能的,因為,連用戶自己也不知道他自己想要什麽
二、具體實例
1.首先准備數據和索引,在ES插入三筆數據,其中title是text類型,like是integer類型(代表點贊量)
{ "title": "ES 入門", "like": 2 }
{ "title": "ES 進階", "like": 5 }
{ "title": "ES 最高難度", "like": 10 }
2.先使用一般的query,查看普通的查詢的評分會是如何
GET 127.0.0.1/mytest/doc/_search
{
"query": {
"match": {
"title": "ES"
}
}
}
"hits": [
{
"_score": 0.2876821,
"_source": { "title": "ES 入門", "like": 2 }
},
{
"_score": 0.20309238,
"_source": { "title": "ES 進階", "like": 5 }
},
{
"_score": 0.16540512,
"_source": { "title": "ES 最高難度", "like": 10 }
}
]
3.使用function_score 的 field_value_factor改變_score,將old_score乘上like的值
- 本來 "ES最高難度" 的score是0.16540512,經過field_value_factor的改變,乘上了那個文檔中的like值(10)之后,新的score變為 1.6540513
GET 127.0.0.1/mytest/doc/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "ES"
}
},
"field_value_factor": {
"field": "like"
}
}
}
}
"hits": [
{
"_score": 1.6540513, //原本是0.16540512
"_source": { "title": "ES 最高難度", "like": 10 }
},
{
"_score": 1.0154619, //原本是0.20309238
"_source": { "title": "ES 進階", "like": 5 }
},
{
"_score": 0.5753642, //原本是0.2876821
"_source": { "title": "ES 入門", "like": 2 }
}
]
4.加上max_boost,限制field_value_factor的最大加強score
-
可以看到ES入門的加強score是2,在max_boost限制裡,所以不受影響
-
而ES進階和ES最高難度的field_value_factor函數產生的加強score因為超過max_boost的限制,所以被設為3
{
"query": {
"function_score": {
"query": {
"match": {
"title": "ES"
}
},
"field_value_factor": {
"field": "like"
},
"max_boost": 3
}
}
}
"hits": [
{
"_score": 0.6092771, //原本是0.20309238
"_source": { "title": "ES 進階", "like": 5 }
},
{
"_score": 0.5753642, //原本是0.2876821
"_source": { "title": "ES 入門", "like": 2 }
},
{
"_score": 0.49621537, //原本是0.16540512
"_source": { "title": "ES 最高難度", "like": 10 }
}
]
5.有時候線性的計算new_score = old_score * like值的效果並不是那麽好,field_value_factor中還支持 modifier、factor 參數,可以改變like值對old_score的影響
-
modifier參數支持的值
- none : new_score = old_score * like值
- 默認狀態就是none,線性
- log1p : new_score = old_score * log(1 + like值)
- 最常用,可以讓like值字段的評分曲線更平滑
- log2p : new_score = old_score * log(2 + like值)
- ln : new_score = old_score * ln(like值)
- ln1p : new_score = old_score * ln(1 + like值)
- ln2p : new_score = old_score * ln(2 + like值)
- square : 計算平方
- sqrt : 計算平方根
- reciprocal : 計算倒數
- none : new_score = old_score * like值
-
factor參數
- factor作為一個調節用的參數,沒有modifier那麽強大會改變整個曲線,他僅改變一些常量值,設置factor>1會提昇效果,factor<1會降低效果
- 假設modifier是log1p,那麽加入了factor的公式就是new_score = old_score * log(1 + factor * like值)
-
對剛剛的例子加上 modifier、factor
GET 127.0.0.1/mytest/doc/_search { "query": { "function_score": { "query": { "match": { "title": "ES" } }, "field_value_factor": { "field": "like", "modifier": "log1p", "factor": 2 } } } }
-
就算加上了modifier,但是 "全文評分 與 field_value_factor函數值乘積" 的效果可能還是太大,我們可以通過參數boost_mode來決定 old_score 和 加強score 合併的方法
-
如果將boost_mode改成sum,可以大幅弱化最終效果,特別是使用一個較小的factor時
-
加入了boost_mode=sum、且factor=0.1的公式變為new_score = old_score + log(1 + 0.1 * like值)
-
-
GET 127.0.0.1/mytest/doc/_search { "query": { "function_score": { "query": { "match": { "title": "ES" } }, "field_value_factor": { "field": "like", "modifier": "log1p", "factor": 0.1 }, "boost_mode": "sum" } } }
-
三、java 代碼分析
List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
// 用戶端默認排序優先使用是否置頂參數
//將置頂的數據設置權重100。old_score*100,提升_score分數
FunctionScoreQueryBuilder.FilterFunctionBuilder isTop = new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery(EsConstant.IS_TOP, 1),ScoreFunctionBuilders.weightFactorFunction(100));
//將非置頂的數據設置權重1。old_score*1,_score分數保持不變
FunctionScoreQueryBuilder.FilterFunctionBuilder noTop = new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery(EsConstant.IS_TOP, 0), ScoreFunctionBuilders.weightFactorFunction(1));
filterFunctionBuilders.add(isTop);
filterFunctionBuilders.add(noTop);
// 評論數 log1p。子分數= log(1 + 0.1 * EsConstant.COMMENT_NUM值)
ScoreFunctionBuilder<FieldValueFactorFunctionBuilder> commentNumScoreFunction = new FieldValueFactorFunctionBuilder(EsConstant.COMMENT_NUM).modifier(FieldValueFactorFunction.Modifier.LOG1P).factor(0.1f);
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(commentNumScoreFunction));
// 銷量數 log1p。子分數= log(1 + 0.1 * EsConstant.SOLD_NUM值)
ScoreFunctionBuilder<FieldValueFactorFunctionBuilder> saleNumScoreFunction = new FieldValueFactorFunctionBuilder(EsConstant.SOLD_NUM).modifier(FieldValueFactorFunction.Modifier.LOG1P).factor(0.1f);
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(saleNumScoreFunction));
filterFunctionBuilders.toArray();
//scoreMode是加強分之間的處理方式:加和,boostMode是old_score與總加強分之間的關系:加和
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQueryBuilder, ArrayUtil.toArray(filterFunctionBuilders, FunctionScoreQueryBuilder.FilterFunctionBuilder.class))
.scoreMode(FunctionScoreQuery.ScoreMode.SUM).boostMode(CombineFunction.SUM);
// 封裝所有的查詢條件(帶有function score)
searchSourceBuilder.query(functionScoreQueryBuilder);
站在巨人肩膀上摘蘋果
https://blog.csdn.net/weixin_40341116/article/details/80913067
https://blog.csdn.net/weixin_40341116/article/details/80913045
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html
https://my.oschina.net/u/3734816/blog/3105125