概要
本篇介紹怎樣在全文字段中搜索到最相關的文檔,包含手動控制搜索的精准度,搜索條件權重控制。
手動控制搜索的精准度
搜索的兩個重要維度:相關性(Relevance)和分析(Analysis)。
相關性是評價查詢條件與結果的相關程度,並對相關程度進行排序,一般使用TF/IDF方法。
分析是指將索引文檔與查詢條件規范化的一個過程,目的是建立倒排索引時,盡可能地提升召回率。
match查詢原理
匹配查詢match是核心查詢語法,它的主要應用場景就是全文搜索,我們舉一個示例:
GET /music/children/_search
{
"query": {
"match": {
"name": "wake"
}
}
}
Elasticsearch執行的步驟:
- 檢索字段類型:match的字段name為text類型,是一個analyzed的字段,那么查詢條件的字符串也應該被analyzed。
- 分析查詢字符串:將查詢字符串"wake"傳入分詞器中(與mapping的分詞器一致),因為只有一個單詞,所以match最終執行的是單個底層的term查詢。
- 查找匹配文檔:用term倒排索引中查找wake然后獲取一組包含該詞的文檔。
- 為每個文檔評分:用term查詢計算每個文檔相關度評分,即TF、IDF、length norm算法。
得到的結果如下:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.2876821,
"hits": [
{
"_index": "music",
"_type": "children",
"_id": "2",
"_score": 0.2876821,
"_source": {
"id": "a810fad4-54cb-59a1-9b7a-82adb46fa58d",
"author": "John Smith",
"name": "wake me, shark me",
"content": "don't let me sleep too late, gonna get up brightly early in the morning",
"language": "english",
"tags": "enlighten",
"length": 55,
"isRelease": true,
"releaseDate": "2019-12-21"
}
}
]
}
}
因為樣本數據的問題,暫時只有一條文檔匹配。
搜索name中包含"you"或"sunshine"的文檔
GET /music/children/_search
{
"query": {
"match": {
"name": "you sunshine"
}
}
}
搜索name中包含"you"和"sunshine"的文檔
GET /music/children/_search
{
"query": {
"match": {
"name": {
"query": "you sunshine",
"operator": "and"
}
}
}
}
搜索精准度控制的第一步:使用and關鍵字。如果希望所有搜索關鍵字都要匹配,可以用and來實現。
搜索"you"、"my"、"sunshine"、"teeth" 4個關鍵字中,至少包含3個的文檔
GET /music/children/_search
{
"query": {
"match": {
"name": {
"query": "you my sunshine teeth",
"minimum_should_match": "75%"
}
}
}
}
搜索精准度控制的第二步:指定至少匹配其中的多少個關鍵字,才能作為結果返回
bool組合多個搜索條件
用bool組合,可以完成更加個性化的搜索需求,例如我們查找名稱包含"sunshine",但不包含"teeth",允許出現"you"、"my"關鍵字,示例如下:
GET /music/children/_search
{
"query": {
"bool": {
"must": { "match": { "name": "sunshine" }},
"must_not": { "match": { "name": "teeth" }},
"should": [
{ "match": { "name": "my" }},
{ "match": { "name": "you" }}
]
}
}
}
should對相關度評分計算的影響
以上面的bool為例子,我們只討論匹配的文檔,按自己的理解,對文檔進行粗略排名:
- 最符合搜索條件的
文檔中同時包含should中的"my"、"you"兩個關鍵字。
- 很符合搜索條件的
文檔中包含should中的"my"或"you"兩個關鍵字的其中一個。
- 符合搜索條件的
文檔中不包含should中的"my"和"you"。
像must not這種硬性條件,不匹配都不會出現在結果集里,主要起到排除文檔作用,不參與評分計算。但should也能影響相關度評分,匹配得越多,評分就越高。
bool查詢會為每個文檔計算相關度評分_score ,再將所有匹配的 must 和 should 語句的分數 _score 求和,最后除以 must 和 should 語句的總數。
這里不詳細講解評分計算的具體細節和分數,了解should對其有影響即可。
should匹配原則
如果查詢條件中有must存在,那么should匹配的數量不作要求;如果沒有must,則should必須要匹配一個,要不然全是should條件的,所有文檔都能匹配,就失去了搜索的意義。
如果帶上minimum_should_match,那么就能做更精細的控制,可以指定必須要匹配幾個should,才能返回結果集,如下兩個示例是等同的:
GET /music/children/_search
{
"query": {
"match": {
"name": {
"query": "you my sunshine teeth",
"minimum_should_match": "75%"
}
}
}
}
GET /music/children/_search
{
"query": {
"bool": {
"should": [
{ "match": { "name": "you" }},
{ "match": { "name": "my" }},
{ "match": { "name": "sunshine" }},
{ "match": { "name": "teeth" }}
],
"minimum_should_match": 3
}
}
}
多詞查詢的底層原理
上一節當中我們提到的多詞match的查詢,Elasticsearch會將多詞的term查詢轉換為bool查詢,or查詢使用should替代, and查詢使用must,我們回顧一下上一節的示例:
or查詢
下面兩個查詢是等價的
GET /music/children/_search
{
"query": {
"match": {
"name": "you sunshine"
}
}
}
GET /music/children/_search
{
"query": {
"bool": {
"should": [
{"term": {"name": "you"}},
{"term": {"name": "sunshine"}}
]
}
}
}
and查詢
下面兩個查詢也是等價的
GET /music/children/_search
{
"query": {
"match": {
"name": {
"query": "you sunshine",
"operator": "and"
}
}
}
}
GET /music/children/_search
{
"query": {
"bool": {
"must": [
{"term": {"name": "you"}},
{"term": {"name": "sunshine"}}
]
}
}
}
minimum_should_match語法
下面兩個查詢仍然是等價的
GET /music/children/_search
{
"query": {
"match": {
"name": {
"query": "you my sunshine teeth",
"minimum_should_match": "75%"
}
}
}
}
GET /music/children/_search
{
"query": {
"bool": {
"should": [
{ "match": { "name": "you" }},
{ "match": { "name": "my" }},
{ "match": { "name": "sunshine" }},
{ "match": { "name": "teeth" }}
],
"minimum_should_match": 3
}
}
}
minimum_should_match的值可以改,也會根據實際的條件來換算,比如我寫個75%,但實際的搜索詞條就只有3個,那么minimum_should_match的值就會變成66.6%,即至少需要匹配2條。
查詢語句權重控制
bool查詢里的多條件並列,默認權重是一樣的,但實際的搜索當中,我們可能會特別某一些關鍵詞給予特殊的關注,希望匹配關注度高的文檔排序能靠前一些,boost語法可以幫助我們實現這一需求。
boost默認是1,可以在查詢語句中自行設置,如下示例,我希望sunshine的權重要大一些:
GET /music/children/_search
{
"query": {
"bool": {
"must": {
"match": {
"name": {
"query": "sunshine",
"boost": 2
}
}
},
"should": [
{
"match": {
"name": "my"
}
},
{
"match": {
"name": "you"
}
}
]
}
}
}
在查詢結果里可以看到,符合條件的文檔的_score值變得更高一些。boost值的設置不是簡單的線性增長,boost設置為2不表示_score也會簡單的翻一倍,boost值在計算時有歸一化處理,但具體的計算數值及過程不在此篇作詳細的解釋。
評分計算的小問題
我們的演示環境是單node多shard模式的,有些示例發現評分會出現特別高的現象,給人感覺不是很准確,這是為什么呢?
不准確的原因
我們簡單回顧一下評分計算的幾個算法:TF、IDF、Length Norm,其中IDF本意上是在所有的document中,搜索關鍵詞的出現次數,實際上IDF默認在本地shard執行的次數統計,一個shard只包含部分數據,不能代表所有數據,這樣做比較高效,但會帶來誤差,也是造成結果不准確的原因。
演示環境中出現較大偏差,還有一個原因是演示數據相對較少,如果只有一個document,並且該document符合查詢條件,那么IDF的分數就會變得很高。
解決辦法
- 生產環境數據量相對較大,默認使用_id進行路由,在概率分布下,其實每個shard的數據基本上比較均勻,不用太擔心演示環境的這種情況。
- 演示環境可以將primary shard設置為1,只有一個shard,那IDF的值肯定是對的。
- 演示環境進行搜索時,帶上search_type=dfs_query_then_fetch參數,會將local IDF取出來計算global IDF。注意性能問題,生產上禁用。
小結
本篇主要介紹了全文搜索幾種手動控制精度的方式:邏輯操作符變換、should命中率設置、權重調整等手段,最后對評分計算的小問題進行的簡單描述,謝謝。
專注Java高並發、分布式架構,更多技術干貨分享與心得,請關注公眾號:Java架構社區
可以掃左邊二維碼添加好友,邀請你加入Java架構社區微信群共同探討技術